From 96c95a45b6dba2e2986c1373da28bde6b88bde8e Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Wed, 4 Nov 2026 13:38:47 -0800 Subject: [PATCH 1/2] fix(client): read login base URLs from a map keyed by login plugin --- packages/client/src/client/credentials.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/client/src/client/credentials.ts b/packages/client/src/client/credentials.ts index b86df94d..02880102 100644 --- a/packages/client/src/client/credentials.ts +++ b/packages/client/src/client/credentials.ts @@ -65,15 +65,19 @@ export type ModelLoginState = Readonly< >; export type { ModelLoginPlugin }; +// CODEX_BASE_URL and XAI_OAUTH_PROXY_BASE_URL: the only endpoints these logins' tokens work on. +const LOGIN_BASE_URLS: Record = { + codex: "https://chatgpt.com/backend-api", + xai: "https://cli-chat-proxy.grok.com/v1", +}; + const DEFAULT_BASE_URLS: Record = { openai: "https://api.openai.com/v1", "openai-responses": "https://api.openai.com/v1", anthropic: "https://api.anthropic.com/v1", "google-genai": "https://generativelanguage.googleapis.com/v1beta", "openai-compatible": "", - // CODEX_BASE_URL and XAI_OAUTH_PROXY_BASE_URL: the only endpoints these logins' tokens work on. - codex: "https://chatgpt.com/backend-api", - xai: "https://cli-chat-proxy.grok.com/v1", + ...LOGIN_BASE_URLS, }; function uniqueSuffix(): string { @@ -205,7 +209,7 @@ export function buildCredentialsMethods( // tenant-owned credential. A new credential name per sign-in, because a tenant-owned login // never replaces an existing credential. async startModelLogin(tenantId, plugin, name, signal) { - const baseURL = DEFAULT_BASE_URLS[plugin]!; + const baseURL = LOGIN_BASE_URLS[plugin]; const nativeProvider = await ensureNativeProvider( tenantId, plugin, @@ -251,7 +255,7 @@ export function buildCredentialsMethods( const created = (await request(`${root}/catalog/providers`, signal, { name: `${name} ${uniqueSuffix()}`, plugin, - baseURL: DEFAULT_BASE_URLS[plugin], + baseURL: LOGIN_BASE_URLS[plugin], credentialId, })) as { id?: unknown }; if (typeof created?.id !== "string") From 79938b785142d32548d556fafc5fea59f809c64d Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Wed, 4 Nov 2026 13:38:49 -0800 Subject: [PATCH 2/2] fix(web): refuse unreadable custom models instead of sending null --- apps/web/src/config.ts | 2 ++ apps/web/src/pages/custom-providers.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/web/src/config.ts b/apps/web/src/config.ts index efcc491b..626b8b79 100644 --- a/apps/web/src/config.ts +++ b/apps/web/src/config.ts @@ -1053,6 +1053,8 @@ export const wording = { keylessHint: `For servers that need no API key; ${productName} sends a placeholder.`, custom: { tile: "Custom endpoint", + modelsUnreadable: + "This provider's models could not be read, so nothing was saved.", tileHint: "Your own server, on the API it speaks.", addTitle: "Add a custom endpoint", addLead: "Connect your own server. It is saved once the test passes.", diff --git a/apps/web/src/pages/custom-providers.tsx b/apps/web/src/pages/custom-providers.tsx index 5179db70..575097b3 100644 --- a/apps/web/src/pages/custom-providers.tsx +++ b/apps/web/src/pages/custom-providers.tsx @@ -443,6 +443,10 @@ export function useCustomProviderForm(apis: Apis, original?: EditableProvider) { ), save: async (): Promise => { + const models = parsedModels( + original ? original.models.map(modelDraftOf) : [draft.model], + ); + if (models === null) throw new Error(copy.custom.modelsUnreadable); if (tests) await testConnection(draft); const tenantId = await workspaceTenantId(); const client = productClient(); @@ -450,14 +454,14 @@ export function useCustomProviderForm(apis: Apis, original?: EditableProvider) { if (!original) { await client.createCustomProvider(tenantId, { name, - models: parsedModels([draft.model])!, + models, connection: endpointOf(draft), }); return; } await client.updateCustomProvider(tenantId, original.id, { name, - models: parsedModels(original.models.map(modelDraftOf))!, + models, ...(tests ? { connection: endpointOf(draft) } : {}), }); },