diff --git a/.github/workflows/mobile-build.yml b/.github/workflows/mobile-build.yml index 7a269bcb1f..dd1f96cf86 100644 --- a/.github/workflows/mobile-build.yml +++ b/.github/workflows/mobile-build.yml @@ -89,4 +89,5 @@ jobs: with: platform: all profile: production - secrets: inherit + secrets: + EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} diff --git a/apps/mobile/src/features/tasks/composer/options.ts b/apps/mobile/src/features/tasks/composer/options.ts index 02f7c8d58e..b1de507a61 100644 --- a/apps/mobile/src/features/tasks/composer/options.ts +++ b/apps/mobile/src/features/tasks/composer/options.ts @@ -48,6 +48,12 @@ export const MODELS: ModelOption[] = [ description: "Most capable, slower", supportsReasoning: true, }, + { + value: "claude-sonnet-5", + label: "Claude Sonnet 5", + description: "Balanced, fast", + supportsReasoning: true, + }, { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", diff --git a/packages/agent/src/adapters/claude/session/models.test.ts b/packages/agent/src/adapters/claude/session/models.test.ts index 3e0fe77ade..b56c9001cf 100644 --- a/packages/agent/src/adapters/claude/session/models.test.ts +++ b/packages/agent/src/adapters/claude/session/models.test.ts @@ -26,6 +26,10 @@ describe("toSdkModelId", () => { expect(toSdkModelId("claude-fable-5")).toBe("claude-fable-5"); }); + it("passes claude-sonnet-5 through unchanged (no SDK alias)", () => { + expect(toSdkModelId("claude-sonnet-5")).toBe("claude-sonnet-5"); + }); + 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"); @@ -34,36 +38,72 @@ describe("toSdkModelId", () => { }); 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(false); - expect(supportsEffort("claude-opus-4-6")).toBe(false); - expect(supportsXhighEffort("claude-opus-4-7")).toBe(true); - expect(supportsXhighEffort("claude-opus-4-6")).toBe(false); - expect(supportsEffort("claude-haiku-4-5")).toBe(false); - }); - - it("flags claude-fable-5 as a flagship model", () => { - expect(supports1MContext("claude-fable-5")).toBe(true); - expect(supportsEffort("claude-fable-5")).toBe(true); - expect(supportsXhighEffort("claude-fable-5")).toBe(true); - expect(supportsMcpInjection("claude-fable-5")).toBe(true); - }); - - 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); - }); + it.each([ + { + modelId: "claude-opus-4-5", + oneMContext: false, + effort: false, + xhighEffort: false, + mcpInjection: true, + }, + { + modelId: "claude-opus-4-6", + oneMContext: false, + effort: false, + xhighEffort: false, + mcpInjection: true, + }, + { + modelId: "claude-opus-4-7", + oneMContext: true, + effort: true, + xhighEffort: true, + mcpInjection: true, + }, + { + modelId: "claude-opus-4-8", + oneMContext: true, + effort: true, + xhighEffort: true, + mcpInjection: true, + }, + { + modelId: "claude-sonnet-4-6", + oneMContext: true, + effort: true, + xhighEffort: false, + mcpInjection: true, + }, + { + modelId: "claude-sonnet-5", + oneMContext: true, + effort: true, + xhighEffort: true, + mcpInjection: true, + }, + { + modelId: "claude-fable-5", + oneMContext: true, + effort: true, + xhighEffort: true, + mcpInjection: true, + }, + { + modelId: "claude-haiku-4-5", + oneMContext: false, + effort: false, + xhighEffort: false, + mcpInjection: false, + }, + ])( + "$modelId capability flags", + ({ modelId, oneMContext, effort, xhighEffort, mcpInjection }) => { + expect(supports1MContext(modelId)).toBe(oneMContext); + expect(supportsEffort(modelId)).toBe(effort); + expect(supportsXhighEffort(modelId)).toBe(xhighEffort); + expect(supportsMcpInjection(modelId)).toBe(mcpInjection); + }, + ); }); describe("resolveEffortForModel", () => { @@ -77,12 +117,14 @@ describe("resolveEffortForModel", () => { ["claude-opus-4-8", undefined, "high"], ["claude-opus-4-7", undefined, "high"], ["claude-sonnet-4-6", undefined, "high"], + ["claude-sonnet-5", undefined, "high"], // Models without effort support stay unset (SDK disables thinking). ["claude-haiku-4-5", undefined, undefined], ["claude-opus-4-6", undefined, undefined], // An explicit choice is always honored, including on adaptive-only models. ["claude-opus-4-8", "low", "low"], ["claude-fable-5", "max", "max"], + ["claude-sonnet-5", "max", "max"], ] as const)( "resolveEffortForModel(%s, %s) === %s", (modelId, effort, expected) => { diff --git a/packages/agent/src/adapters/claude/session/models.ts b/packages/agent/src/adapters/claude/session/models.ts index 3338c60af0..5fad1e729b 100644 --- a/packages/agent/src/adapters/claude/session/models.ts +++ b/packages/agent/src/adapters/claude/session/models.ts @@ -21,6 +21,7 @@ const MODELS_WITH_1M_CONTEXT = new Set([ "claude-opus-4-7", "claude-opus-4-8", "claude-sonnet-4-6", + "claude-sonnet-5", "claude-fable-5", ]); @@ -32,12 +33,14 @@ const MODELS_WITH_EFFORT = new Set([ "claude-opus-4-7", "claude-opus-4-8", "claude-sonnet-4-6", + "claude-sonnet-5", "claude-fable-5", ]); const MODELS_WITH_XHIGH_EFFORT = new Set([ "claude-opus-4-7", "claude-opus-4-8", + "claude-sonnet-5", "claude-fable-5", ]); diff --git a/packages/agent/src/gateway-models.test.ts b/packages/agent/src/gateway-models.test.ts index cf51196027..b9a98833e1 100644 --- a/packages/agent/src/gateway-models.test.ts +++ b/packages/agent/src/gateway-models.test.ts @@ -79,6 +79,7 @@ describe("getClaudeModelRecency", () => { ["claude-sonnet-4-6", 4006], ["claude-opus-4-7", 4007], ["claude-opus-4-8", 4008], + ["claude-sonnet-5", 5000], ["claude-fable-5", 5000], ])("ranks %s by its embedded version (%i)", (modelId, rank) => { expect(getClaudeModelRecency(modelId)).toBe(rank); diff --git a/packages/ui/src/features/message-editor/components/PromptInput.stories.tsx b/packages/ui/src/features/message-editor/components/PromptInput.stories.tsx index 4c45d989ad..043d3ebf98 100644 --- a/packages/ui/src/features/message-editor/components/PromptInput.stories.tsx +++ b/packages/ui/src/features/message-editor/components/PromptInput.stories.tsx @@ -24,6 +24,7 @@ const mockModelOption = { name: "Recommended", options: [ { value: "gpt-5.5", name: "gpt-5.5" }, + { value: "claude-sonnet-5", name: "Claude Sonnet 5" }, { value: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, ], },