Add canonical per-provider model lists with picker UI

The catalog only carried one defaultModel string per provider, which
forced users to type model ids by hand and made the onboarding /
providers panel feel like the original AgentCompany was missing models.

- src/shared/provider-models.ts holds the canonical id/name list per
  provider key (~165 entries across OpenAI, Anthropic, xAI, Groq,
  GitHub Copilot, Antigravity, GPT account, Azure, Bedrock, Vertex)
- ProviderCatalogEntry now exposes a models[] array
- Onboarding ProviderStep renders a <select> populated from the active
  provider's catalog instead of a freeform text input
- ProvidersPanel connect dialog uses the same picker for API key,
  OAuth, and account auth flows
- TeamPage hire dialog adds a <datalist> with every catalog model so
  manual entry is autocompletable
This commit is contained in:
ZacharyZhang-NY committed 2026-05-21 21:39:36 -04:00
1 parent dc791ec984
commit 2c3760ca11
6 files changed
+257 -6

No files matched your search

@@ -266,7 +266,19 @@ function ProviderStep({
/>
</Field>
<Field label="Model">
<input className="input" value={draft.providerModel} onChange={(event) => onChange({ providerModel: event.target.value })} />
{providers.find((entry) => entry.key === draft.providerKey)?.models.length ? (
<select
className="input"
value={draft.providerModel}
onChange={(event) => onChange({ providerModel: event.target.value })}
>
{providers.find((entry) => entry.key === draft.providerKey)?.models.map((model) => (
<option key={model.id} value={model.id}>{model.name}</option>
))}
</select>
) : (
<input className="input" value={draft.providerModel} onChange={(event) => onChange({ providerModel: event.target.value })} />
)}
</Field>
<Actions onBack={onBack} onNext={onNext} />
</div>
@@ -113,7 +113,21 @@ export function TeamPage({ snapshot, actions }: PageProps) {
{providers.map((provider) => <option key={provider.id} value={provider.id}>{provider.label}</option>)}
</select>
</Field>
<Field label="Model"><input className="input" name="modelId" /></Field>
<Field label="Model">
<input
className="input"
name="modelId"
list="agent-model-suggestions"
placeholder="Leave blank to inherit provider default"
/>
</Field>
<datalist id="agent-model-suggestions">
{snapshot.providerCatalog.flatMap((entry) =>
entry.models.map((model) => (
<option key={`${entry.key}-${model.id}`} value={model.id}>{entry.label} · {model.name}</option>
)),
)}
</datalist>
<Field label="Monthly budget"><input className="input" name="budget" type="number" min="0" step="1" defaultValue="250" /></Field>
<Field label="Seniority">
<select className="input" name="roleLevel" defaultValue="staff">
@@ -125,7 +125,7 @@ function ProviderConnectFields({
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="API key"><input className="input" name="secret" type="password" required autoFocus /></Field>
<Field label="Default model"><input className="input" name="model" defaultValue={provider.model ?? entry.defaultModel ?? ""} /></Field>
<ModelPicker entry={entry} provider={provider} />
</>
);
}
@@ -140,13 +140,33 @@ function ProviderConnectFields({
{entry.signInUrl && <span className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{entry.signInUrl}</span>}
</div>
<Field label="Access token"><input className="input" name="secret" type="password" required /></Field>
<Field label="Default model"><input className="input" name="model" defaultValue={provider.model ?? entry.defaultModel ?? ""} /></Field>
<ModelPicker entry={entry} provider={provider} />
</>
);
}
return <CloudFieldsForm entry={entry} provider={provider} />;
}
function ModelPicker({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
const defaultValue = provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? "";
if (entry.models.length === 0) {
return (
<Field label="Default model">
<input className="input" name="model" defaultValue={defaultValue} />
</Field>
);
}
return (
<Field label="Default model">
<select className="input" name="model" defaultValue={defaultValue}>
{entry.models.map((model) => (
<option key={model.id} value={model.id}>{model.name}</option>
))}
</select>
</Field>
);
}
function CloudFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
const [state, setState] = useState<CloudFields>(CLOUD_DEFAULTS);
const update = (patch: Partial<CloudFields>) => setState((current) => ({ ...current, ...patch }));
+6
View File
@@ -273,6 +273,11 @@ export interface ProviderRecord {
createdAt: string;
}
export interface ProviderCatalogModel {
id: string;
name: string;
}
export interface ProviderCatalogEntry {
key: string;
label: string;
@@ -281,6 +286,7 @@ export interface ProviderCatalogEntry {
capabilityTags: string[];
signInUrl: string | null;
credentialHelp: string;
models: ProviderCatalogModel[];
}
export interface ProviderRequestRecord {
@@ -1,11 +1,12 @@
import { PROVIDER_AUTH_TYPE, type ProviderCatalogEntry } from "./domain";
import { listProviderModels } from "./provider-models";
export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
const RAW_CATALOG: Omit<ProviderCatalogEntry, "models">[] = [
{
key: "openai",
label: "OpenAI",
authType: PROVIDER_AUTH_TYPE.API_KEY,
defaultModel: "gpt-5.1",
defaultModel: "gpt-5.4",
capabilityTags: ["responses", "tools", "background"],
signInUrl: null,
credentialHelp: "Generate an API key at platform.openai.com → API keys.",
@@ -92,3 +93,8 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
credentialHelp: "Provide GCP project id, region, and a service-account JSON key with aiplatform.user role.",
},
];
export const PROVIDER_CATALOG: ProviderCatalogEntry[] = RAW_CATALOG.map((entry) => ({
...entry,
models: listProviderModels(entry.key),
}));
@@ -0,0 +1,193 @@
export interface ProviderModelEntry {
id: string;
name: string;
}
export const PROVIDER_MODELS: Record<string, ProviderModelEntry[]> = {
openai: [
{ id: "gpt-5.4-pro", name: "GPT-5.4 Pro" },
{ id: "gpt-5.4", name: "GPT-5.4" },
{ id: "gpt-5.4-mini", name: "GPT-5.4 mini" },
{ id: "gpt-5.4-nano", name: "GPT-5.4 nano" },
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
{ id: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark" },
{ id: "gpt-5.3-chat-latest", name: "GPT-5.3 Chat" },
{ id: "gpt-5.2-pro", name: "GPT-5.2 Pro" },
{ id: "gpt-5.2", name: "GPT-5.2" },
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
{ id: "gpt-5.2-chat-latest", name: "GPT-5.2 Chat" },
{ id: "gpt-5.1", name: "GPT-5.1" },
{ id: "gpt-5.1-codex", name: "GPT-5.1 Codex" },
{ id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" },
{ id: "gpt-5.1-codex-mini", name: "GPT-5.1 Codex mini" },
{ id: "gpt-5.1-chat-latest", name: "GPT-5.1 Chat" },
{ id: "gpt-5-pro", name: "GPT-5 Pro" },
{ id: "gpt-5", name: "GPT-5" },
{ id: "gpt-5-codex", name: "GPT-5 Codex" },
{ id: "gpt-5-mini", name: "GPT-5 mini" },
{ id: "gpt-5-nano", name: "GPT-5 nano" },
{ id: "gpt-5-chat-latest", name: "GPT-5 Chat" },
{ id: "o4-mini", name: "o4-mini" },
{ id: "o4-mini-deep-research", name: "o4-mini deep research" },
{ id: "o3", name: "o3" },
{ id: "o3-pro", name: "o3-pro" },
{ id: "o3-mini", name: "o3-mini" },
{ id: "o3-deep-research", name: "o3 deep research" },
{ id: "o1", name: "o1" },
{ id: "o1-pro", name: "o1-pro" },
{ id: "gpt-4.1", name: "GPT-4.1" },
{ id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
{ id: "gpt-4.1-nano", name: "GPT-4.1 nano" },
{ id: "gpt-4o", name: "GPT-4o" },
{ id: "gpt-4o-mini", name: "GPT-4o mini" },
{ id: "gpt-4-turbo", name: "GPT-4 Turbo" },
{ id: "gpt-4", name: "GPT-4" },
],
anthropic: [
{ id: "claude-opus-4-7", name: "Claude Opus 4.7" },
{ id: "claude-opus-4-6", name: "Claude Opus 4.6" },
{ id: "claude-opus-4-5", name: "Claude Opus 4.5 (latest)" },
{ id: "claude-opus-4-1", name: "Claude Opus 4.1 (latest)" },
{ id: "claude-opus-4-0", name: "Claude Opus 4 (latest)" },
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5 (latest)" },
{ id: "claude-sonnet-4-0", name: "Claude Sonnet 4 (latest)" },
{ id: "claude-haiku-4-5", name: "Claude Haiku 4.5 (latest)" },
{ id: "claude-3-7-sonnet-20250219", name: "Claude Sonnet 3.7" },
{ id: "claude-3-5-sonnet-20241022", name: "Claude Sonnet 3.5 v2" },
{ id: "claude-3-5-sonnet-20240620", name: "Claude Sonnet 3.5" },
{ id: "claude-3-5-haiku-latest", name: "Claude Haiku 3.5 (latest)" },
{ id: "claude-3-5-haiku-20241022", name: "Claude Haiku 3.5" },
{ id: "claude-3-haiku-20240307", name: "Claude Haiku 3" },
{ id: "claude-3-sonnet-20240229", name: "Claude Sonnet 3" },
{ id: "claude-3-opus-20240229", name: "Claude Opus 3" },
],
xai: [
{ id: "grok-4", name: "Grok 4" },
{ id: "grok-4-fast", name: "Grok 4 Fast" },
{ id: "grok-4-fast-non-reasoning", name: "Grok 4 Fast (Non-Reasoning)" },
{ id: "grok-4-1-fast", name: "Grok 4.1 Fast" },
{ id: "grok-4-1-fast-non-reasoning", name: "Grok 4.1 Fast (Non-Reasoning)" },
{ id: "grok-4.20-0309-reasoning", name: "Grok 4.20 (Reasoning)" },
{ id: "grok-4.20-0309-non-reasoning", name: "Grok 4.20 (Non-Reasoning)" },
{ id: "grok-code-fast-1", name: "Grok Code Fast 1" },
{ id: "grok-3", name: "Grok 3" },
{ id: "grok-3-fast", name: "Grok 3 Fast" },
{ id: "grok-3-mini", name: "Grok 3 Mini" },
{ id: "grok-3-mini-fast", name: "Grok 3 Mini Fast" },
{ id: "grok-2", name: "Grok 2" },
{ id: "grok-2-vision", name: "Grok 2 Vision" },
{ id: "grok-beta", name: "Grok Beta" },
{ id: "grok-vision-beta", name: "Grok Vision Beta" },
],
groq: [
{ id: "groq/compound", name: "Compound" },
{ id: "groq/compound-mini", name: "Compound Mini" },
{ id: "moonshotai/kimi-k2-instruct-0905", name: "Kimi K2 Instruct 0905" },
{ id: "moonshotai/kimi-k2-instruct", name: "Kimi K2 Instruct" },
{ id: "openai/gpt-oss-120b", name: "GPT OSS 120B" },
{ id: "openai/gpt-oss-20b", name: "GPT OSS 20B" },
{ id: "openai/gpt-oss-safeguard-20b", name: "Safety GPT OSS 20B" },
{ id: "llama-3.3-70b-versatile", name: "Llama 3.3 70B Versatile" },
{ id: "llama-3.1-8b-instant", name: "Llama 3.1 8B Instant" },
{ id: "meta-llama/llama-4-maverick-17b-128e-instruct", name: "Llama 4 Maverick 17B" },
{ id: "meta-llama/llama-4-scout-17b-16e-instruct", name: "Llama 4 Scout 17B" },
{ id: "qwen-qwq-32b", name: "Qwen QwQ 32B" },
{ id: "qwen/qwen3-32b", name: "Qwen3 32B" },
{ id: "mistral-saba-24b", name: "Mistral Saba 24B" },
{ id: "deepseek-r1-distill-llama-70b", name: "DeepSeek R1 Distill Llama 70B" },
{ id: "gemma2-9b-it", name: "Gemma 2 9B" },
],
"github-copilot": [
{ id: "claude-opus-4.7", name: "Claude Opus 4.7" },
{ id: "claude-opus-4.6", name: "Claude Opus 4.6" },
{ id: "claude-opus-4.5", name: "Claude Opus 4.5" },
{ id: "claude-sonnet-4.6", name: "Claude Sonnet 4.6" },
{ id: "claude-sonnet-4.5", name: "Claude Sonnet 4.5" },
{ id: "claude-sonnet-4", name: "Claude Sonnet 4" },
{ id: "claude-haiku-4.5", name: "Claude Haiku 4.5" },
{ id: "gpt-5.4", name: "GPT-5.4" },
{ id: "gpt-5.4-mini", name: "GPT-5.4 mini" },
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
{ id: "gpt-5.2", name: "GPT-5.2" },
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
{ id: "gpt-5.1", name: "GPT-5.1" },
{ id: "gpt-5.1-codex", name: "GPT-5.1 Codex" },
{ id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" },
{ id: "gpt-5.1-codex-mini", name: "GPT-5.1 Codex Mini" },
{ id: "gpt-5", name: "GPT-5" },
{ id: "gpt-5-mini", name: "GPT-5 mini" },
{ id: "gpt-4.1", name: "GPT-4.1" },
{ id: "gpt-4o", name: "GPT-4o" },
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
{ id: "gemini-3-pro-preview", name: "Gemini 3 Pro Preview" },
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash" },
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
{ id: "grok-code-fast-1", name: "Grok Code Fast 1" },
],
"google-antigravity": [
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6 (Antigravity)" },
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5 (Antigravity)" },
{ id: "claude-sonnet-4-5-thinking", name: "Claude Sonnet 4.5 Thinking" },
{ id: "claude-opus-4-6-thinking", name: "Claude Opus 4.6 Thinking" },
{ id: "claude-opus-4-5-thinking", name: "Claude Opus 4.5 Thinking" },
{ id: "gemini-3.1-pro-high", name: "Gemini 3.1 Pro High" },
{ id: "gemini-3.1-pro-low", name: "Gemini 3.1 Pro Low" },
{ id: "gemini-3-flash", name: "Gemini 3 Flash" },
{ id: "gpt-oss-120b-medium", name: "GPT-OSS 120B Medium" },
],
"gpt-account": [
{ id: "gpt-5.4", name: "GPT-5.4" },
{ id: "gpt-5.4-mini", name: "GPT-5.4 Mini" },
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
{ id: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark" },
{ id: "gpt-5.2", name: "GPT-5.2" },
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
{ id: "gpt-5.1", name: "GPT-5.1" },
{ id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" },
{ id: "gpt-5.1-codex-mini", name: "GPT-5.1 Codex Mini" },
],
"azure-openai": [
{ id: "gpt-5.4", name: "GPT-5.4" },
{ id: "gpt-5.4-mini", name: "GPT-5.4 mini" },
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
{ id: "gpt-5.2", name: "GPT-5.2" },
{ id: "gpt-5.1", name: "GPT-5.1" },
{ id: "gpt-5", name: "GPT-5" },
{ id: "gpt-5-codex", name: "GPT-5 Codex" },
{ id: "gpt-4.1", name: "GPT-4.1" },
{ id: "gpt-4o", name: "GPT-4o" },
{ id: "o4-mini", name: "o4-mini" },
{ id: "o3", name: "o3" },
],
"aws-bedrock": [
{ id: "anthropic.claude-opus-4-7", name: "Claude Opus 4.7" },
{ id: "anthropic.claude-opus-4-6-v1", name: "Claude Opus 4.6" },
{ id: "anthropic.claude-opus-4-5-20251101-v1:0", name: "Claude Opus 4.5" },
{ id: "anthropic.claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
{ id: "anthropic.claude-sonnet-4-5-20250929-v1:0", name: "Claude Sonnet 4.5" },
{ id: "anthropic.claude-haiku-4-5-20251001-v1:0", name: "Claude Haiku 4.5" },
{ id: "amazon.nova-2-lite-v1:0", name: "Nova 2 Lite" },
{ id: "amazon.nova-premier-v1:0", name: "Nova Premier" },
{ id: "amazon.nova-pro-v1:0", name: "Nova Pro" },
{ id: "deepseek.v3.2", name: "DeepSeek V3.2" },
{ id: "meta.llama3-3-70b-instruct-v1:0", name: "Llama 3.3 70B" },
{ id: "moonshotai.kimi-k2.5", name: "Kimi K2.5" },
],
"vertex-ai": [
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
{ id: "gemini-3-pro-preview", name: "Gemini 3 Pro Preview" },
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview" },
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
{ id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash Lite" },
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
{ id: "gemini-2.0-flash-lite", name: "Gemini 2.0 Flash Lite" },
{ id: "gemini-1.5-pro", name: "Gemini 1.5 Pro" },
{ id: "gemini-1.5-flash", name: "Gemini 1.5 Flash" },
],
};
export function listProviderModels(providerKey: string): ProviderModelEntry[] {
return PROVIDER_MODELS[providerKey] ?? [];
}