feat: goose2 add support for custom providers in ui & acp (#8924)

This commit is contained in:
Kalvin C
2026-04-30 14:05:10 -07:00
committed by GitHub
parent 7d69e1448d
commit ef73610cc1
57 changed files with 7047 additions and 854 deletions
@@ -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.",
+11 -9
View File
@@ -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>
);
}
+19 -17
View File
@@ -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;
+1 -1
View File
@@ -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,