feat: extensions read config (#1637)
This commit is contained in:
+235
-14
@@ -33,7 +33,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/extension": {
|
||||
"/config/extensions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "get_extensions",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "All extensions retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ExtensionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
@@ -67,12 +88,24 @@
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/config/extensions/{name}": {
|
||||
"put": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "update_extension",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
@@ -85,7 +118,7 @@
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Extension configuration updated successfully",
|
||||
"description": "Extension updated successfully",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
@@ -107,16 +140,16 @@
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "remove_extension",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConfigKeyQuery"
|
||||
}
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Extension removed successfully",
|
||||
@@ -259,6 +292,42 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/extensions/{name}/toggle": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "toggle_extension",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Extension toggled successfully",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Extension not found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -313,19 +382,171 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Envs": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "A map of environment variables to set, e.g. API_KEY -> some_secret, HOST -> host"
|
||||
}
|
||||
},
|
||||
"ExtensionConfig": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Server-sent events client with a URI endpoint",
|
||||
"required": [
|
||||
"name",
|
||||
"uri",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"envs": {
|
||||
"$ref": "#/components/schemas/Envs"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name used to identify this extension"
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"nullable": true,
|
||||
"minimum": 0
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"sse"
|
||||
]
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Standard I/O client with command and arguments",
|
||||
"required": [
|
||||
"name",
|
||||
"cmd",
|
||||
"args",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"args": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"cmd": {
|
||||
"type": "string"
|
||||
},
|
||||
"envs": {
|
||||
"$ref": "#/components/schemas/Envs"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name used to identify this extension"
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"nullable": true,
|
||||
"minimum": 0
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"stdio"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Built-in extension that is part of the goose binary",
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name used to identify this extension"
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"nullable": true,
|
||||
"minimum": 0
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"builtin"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "Represents the different types of MCP extensions that can be added to the manager",
|
||||
"discriminator": {
|
||||
"propertyName": "type"
|
||||
}
|
||||
},
|
||||
"ExtensionEntry": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ExtensionConfig"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ExtensionQuery": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"config"
|
||||
"config",
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"config": {},
|
||||
"config": {
|
||||
"$ref": "#/components/schemas/ExtensionConfig"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ExtensionResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"extensions"
|
||||
],
|
||||
"properties": {
|
||||
"extensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ExtensionEntry"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProviderDetails": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
|
||||
import type { ReadAllConfigData, ReadAllConfigResponse, RemoveExtensionData, RemoveExtensionResponse, AddExtensionData, AddExtensionResponse, UpdateExtensionData, UpdateExtensionResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse } from './types.gen';
|
||||
import type { ReadAllConfigData, ReadAllConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, UpdateExtensionData, UpdateExtensionResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse, ToggleExtensionData, ToggleExtensionResponse } from './types.gen';
|
||||
import { client as _heyApiClient } from './client.gen';
|
||||
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
||||
@@ -25,20 +25,16 @@ export const readAllConfig = <ThrowOnError extends boolean = false>(options?: Op
|
||||
});
|
||||
};
|
||||
|
||||
export const removeExtension = <ThrowOnError extends boolean = false>(options: Options<RemoveExtensionData, ThrowOnError>) => {
|
||||
return (options.client ?? _heyApiClient).delete<RemoveExtensionResponse, unknown, ThrowOnError>({
|
||||
url: '/config/extension',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers
|
||||
}
|
||||
export const getExtensions = <ThrowOnError extends boolean = false>(options?: Options<GetExtensionsData, ThrowOnError>) => {
|
||||
return (options?.client ?? _heyApiClient).get<GetExtensionsResponse, unknown, ThrowOnError>({
|
||||
url: '/config/extensions',
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const addExtension = <ThrowOnError extends boolean = false>(options: Options<AddExtensionData, ThrowOnError>) => {
|
||||
return (options.client ?? _heyApiClient).post<AddExtensionResponse, unknown, ThrowOnError>({
|
||||
url: '/config/extension',
|
||||
url: '/config/extensions',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -47,9 +43,16 @@ export const addExtension = <ThrowOnError extends boolean = false>(options: Opti
|
||||
});
|
||||
};
|
||||
|
||||
export const removeExtension = <ThrowOnError extends boolean = false>(options: Options<RemoveExtensionData, ThrowOnError>) => {
|
||||
return (options.client ?? _heyApiClient).delete<RemoveExtensionResponse, unknown, ThrowOnError>({
|
||||
url: '/config/extensions/{name}',
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const updateExtension = <ThrowOnError extends boolean = false>(options: Options<UpdateExtensionData, ThrowOnError>) => {
|
||||
return (options.client ?? _heyApiClient).put<UpdateExtensionResponse, unknown, ThrowOnError>({
|
||||
url: '/config/extension',
|
||||
url: '/config/extensions/{name}',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -96,4 +99,11 @@ export const upsertConfig = <ThrowOnError extends boolean = false>(options: Opti
|
||||
...options?.headers
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const toggleExtension = <ThrowOnError extends boolean = false>(options: Options<ToggleExtensionData, ThrowOnError>) => {
|
||||
return (options.client ?? _heyApiClient).post<ToggleExtensionResponse, unknown, ThrowOnError>({
|
||||
url: '/extensions/{name}/toggle',
|
||||
...options
|
||||
});
|
||||
};
|
||||
+120
-18
@@ -16,9 +16,55 @@ export type ConfigResponse = {
|
||||
config: {};
|
||||
};
|
||||
|
||||
export type ExtensionQuery = {
|
||||
config: unknown;
|
||||
export type Envs = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the different types of MCP extensions that can be added to the manager
|
||||
*/
|
||||
export type ExtensionConfig = {
|
||||
envs?: Envs;
|
||||
/**
|
||||
* The name used to identify this extension
|
||||
*/
|
||||
name: string;
|
||||
timeout?: number | null;
|
||||
type: 'sse';
|
||||
uri: string;
|
||||
} | {
|
||||
args: Array<string>;
|
||||
cmd: string;
|
||||
envs?: Envs;
|
||||
/**
|
||||
* The name used to identify this extension
|
||||
*/
|
||||
name: string;
|
||||
timeout?: number | null;
|
||||
type: 'stdio';
|
||||
} | {
|
||||
/**
|
||||
* The name used to identify this extension
|
||||
*/
|
||||
name: string;
|
||||
timeout?: number | null;
|
||||
type: 'builtin';
|
||||
};
|
||||
|
||||
export type ExtensionEntry = ExtensionConfig & {
|
||||
type?: 'ExtensionEntry';
|
||||
} & {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type ExtensionQuery = {
|
||||
config: ExtensionConfig;
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ExtensionResponse = {
|
||||
extensions: Array<ExtensionEntry>;
|
||||
};
|
||||
|
||||
export type ProviderDetails = {
|
||||
@@ -94,38 +140,34 @@ export type ReadAllConfigResponses = {
|
||||
|
||||
export type ReadAllConfigResponse = ReadAllConfigResponses[keyof ReadAllConfigResponses];
|
||||
|
||||
export type RemoveExtensionData = {
|
||||
body: ConfigKeyQuery;
|
||||
export type GetExtensionsData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/config/extension';
|
||||
url: '/config/extensions';
|
||||
};
|
||||
|
||||
export type RemoveExtensionErrors = {
|
||||
/**
|
||||
* Extension not found
|
||||
*/
|
||||
404: unknown;
|
||||
export type GetExtensionsErrors = {
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type RemoveExtensionResponses = {
|
||||
export type GetExtensionsResponses = {
|
||||
/**
|
||||
* Extension removed successfully
|
||||
* All extensions retrieved successfully
|
||||
*/
|
||||
200: string;
|
||||
200: ExtensionResponse;
|
||||
};
|
||||
|
||||
export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses];
|
||||
export type GetExtensionsResponse = GetExtensionsResponses[keyof GetExtensionsResponses];
|
||||
|
||||
export type AddExtensionData = {
|
||||
body: ExtensionQuery;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/config/extension';
|
||||
url: '/config/extensions';
|
||||
};
|
||||
|
||||
export type AddExtensionErrors = {
|
||||
@@ -148,11 +190,42 @@ export type AddExtensionResponses = {
|
||||
|
||||
export type AddExtensionResponse = AddExtensionResponses[keyof AddExtensionResponses];
|
||||
|
||||
export type RemoveExtensionData = {
|
||||
body?: never;
|
||||
path: {
|
||||
name: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/extensions/{name}';
|
||||
};
|
||||
|
||||
export type RemoveExtensionErrors = {
|
||||
/**
|
||||
* Extension not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type RemoveExtensionResponses = {
|
||||
/**
|
||||
* Extension removed successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses];
|
||||
|
||||
export type UpdateExtensionData = {
|
||||
body: ExtensionQuery;
|
||||
path?: never;
|
||||
path: {
|
||||
name: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/extension';
|
||||
url: '/config/extensions/{name}';
|
||||
};
|
||||
|
||||
export type UpdateExtensionErrors = {
|
||||
@@ -168,7 +241,7 @@ export type UpdateExtensionErrors = {
|
||||
|
||||
export type UpdateExtensionResponses = {
|
||||
/**
|
||||
* Extension configuration updated successfully
|
||||
* Extension updated successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
@@ -262,6 +335,35 @@ export type UpsertConfigResponses = {
|
||||
|
||||
export type UpsertConfigResponse = UpsertConfigResponses[keyof UpsertConfigResponses];
|
||||
|
||||
export type ToggleExtensionData = {
|
||||
body?: never;
|
||||
path: {
|
||||
name: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/extensions/{name}/toggle';
|
||||
};
|
||||
|
||||
export type ToggleExtensionErrors = {
|
||||
/**
|
||||
* Extension not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type ToggleExtensionResponses = {
|
||||
/**
|
||||
* Extension toggled successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type ToggleExtensionResponse = ToggleExtensionResponses[keyof ToggleExtensionResponses];
|
||||
|
||||
export type ClientOptions = {
|
||||
baseUrl: `${string}://${string}` | (string & {});
|
||||
};
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
readConfig,
|
||||
removeConfig,
|
||||
upsertConfig,
|
||||
getExtensions as apiGetExtensions,
|
||||
toggleExtension as apiToggleExtension,
|
||||
addExtension as apiAddExtension,
|
||||
removeExtension as apiRemoveExtension,
|
||||
updateExtension as apiUpdateExtension,
|
||||
@@ -14,8 +16,11 @@ import type {
|
||||
ConfigResponse,
|
||||
UpsertConfigQuery,
|
||||
ConfigKeyQuery,
|
||||
ExtensionQuery,
|
||||
ExtensionResponse,
|
||||
ExtensionEntry,
|
||||
ProviderDetails,
|
||||
ExtensionQuery,
|
||||
ExtensionConfig,
|
||||
} from '../api/types.gen';
|
||||
|
||||
// Initialize client configuration
|
||||
@@ -33,10 +38,12 @@ interface ConfigContextType {
|
||||
upsert: (key: string, value: unknown, is_secret: boolean) => Promise<void>;
|
||||
read: (key: string, is_secret: boolean) => Promise<unknown>;
|
||||
remove: (key: string, is_secret: boolean) => Promise<void>;
|
||||
addExtension: (name: string, config: unknown) => Promise<void>;
|
||||
updateExtension: (name: string, config: unknown) => Promise<void>;
|
||||
addExtension: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
updateExtension: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
toggleExtension: (name: string) => Promise<void>;
|
||||
removeExtension: (name: string) => Promise<void>;
|
||||
getProviders: (b: boolean) => Promise<ProviderDetails[]>;
|
||||
getExtensions: (b: boolean) => Promise<ExtensionEntry[]>
|
||||
}
|
||||
|
||||
interface ConfigProviderProps {
|
||||
@@ -48,6 +55,7 @@ const ConfigContext = createContext<ConfigContextType | undefined>(undefined);
|
||||
export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
const [config, setConfig] = useState<ConfigResponse['config']>({});
|
||||
const [providersList, setProvidersList] = useState<ProviderDetails[]>([]);
|
||||
const [extensionsList, setExtensionsList] = useState<ExtensionEntry[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
// Load all configuration data and providers on mount
|
||||
@@ -63,6 +71,15 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
} catch (error) {
|
||||
console.error('Failed to load providers:', error);
|
||||
}
|
||||
|
||||
// Load extensions
|
||||
try {
|
||||
const extensionsResponse = await apiGetExtensions()
|
||||
setExtensionsList(extensionsResponse.data.extensions)
|
||||
} catch (error) {
|
||||
console.error('Failed to load extensions:', error)
|
||||
}
|
||||
|
||||
})();
|
||||
}, []);
|
||||
|
||||
@@ -99,8 +116,8 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
await reloadConfig();
|
||||
};
|
||||
|
||||
const addExtension = async (name: string, config: unknown) => {
|
||||
const query: ExtensionQuery = { name, config };
|
||||
const addExtension = async (name: string, config: ExtensionConfig, enabled: boolean) => {
|
||||
const query: ExtensionQuery = { name, config, enabled };
|
||||
await apiAddExtension({
|
||||
body: query,
|
||||
});
|
||||
@@ -108,18 +125,21 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
};
|
||||
|
||||
const removeExtension = async (name: string) => {
|
||||
const query: ConfigKeyQuery = { key: name, is_secret: false };
|
||||
await apiRemoveExtension({
|
||||
await apiRemoveExtension({path: {name: name}});
|
||||
await reloadConfig();
|
||||
};
|
||||
|
||||
const updateExtension = async (name: string, config: ExtensionConfig, enabled: boolean) => {
|
||||
const query: ExtensionQuery = { name, config, enabled };
|
||||
await apiUpdateExtension({
|
||||
body: query,
|
||||
path: {name: name}
|
||||
});
|
||||
await reloadConfig();
|
||||
};
|
||||
|
||||
const updateExtension = async (name: string, config: unknown) => {
|
||||
const query: ExtensionQuery = { name, config };
|
||||
await apiUpdateExtension({
|
||||
body: query,
|
||||
});
|
||||
const toggleExtension = async (name: string) => {
|
||||
await apiToggleExtension({path: {name: name}});
|
||||
await reloadConfig();
|
||||
};
|
||||
|
||||
@@ -134,6 +154,18 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
return providersList;
|
||||
};
|
||||
|
||||
const getExtensions = async (forceRefresh = false): Promise<ExtensionEntry[]> => {
|
||||
if (forceRefresh || extensionsList.length === 0) {
|
||||
// If a refresh is forced, or we don't have providers yet
|
||||
const response = await apiGetExtensions();
|
||||
const extensionResponse: ExtensionResponse = response.data
|
||||
setExtensionsList(extensionResponse.extensions);
|
||||
return extensionResponse.extensions;
|
||||
}
|
||||
// Otherwise return the cached providers
|
||||
return extensionsList;
|
||||
};
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
config,
|
||||
@@ -144,7 +176,9 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
addExtension,
|
||||
updateExtension,
|
||||
removeExtension,
|
||||
toggleExtension,
|
||||
getProviders,
|
||||
getExtensions,
|
||||
}),
|
||||
[config, providersList]
|
||||
); // Functions don't need to be dependencies as they don't change
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useConfig } from '../ConfigContext';
|
||||
import { Button } from '../ui/button';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { Gear } from '../icons/Gear';
|
||||
import ExtensionsSection from './ExtensionsSection';
|
||||
import ExtensionsSection from './extensions/ExtensionsSection';
|
||||
|
||||
interface ModelOption {
|
||||
id: string;
|
||||
|
||||
+14
-13
@@ -1,14 +1,14 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button } from '../ui/button';
|
||||
import { Switch } from '../ui/switch';
|
||||
import { Button } from '../../ui/button';
|
||||
import { Switch } from '../../ui/switch';
|
||||
import { Plus, X } from 'lucide-react';
|
||||
import { Gear } from '../icons/Gear';
|
||||
import { GPSIcon } from '../ui/icons';
|
||||
import { useConfig } from '../ConfigContext';
|
||||
import Modal from '../Modal';
|
||||
import { Input } from '../ui/input';
|
||||
import { Gear } from '../../icons/Gear';
|
||||
import { GPSIcon } from '../../ui/icons';
|
||||
import { useConfig } from '../../ConfigContext';
|
||||
import Modal from '../../Modal';
|
||||
import { Input } from '../../ui/input';
|
||||
import Select from 'react-select';
|
||||
import { createDarkSelectStyles, darkSelectTheme } from '../ui/select-styles';
|
||||
import { createDarkSelectStyles, darkSelectTheme } from '../../ui/select-styles';
|
||||
|
||||
interface ExtensionConfig {
|
||||
args?: string[];
|
||||
@@ -16,7 +16,7 @@ interface ExtensionConfig {
|
||||
enabled: boolean;
|
||||
envs?: Record<string, string>;
|
||||
name: string;
|
||||
type: 'stdio' | 'sse';
|
||||
type: 'stdio' | 'sse' | 'builtin';
|
||||
}
|
||||
|
||||
interface ExtensionItem {
|
||||
@@ -50,7 +50,7 @@ const getSubtitle = (config: ExtensionConfig): string => {
|
||||
};
|
||||
|
||||
export default function ExtensionsSection() {
|
||||
const { config, updateExtension, addExtension } = useConfig();
|
||||
const { config, read, updateExtension, addExtension } = useConfig();
|
||||
const [extensions, setExtensions] = useState<ExtensionItem[]>([]);
|
||||
const [selectedExtension, setSelectedExtension] = useState<ExtensionItem | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
@@ -74,8 +74,9 @@ export default function ExtensionsSection() {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (config.extensions) {
|
||||
const extensionItems: ExtensionItem[] = Object.entries(config.extensions).map(
|
||||
const extensions = read('extensions', false)
|
||||
if (extensions) {
|
||||
const extensionItems: ExtensionItem[] = Object.entries(extensions).map(
|
||||
([name, ext]) => {
|
||||
const extensionConfig = ext as ExtensionConfig;
|
||||
return {
|
||||
@@ -90,7 +91,7 @@ export default function ExtensionsSection() {
|
||||
);
|
||||
setExtensions(extensionItems);
|
||||
}
|
||||
}, [config.extensions]);
|
||||
}, [read]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedExtension) {
|
||||
@@ -0,0 +1,121 @@
|
||||
// TODO: copied this from old code
|
||||
|
||||
// import {View} from '../../../App'
|
||||
// import {SettingsViewOptions} from "../SettingsView";
|
||||
// import {toast} from "react-toastify";
|
||||
//
|
||||
//
|
||||
// export async function addExtensionFromDeepLink(
|
||||
// url: string,
|
||||
// setView: (view: View, options: SettingsViewOptions) => void
|
||||
// ) {
|
||||
// if (!url.startsWith('goose://extension')) {
|
||||
// handleError(
|
||||
// 'Failed to install extension: Invalid URL: URL must use the goose://extension scheme'
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const parsedUrl = new URL(url);
|
||||
//
|
||||
// if (parsedUrl.protocol !== 'goose:') {
|
||||
// handleError(
|
||||
// 'Failed to install extension: Invalid protocol: URL must use the goose:// scheme',
|
||||
// true
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// // Check that all required fields are present and not empty
|
||||
// const requiredFields = ['name', 'description'];
|
||||
//
|
||||
// for (const field of requiredFields) {
|
||||
// const value = parsedUrl.searchParams.get(field);
|
||||
// if (!value || value.trim() === '') {
|
||||
// handleError(
|
||||
// `Failed to install extension: The link is missing required field '${field}'`,
|
||||
// true
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// const cmd = parsedUrl.searchParams.get('cmd');
|
||||
// if (!cmd) {
|
||||
// handleError("Failed to install extension: Missing required 'cmd' parameter in the URL", true);
|
||||
// }
|
||||
//
|
||||
// // Validate that the command is one of the allowed commands
|
||||
// const allowedCommands = ['npx', 'uvx', 'goosed'];
|
||||
// if (!allowedCommands.includes(cmd)) {
|
||||
// handleError(
|
||||
// `Failed to install extension: Invalid command: ${cmd}. Only ${allowedCommands.join(', ')} are allowed.`,
|
||||
// true
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// // Check for security risk with npx -c command
|
||||
// const args = parsedUrl.searchParams.getAll('arg');
|
||||
// if (cmd === 'npx' && args.includes('-c')) {
|
||||
// handleError(
|
||||
// 'Failed to install extension: npx with -c argument can lead to code injection',
|
||||
// true
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// const envList = parsedUrl.searchParams.getAll('env');
|
||||
// const id = parsedUrl.searchParams.get('id');
|
||||
// const name = parsedUrl.searchParams.get('name');
|
||||
// const description = parsedUrl.searchParams.get('description');
|
||||
// const timeout = parsedUrl.searchParams.get('timeout');
|
||||
//
|
||||
// // split env based on delimiter to a map
|
||||
// const envs = envList.reduce(
|
||||
// (acc, env) => {
|
||||
// const [key, value] = env.split('=');
|
||||
// acc[key] = value;
|
||||
// return acc;
|
||||
// },
|
||||
// {} as Record<string, string>
|
||||
// );
|
||||
//
|
||||
// // Create a ExtensionConfig from the URL parameters
|
||||
// // Parse timeout if provided, otherwise use default
|
||||
// const parsedTimeout = timeout ? parseInt(timeout, 10) : null;
|
||||
//
|
||||
// const extensionConfig: ExtensionConfig = {
|
||||
// id,
|
||||
// name,
|
||||
// type: 'stdio',
|
||||
// cmd,
|
||||
// args,
|
||||
// description,
|
||||
// enabled: true,
|
||||
// env_keys: Object.keys(envs).length > 0 ? Object.keys(envs) : [],
|
||||
// timeout:
|
||||
// parsedTimeout !== null && !isNaN(parsedTimeout) && Number.isInteger(parsedTimeout)
|
||||
// ? parsedTimeout
|
||||
// : DEFAULT_EXTENSION_TIMEOUT,
|
||||
// };
|
||||
//
|
||||
// // Store the extension config regardless of env vars status
|
||||
// storeExtensionConfig(extensionConfig);
|
||||
//
|
||||
// // Check if extension requires env vars and go to settings if so
|
||||
// if (envVarsRequired(extensionConfig)) {
|
||||
// console.log('Environment variables required, redirecting to settings');
|
||||
// setView('settings', { extensionId: extensionConfig.id, showEnvVars: true });
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // If no env vars are required, proceed with extending Goosed
|
||||
// await addExtension(extensionConfig);
|
||||
// }
|
||||
//
|
||||
// function handleError(message: string, shouldThrow = false): void {
|
||||
// toast.error(message);
|
||||
// console.error(message);
|
||||
// if (shouldThrow) {
|
||||
// throw new Error(message);
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO: when rust app starts, add built-in extensions to config.yaml if they aren't there already
|
||||
Reference in New Issue
Block a user