From 2dbab2c1fed83a6d1f35639b23c45b30f9e5a109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sat, 30 May 2026 01:27:38 -0400 Subject: [PATCH] Use a free-text model id for providers without an enumerable model list --- .../src/renderer/src/app/Onboarding.tsx | 13 ++++++++++--- .../renderer/src/pages/settings/ProvidersPanel.tsx | 2 +- AgentCompany-New/src/shared/domain.ts | 4 ++++ AgentCompany-New/src/shared/provider-catalog.ts | 14 +++++++++++++- .../tests/contract/provider-contracts.test.ts | 1 + 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/AgentCompany-New/src/renderer/src/app/Onboarding.tsx b/AgentCompany-New/src/renderer/src/app/Onboarding.tsx index 0b746283..edad9839 100644 --- a/AgentCompany-New/src/renderer/src/app/Onboarding.tsx +++ b/AgentCompany-New/src/renderer/src/app/Onboarding.tsx @@ -297,6 +297,7 @@ function ProviderCredentialFields({ }) { const isOAuthLike = selected.authType === "oauth_pkce" || selected.authType === "account_subscription"; const isCustom = Boolean(selected.customEndpoint); + const live = selected.supportsLiveModels; const [fetched, setFetched] = useState(null); const [loading, setLoading] = useState(false); const [loadError, setLoadError] = useState(null); @@ -340,7 +341,7 @@ function ProviderCredentialFields({ buildInput={() => ({ providerKey: selected.key })} onComplete={(secret) => { onChange({ providerSecret: secret ?? "" }); - if (secret) void loadModels(secret); + if (secret && live) void loadModels(secret); }} /> ) : isCustom ? ( @@ -374,7 +375,7 @@ function ProviderCredentialFields({ /> )} - {!isOAuthLike && ( + {!isOAuthLike && live && ( @@ -396,7 +397,7 @@ function ProviderCredentialFields({ onChange({ providerModel: event.target.value })} /> )} @@ -406,6 +407,12 @@ function ProviderCredentialFields({ ); } +function modelPlaceholder(live: boolean, isOAuthLike: boolean, loading: boolean): string { + if (!live) return "Type the model id (e.g. gpt-5.5)"; + if (loading || isOAuthLike) return "Loading…"; + return "Click “Get models”, or type a model id"; +} + function authBadge(authType: string): string { if (authType === "api_key") return "API key"; if (authType === "oauth_pkce") return "OAuth"; diff --git a/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx index ad2b7862..d8988a6c 100644 --- a/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx +++ b/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx @@ -359,7 +359,7 @@ function ModelPicker({ entry, provider }: { entry: ProviderCatalogEntry; provide const [error, setError] = useState(null); const [value, setValue] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? ""); const models = fetched ?? entry.models; - const connected = Boolean(provider.credentialId) && provider.enabled && provider.authState === "configured"; + const connected = entry.supportsLiveModels && Boolean(provider.credentialId) && provider.enabled && provider.authState === "configured"; const refresh = async () => { setLoading(true); diff --git a/AgentCompany-New/src/shared/domain.ts b/AgentCompany-New/src/shared/domain.ts index 86e48c72..a454b39e 100644 --- a/AgentCompany-New/src/shared/domain.ts +++ b/AgentCompany-New/src/shared/domain.ts @@ -292,6 +292,10 @@ export interface ProviderCatalogEntry { signInUrl: string | null; credentialHelp: string; models: ProviderCatalogModel[]; + /** True when the provider exposes a real models endpoint we can fetch from. + * When false (e.g. the Codex subscription, which has no enumerable list), the + * UI offers a free-text model id instead of a fixed dropdown. */ + supportsLiveModels: boolean; /** Set for user-configured OpenAI/Anthropic-compatible endpoints: the UI * collects a base URL + API key + model instead of a fixed provider form. */ customEndpoint?: "openai" | "anthropic"; diff --git a/AgentCompany-New/src/shared/provider-catalog.ts b/AgentCompany-New/src/shared/provider-catalog.ts index d5a2be3a..29b339e2 100644 --- a/AgentCompany-New/src/shared/provider-catalog.ts +++ b/AgentCompany-New/src/shared/provider-catalog.ts @@ -1,7 +1,7 @@ import { PROVIDER_AUTH_TYPE, type ProviderCatalogEntry } from "./domain"; import { listProviderModels } from "./provider-models"; -const RAW_CATALOG: Omit[] = [ +const RAW_CATALOG: Omit[] = [ { key: "openai", label: "OpenAI", @@ -150,7 +150,19 @@ const RAW_CATALOG: Omit[] = [ }, ]; +// Providers with no enumerable models endpoint: the Codex subscription and the +// browser-token / cloud connectors. Their model id is entered as free text. +const CURATED_MODEL_PROVIDERS = new Set([ + "github-copilot", + "google-antigravity", + "gpt-account", + "azure-openai", + "aws-bedrock", + "vertex-ai", +]); + export const PROVIDER_CATALOG: ProviderCatalogEntry[] = RAW_CATALOG.map((entry) => ({ ...entry, models: listProviderModels(entry.key), + supportsLiveModels: !CURATED_MODEL_PROVIDERS.has(entry.key), })); diff --git a/AgentCompany-New/tests/contract/provider-contracts.test.ts b/AgentCompany-New/tests/contract/provider-contracts.test.ts index 0c531751..a38a69cd 100644 --- a/AgentCompany-New/tests/contract/provider-contracts.test.ts +++ b/AgentCompany-New/tests/contract/provider-contracts.test.ts @@ -98,6 +98,7 @@ describe("provider contracts", () => { const adapter = providerAdapters[provider.key]; const authProfile = adapter.parseAuthProfile(PROVIDER_SECRET_SAMPLES[provider.key]); expect(typeof adapter.listModels).toBe("function"); + expect(provider.supportsLiveModels).toBe(!CURATED_MODEL_PROVIDERS.has(provider.key)); if (CURATED_MODEL_PROVIDERS.has(provider.key)) { await expect(adapter.listModels({ authProfile, model: provider.defaultModel })).resolves.toEqual(provider.models); }