feat: goose2 add support for custom providers in ui & acp (#8924)
This commit is contained in:
@@ -224,6 +224,94 @@
|
||||
},
|
||||
"title": "Agent harnesses"
|
||||
},
|
||||
"custom": {
|
||||
"actions": {
|
||||
"addHeader": "Add header",
|
||||
"addModel": "Add model",
|
||||
"back": "Back",
|
||||
"create": "Create provider",
|
||||
"delete": "Delete",
|
||||
"deleteProvider": "Delete {{name}}",
|
||||
"deleting": "Deleting...",
|
||||
"editProvider": "Edit {{name}}",
|
||||
"hideApiKey": "Hide API key",
|
||||
"removeHeader": "Remove header",
|
||||
"removeModel": "Remove {{model}}",
|
||||
"save": "Save changes",
|
||||
"saving": "Saving...",
|
||||
"showApiKey": "Show API key"
|
||||
},
|
||||
"addButton": "Add custom provider",
|
||||
"addTitle": "Add custom provider",
|
||||
"confirmDelete": "This removes {{name}} and any API key owned by it.",
|
||||
"confirmDeleteTitle": "Delete {{name}}?",
|
||||
"description": "Connect an OpenAI-compatible, Anthropic-compatible, or local model endpoint.",
|
||||
"editTitle": "Edit custom provider",
|
||||
"emptyHeaders": "No custom headers.",
|
||||
"emptyModels": "Add at least one model.",
|
||||
"engines": {
|
||||
"anthropic_compatible": "Anthropic-compatible",
|
||||
"ollama_compatible": "Ollama-compatible",
|
||||
"openai_compatible": "OpenAI-compatible"
|
||||
},
|
||||
"errors": {
|
||||
"deleteFailed": "Failed to delete custom provider.",
|
||||
"loadFailed": "Failed to load custom provider.",
|
||||
"saveFailed": "Failed to save custom provider.",
|
||||
"templatesFailed": "Failed to load provider templates."
|
||||
},
|
||||
"fields": {
|
||||
"apiKey": "API key",
|
||||
"apiKeyEditPlaceholder": "Leave blank to keep the saved key",
|
||||
"apiKeyPlaceholder": "Paste your API key",
|
||||
"apiUrl": "API URL",
|
||||
"apiUrlPlaceholder": "https://api.example.com/v1",
|
||||
"basePath": "Base path",
|
||||
"basePathPlaceholder": "/v1/chat/completions",
|
||||
"displayName": "Display name",
|
||||
"displayNamePlaceholder": "My provider",
|
||||
"engine": "Engine",
|
||||
"headerKey": "Header",
|
||||
"headerValue": "Value",
|
||||
"headers": "Custom headers",
|
||||
"models": "Models",
|
||||
"modelsPlaceholder": "gpt-4.1, claude-3-5-sonnet",
|
||||
"requiresAuth": "Requires authentication",
|
||||
"requiresAuthDescription": "Store an API key for requests to this provider.",
|
||||
"supportsStreaming": "Supports streaming",
|
||||
"supportsStreamingDescription": "Stream responses as the model generates tokens."
|
||||
},
|
||||
"modelCount_one": "{{count}} model",
|
||||
"modelCount_other": "{{count}} models",
|
||||
"notConfigured": "Not configured",
|
||||
"validation": {
|
||||
"apiKeyRequired": "API key is required.",
|
||||
"apiUrlInvalid": "Enter a valid HTTP or HTTPS URL.",
|
||||
"apiUrlRequired": "API URL is required.",
|
||||
"displayNameRequired": "Display name is required.",
|
||||
"engineRequired": "Choose a provider engine.",
|
||||
"headerDuplicate": "Header names must be unique.",
|
||||
"headerNameInvalid": "Header names can only contain valid HTTP token characters.",
|
||||
"headerNameRequired": "Header name is required.",
|
||||
"headerValueRequired": "Header value is required.",
|
||||
"modelsRequired": "Add at least one model."
|
||||
},
|
||||
"sections": {
|
||||
"template": "Start from template"
|
||||
},
|
||||
"templates": {
|
||||
"compatibility": {
|
||||
"all": "All compatibility types"
|
||||
},
|
||||
"clear": "Clear template",
|
||||
"empty": "No templates available yet.",
|
||||
"manual": "Fully custom",
|
||||
"manualDescription": "Start from a blank provider configuration.",
|
||||
"searchPlaceholder": "Search templates",
|
||||
"useTemplate": "Use a template",
|
||||
"useTemplateDescription": "Start with endpoint and model defaults for a known provider."
|
||||
}
|
||||
},
|
||||
"disconnect": "Disconnect",
|
||||
"models": {
|
||||
"description": "AI models power your agents. Goose requires one to work, but some agents bring their own.",
|
||||
|
||||
@@ -224,6 +224,94 @@
|
||||
},
|
||||
"title": "Arneses de agentes"
|
||||
},
|
||||
"custom": {
|
||||
"actions": {
|
||||
"addHeader": "Agregar encabezado",
|
||||
"addModel": "Agregar modelo",
|
||||
"back": "Atrás",
|
||||
"create": "Crear proveedor",
|
||||
"delete": "Eliminar",
|
||||
"deleteProvider": "Eliminar {{name}}",
|
||||
"deleting": "Eliminando...",
|
||||
"editProvider": "Editar {{name}}",
|
||||
"hideApiKey": "Ocultar clave API",
|
||||
"removeHeader": "Eliminar encabezado",
|
||||
"removeModel": "Eliminar {{model}}",
|
||||
"save": "Guardar cambios",
|
||||
"saving": "Guardando...",
|
||||
"showApiKey": "Mostrar clave API"
|
||||
},
|
||||
"addButton": "Agregar proveedor personalizado",
|
||||
"addTitle": "Agregar proveedor personalizado",
|
||||
"confirmDelete": "Esto elimina {{name}} y cualquier clave API que le pertenezca.",
|
||||
"confirmDeleteTitle": "¿Eliminar {{name}}?",
|
||||
"description": "Conecta un endpoint de modelo compatible con OpenAI, Anthropic o local.",
|
||||
"editTitle": "Editar proveedor personalizado",
|
||||
"emptyHeaders": "No hay encabezados personalizados.",
|
||||
"emptyModels": "Agrega al menos un modelo.",
|
||||
"engines": {
|
||||
"anthropic_compatible": "Compatible con Anthropic",
|
||||
"ollama_compatible": "Compatible con Ollama",
|
||||
"openai_compatible": "Compatible con OpenAI"
|
||||
},
|
||||
"errors": {
|
||||
"deleteFailed": "No se pudo eliminar el proveedor personalizado.",
|
||||
"loadFailed": "No se pudo cargar el proveedor personalizado.",
|
||||
"saveFailed": "No se pudo guardar el proveedor personalizado.",
|
||||
"templatesFailed": "No se pudieron cargar las plantillas de proveedores."
|
||||
},
|
||||
"fields": {
|
||||
"apiKey": "Clave API",
|
||||
"apiKeyEditPlaceholder": "Déjalo en blanco para conservar la clave guardada",
|
||||
"apiKeyPlaceholder": "Pega tu clave API",
|
||||
"apiUrl": "URL de API",
|
||||
"apiUrlPlaceholder": "https://api.example.com/v1",
|
||||
"basePath": "Ruta base",
|
||||
"basePathPlaceholder": "/v1/chat/completions",
|
||||
"displayName": "Nombre visible",
|
||||
"displayNamePlaceholder": "Mi proveedor",
|
||||
"engine": "Motor",
|
||||
"headerKey": "Encabezado",
|
||||
"headerValue": "Valor",
|
||||
"headers": "Encabezados personalizados",
|
||||
"models": "Modelos",
|
||||
"modelsPlaceholder": "gpt-4.1, claude-3-5-sonnet",
|
||||
"requiresAuth": "Requiere autenticación",
|
||||
"requiresAuthDescription": "Guarda una clave API para las solicitudes a este proveedor.",
|
||||
"supportsStreaming": "Admite streaming",
|
||||
"supportsStreamingDescription": "Transmite respuestas mientras el modelo genera tokens."
|
||||
},
|
||||
"modelCount_one": "{{count}} modelo",
|
||||
"modelCount_other": "{{count}} modelos",
|
||||
"notConfigured": "No configurado",
|
||||
"validation": {
|
||||
"apiKeyRequired": "La clave API es obligatoria.",
|
||||
"apiUrlInvalid": "Introduce una URL HTTP o HTTPS válida.",
|
||||
"apiUrlRequired": "La URL de API es obligatoria.",
|
||||
"displayNameRequired": "El nombre visible es obligatorio.",
|
||||
"engineRequired": "Elige un motor de proveedor.",
|
||||
"headerDuplicate": "Los nombres de encabezado deben ser únicos.",
|
||||
"headerNameInvalid": "Los nombres de encabezado solo pueden contener caracteres HTTP token válidos.",
|
||||
"headerNameRequired": "El nombre del encabezado es obligatorio.",
|
||||
"headerValueRequired": "El valor del encabezado es obligatorio.",
|
||||
"modelsRequired": "Agrega al menos un modelo."
|
||||
},
|
||||
"sections": {
|
||||
"template": "Comenzar con plantilla"
|
||||
},
|
||||
"templates": {
|
||||
"compatibility": {
|
||||
"all": "Todos los tipos de compatibilidad"
|
||||
},
|
||||
"clear": "Borrar plantilla",
|
||||
"empty": "Todavía no hay plantillas disponibles.",
|
||||
"manual": "Totalmente personalizado",
|
||||
"manualDescription": "Comienza con una configuración de proveedor en blanco.",
|
||||
"searchPlaceholder": "Buscar plantillas",
|
||||
"useTemplate": "Usar una plantilla",
|
||||
"useTemplateDescription": "Comienza con valores predeterminados de endpoint y modelos para un proveedor conocido."
|
||||
}
|
||||
},
|
||||
"disconnect": "Desconectar",
|
||||
"models": {
|
||||
"description": "Necesitas al menos un proveedor de modelos para usar el agente Goose. Algunos agentes pueden traer sus propias conexiones de modelo.",
|
||||
|
||||
@@ -34,7 +34,7 @@ function AlertDialogOverlay({
|
||||
<AlertDialogPrimitive.Overlay
|
||||
data-slot="alert-dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[80] bg-black/50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -49,14 +49,16 @@ function AlertDialogContent({
|
||||
return (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
data-slot="alert-dialog-content"
|
||||
className={cn(
|
||||
"bg-background shadow-modal data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-modal border p-6 duration-200 sm:max-w-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<div className="pointer-events-none fixed inset-0 z-[81] grid place-items-center p-4">
|
||||
<AlertDialogPrimitive.Content
|
||||
data-slot="alert-dialog-content"
|
||||
className={cn(
|
||||
"pointer-events-auto bg-background shadow-modal data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 relative grid w-full max-w-[calc(100vw-2rem)] gap-4 rounded-modal border p-6 duration-200 sm:max-w-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</AlertDialogPortal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ function DialogOverlay({
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[60] bg-black/50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -55,22 +55,24 @@ function DialogContent({
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background shadow-modal data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-modal border p-6 duration-200 sm:max-w-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<DialogPrimitive.Close className="ring-offset-background focus-visible:ring-ring data-[state=open]:bg-muted data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
<div className="pointer-events-none fixed inset-0 z-[61] grid place-items-center p-4">
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"pointer-events-auto bg-background shadow-modal data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 relative grid w-full max-w-[calc(100vw-2rem)] gap-4 rounded-modal border p-6 duration-200 sm:max-w-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<DialogPrimitive.Close className="ring-offset-background focus-visible:ring-ring data-[state=open]:bg-muted data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</div>
|
||||
</DialogPortal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -440,13 +440,16 @@ const PROVIDER_ICON_MAP: Record<string, (className: string) => ReactNode> = {
|
||||
amp: (className) => <AmpIcon className={className} />,
|
||||
"amp-acp": (className) => <AmpIcon className={className} />,
|
||||
azure: (className) => <AzureOpenAIIcon className={className} />,
|
||||
azure_openai: (className) => <AzureOpenAIIcon className={className} />,
|
||||
bedrock: (className) => <BedrockIcon className={className} />,
|
||||
aws_bedrock: (className) => <BedrockIcon className={className} />,
|
||||
databricks: (className) => <DatabricksIcon className={className} />,
|
||||
gcp_vertex_ai: (className) => <VertexAIIcon className={className} />,
|
||||
ollama: (className) => <OllamaIcon className={className} />,
|
||||
openrouter: (className) => <OpenRouterIcon className={className} />,
|
||||
snowflake: (className) => <SnowflakeIcon className={className} />,
|
||||
xai: (className) => <XAIIcon className={className} />,
|
||||
lmstudio: (className) => <OllamaIcon className={className} />,
|
||||
};
|
||||
|
||||
function normalizeProviderId(providerId: string) {
|
||||
@@ -482,10 +485,11 @@ export function getProviderIcon(
|
||||
return NORMALIZED_PROVIDER_ICON_MAP[normalizedId](className);
|
||||
}
|
||||
|
||||
for (const [key, render] of Object.entries(NORMALIZED_PROVIDER_ICON_MAP)) {
|
||||
if (normalizedId.includes(key)) {
|
||||
return render(className);
|
||||
}
|
||||
const fallback = Object.entries(NORMALIZED_PROVIDER_ICON_MAP).find(
|
||||
([providerFamily]) => normalizedId.includes(providerFamily),
|
||||
)?.[1];
|
||||
if (fallback) {
|
||||
return fallback(className);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -59,7 +59,7 @@ function SelectContent({
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground shadow-popover data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-overlay border",
|
||||
"bg-popover text-popover-foreground shadow-popover data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-[70] max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-overlay border",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className,
|
||||
|
||||
Reference in New Issue
Block a user