diff --git a/apps/code/src/main/services/agent/service.test.ts b/apps/code/src/main/services/agent/service.test.ts index fe726d994..5e277e6ad 100644 --- a/apps/code/src/main/services/agent/service.test.ts +++ b/apps/code/src/main/services/agent/service.test.ts @@ -89,9 +89,12 @@ vi.mock("@posthog/agent/posthog-api", () => ({ })); vi.mock("@posthog/agent/gateway-models", () => ({ + DEFAULT_GATEWAY_MODEL: "claude-opus-4-8", + DEFAULT_CODEX_MODEL: "gpt-5.5", fetchGatewayModels: vi.fn().mockResolvedValue([]), formatGatewayModelName: vi.fn(), getProviderName: vi.fn(), + isBlockedModelId: vi.fn().mockReturnValue(false), })); vi.mock("@posthog/agent/adapters/claude/session/jsonl-hydration", () => ({ diff --git a/apps/code/src/main/services/llm-gateway/schemas.ts b/apps/code/src/main/services/llm-gateway/schemas.ts index b14fffd25..7c569c895 100644 --- a/apps/code/src/main/services/llm-gateway/schemas.ts +++ b/apps/code/src/main/services/llm-gateway/schemas.ts @@ -1,3 +1,4 @@ +import { DEFAULT_GATEWAY_MODEL } from "@posthog/agent/gateway-models"; import { z } from "zod"; export const llmMessageSchema = z.object({ @@ -11,7 +12,7 @@ export const promptInput = z.object({ system: z.string().optional(), messages: z.array(llmMessageSchema), maxTokens: z.number().optional(), - model: z.string().default("claude-haiku-4-5"), + model: z.string().default(DEFAULT_GATEWAY_MODEL), }); export type PromptInput = z.infer; diff --git a/apps/code/src/main/services/llm-gateway/service.ts b/apps/code/src/main/services/llm-gateway/service.ts index 2a6ac5a26..11813e474 100644 --- a/apps/code/src/main/services/llm-gateway/service.ts +++ b/apps/code/src/main/services/llm-gateway/service.ts @@ -1,3 +1,4 @@ +import { DEFAULT_GATEWAY_MODEL } from "@posthog/agent/gateway-models"; import { getGatewayInvalidatePlanCacheUrl, getGatewayUsageUrl, @@ -51,7 +52,7 @@ export class LlmGatewayService { const { system, maxTokens, - model = "claude-haiku-4-5", + model = DEFAULT_GATEWAY_MODEL, signal, timeoutMs = 60_000, } = options; diff --git a/apps/code/src/renderer/api/posthogClient.test.ts b/apps/code/src/renderer/api/posthogClient.test.ts index f684aa266..2e0f29964 100644 --- a/apps/code/src/renderer/api/posthogClient.test.ts +++ b/apps/code/src/renderer/api/posthogClient.test.ts @@ -115,11 +115,11 @@ describe("PostHogAPIClient", () => { await expect( client.runTaskInCloud("task-123", "feature/legacy-effort", { adapter: "claude", - model: "claude-opus-4-6", + model: "claude-opus-4-8", reasoningLevel: "minimal", }), ).rejects.toThrow( - "Reasoning effort 'minimal' is not supported for claude model 'claude-opus-4-6'.", + "Reasoning effort 'minimal' is not supported for claude model 'claude-opus-4-8'.", ); expect(post).not.toHaveBeenCalled(); diff --git a/apps/code/src/renderer/features/message-editor/components/PromptInput.stories.tsx b/apps/code/src/renderer/features/message-editor/components/PromptInput.stories.tsx index 9fd2f9938..bc67302db 100644 --- a/apps/code/src/renderer/features/message-editor/components/PromptInput.stories.tsx +++ b/apps/code/src/renderer/features/message-editor/components/PromptInput.stories.tsx @@ -15,13 +15,13 @@ const mockModelOption = { id: "model", name: "Model", type: "select" as const, - currentValue: "gpt-5.4", + currentValue: "gpt-5.5", options: [ { group: "recommended", name: "Recommended", options: [ - { value: "gpt-5.4", name: "GPT 5.4" }, + { value: "gpt-5.5", name: "gpt-5.5" }, { value: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, ], }, @@ -29,9 +29,8 @@ const mockModelOption = { group: "other", name: "Other", options: [ - { value: "claude-opus-4-6", name: "Claude Opus 4.6" }, + { value: "claude-opus-4-8", name: "Claude Opus 4.8" }, { value: "o3-pro", name: "o3-pro" }, - { value: "claude-haiku-4-5", name: "Claude Haiku 4.5" }, ], }, ], diff --git a/apps/mobile/src/features/inbox/components/TinderView.tsx b/apps/mobile/src/features/inbox/components/TinderView.tsx index 7eb2b5fd9..ae468b8c4 100644 --- a/apps/mobile/src/features/inbox/components/TinderView.tsx +++ b/apps/mobile/src/features/inbox/components/TinderView.tsx @@ -17,6 +17,7 @@ import { } from "react-native-safe-area-context"; import { MarkdownText } from "@/features/chat/components/MarkdownText"; import { createTask, runTaskInCloud } from "@/features/tasks/api"; +import { DEFAULT_MODEL } from "@/features/tasks/composer/options"; import type { CreateTaskOptions, RepositoryOption, @@ -206,7 +207,7 @@ export function TinderView({ await runTaskInCloud(task.id, { pendingUserMessage: prompt, runtimeAdapter: "claude", - model: "claude-opus-4-7", + model: DEFAULT_MODEL, initialPermissionMode: "plan", runSource: "signal_report", signalReportId: report.id, diff --git a/apps/mobile/src/features/tasks/api.ts b/apps/mobile/src/features/tasks/api.ts index d09d2afc5..2375aa620 100644 --- a/apps/mobile/src/features/tasks/api.ts +++ b/apps/mobile/src/features/tasks/api.ts @@ -406,7 +406,7 @@ export interface RunTaskInCloudOptions { mode?: "interactive" | "background"; /** Adapter to use on the cloud runner. Currently only "claude" on mobile. */ runtimeAdapter?: "claude" | "codex"; - /** Gateway model ID, e.g. "claude-opus-4-7". */ + /** Gateway model ID, e.g. "claude-opus-4-8". */ model?: string; /** Reasoning effort: "low" | "medium" | "high" (model-dependent). */ reasoningEffort?: string; diff --git a/apps/mobile/src/features/tasks/composer/options.ts b/apps/mobile/src/features/tasks/composer/options.ts index 885554cf3..6e40ead38 100644 --- a/apps/mobile/src/features/tasks/composer/options.ts +++ b/apps/mobile/src/features/tasks/composer/options.ts @@ -37,8 +37,8 @@ export interface ModelOption { export const MODELS: ModelOption[] = [ { - value: "claude-opus-4-7", - label: "Claude Opus 4.7", + value: "claude-opus-4-8", + label: "Claude Opus 4.8", description: "Most capable, slower", supportsReasoning: true, }, @@ -48,12 +48,6 @@ export const MODELS: ModelOption[] = [ description: "Balanced", supportsReasoning: true, }, - { - value: "claude-haiku-4-5", - label: "Claude Haiku 4.5", - description: "Fastest", - supportsReasoning: false, - }, ]; export const REASONING_LEVELS: { @@ -68,7 +62,7 @@ export const REASONING_LEVELS: { ]; export const DEFAULT_EXECUTION_MODE: ExecutionMode = "plan"; -export const DEFAULT_MODEL = "claude-opus-4-7"; +export const DEFAULT_MODEL = "claude-opus-4-8"; export const DEFAULT_REASONING: ReasoningEffort = "high"; export function modelLabel(value: string): string { diff --git a/packages/agent/src/adapters/claude/claude-agent.ts b/packages/agent/src/adapters/claude/claude-agent.ts index 7c960d4f7..cf900998d 100644 --- a/packages/agent/src/adapters/claude/claude-agent.ts +++ b/packages/agent/src/adapters/claude/claude-agent.ts @@ -1182,7 +1182,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent { // For model options, fall back to alias resolution when exact match fails. // This lets callers use human-friendly aliases like "opus" or "sonnet" - // instead of full model IDs like "claude-opus-4-6". + // instead of full model IDs like "claude-opus-4-8". if (!validValue && params.configId === "model") { const resolved = resolveModelPreference(params.value, allValues); if (resolved) { diff --git a/packages/agent/src/adapters/claude/session/jsonl-hydration.test.ts b/packages/agent/src/adapters/claude/session/jsonl-hydration.test.ts index 3fad3f780..9d42cfe2a 100644 --- a/packages/agent/src/adapters/claude/session/jsonl-hydration.test.ts +++ b/packages/agent/src/adapters/claude/session/jsonl-hydration.test.ts @@ -376,7 +376,7 @@ describe("conversationTurnsToJsonlEntries", () => { { type: "text", text: "running" }, ]); expect(conv[0].message.stop_reason).toBeNull(); - expect(conv[0].message.model).toBe("claude-opus-4-6"); + expect(conv[0].message.model).toBe("claude-opus-4-8"); expect(conv[0].message.id).toMatch(/^msg_01[A-Za-z0-9]{24}$/); expect(conv[1].type).toBe("assistant"); @@ -490,13 +490,13 @@ describe("conversationTurnsToJsonlEntries", () => { { role: "user", content: [{ type: "text", text: "hi" }] }, { role: "assistant", content: [{ type: "text", text: "hello" }] }, ], - { sessionId: "s", cwd: "/", model: "claude-opus-4-6", version: "3.0.0" }, + { sessionId: "s", cwd: "/", model: "claude-opus-4-7", version: "3.0.0" }, ); const conv = parseConversationEntries(lines); expect(conv[0].version).toBe("3.0.0"); expect(conv[1].version).toBe("3.0.0"); - expect(conv[1].message.model).toBe("claude-opus-4-6"); + expect(conv[1].message.model).toBe("claude-opus-4-7"); }); it("passes gitBranch, slug and permissionMode from config", () => { @@ -728,7 +728,7 @@ describe("end-to-end: S3 log entries -> JSONL output", () => { // All assistant blocks in same turn share message.id expect(msg1.id).toBe(msg2.id); expect(msg2.id).toBe(msg3.id); - expect(msg3.model).toBe("claude-opus-4-6"); + expect(msg3.model).toBe("claude-opus-4-8"); expect(msg3.id).toMatch(/^msg_01[A-Za-z0-9]{24}$/); // Verify Bash tool_result entry diff --git a/packages/agent/src/adapters/claude/session/jsonl-hydration.ts b/packages/agent/src/adapters/claude/session/jsonl-hydration.ts index 16d1a54ce..14b39a71b 100644 --- a/packages/agent/src/adapters/claude/session/jsonl-hydration.ts +++ b/packages/agent/src/adapters/claude/session/jsonl-hydration.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import type { ContentBlock } from "@agentclientprotocol/sdk"; +import { DEFAULT_GATEWAY_MODEL } from "../../../gateway-models"; import type { PostHogAPIClient } from "../../../posthog-api"; import type { StoredEntry } from "../../../types"; import { supports1MContext } from "./models"; @@ -312,7 +313,7 @@ export function conversationTurnsToJsonlEntries( ): string[] { const lines: string[] = []; let parentUuid: string | null = null; - const model = config.model ?? "claude-opus-4-6"; + const model = config.model ?? DEFAULT_GATEWAY_MODEL; const version = config.version ?? "2.1.63"; const gitBranch = config.gitBranch ?? ""; const slug = config.slug ?? generateSlug(); diff --git a/packages/agent/src/adapters/claude/session/model-config.test.ts b/packages/agent/src/adapters/claude/session/model-config.test.ts index 1225885aa..98b0b2ddf 100644 --- a/packages/agent/src/adapters/claude/session/model-config.test.ts +++ b/packages/agent/src/adapters/claude/session/model-config.test.ts @@ -15,7 +15,7 @@ const rawModelOptions = { describe("applyAvailableModelsAllowlist", () => { it("falls back to the unfiltered gateway list when every allowlisted model is unknown", () => { expect( - applyAvailableModelsAllowlist(rawModelOptions, ["claude-opus-4-5"]), + applyAvailableModelsAllowlist(rawModelOptions, ["claude-unknown-model"]), ).toEqual(rawModelOptions); }); diff --git a/packages/agent/src/adapters/claude/session/models.test.ts b/packages/agent/src/adapters/claude/session/models.test.ts index 724c4fe73..73a271721 100644 --- a/packages/agent/src/adapters/claude/session/models.test.ts +++ b/packages/agent/src/adapters/claude/session/models.test.ts @@ -14,30 +14,41 @@ describe("toSdkModelId", () => { expect(toSdkModelId("claude-opus-4-7")).toBe("opus"); expect(toSdkModelId("claude-opus-4-8")).toBe("opus"); expect(toSdkModelId("claude-sonnet-4-6")).toBe("sonnet"); - expect(toSdkModelId("claude-haiku-4-5")).toBe("haiku"); }); it("passes unknown IDs through unchanged", () => { expect(toSdkModelId("custom-model")).toBe("custom-model"); }); + + it("passes deprecated gateway IDs through unchanged", () => { + expect(toSdkModelId("claude-opus-4-6")).toBe("claude-opus-4-6"); + expect(toSdkModelId("claude-sonnet-4-5")).toBe("claude-sonnet-4-5"); + expect(toSdkModelId("claude-haiku-4-5")).toBe("claude-haiku-4-5"); + }); }); describe("model capability flags", () => { it("flags 1M context support", () => { + expect(supports1MContext("claude-opus-4-6")).toBe(false); expect(supports1MContext("claude-opus-4-7")).toBe(true); expect(supports1MContext("claude-sonnet-4-6")).toBe(true); expect(supports1MContext("claude-haiku-4-5")).toBe(false); }); it("flags effort support and xhigh-effort support", () => { - expect(supportsEffort("claude-opus-4-5")).toBe(true); + expect(supportsEffort("claude-opus-4-5")).toBe(false); + expect(supportsEffort("claude-opus-4-6")).toBe(false); expect(supportsXhighEffort("claude-opus-4-7")).toBe(true); - expect(supportsXhighEffort("claude-opus-4-5")).toBe(false); + expect(supportsXhighEffort("claude-opus-4-6")).toBe(false); expect(supportsEffort("claude-haiku-4-5")).toBe(false); }); - it("excludes MCP injection only for Haiku", () => { + it("allows MCP injection for supported Claude models", () => { expect(supportsMcpInjection("claude-opus-4-7")).toBe(true); + expect(supportsMcpInjection("claude-sonnet-4-6")).toBe(true); + }); + + it("keeps deprecated Haiku sessions excluded from MCP injection", () => { expect(supportsMcpInjection("claude-haiku-4-5")).toBe(false); }); }); @@ -45,10 +56,11 @@ describe("model capability flags", () => { describe("getEffortOptions", () => { it("returns null for models without effort support", () => { expect(getEffortOptions("claude-haiku-4-5")).toBeNull(); + expect(getEffortOptions("claude-opus-4-6")).toBeNull(); }); it("returns low/medium/high for effort-supporting models", () => { - const opts = getEffortOptions("claude-opus-4-5"); + const opts = getEffortOptions("claude-sonnet-4-6"); expect(opts?.map((o) => o.value)).toEqual(["low", "medium", "high"]); }); @@ -68,9 +80,7 @@ describe("resolveModelPreference", () => { const options = [ { value: "claude-opus-4-8", name: "Claude Opus 4.8" }, { value: "claude-opus-4-7", name: "Claude Opus 4.7" }, - { value: "claude-opus-4-6", name: "Claude Opus 4.6" }, { value: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, - { value: "claude-haiku-4-5", name: "Claude Haiku 4.5" }, ]; it("returns null for empty preference", () => { @@ -85,8 +95,8 @@ describe("resolveModelPreference", () => { }); it("matches case-insensitively on display name", () => { - expect(resolveModelPreference("claude haiku 4.5", options)).toBe( - "claude-haiku-4-5", + expect(resolveModelPreference("claude sonnet 4.6", options)).toBe( + "claude-sonnet-4-6", ); }); @@ -100,11 +110,11 @@ describe("resolveModelPreference", () => { it("refuses cross-version alias matches", () => { const optionsWithAlias = [ - { value: "opus", name: "Claude Opus 4.7" }, - { value: "claude-opus-4-6", name: "Claude Opus 4.6" }, + { value: "opus", name: "Claude Opus 4.8" }, + { value: "claude-opus-4-7", name: "Claude Opus 4.7" }, ]; - expect(resolveModelPreference("claude-opus-4-6", optionsWithAlias)).toBe( - "claude-opus-4-6", + expect(resolveModelPreference("claude-opus-4-7", optionsWithAlias)).toBe( + "claude-opus-4-7", ); }); diff --git a/packages/agent/src/adapters/claude/session/models.ts b/packages/agent/src/adapters/claude/session/models.ts index 6d8c73199..e98ce08b4 100644 --- a/packages/agent/src/adapters/claude/session/models.ts +++ b/packages/agent/src/adapters/claude/session/models.ts @@ -1,13 +1,9 @@ export const DEFAULT_MODEL = "opus"; const GATEWAY_TO_SDK_MODEL: Record = { - "claude-opus-4-5": "opus", - "claude-opus-4-6": "opus", "claude-opus-4-7": "opus", "claude-opus-4-8": "opus", - "claude-sonnet-4-5": "sonnet", "claude-sonnet-4-6": "sonnet", - "claude-haiku-4-5": "haiku", }; export function toSdkModelId(modelId: string): string { @@ -15,7 +11,6 @@ export function toSdkModelId(modelId: string): string { } const MODELS_WITH_1M_CONTEXT = new Set([ - "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-sonnet-4-6", @@ -26,15 +21,12 @@ export function supports1MContext(modelId: string): boolean { } const MODELS_WITH_EFFORT = new Set([ - "claude-opus-4-5", - "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-sonnet-4-6", ]); const MODELS_WITH_XHIGH_EFFORT = new Set([ - "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", ]); @@ -78,7 +70,7 @@ export function getEffortOptions(modelId: string): EffortOption[] | null { } // Model alias resolution — lets callers use human-friendly aliases like -// "opus" or "sonnet" instead of full model IDs like "claude-opus-4-6". +// "opus" or "sonnet" instead of full model IDs like "claude-opus-4-8". const MODEL_CONTEXT_HINT_PATTERN = /\[(\d+m)\]$/i; @@ -112,8 +104,8 @@ interface ModelOption { } // Captures a model family version such as `4-6` or `4.7` so we can keep -// `claude-opus-4-6` from being copied onto the SDK's `opus` alias when that -// alias currently resolves to a different family version (e.g. Opus 4.7). +// `claude-opus-4-7` from being copied onto the SDK's `opus` alias when that +// alias currently resolves to a different family version (e.g. Opus 4.8). const MODEL_FAMILY_VERSION_PATTERN = /\b(\d+)[-.](\d+)\b/; function extractModelFamilyVersion(s: string | undefined): string | null { diff --git a/packages/agent/src/adapters/claude/session/options.ts b/packages/agent/src/adapters/claude/session/options.ts index 610a7a257..b3c2683a7 100644 --- a/packages/agent/src/adapters/claude/session/options.ts +++ b/packages/agent/src/adapters/claude/session/options.ts @@ -183,7 +183,7 @@ function buildHooks( } /** - * Read-only Haiku-powered exploration agent. Registered under the `ph-explore` + * Read-only exploration agent. Registered under the `ph-explore` * name rather than `Explore` to work around a Claude Agent SDK bug where * `options.agents` cannot shadow built-in agent definitions. The * `createSubagentRewriteHook` rewrites `subagent_type: "Explore"` to diff --git a/packages/agent/src/adapters/codex/models.test.ts b/packages/agent/src/adapters/codex/models.test.ts new file mode 100644 index 000000000..b31a039ac --- /dev/null +++ b/packages/agent/src/adapters/codex/models.test.ts @@ -0,0 +1,8 @@ +import { describe, expect, it } from "vitest"; +import { formatCodexModelName } from "./models"; + +describe("formatCodexModelName", () => { + it("uses raw lowercase model ids", () => { + expect(formatCodexModelName("GPT-5.5")).toBe("gpt-5.5"); + }); +}); diff --git a/packages/agent/src/adapters/codex/models.ts b/packages/agent/src/adapters/codex/models.ts index 635e631c7..3264974fc 100644 --- a/packages/agent/src/adapters/codex/models.ts +++ b/packages/agent/src/adapters/codex/models.ts @@ -21,21 +21,8 @@ export function getReasoningEffortOptions( return CODEX_REASONING_EFFORT_OPTIONS; } -const CODEX_ACRONYMS: Record = { - gpt: "GPT", -}; - export function formatCodexModelName(value: string): string { - const normalized = value.replace(/(\d)-(\d)/g, "$1.$2"); - return normalized - .split("-") - .map((part) => { - const lower = part.toLowerCase(); - if (CODEX_ACRONYMS[lower]) return CODEX_ACRONYMS[lower]; - if (/^[0-9.]+$/.test(part)) return part; - return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); - }) - .join("-"); + return value.toLowerCase(); } export function normalizeCodexConfigOptions( diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index e4b3d9bfe..4456e63be 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -3,10 +3,10 @@ import { type InProcessAcpConnection, } from "./adapters/acp-connection"; import { - BLOCKED_MODELS, DEFAULT_CODEX_MODEL, DEFAULT_GATEWAY_MODEL, fetchModelsList, + isBlockedModelId, } from "./gateway-models"; import { PostHogAPIClient, type TaskRunUpdate } from "./posthog-api"; import { SessionLogWriter } from "./session-log-writer"; @@ -84,7 +84,7 @@ export class Agent { let allowedModelIds: Set | undefined; let sanitizedModel = - options.model && !BLOCKED_MODELS.has(options.model) + options.model && !isBlockedModelId(options.model) ? options.model : undefined; if (options.adapter === "codex" && gatewayConfig) { @@ -93,7 +93,7 @@ export class Agent { }); const codexModelIds = models .filter((model) => { - if (BLOCKED_MODELS.has(model.id)) return false; + if (isBlockedModelId(model.id)) return false; if (model.owned_by) { return model.owned_by === "openai"; } diff --git a/packages/agent/src/gateway-models.test.ts b/packages/agent/src/gateway-models.test.ts new file mode 100644 index 000000000..42f4ac45c --- /dev/null +++ b/packages/agent/src/gateway-models.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "vitest"; +import { formatGatewayModelName, isBlockedModelId } from "./gateway-models"; + +describe("formatGatewayModelName", () => { + it("keeps Claude models in friendly title case", () => { + expect( + formatGatewayModelName({ + id: "claude-opus-4-8", + owned_by: "anthropic", + context_window: 200000, + supports_streaming: true, + supports_vision: true, + }), + ).toBe("Claude Opus 4.8"); + }); + + it("formats OpenAI models as raw lowercase model ids", () => { + expect( + formatGatewayModelName({ + id: "GPT-5.5", + owned_by: "openai", + context_window: 200000, + supports_streaming: true, + supports_vision: true, + }), + ).toBe("gpt-5.5"); + }); + + it("strips the openai/ prefix from OpenAI model ids", () => { + expect( + formatGatewayModelName({ + id: "openai/gpt-5.5", + owned_by: "openai", + context_window: 200000, + supports_streaming: true, + supports_vision: true, + }), + ).toBe("gpt-5.5"); + }); + + it("blocks deprecated Claude gateway models", () => { + expect(isBlockedModelId("claude-opus-4-5")).toBe(true); + expect(isBlockedModelId("claude-opus-4-6")).toBe(true); + expect(isBlockedModelId("claude-sonnet-4-5")).toBe(true); + expect(isBlockedModelId("claude-haiku-4-5")).toBe(true); + expect(isBlockedModelId("ANTHROPIC/CLAUDE-HAIKU-4-5")).toBe(true); + }); + + it("blocks deprecated Codex gateway models", () => { + expect(isBlockedModelId("gpt-5.2")).toBe(true); + expect(isBlockedModelId("gpt-5.3")).toBe(true); + expect(isBlockedModelId("gpt-5.3-codex")).toBe(true); + expect(isBlockedModelId("openai/gpt-5.2")).toBe(true); + expect(isBlockedModelId("OPENAI/GPT-5.3")).toBe(true); + expect(isBlockedModelId("OPENAI/GPT-5.3-CODEX")).toBe(true); + }); +}); diff --git a/packages/agent/src/gateway-models.ts b/packages/agent/src/gateway-models.ts index 01193777b..b8aafc78f 100644 --- a/packages/agent/src/gateway-models.ts +++ b/packages/agent/src/gateway-models.ts @@ -15,11 +15,32 @@ export interface FetchGatewayModelsOptions { gatewayUrl: string; } -export const DEFAULT_GATEWAY_MODEL = "claude-opus-4-7"; - -export const DEFAULT_CODEX_MODEL = "gpt-5.4"; - -export const BLOCKED_MODELS = new Set(["gpt-5-mini", "openai/gpt-5-mini"]); +export const DEFAULT_GATEWAY_MODEL = "claude-opus-4-8"; + +export const DEFAULT_CODEX_MODEL = "gpt-5.5"; + +const BLOCKED_MODELS = new Set([ + "gpt-5-mini", + "openai/gpt-5-mini", + "gpt-5.2", + "openai/gpt-5.2", + "gpt-5.3", + "openai/gpt-5.3", + "gpt-5.3-codex", + "openai/gpt-5.3-codex", + "claude-opus-4-5", + "anthropic/claude-opus-4-5", + "claude-opus-4-6", + "anthropic/claude-opus-4-6", + "claude-sonnet-4-5", + "anthropic/claude-sonnet-4-5", + "claude-haiku-4-5", + "anthropic/claude-haiku-4-5", +]); + +export function isBlockedModelId(modelId: string): boolean { + return BLOCKED_MODELS.has(modelId.toLowerCase()); +} type ModelsListResponse = | { @@ -62,7 +83,7 @@ export async function fetchGatewayModels( } const data = (await response.json()) as GatewayModelsResponse; - const models = (data.data ?? []).filter((m) => !BLOCKED_MODELS.has(m.id)); + const models = (data.data ?? []).filter((m) => !isBlockedModelId(m.id)); gatewayModelsCache = { models, expiry: Date.now() + CACHE_TTL, @@ -129,6 +150,7 @@ export async function fetchModelsList( for (const model of models) { const id = model?.id ? String(model.id) : ""; if (!id) continue; + if (isBlockedModelId(id)) continue; results.push({ id, owned_by: model?.owned_by }); } modelsListCache = { @@ -155,9 +177,22 @@ export function getProviderName(ownedBy: string): string { const PROVIDER_PREFIXES = ["anthropic/", "openai/", "google-vertex/"]; export function formatGatewayModelName(model: GatewayModel): string { + if (isOpenAIModel(model)) { + return stripProviderPrefix(model.id).toLowerCase(); + } + return formatModelId(model.id); } +function stripProviderPrefix(modelId: string): string { + for (const prefix of PROVIDER_PREFIXES) { + if (modelId.startsWith(prefix)) { + return modelId.slice(prefix.length); + } + } + return modelId; +} + export function formatModelId(modelId: string): string { let cleanId = modelId; for (const prefix of PROVIDER_PREFIXES) { diff --git a/packages/agent/src/test/mocks/claude-sdk.ts b/packages/agent/src/test/mocks/claude-sdk.ts index d2ec6c797..e54cb05cc 100644 --- a/packages/agent/src/test/mocks/claude-sdk.ts +++ b/packages/agent/src/test/mocks/claude-sdk.ts @@ -244,7 +244,7 @@ export function createInitMessage(sessionId = "test-session"): SDKMessage { cwd: "/tmp", tools: [], mcp_servers: [], - model: "claude-sonnet-4-5-20250929", + model: "claude-sonnet-4-6", permissionMode: "default", slash_commands: [], output_style: "default", diff --git a/packages/agent/src/test/mocks/msw-handlers.ts b/packages/agent/src/test/mocks/msw-handlers.ts index efbb865f2..c73fe912c 100644 --- a/packages/agent/src/test/mocks/msw-handlers.ts +++ b/packages/agent/src/test/mocks/msw-handlers.ts @@ -29,14 +29,14 @@ export function createPostHogHandlers(options: PostHogHandlersOptions = {}) { object: "list", data: [ { - id: "claude-opus-4-7", + id: "claude-opus-4-8", owned_by: "anthropic", context_window: 200000, supports_streaming: true, supports_vision: true, }, { - id: "gpt-5.4", + id: "gpt-5.5", owned_by: "openai", context_window: 200000, supports_streaming: true,