diff --git a/apps/ade-cli/src/cli.test.ts b/apps/ade-cli/src/cli.test.ts index 005682086..9cf9263f1 100644 --- a/apps/ade-cli/src/cli.test.ts +++ b/apps/ade-cli/src/cli.test.ts @@ -2489,6 +2489,47 @@ describe("ADE CLI", () => { expect(value.input).not.toHaveProperty("title"); }); + it("prints a non-billing Claude Opus 5 launch plan with effort and fast mode", () => { + const staticPlan = expectStaticPlan(buildCliPlan([ + "chat", + "create", + "--lane", + "lane-1", + "--provider", + "claude", + "--model", + "anthropic/claude-opus-5", + "--reasoning-effort", + "high", + "--fast", + "--dry-run", + ])); + const value = staticPlan.value as { + input: Record; + resolved: Record; + }; + + expect(value).toMatchObject({ + ok: true, + dryRun: true, + action: "chat.createSession", + }); + expect(value.input).toMatchObject({ + laneId: "lane-1", + provider: "claude", + model: "anthropic/claude-opus-5", + modelId: "anthropic/claude-opus-5", + reasoningEffort: "high", + fastMode: true, + }); + expect(value.resolved).toMatchObject({ + provider: "claude", + model: "anthropic/claude-opus-5", + reasoningEffort: "high", + fastMode: true, + }); + }); + describe("chat create parent lineage", () => { const savedParentEnv = process.env.ADE_CHAT_SESSION_ID; afterEach(() => { diff --git a/apps/ade-cli/src/cli.ts b/apps/ade-cli/src/cli.ts index 7bef15218..ee0aeeee3 100644 --- a/apps/ade-cli/src/cli.ts +++ b/apps/ade-cli/src/cli.ts @@ -1386,7 +1386,7 @@ const HELP_BY_COMMAND: Record = { $ ade new chat --mode chat --lane --provider codex --model openai/gpt-5.6-sol --reasoning-effort xhigh --permissions full-auto --no-fast --prompt "Fix the tests" $ ade new chat --mode cli --lane --provider codex --model openai/gpt-5.6-sol --reasoning-effort xhigh --permissions full-auto --no-fast --prompt "Fix the tests" $ ade new chat --mode chat --auto-create-lane --prompt "Fix login" - $ ade new cli --lane --provider claude --model anthropic/claude-opus-4-8 --effort ultracode --prompt "Review the diff" + $ ade new cli --lane --provider claude --model anthropic/claude-opus-5 --effort high --prompt "Review the diff" Flags: --mode Select a persistent ADE chat or tracked provider CLI session. @@ -1654,7 +1654,7 @@ const HELP_BY_COMMAND: Record = { $ ade chat list --include-automation --no-archived --text $ ade chat create --lane --provider codex --model openai/gpt-5.6-sol --reasoning-effort xhigh --no-fast --permissions full-auto $ ade chat create --personal --provider codex --model openai/gpt-5.6-sol --prompt "Plan my trip" - $ ade chat create --lane --provider claude --model anthropic/claude-opus-4-8 --prompt "fix the tests" + $ ade chat create --lane --provider claude --model anthropic/claude-opus-5 --prompt "fix the tests" $ ade chat create --from-linear-issue ENG-431 Start a chat with an attached issue + kickoff (alias: --linear-issue-json) $ ade chat send --text "next step" Send a message; steers automatically if the turn is active $ ade chat note "running e2e shard 2/4" Update this session's Work sidebar status line @@ -1744,8 +1744,8 @@ const HELP_BY_COMMAND: Record = { Create a persistent ADE Work chat session with provider/model/runtime settings. $ ade chat create --lane --provider codex --model openai/gpt-5.6-sol --reasoning-effort xhigh --no-fast --permissions full-auto - $ ade chat create --lane --provider claude --model anthropic/claude-opus-4-8 --effort high --permissions plan - $ ade chat create --lane --provider claude --model anthropic/claude-opus-4-8 --effort ultracode --prompt "fix" + $ ade chat create --lane --provider claude --model anthropic/claude-opus-5 --effort high --permissions plan + $ ade chat create --lane --provider claude --model anthropic/claude-opus-5 --effort max --prompt "fix" $ ade chat create --lane --provider cursor --model cursor/ --standard --print-config --json $ ade chat create --from-linear-issue ENG-431 --provider codex --model openai/gpt-5.6-sol --prompt "Work this issue" @@ -1839,7 +1839,7 @@ const HELP_BY_COMMAND: Record = { reasoning/fast launch settings. This command does not support reasoning effort. $ ade agent spawn --lane --provider codex --model openai/gpt-5.6-sol --permissions full-auto --prompt "Fix the failing test" - $ ade agent spawn --lane --provider claude --model claude-opus-4-8 --permissions plan --prompt "Review the diff" + $ ade agent spawn --lane --provider claude --model claude-opus-5 --permissions plan --prompt "Review the diff" Flags: --lane Required lane/worktree. diff --git a/apps/ade-cli/src/tuiClient/components/ModelPicker/modelPickerLayout.test.ts b/apps/ade-cli/src/tuiClient/components/ModelPicker/modelPickerLayout.test.ts index d9d77d827..7159ff946 100644 --- a/apps/ade-cli/src/tuiClient/components/ModelPicker/modelPickerLayout.test.ts +++ b/apps/ade-cli/src/tuiClient/components/ModelPicker/modelPickerLayout.test.ts @@ -96,9 +96,15 @@ describe("buildModelPickerLayout", () => { focusedIndex: 0, searchMode: false, }); - expect(layout.entries.length).toBeGreaterThan(0); + expect(layout.entries.map((entry) => entry.modelId)).toEqual([ + "anthropic/claude-fable-5", + "anthropic/claude-opus-5", + "anthropic/claude-sonnet-5", + "anthropic/claude-haiku-4-5", + "anthropic/claude-opus-4-8", + "anthropic/claude-opus-4-7-1m", + ]); expect(layout.entries.every((entry) => entry.family === "claude")).toBe(true); - expect(layout.entries.some((entry) => entry.displayName.includes("Claude"))).toBe(true); }); it("normalizes catalog provider aliases like anthropic into the Anthropic rail", () => { diff --git a/apps/desktop/resources/ade-cli-help.txt b/apps/desktop/resources/ade-cli-help.txt index 22634e497..bec08da3c 100644 --- a/apps/desktop/resources/ade-cli-help.txt +++ b/apps/desktop/resources/ade-cli-help.txt @@ -15,7 +15,12 @@ _ ____ _____ catalog, sync endpoint, and execution authority for the channel. $ ade help Display help for a command - $ ade auth status Check local ADE CLI readiness + $ ade login [--headless] [--max-wait ] Sign in to the optional ADE account + $ ade logout Sign out of the ADE account + $ ade auth status Show ADE account sign-in status + $ ade account token create Print a durable token for ADE_ACCOUNT_TOKEN + $ ade machines list List machines from the ADE account directory + $ ade machines connect Connect ADE Code to an account machine $ ade code Open ADE Work chat in the terminal $ ade new chat --mode chat|cli --prompt "fix" Start an ADE Work chat or tracked CLI session $ ade desktop Launch the installed desktop app @@ -31,7 +36,7 @@ _ ____ _____ $ ade projects list List projects registered on this machine $ ade sync web [--open] [--no-clipboard] Print (and copy) the web client pairing link + code $ ade sync status | pin generate Manage machine sync and phone pairing - $ ade doctor Inspect project, brain, runtime, and tool availability + $ ade doctor [--online] Inspect installed app and machine-brain health $ ade lanes list | show | create | child Work with lanes and lane stacks $ ade git status | commit | push | stash Run ADE-aware git operations $ ade operations status | wait Poll operation/test/chat/run status @@ -44,7 +49,9 @@ _ ____ _____ $ ade terminal list | resume | read | write | signal Control an attached session terminal $ ade history list | show | commits | export Inspect ADE operation timeline and lane commits - $ ade chat list | create | send | interrupt Work with ADE agent chats + $ ade chat list | create | send | ask | note + settle | unsettle | interrupt + Work with ADE agent chats $ ade linear attach | comment | set-state | issue | graphql Read and write attached Linear issues $ ade github app-auth login | status | clear Authorize the machine ADE GitHub App (device flow) @@ -56,6 +63,7 @@ _ ____ _____ $ ade app-control launch | snapshot | click Inspect and drive Electron apps $ ade browser open | tabs | screenshot Use ADE's built-in browser pane $ ade usage snapshot | refresh | budget Read provider quota usage and budget guardrails + $ ade storage snapshot | compress Inspect ADE disk usage and compress old history $ ade secrets list | get | set | delete Manage encrypted ADE project secrets for agents $ ade settings pr-transcript-gists enable Attach ADE chat transcript links to new PRs $ ade settings action Call project config actions @@ -115,105 +123,21 @@ _ ____ _____ / ___ \| |_| | |___ /_/ \_\____/|_____| - Agent-focused command-line interface for ADE. - - ADE CLI commands operate through the machine ADE brain by default. - The brain is the always-on ADE process for this machine: it owns the project - catalog, sync endpoint, and execution authority for the channel. - - $ ade help Display help for a command - $ ade auth status Check local ADE CLI readiness - $ ade code Open ADE Work chat in the terminal - $ ade new chat --mode chat|cli --prompt "fix" Start an ADE Work chat or tracked CLI session - $ ade desktop Launch the installed desktop app - $ ade open Open an ade:// or ade-app.dev deeplink via the OS - $ ade link lane | session | file | commit | artifact | branch | pr | linear-issue - Build a shareable deeplink (copies to clipboard) - $ ade linear install Register ADE as Linear's "Open in coding tool" target - $ ade skill list | show Browse ADE's bundled agent skills (local) - $ ade brain start | stop | status Manage the background ADE brain - $ ade runtime run --socket Run a manual runtime for dev/test work - $ ade rpc --stdio Speak ADE JSON-RPC over stdin/stdout - $ ade init [path] Register a project with this machine brain - $ ade projects list List projects registered on this machine - $ ade sync web [--open] [--no-clipboard] Print (and copy) the web client pairing link + code - $ ade sync status | pin generate Manage machine sync and phone pairing - $ ade doctor Inspect project, brain, runtime, and tool availability - $ ade lanes list | show | create | child Work with lanes and lane stacks - $ ade git status | commit | push | stash Run ADE-aware git operations - $ ade operations status | wait Poll operation/test/chat/run status - $ ade diff changes | file | patch Inspect lane diffs (including raw git patch text) - $ ade files tree | read | write | search Read and edit lane workspaces - $ ade search "" --text Search chats, terminals, PRs, commits, lanes, files, Linear - $ ade prs list | create | show | checks Manage PRs, queues, and GitHub integration - $ ade run defs | ps | start | logs Manage Run tab process definitions and runtime - $ ade shell start | write | resize | close Launch and control tracked shell sessions - $ ade terminal list | resume | read | write | signal - Control an attached session terminal - $ ade history list | show | commits | export Inspect ADE operation timeline and lane commits - $ ade chat list | create | send | interrupt Work with ADE agent chats - $ ade linear attach | comment | set-state | issue | graphql - Read and write attached Linear issues - $ ade github app-auth login | status | clear Authorize the machine ADE GitHub App (device flow) - $ ade automations list | create | run | runs Manage automation rules - $ ade coordinator Call coordinator runtime tools - $ ade tests list | run | stop | runs | logs Run configured test suites - $ ade proof status | list | screenshot | record Manage proof and computer-use artifacts - $ ade ios-sim devices | apps | launch | tap Control iOS Simulator apps, capture, and input - $ ade app-control launch | snapshot | click Inspect and drive Electron apps - $ ade browser open | tabs | screenshot Use ADE's built-in browser pane - $ ade usage snapshot | refresh | budget Read provider quota usage and budget guardrails - $ ade secrets list | get | set | delete Manage encrypted ADE project secrets for agents - $ ade settings pr-transcript-gists enable Attach ADE chat transcript links to new PRs - $ ade settings action Call project config actions - $ ade update status | check | install | dismiss Read auto-update state and drive install - $ ade actions list | run | status | wait Escape hatch for every ADE service action - $ ade cursor cloud agents | runs | artifacts | repos | models | me - Drive Cursor Cloud agents via @cursor/sdk + ADE Account - Global options: - --project-root ADE project root. Inside .ade/worktrees/, this resolves to the parent project. - --workspace-root Lane/worktree to treat as the active workspace. - --headless Skip the machine brain and run an in-process ADE runtime. - --socket Require a live ADE endpoint; fail instead of falling back to headless. - --json Print machine-readable JSON. This is the default output mode. - --text Print a compact human-readable summary when a formatter exists. - --timeout-ms Per-request timeout. Long agent/PR workflows may need several minutes. + ADE accounts are optional. Signing in unlocks remote-machine and account + directory features; every local ADE workflow continues to work signed out. - Common agent flows: - $ ade doctor --text - $ ade lanes list --text - $ ade lanes create --name fix-login --description "Repair login redirect" - $ ade git status --lane --text - $ ade git status --full --lane --text - $ ade git sync --lane --rebase --base main - $ ade git stage --lane src/index.ts - $ ade git commit --lane -m "Fix login redirect" - $ ade prs create --lane --base main --draft - $ ade prs checks --text - $ ade proof record --seconds 20 - $ ade ios-sim apps --text - $ ade ios-sim launch --target --text - $ ade app-control launch --command "pnpm dev" --text - $ ade --socket browser open http://localhost:5173 --new-tab --text - $ ade terminal read --chat-session --text - $ ade terminal read --pty --text - $ ade new chat --mode chat --lane --provider codex --model openai/gpt-5.6-sol --reasoning-effort xhigh --permissions full-auto --no-fast --prompt "Fix the tests" - $ ade new chat --mode cli --lane --provider codex --model openai/gpt-5.6-sol --reasoning-effort xhigh --permissions full-auto --no-fast --prompt "Fix the tests" + $ ade login Sign in with loopback OAuth or auto-detected device flow + $ ade login --headless Print a verification URL + code for another browser + $ ade logout Clear the shared machine account session + $ ade auth status --text Show the shared machine account status + $ ade account token create Print a self-contained durable token once for agent/CI setup - Generic ADE action JSON contract: - Object-shaped call: - $ ade actions run git.push --input-json '{"laneId":"lane-1","setUpstream":true}' - $ ade actions run git.push --arg laneId=lane-1 --arg setUpstream=true - JSON value fields: - $ ade actions run pr.setLabels --arg prId=123 --arg-json 'labels=["ready","ship"]' - Multi-parameter service call: - $ ade actions run pr.submitReview --args-list-json '["pr-1",{"event":"APPROVE"}]' - $ ade actions list --text - $ ade actions list --domain pr --text - $ ade actions run --input-json '{"key":"value"}' - - Start with: ade doctor --text + Flags (login): + --headless Force the copy-paste device authorization flow. + --max-wait Give up waiting before the authorization + session expires. ## ade doctor --help _ ____ _____ @@ -229,7 +153,12 @@ _ ____ _____ catalog, sync endpoint, and execution authority for the channel. $ ade help Display help for a command - $ ade auth status Check local ADE CLI readiness + $ ade login [--headless] [--max-wait ] Sign in to the optional ADE account + $ ade logout Sign out of the ADE account + $ ade auth status Show ADE account sign-in status + $ ade account token create Print a durable token for ADE_ACCOUNT_TOKEN + $ ade machines list List machines from the ADE account directory + $ ade machines connect Connect ADE Code to an account machine $ ade code Open ADE Work chat in the terminal $ ade new chat --mode chat|cli --prompt "fix" Start an ADE Work chat or tracked CLI session $ ade desktop Launch the installed desktop app @@ -245,7 +174,7 @@ _ ____ _____ $ ade projects list List projects registered on this machine $ ade sync web [--open] [--no-clipboard] Print (and copy) the web client pairing link + code $ ade sync status | pin generate Manage machine sync and phone pairing - $ ade doctor Inspect project, brain, runtime, and tool availability + $ ade doctor [--online] Inspect installed app and machine-brain health $ ade lanes list | show | create | child Work with lanes and lane stacks $ ade git status | commit | push | stash Run ADE-aware git operations $ ade operations status | wait Poll operation/test/chat/run status @@ -258,7 +187,9 @@ _ ____ _____ $ ade terminal list | resume | read | write | signal Control an attached session terminal $ ade history list | show | commits | export Inspect ADE operation timeline and lane commits - $ ade chat list | create | send | interrupt Work with ADE agent chats + $ ade chat list | create | send | ask | note + settle | unsettle | interrupt + Work with ADE agent chats $ ade linear attach | comment | set-state | issue | graphql Read and write attached Linear issues $ ade github app-auth login | status | clear Authorize the machine ADE GitHub App (device flow) @@ -270,6 +201,7 @@ _ ____ _____ $ ade app-control launch | snapshot | click Inspect and drive Electron apps $ ade browser open | tabs | screenshot Use ADE's built-in browser pane $ ade usage snapshot | refresh | budget Read provider quota usage and budget guardrails + $ ade storage snapshot | compress Inspect ADE disk usage and compress old history $ ade secrets list | get | set | delete Manage encrypted ADE project secrets for agents $ ade settings pr-transcript-gists enable Attach ADE chat transcript links to new PRs $ ade settings action Call project config actions @@ -343,6 +275,8 @@ _ ____ _____ $ ade code --lane Launch focused on a specific lane $ ade code remote --target --project Launch against a saved desktop remote machine + $ ade code remote --target --route + Choose or constrain the paired connection path $ ade code remote session --target --project --session Open a specific remote chat or provider CLI terminal session $ ade code remote --list-targets List saved remote machines @@ -411,8 +345,11 @@ _ ____ _____ $ ade lanes batch-create-from-linear --linear-issues-json '[{...},{...}]' Create one lane per issue (partial success, no orphans) $ ade lanes create --base Override the base ref (omit to use the configured new-lane base, remote-first by default) + $ ade lanes create --parent --name + Create from a parent lane's HEAD $ ade lanes create --branch-name Override the auto-generated branch name $ ade lanes child --lane --name Create a child lane under a parent + Child lanes carry the parent's unmerged work $ ade lanes import --branch Register an existing branch/worktree $ ade lanes archive Archive a lane in ADE $ ade lanes unarchive Restore an archived lane @@ -566,8 +503,10 @@ _ ____ _____ $ ade prs link --lane --url Map an existing GitHub PR to a lane $ ade prs checks --text Show check status $ ade prs comments --text Show unresolved review work - $ ade prs github-snapshot --include-external-closed - Include closed external PR history in the GitHub snapshot + $ ade prs github-snapshot --include-external-closed --history-page-limit 4 + Include bounded closed PR history in the GitHub snapshot + $ ade prs github-snapshot --include-state-counts --no-revalidate + Include exact state totals without a background refresh $ ade prs resolve-thread --thread Resolve a review thread $ ade prs labels set ready-to-merge Replace labels $ ade prs reviewers request alice bob Request reviewers @@ -657,26 +596,56 @@ _ ____ _____ requires an attached runtime because it owns provider/session state. $ ade chat list --lane --text List chat sessions + $ ade chat list --personal --text List machine personal chats (no project required) + $ ade chat actions --personal --text List machine personal-chat actions + $ ade chat action --personal models --input-json '{"provider":"codex"}' $ ade chat list --include-automation --no-archived --text $ ade chat create --lane --provider codex --model openai/gpt-5.6-sol --reasoning-effort xhigh --no-fast --permissions full-auto - $ ade chat create --lane --provider claude --model anthropic/claude-opus-4-8 --prompt "fix the tests" + $ ade chat create --personal --provider codex --model openai/gpt-5.6-sol --prompt "Plan my trip" + $ ade chat create --lane --provider claude --model anthropic/claude-opus-5 --prompt "fix the tests" $ ade chat create --from-linear-issue ENG-431 Start a chat with an attached issue + kickoff (alias: --linear-issue-json) $ ade chat send --text "next step" Send a message; steers automatically if the turn is active + $ ade chat note "running e2e shard 2/4" Update this session's Work sidebar status line + $ ade chat ask "Which account should I use?" Escalate a blocking question to the user + $ ade chat settle --outcome "PR #841, CI green" Mark this session complete + $ ade chat unsettle Return this session to the active lifecycle + These four commands default to the caller and accept --session + $ ade chat steer --personal --text "focus on the tradeoffs" + $ ade chat models --personal --provider codex + $ ade chat update --personal --title "Trip planning" + $ ade chat update --personal --tag "review-ready" + Claude-only; --tag "" clears it $ ade chat message --kind auto --text "status" Deliver via auto | queue | wake | interrupt-replace $ ade chat steer --text "context" Steer/queue context into an active turn $ ade chat wait --for idle --timeout-ms 600000 Wait for idle, active, awaiting-input, or terminal + $ ade chat recover --turn --action nudge + Recover a stalled Codex turn: wait, nudge, retry, or resume + $ ade chat models --provider codex --json List models and supported reasoning tiers $ ade chat read --limit 20 --text Read recent chat messages $ ade chat goal --objective "Ship it" Set or inspect a Codex goal $ ade chat goal --status paused Update a Codex goal status $ ade chat handoff --model openai/gpt-5.6-sol --note "focus on tests" Start a new chat with an extra handoff note + $ ade chat handoff --model openai/gpt-5.6-sol --target-lane + Brief handoff into a different lane (same project) $ ade chat fork --model openai/gpt-5.6-sol Fork full provider history into a new chat $ ade chat rewind-files --message --dry-run Preview or apply file/context rewind $ ade chat subagents --text List child agents for a chat + $ ade chat schedules --pause Pause this agent session's durable wakeups/cron/loops + $ ade chat schedules Inspect pause state + next armed wake (--resume to re-arm) + $ ade chat scheduled-work list [session] List durable jobs (--all includes recent history) + $ ade chat scheduled-work create --in 12m --prompt "" + Relative one-shot; supports s, m, h, d, w + $ ade chat scheduled-work create --at "2026-07-23T01:05:00-04:00" --prompt "" + Absolute one-shot; offset or Z is required + $ ade chat scheduled-work create --cron "" --prompt "" [--once] + Cron uses the brain machine's local timezone + Optional: --reason "" --session + $ ade chat scheduled-work cancel Cancel one job; Claude crons also request CronDelete $ ade new chat --mode cli --lane --provider claude --reasoning-effort ultracode --prompt "fix" Start a tracked provider CLI session $ ade chat attach-linear-issue --issue-id ENG-431 @@ -693,7 +662,7 @@ _ ____ _____ --provider claude | codex | cursor | droid | opencode. --model Model id, also sent as modelId for runtime parity. --reasoning-effort Reasoning tier when the selected model supports it. - Common tiers: minimal, low, medium, high, xhigh, max, ultracode. + Common tiers: minimal, low, medium, high, xhigh, ultra, ultracode. --prompt Create the chat, then send this as the first message. --permissions Alias for --permission-mode. --permission-mode default | auto | plan | edit | full-auto | config-toml. @@ -711,6 +680,12 @@ _ ____ _____ Use ade actions run chat.modelCatalog --json to inspect model-specific reasoning tiers and fast-mode support. + Personal chats attach to the machine-owned ADE brain and never register a + project. They work with a desktopless brain and through the same + 'ade rpc --stdio' transport used by remote desktops. One-shot '--headless' + is intentionally unsupported because it would dispose the agent runtime when + the command exits. + ## ade agent --help _ ____ _____ / \ | _ \| ____| @@ -756,7 +731,12 @@ _ ____ _____ catalog, sync endpoint, and execution authority for the channel. $ ade help Display help for a command - $ ade auth status Check local ADE CLI readiness + $ ade login [--headless] [--max-wait ] Sign in to the optional ADE account + $ ade logout Sign out of the ADE account + $ ade auth status Show ADE account sign-in status + $ ade account token create Print a durable token for ADE_ACCOUNT_TOKEN + $ ade machines list List machines from the ADE account directory + $ ade machines connect Connect ADE Code to an account machine $ ade code Open ADE Work chat in the terminal $ ade new chat --mode chat|cli --prompt "fix" Start an ADE Work chat or tracked CLI session $ ade desktop Launch the installed desktop app @@ -772,7 +752,7 @@ _ ____ _____ $ ade projects list List projects registered on this machine $ ade sync web [--open] [--no-clipboard] Print (and copy) the web client pairing link + code $ ade sync status | pin generate Manage machine sync and phone pairing - $ ade doctor Inspect project, brain, runtime, and tool availability + $ ade doctor [--online] Inspect installed app and machine-brain health $ ade lanes list | show | create | child Work with lanes and lane stacks $ ade git status | commit | push | stash Run ADE-aware git operations $ ade operations status | wait Poll operation/test/chat/run status @@ -785,7 +765,9 @@ _ ____ _____ $ ade terminal list | resume | read | write | signal Control an attached session terminal $ ade history list | show | commits | export Inspect ADE operation timeline and lane commits - $ ade chat list | create | send | interrupt Work with ADE agent chats + $ ade chat list | create | send | ask | note + settle | unsettle | interrupt + Work with ADE agent chats $ ade linear attach | comment | set-state | issue | graphql Read and write attached Linear issues $ ade github app-auth login | status | clear Authorize the machine ADE GitHub App (device flow) @@ -797,6 +779,7 @@ _ ____ _____ $ ade app-control launch | snapshot | click Inspect and drive Electron apps $ ade browser open | tabs | screenshot Use ADE's built-in browser pane $ ade usage snapshot | refresh | budget Read provider quota usage and budget guardrails + $ ade storage snapshot | compress Inspect ADE disk usage and compress old history $ ade secrets list | get | set | delete Manage encrypted ADE project secrets for agents $ ade settings pr-transcript-gists enable Attach ADE chat transcript links to new PRs $ ade settings action Call project config actions @@ -912,6 +895,14 @@ _ ____ _____ $ ade --role cto automations ingress set-url Save the public gateway URL $ ade --role cto automations ingress clear-url Clear the public gateway URL + $ ade automations linear-ingress status [--text] Show Linear webhook ingress status + $ ade --role cto automations linear-ingress connect + Register the Linear webhook (CTO only) + $ ade --role cto automations linear-ingress disconnect + Remove the Linear webhook (CTO only) + $ ade automations linear-ingress poll [--text] Drain queued Linear events now + $ ade automations cleanups list [--text] List scheduled lane cleanups + $ ade automations cleanups cancel Cancel a scheduled lane cleanup $ ade automations runs [--rule ] [--status ] [--limit 50] $ ade automations run-show [--json] Inspect a run $ ade automations example Print an example rule (stdout) @@ -941,7 +932,12 @@ _ ____ _____ catalog, sync endpoint, and execution authority for the channel. $ ade help Display help for a command - $ ade auth status Check local ADE CLI readiness + $ ade login [--headless] [--max-wait ] Sign in to the optional ADE account + $ ade logout Sign out of the ADE account + $ ade auth status Show ADE account sign-in status + $ ade account token create Print a durable token for ADE_ACCOUNT_TOKEN + $ ade machines list List machines from the ADE account directory + $ ade machines connect Connect ADE Code to an account machine $ ade code Open ADE Work chat in the terminal $ ade new chat --mode chat|cli --prompt "fix" Start an ADE Work chat or tracked CLI session $ ade desktop Launch the installed desktop app @@ -957,7 +953,7 @@ _ ____ _____ $ ade projects list List projects registered on this machine $ ade sync web [--open] [--no-clipboard] Print (and copy) the web client pairing link + code $ ade sync status | pin generate Manage machine sync and phone pairing - $ ade doctor Inspect project, brain, runtime, and tool availability + $ ade doctor [--online] Inspect installed app and machine-brain health $ ade lanes list | show | create | child Work with lanes and lane stacks $ ade git status | commit | push | stash Run ADE-aware git operations $ ade operations status | wait Poll operation/test/chat/run status @@ -970,7 +966,9 @@ _ ____ _____ $ ade terminal list | resume | read | write | signal Control an attached session terminal $ ade history list | show | commits | export Inspect ADE operation timeline and lane commits - $ ade chat list | create | send | interrupt Work with ADE agent chats + $ ade chat list | create | send | ask | note + settle | unsettle | interrupt + Work with ADE agent chats $ ade linear attach | comment | set-state | issue | graphql Read and write attached Linear issues $ ade github app-auth login | status | clear Authorize the machine ADE GitHub App (device flow) @@ -982,6 +980,7 @@ _ ____ _____ $ ade app-control launch | snapshot | click Inspect and drive Electron apps $ ade browser open | tabs | screenshot Use ADE's built-in browser pane $ ade usage snapshot | refresh | budget Read provider quota usage and budget guardrails + $ ade storage snapshot | compress Inspect ADE disk usage and compress old history $ ade secrets list | get | set | delete Manage encrypted ADE project secrets for agents $ ade settings pr-transcript-gists enable Attach ADE chat transcript links to new PRs $ ade settings action Call project config actions @@ -1218,11 +1217,12 @@ _ ____ _____ ADE browser - Browser commands control ADE's project-scoped built-in browser pane. Use - desktop bridge mode so CLI calls, chat link clicks, terminal localhost links, - and the Work sidebar share the browser for the active project only. Browser - tabs, cookies, and storage are isolated between separate projects. - The browser is project-scoped, not lane-scoped. Ownership is per tab/session: + Browser commands control ADE's built-in browser pane. ADE uses one persistent + authentication profile per installation/channel, so cookies and site storage + are shared across projects. Visible tabs remain independent per ADE + window/project (with a separate personal-chat collection). Desktop bridge + calls use the project root only to route to the right tab collection. + Ownership is per tab/session: tab creation, explicit claims, sessions, and page actions read ADE_LANE_ID/ADE_CHAT_SESSION_ID for agent CLI calls. Panel reveal and plain tab switching are passive view operations; use @@ -1231,10 +1231,15 @@ _ ____ _____ by their current chat. Plain "browser open " reuses that owned tab for ADE-launched agents and creates one only when none exists, without revealing the Browser panel unless --panel is passed. Use --new-tab only when the task - truly needs another tab; --active-tab and --tab stay explicit. + truly needs another tab; --active-tab and --tab stay explicit. The runtime + accepts browser commands only from ADE-launched chat/terminal sessions with + a browser capability, validates lane/chat identity, and rejects agent force + takeovers. Profile diagnostics and remembered-permission administration stay + in the trusted ADE renderer. Tabs and navigation: $ ade --socket browser status --text Show active tab and tab list + $ ade --socket browser authorize --tab Request human access to an authenticated origin $ ade --socket browser claim --lane Attribute the active browser tab to a lane $ ade --socket browser panel --text Open the Work sidebar Browser panel $ ade --socket browser open https://example.com --text @@ -1323,7 +1328,7 @@ _ ____ _____ --wait-after-ms Delay before post-action observation (default 150). --fast Alias for --wait-after-ms 0 on browser actions. --no-observe Do not capture the post-action scratch observation. - --force Take over a still-leased tab from another lane. + --force Reserved takeover flag; ADE agent calls are rejected. --lease-ttl-ms Override tab lease TTL for lane-owned actions. --lane, --lane-id Claim lane for open/new-tab/claim/session/actions. On panel/switch, claims only when passed explicitly. @@ -1344,7 +1349,12 @@ _ ____ _____ catalog, sync endpoint, and execution authority for the channel. $ ade help Display help for a command - $ ade auth status Check local ADE CLI readiness + $ ade login [--headless] [--max-wait ] Sign in to the optional ADE account + $ ade logout Sign out of the ADE account + $ ade auth status Show ADE account sign-in status + $ ade account token create Print a durable token for ADE_ACCOUNT_TOKEN + $ ade machines list List machines from the ADE account directory + $ ade machines connect Connect ADE Code to an account machine $ ade code Open ADE Work chat in the terminal $ ade new chat --mode chat|cli --prompt "fix" Start an ADE Work chat or tracked CLI session $ ade desktop Launch the installed desktop app @@ -1360,7 +1370,7 @@ _ ____ _____ $ ade projects list List projects registered on this machine $ ade sync web [--open] [--no-clipboard] Print (and copy) the web client pairing link + code $ ade sync status | pin generate Manage machine sync and phone pairing - $ ade doctor Inspect project, brain, runtime, and tool availability + $ ade doctor [--online] Inspect installed app and machine-brain health $ ade lanes list | show | create | child Work with lanes and lane stacks $ ade git status | commit | push | stash Run ADE-aware git operations $ ade operations status | wait Poll operation/test/chat/run status @@ -1373,7 +1383,9 @@ _ ____ _____ $ ade terminal list | resume | read | write | signal Control an attached session terminal $ ade history list | show | commits | export Inspect ADE operation timeline and lane commits - $ ade chat list | create | send | interrupt Work with ADE agent chats + $ ade chat list | create | send | ask | note + settle | unsettle | interrupt + Work with ADE agent chats $ ade linear attach | comment | set-state | issue | graphql Read and write attached Linear issues $ ade github app-auth login | status | clear Authorize the machine ADE GitHub App (device flow) @@ -1385,6 +1397,7 @@ _ ____ _____ $ ade app-control launch | snapshot | click Inspect and drive Electron apps $ ade browser open | tabs | screenshot Use ADE's built-in browser pane $ ade usage snapshot | refresh | budget Read provider quota usage and budget guardrails + $ ade storage snapshot | compress Inspect ADE disk usage and compress old history $ ade secrets list | get | set | delete Manage encrypted ADE project secrets for agents $ ade settings pr-transcript-gists enable Attach ADE chat transcript links to new PRs $ ade settings action Call project config actions diff --git a/apps/desktop/src/main/services/ai/claudeModelUtils.test.ts b/apps/desktop/src/main/services/ai/claudeModelUtils.test.ts index 342d33375..c048662cf 100644 --- a/apps/desktop/src/main/services/ai/claudeModelUtils.test.ts +++ b/apps/desktop/src/main/services/ai/claudeModelUtils.test.ts @@ -2,6 +2,12 @@ import { describe, expect, it } from "vitest"; import { resolveClaudeCliModel } from "./claudeModelUtils"; describe("resolveClaudeCliModel", () => { + it("normalizes Opus 5 ids and the current Opus alias", () => { + expect(resolveClaudeCliModel("claude-opus-5")).toBe("claude-opus-5"); + expect(resolveClaudeCliModel("anthropic/claude-opus-5")).toBe("claude-opus-5"); + expect(resolveClaudeCliModel("opus")).toBe("claude-opus-5"); + }); + it("normalizes Opus 4.8 1M aliases to the exact Opus 4.8 model", () => { expect(resolveClaudeCliModel("claude-opus-4-8")).toBe("claude-opus-4-8"); expect(resolveClaudeCliModel("opus-4.8-1m")).toBe("claude-opus-4-8"); diff --git a/apps/desktop/src/main/services/ai/claudeModelUtils.ts b/apps/desktop/src/main/services/ai/claudeModelUtils.ts index 6bbb5ccec..7eb5a94f2 100644 --- a/apps/desktop/src/main/services/ai/claudeModelUtils.ts +++ b/apps/desktop/src/main/services/ai/claudeModelUtils.ts @@ -3,7 +3,7 @@ import { getDefaultModelDescriptor, getModelById, resolveModelAlias } from "../. /** * Normalize arbitrary Claude model strings into CLI-safe values accepted by - * Claude Code (`claude-fable-5`, `claude-opus-4-8`, `claude-sonnet-5`, + * Claude Code (`claude-fable-5`, `claude-opus-5`, `claude-opus-4-8`, `claude-sonnet-5`, * `claude-haiku-4-5`, `claude-opus-4-7[1m]`) where possible. */ export function resolveClaudeCliModel(model: string | null | undefined): string { diff --git a/apps/desktop/src/main/services/ai/tools/ctoOperatorTools.ts b/apps/desktop/src/main/services/ai/tools/ctoOperatorTools.ts index 61fa44c71..3baf370a5 100644 --- a/apps/desktop/src/main/services/ai/tools/ctoOperatorTools.ts +++ b/apps/desktop/src/main/services/ai/tools/ctoOperatorTools.ts @@ -334,7 +334,7 @@ export function createCtoOperatorTools(deps: CtoOperatorToolDeps): Record { }); it.each([ - { reportedModel: "opus", usageModel: "claude-opus-4-8", expectedModel: "opus" }, + { reportedModel: "opus", usageModel: "claude-opus-4-8", expectedModel: "claude-opus-4-8" }, { reportedModel: "claude-opus-4-7-1m", usageModel: "claude-opus-4-7-1m", expectedModel: "claude-opus-4-8" }, ])("preserves the Claude Opus 4.8 modelId in done events when the SDK reports $reportedModel", async ({ reportedModel, diff --git a/apps/desktop/src/main/services/chat/agentChatService.ts b/apps/desktop/src/main/services/chat/agentChatService.ts index 010ad7037..592c8ff28 100644 --- a/apps/desktop/src/main/services/chat/agentChatService.ts +++ b/apps/desktop/src/main/services/chat/agentChatService.ts @@ -4316,6 +4316,12 @@ function isBareClaudeOpus47RuntimeValue(model: string): boolean { || normalized === "opus-4.7"; } +function isBareClaudeOpusRuntimeAlias(model: string): boolean { + return model.trim().toLowerCase() + .replace(/^anthropic\//, "") + .replace(/-api$/, "") === "opus"; +} + function resolveModelIdFromStoredValue( model: string, providerHint?: AgentChatProvider, @@ -4399,9 +4405,7 @@ function resolveClaudeTurnModelPayload( if (!reportedModelId || reportedModelId === session.modelId) return false; const reportedDescriptor = getModelById(reportedModelId) ?? resolveModelAlias(reportedModelId); if (selectedIsOpus48) { - return ( - reportedDescriptor?.id === "anthropic/claude-opus-4-7-1m" - ); + return reportedDescriptor?.id === "anthropic/claude-opus-4-7-1m"; } if (!selectedIsOpusOneMillion) return false; if (reportedModel && isBareClaudeOpus47RuntimeValue(reportedModel)) return true; @@ -4416,6 +4420,9 @@ function resolveClaudeTurnModelPayload( resolveClaudeCliModelIdFromRuntimeValue(normalized) ?? resolveClaudeCliModelIdFromRuntimeValue(normalizedCliModel); if (resolvedCliModelId) { + if (selectedIsOpus48 && isBareClaudeOpusRuntimeAlias(normalized)) { + return sessionPayload; + } if (shouldPreserveSelectedModel(resolvedCliModelId, normalized)) return sessionPayload; const descriptor = getModelById(resolvedCliModelId); const reportedMatchesCanonical = descriptor?.providerModelId === normalizedCliModel; diff --git a/apps/desktop/src/main/services/chat/droidModelsDiscovery.test.ts b/apps/desktop/src/main/services/chat/droidModelsDiscovery.test.ts index 4b8ca00ec..25b353c64 100644 --- a/apps/desktop/src/main/services/chat/droidModelsDiscovery.test.ts +++ b/apps/desktop/src/main/services/chat/droidModelsDiscovery.test.ts @@ -156,14 +156,17 @@ describe("discoverDroidCliModelDescriptors", () => { expect(descriptors.map((descriptor) => descriptor.id)).toEqual([ "droid/claude-opus-4-8", + "droid/claude-opus-5", "droid/claude-sonnet-5", ]); expect(descriptors.map((descriptor) => descriptor.displayName)).toEqual([ "Opus 4.8 1M", + "Opus 5", "Sonnet 5 (1.2x)", ]); expect(descriptors.map((descriptor) => descriptor.providerModelId)).toEqual([ "claude-opus-4-8", + "claude-opus-5", "claude-sonnet-5", ]); }); @@ -193,16 +196,17 @@ describe("discoverDroidCliModelDescriptors", () => { }), reasoningTiers: ["low", "medium", "high", "max"], }); - expect(descriptors.find((descriptor) => descriptor.id === "droid/claude-opus-4-8")).toMatchObject({ - displayName: "Opus 4.8 1M", - providerModelId: "claude-opus-4-8", + expect(descriptors.find((descriptor) => descriptor.id === "droid/claude-opus-5")).toMatchObject({ + displayName: "Opus 5", + providerModelId: "claude-opus-5", capabilities: expect.objectContaining({ vision: true, reasoning: true, }), - reasoningTiers: ["low", "medium", "high", "xhigh", "max", "ultracode"], - serviceTiers: ["fast"], + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", }); + expect(descriptors.find((descriptor) => descriptor.id === "droid/claude-opus-5")?.serviceTiers).toBeUndefined(); }); it("prefers canonical Droid rows over normalized retired aliases", async () => { @@ -321,6 +325,10 @@ describe("discoverDroidCliModelDescriptors", () => { model: "opus-4-7", model_display_name: "Retired Opus custom alias", }, + { + model: "opus", + model_display_name: "Current Opus custom alias", + }, { model: "custom-real-model", model_display_name: "Custom Real Model", @@ -340,6 +348,7 @@ describe("discoverDroidCliModelDescriptors", () => { expect(new Set(descriptors.map((descriptor) => descriptor.id))).toEqual(new Set([ "droid/custom:claude-opus-4-8", + "droid/custom:claude-opus-5", "droid/custom:claude-sonnet-5", "droid/custom:custom-real-model", ])); @@ -360,6 +369,18 @@ describe("discoverDroidCliModelDescriptors", () => { serviceTiers: ["fast"], reasoningTiers: ["low", "medium", "high", "xhigh", "max", "ultracode"], }); + expect(descriptors.find((descriptor) => descriptor.id === "droid/custom:claude-opus-5")).toMatchObject({ + providerModelId: "custom:claude-opus-5", + displayName: "Opus 5", + customProxy: true, + contextWindow: 1_000_000, + maxOutputTokens: 128_000, + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", + }); + expect( + descriptors.find((descriptor) => descriptor.id === "droid/custom:claude-opus-5")?.serviceTiers, + ).toBeUndefined(); expect(descriptors.find((descriptor) => descriptor.id === "droid/custom:custom-real-model")).toMatchObject({ displayName: "Custom Real Model", customProxy: true, diff --git a/apps/desktop/src/main/services/chat/droidModelsDiscovery.ts b/apps/desktop/src/main/services/chat/droidModelsDiscovery.ts index 16bfb49f7..8ff274e5c 100644 --- a/apps/desktop/src/main/services/chat/droidModelsDiscovery.ts +++ b/apps/desktop/src/main/services/chat/droidModelsDiscovery.ts @@ -16,6 +16,7 @@ export type DroidExecHelpModelRow = { /** True when sourced from ~/.factory/config.json (vibeproxy / custom proxy). */ customProxy?: boolean; reasoningTiers?: string[]; + defaultReasoningEffort?: string; serviceTiers?: string[]; contextWindow?: number; maxOutputTokens?: number; @@ -276,6 +277,21 @@ function canonicalDroidReplacementForAlias( reasoningTiers: ["low", "medium", "high", "max"], }; } + if ( + normalized === "opus" + || (options?.customProxy && normalized === "claude-opus-5") + ) { + return { + id: `${idPrefix}claude-opus-5`, + displayName: "Opus 5", + ...customProxy, + contextWindow: 1_000_000, + maxOutputTokens: 128_000, + capabilities: CANONICAL_DROID_ANTHROPIC_CAPABILITIES, + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", + }; + } if ( normalized === "claude-opus-4-7" || normalized === "opus-4-7" @@ -284,7 +300,6 @@ function canonicalDroidReplacementForAlias( || normalized === "claude-opus-4-6-fast" || normalized === "opus-4-6" || normalized === "opus-4.6" - || normalized === "opus" || (options?.customProxy && normalized === "claude-opus-4-8") ) { return { @@ -441,6 +456,7 @@ export async function discoverDroidCliModelDescriptors( const descriptor = createDynamicDroidCliModelDescriptor(trimmed, row.displayName, { customProxy: row.customProxy, ...(row.reasoningTiers?.length ? { reasoningTiers: row.reasoningTiers } : {}), + ...(row.defaultReasoningEffort ? { defaultReasoningEffort: row.defaultReasoningEffort } : {}), ...(row.serviceTiers?.length ? { serviceTiers: row.serviceTiers } : {}), ...(row.contextWindow ? { contextWindow: row.contextWindow } : {}), ...(row.maxOutputTokens ? { maxOutputTokens: row.maxOutputTokens } : {}), diff --git a/apps/desktop/src/main/services/cto/ctoStateService.ts b/apps/desktop/src/main/services/cto/ctoStateService.ts index d6494e5b6..beb9f67c3 100644 --- a/apps/desktop/src/main/services/cto/ctoStateService.ts +++ b/apps/desktop/src/main/services/cto/ctoStateService.ts @@ -182,7 +182,7 @@ function buildCtoEnvironmentKnowledge(): string { " - spawnChat: Creates a native ADE chat session with AI, streaming, tool approval, and service integration. Use when the user wants an AI agent, a chat, or AI-powered work.", " - createTerminal: Opens a shell (PTY) for raw CLI commands. Use when the user wants a terminal, shell, or to run a specific CLI tool.", " - spawnChat creates ADE-managed agent chats. createTerminal opens a raw shell for CLI commands. When the user says 'start a chat' or 'launch an agent', prefer spawnChat unless they explicitly ask for a terminal.", - " - Example: 'Launch a chat with opus' → spawnChat({ modelId: 'anthropic/claude-opus-4-8', ... }). 'Open a terminal' → createTerminal. 'Run npm test' → createTerminal({ startupCommand: 'npm test' }).", + " - Example: 'Launch a chat with opus' → spawnChat({ modelId: 'anthropic/claude-opus-5', ... }). 'Open a terminal' → createTerminal. 'Run npm test' → createTerminal({ startupCommand: 'npm test' }).", "", "Tool calling convention:", " - Use the `ade` CLI per the ADE CLI operating guidance in your doctrine.", diff --git a/apps/desktop/src/main/services/opencode/openCodeInventory.test.ts b/apps/desktop/src/main/services/opencode/openCodeInventory.test.ts index 62633eae2..9ff7db838 100644 --- a/apps/desktop/src/main/services/opencode/openCodeInventory.test.ts +++ b/apps/desktop/src/main/services/opencode/openCodeInventory.test.ts @@ -331,6 +331,30 @@ describe("openCodeInventory", () => { stale: {}, }, }, + opus: { + id: "opus", + name: "Opus", + capabilities: { + reasoning: false, + toolcall: false, + }, + variants: { + stale: {}, + }, + }, + "claude-opus-5": { + id: "claude-opus-5", + name: "Claude Opus 5", + capabilities: { + reasoning: true, + toolcall: true, + input: { image: true }, + }, + variants: { + high: {}, + fast: {}, + }, + }, "claude-opus-4-8": { id: "claude-opus-4-8", name: "Claude Opus 4.8 1M", @@ -358,8 +382,10 @@ describe("openCodeInventory", () => { }); const descriptor = result.descriptors.find((entry) => entry.id === "opencode/anthropic/claude-sonnet-5"); + const opus5Descriptor = result.descriptors.find((entry) => entry.id === "opencode/anthropic/claude-opus-5"); const opusDescriptor = result.descriptors.find((entry) => entry.id === "opencode/anthropic/claude-opus-4-8"); expect(result.descriptors.filter((entry) => entry.id === "opencode/anthropic/claude-sonnet-5")).toHaveLength(1); + expect(result.descriptors.filter((entry) => entry.id === "opencode/anthropic/claude-opus-5")).toHaveLength(1); expect(result.descriptors.filter((entry) => entry.id === "opencode/anthropic/claude-opus-4-8")).toHaveLength(1); expect(result.modelIds).not.toContain("opencode/anthropic/claude-opus-4-6"); expect(result.modelIds).not.toContain("opencode/anthropic/opus-4-6"); @@ -371,6 +397,16 @@ describe("openCodeInventory", () => { }); expect(descriptor?.reasoningTiers).toEqual(["high"]); expect(descriptor?.serviceTiers).toEqual(["fast"]); + expect(opus5Descriptor).toMatchObject({ + displayName: "Claude Opus 5", + openCodeModelId: "claude-opus-5", + providerModelId: "anthropic/claude-opus-5", + contextWindow: 1_000_000, + maxOutputTokens: 128_000, + reasoningTiers: ["high"], + defaultReasoningEffort: "high", + serviceTiers: ["fast"], + }); expect(opusDescriptor?.capabilities).toMatchObject({ tools: true, vision: true, @@ -420,7 +456,7 @@ describe("openCodeInventory", () => { }); expect(result.modelIds).toContain("opencode/anthropic/claude-sonnet-5"); - expect(result.modelIds).toContain("opencode/anthropic/claude-opus-4-8"); + expect(result.modelIds).toContain("opencode/anthropic/claude-opus-5"); expect(result.modelIds).not.toContain("opencode/anthropic/claude-sonnet-4-6"); expect(result.modelIds).not.toContain("opencode/anthropic/opus"); expect(result.descriptors.find((entry) => entry.id === "opencode/anthropic/claude-sonnet-5")).toMatchObject({ @@ -436,8 +472,8 @@ describe("openCodeInventory", () => { }), reasoningTiers: ["low", "medium", "high", "max"], }); - expect(result.descriptors.find((entry) => entry.id === "opencode/anthropic/claude-opus-4-8")).toMatchObject({ - displayName: "Claude Opus 4.8 1M", + expect(result.descriptors.find((entry) => entry.id === "opencode/anthropic/claude-opus-5")).toMatchObject({ + displayName: "Claude Opus 5", openCodeModelId: "opus", providerModelId: "anthropic/opus", contextWindow: 1_000_000, @@ -447,7 +483,8 @@ describe("openCodeInventory", () => { vision: true, reasoning: true, }), - reasoningTiers: ["low", "medium", "high", "xhigh", "max", "ultracode"], + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", serviceTiers: ["fast"], }); }); diff --git a/apps/desktop/src/main/services/opencode/openCodeInventory.ts b/apps/desktop/src/main/services/opencode/openCodeInventory.ts index 4e24bef0d..4e826853b 100644 --- a/apps/desktop/src/main/services/opencode/openCodeInventory.ts +++ b/apps/desktop/src/main/services/opencode/openCodeInventory.ts @@ -273,6 +273,7 @@ function normalizeOpenCodeProviderModel( contextWindow?: number; maxOutputTokens?: number; reasoningTiers?: string[]; + defaultReasoningEffort?: string; serviceTiers?: string[]; capabilities?: ModelCapabilities; preferredDuplicateSource: boolean; @@ -281,7 +282,10 @@ function normalizeOpenCodeProviderModel( return { modelId, ...(displayName ? { displayName } : {}), preferredDuplicateSource: true }; } const normalized = modelId.trim().toLowerCase(); - const preferredDuplicateSource = normalized === "claude-sonnet-5" || normalized === "claude-opus-4-8"; + const preferredDuplicateSource = + normalized === "claude-sonnet-5" + || normalized === "claude-opus-5" + || normalized === "claude-opus-4-8"; if (normalized === "claude-sonnet-5") { return { modelId: "claude-sonnet-5", @@ -300,6 +304,15 @@ function normalizeOpenCodeProviderModel( preferredDuplicateSource, }; } + if (normalized === "claude-opus-5") { + return { + modelId: "claude-opus-5", + displayName: displayName ?? "Claude Opus 5", + contextWindow: 1_000_000, + maxOutputTokens: 128_000, + preferredDuplicateSource, + }; + } if (normalized === "claude-sonnet-4-6" || normalized === "sonnet-4-6") { return { modelId: "claude-sonnet-5", @@ -311,13 +324,27 @@ function normalizeOpenCodeProviderModel( preferredDuplicateSource, }; } + if ( + normalized === "opus" + ) { + return { + modelId: "claude-opus-5", + displayName: "Claude Opus 5", + contextWindow: 1_000_000, + maxOutputTokens: 128_000, + capabilities: CANONICAL_ANTHROPIC_MODEL_CAPABILITIES, + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", + serviceTiers: ["fast"], + preferredDuplicateSource, + }; + } if ( normalized === "claude-opus-4-7" || normalized === "opus-4-7" || normalized === "claude-opus-4-6" || normalized === "opus-4-6" || normalized === "opus-4.6" - || normalized === "opus" ) { return { modelId: "claude-opus-4-8", @@ -482,6 +509,9 @@ export async function probeOpenCodeProviderInventory(args: { ...(variants.reasoningTiers.length ? { reasoningTiers: variants.reasoningTiers } : {}), ...(variants.serviceTiers.length ? { serviceTiers: variants.serviceTiers } : {}), ...(normalizedModel.reasoningTiers?.length ? { reasoningTiers: normalizedModel.reasoningTiers } : {}), + ...(normalizedModel.defaultReasoningEffort + ? { defaultReasoningEffort: normalizedModel.defaultReasoningEffort } + : {}), ...(normalizedModel.serviceTiers?.length ? { serviceTiers: normalizedModel.serviceTiers } : {}), capabilities: normalizedModel.capabilities ?? readOpenCodeModelCapabilities(modelRecord), }); diff --git a/apps/desktop/src/main/services/usage/usagePricing.ts b/apps/desktop/src/main/services/usage/usagePricing.ts index 6b0eeddf3..4e1962eb5 100644 --- a/apps/desktop/src/main/services/usage/usagePricing.ts +++ b/apps/desktop/src/main/services/usage/usagePricing.ts @@ -41,6 +41,7 @@ const STATIC_TOKEN_PRICES: Record = { "claude-3-7-sonnet": tokenPrice(3, 15, 0.3, 3.75), "claude-3-haiku": tokenPrice(0.25, 1.25, 0.03, 0.3), "claude-fable-5": tokenPrice(10, 50, 1, 12.5), + "claude-opus-5": tokenPrice(5, 25, 0.5, 6.25), "claude-opus-4-1": tokenPrice(15, 75, 1.5, 18.75), "claude-opus-4": tokenPrice(15, 75, 1.5, 18.75), "claude-opus-4-8": tokenPrice(5, 25, 0.5, 6.25), diff --git a/apps/desktop/src/main/services/usage/usageTrackingService.test.ts b/apps/desktop/src/main/services/usage/usageTrackingService.test.ts index 59a9489bc..23180b0d5 100644 --- a/apps/desktop/src/main/services/usage/usageTrackingService.test.ts +++ b/apps/desktop/src/main/services/usage/usageTrackingService.test.ts @@ -847,6 +847,14 @@ describe("resolveTokenPrice", () => { expect(price.output).toBe(75 / 1_000_000); }); + it("uses Opus 5 pricing for the static fallback", () => { + const price = resolveTokenPrice("claude-opus-5"); + expect(price.input).toBe(5 / 1_000_000); + expect(price.output).toBe(25 / 1_000_000); + expect(price.cacheRead).toBe(0.5 / 1_000_000); + expect(price.cacheWrite).toBe(6.25 / 1_000_000); + }); + it("returns sonnet pricing for sonnet models", () => { const price = resolveTokenPrice("claude-3-5-sonnet"); expect(price.input).toBe(3 / 1_000_000); diff --git a/apps/desktop/src/renderer/components/terminals/cliLaunch.test.ts b/apps/desktop/src/renderer/components/terminals/cliLaunch.test.ts index 0eaea0069..58f5fade7 100644 --- a/apps/desktop/src/renderer/components/terminals/cliLaunch.test.ts +++ b/apps/desktop/src/renderer/components/terminals/cliLaunch.test.ts @@ -430,6 +430,32 @@ describe("buildTrackedCliStartupCommand", () => { expect(launch.startupCommand).toContain("claude-opus-4-8"); }); + it("launches Claude Opus 5 with its supported effort and fast-mode settings", () => { + const launch = buildTrackedCliLaunchCommand({ + provider: "claude", + permissionMode: "default", + sessionId: "00000000-0000-0000-0000-000000000001", + model: "anthropic/claude-opus-5", + reasoningEffort: "high", + fastMode: true, + }); + + expect(launch.args).toEqual(expect.arrayContaining([ + "--model", + "claude-opus-5", + "--effort", + "high", + ])); + const settingsIndex = launch.args.indexOf("--settings"); + expect(launch.args.slice(settingsIndex, settingsIndex + 2)).toEqual([ + "--settings", + JSON.stringify({ fastMode: true }), + ]); + expect(launch.startupCommand).toContain("--model claude-opus-5"); + expect(launch.startupCommand).toContain("--effort high"); + expect(launch.startupCommand).toContain('--settings "{\\"fastMode\\":true}"'); + }); + it("passes Claude fast mode as per-session settings for fresh launches", () => { const fastLaunch = buildTrackedCliLaunchCommand({ provider: "claude", diff --git a/apps/desktop/src/shared/claudeCliModels.ts b/apps/desktop/src/shared/claudeCliModels.ts index 8a61363d0..86935fb16 100644 --- a/apps/desktop/src/shared/claudeCliModels.ts +++ b/apps/desktop/src/shared/claudeCliModels.ts @@ -1,5 +1,6 @@ export type ClaudeCliModelAlias = | "claude-fable-5" + | "claude-opus-5" | "claude-opus-4-8" | "claude-sonnet-5" | "claude-haiku-4-5" @@ -10,7 +11,13 @@ export const CLAUDE_CLI_MODEL_ALIAS_MAP: Readonly { it("canonicalizes persisted OpenCode Anthropic aliases before launch", () => { const sonnet = getModelById("opencode/anthropic/claude-sonnet-4-6"); + const currentOpus = getModelById("opencode/anthropic/opus"); const opus = getModelById("opencode/anthropic/opus-4.6"); expect(sonnet).toMatchObject({ @@ -76,6 +78,16 @@ describe("modelRegistry", () => { reasoningTiers: ["low", "medium", "high", "xhigh", "max", "ultracode"], serviceTiers: ["fast"], }); + expect(currentOpus).toMatchObject({ + id: "opencode/anthropic/claude-opus-5", + displayName: "Claude Opus 5", + providerModelId: "anthropic/claude-opus-5", + openCodeModelId: "claude-opus-5", + contextWindow: 1_000_000, + maxOutputTokens: 128_000, + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + serviceTiers: ["fast"], + }); }); it("resolves runtime-discovered local model ids", () => { @@ -316,11 +328,12 @@ describe("modelRegistry", () => { describe("Claude descriptors", () => { it("orders the Claude model registry for picker display", () => { - expect(MODEL_REGISTRY.filter((model) => model.family === "anthropic").slice(0, 5).map((model) => model.id)).toEqual([ + expect(MODEL_REGISTRY.filter((model) => model.family === "anthropic").slice(0, 6).map((model) => model.id)).toEqual([ "anthropic/claude-fable-5", - "anthropic/claude-opus-4-8", + "anthropic/claude-opus-5", "anthropic/claude-sonnet-5", "anthropic/claude-haiku-4-5", + "anthropic/claude-opus-4-8", "anthropic/claude-opus-4-7-1m", ]); const fable = getModelById("anthropic/claude-fable-5"); @@ -339,9 +352,28 @@ describe("modelRegistry", () => { expect(fable?.reasoningTiers).toEqual(["low", "medium", "high", "xhigh", "max", "ultracode"]); expect(resolveModelAlias("fable")?.id).toBe("anthropic/claude-fable-5"); - const opus = getModelById("anthropic/claude-opus-4-8"); - expect(opus).toBeTruthy(); - expect(opus).toMatchObject({ + const opus5 = getModelById("anthropic/claude-opus-5"); + expect(opus5).toBeTruthy(); + expect(opus5).toMatchObject({ + displayName: "Claude Opus 5", + shortId: "opus", + family: "anthropic", + providerRoute: "claude-cli", + providerModelId: "claude-opus-5", + contextWindow: 1_000_000, + maxOutputTokens: 128_000, + inputPricePer1M: 5, + outputPricePer1M: 25, + defaultReasoningEffort: "high", + }); + expect(opus5?.reasoningTiers).toEqual(["low", "medium", "high", "xhigh", "max"]); + expect(opus5?.serviceTiers).toEqual(["fast"]); + expect(resolveModelAlias("opus")?.id).toBe("anthropic/claude-opus-5"); + expect(getRuntimeModelRefForDescriptor(opus5!, "claude")).toBe("claude-opus-5"); + + const opus48 = getModelById("anthropic/claude-opus-4-8"); + expect(opus48).toBeTruthy(); + expect(opus48).toMatchObject({ displayName: "Claude Opus 4.8 1M", shortId: "opus-4.8-1m", family: "anthropic", @@ -352,8 +384,8 @@ describe("modelRegistry", () => { inputPricePer1M: 5, outputPricePer1M: 25, }); - expect(opus?.reasoningTiers).toEqual(["low", "medium", "high", "xhigh", "max", "ultracode"]); - expect(opus?.serviceTiers).toEqual(["fast"]); + expect(opus48?.reasoningTiers).toEqual(["low", "medium", "high", "xhigh", "max", "ultracode"]); + expect(opus48?.serviceTiers).toEqual(["fast"]); expect(getDefaultModelDescriptor("claude")?.id).toBe("anthropic/claude-fable-5"); }); @@ -379,7 +411,6 @@ describe("modelRegistry", () => { contextWindow: 1_000_000, serviceTiers: ["fast"], }); - expect(resolveModelAlias("opus")?.id).toBe("anthropic/claude-opus-4-8"); expect(resolveModelAlias("opus[1m]")?.id).toBe("anthropic/claude-opus-4-7-1m"); expect(resolveModelAlias("anthropic/claude-opus-4-6")?.id).toBe("anthropic/claude-opus-4-8"); expect(resolveModelAlias("anthropic/claude-opus-4-7")?.id).toBe("anthropic/claude-opus-4-8"); @@ -446,6 +477,7 @@ describe("modelRegistry", () => { it("canonicalizes persisted Droid Anthropic aliases before launch", () => { const sonnet = getModelById("droid/claude-sonnet-4-6"); const opus = getModelById("droid/opus-4-6"); + const opus5 = getModelById("droid/opus"); expect(sonnet).toMatchObject({ id: "droid/claude-sonnet-5", @@ -469,6 +501,36 @@ describe("modelRegistry", () => { providerModelId: "claude-opus-4-8", displayName: "Opus 4.8 1M", }); + expect(opus5).toMatchObject({ + id: "droid/claude-opus-5", + providerModelId: "claude-opus-5", + displayName: "Opus 5", + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", + }); + expect(opus5?.serviceTiers).toBeUndefined(); + }); + + it("does not advertise unsupported Fast mode for Droid Opus 5 custom proxies", () => { + const descriptor = createDynamicDroidCliModelDescriptor( + "custom:claude-opus-5", + "Opus 5", + { + customProxy: true, + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", + serviceTiers: ["fast"], + }, + ); + + expect(descriptor).toMatchObject({ + id: "droid/custom:claude-opus-5", + providerModelId: "custom:claude-opus-5", + customProxy: true, + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", + }); + expect(descriptor.serviceTiers).toBeUndefined(); }); diff --git a/apps/desktop/src/shared/modelRegistry.ts b/apps/desktop/src/shared/modelRegistry.ts index 9a716cb7c..848845d16 100644 --- a/apps/desktop/src/shared/modelRegistry.ts +++ b/apps/desktop/src/shared/modelRegistry.ts @@ -270,42 +270,28 @@ export const MODEL_REGISTRY: ModelDescriptor[] = [ costTier: "very_high", }, { - id: "anthropic/claude-opus-4-8", - shortId: "opus-4.8-1m", + id: "anthropic/claude-opus-5", + shortId: "opus", aliases: [ - "claude-opus-4-8", "opus", - "opus-4.8", - "opus-4-8", - "opus-4.8-1m", - "opus-4.8[1m]", - "opus-4-8-1m", - "claude-opus-4-8-1m", - "claude-opus-4-8[1m]", - "anthropic/claude-opus-4-8-1m", - "anthropic/claude-opus-4-8-api", - "opus-4.7", - "opus-4-7", - "opus-4.6", - "opus-4-6", - "claude-opus-4-6", - "claude-opus-4-7", - "anthropic/claude-opus-4-6", - "anthropic/claude-opus-4-6-api", - "anthropic/claude-opus-4-7", - "anthropic/claude-opus-4-7-api", + "opus-5", + "opus-5.0", + "opus-5-0", + "claude-opus-5", + "anthropic/claude-opus-5-api", ], - displayName: "Claude Opus 4.8 1M", + displayName: "Claude Opus 5", family: "anthropic", authTypes: ["cli-subscription"], contextWindow: 1_000_000, maxOutputTokens: 128_000, capabilities: ALL_CAPS, - reasoningTiers: ["low", "medium", "high", "xhigh", "max", "ultracode"], + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", serviceTiers: ["fast"], color: "#D97706", providerRoute: "claude-cli", - providerModelId: "claude-opus-4-8", + providerModelId: "claude-opus-5", cliCommand: "claude", isCliWrapped: true, inputPricePer1M: 5, @@ -360,6 +346,48 @@ export const MODEL_REGISTRY: ModelDescriptor[] = [ outputPricePer1M: 5, costTier: "low", }, + { + id: "anthropic/claude-opus-4-8", + shortId: "opus-4.8-1m", + aliases: [ + "claude-opus-4-8", + "opus-4.8", + "opus-4-8", + "opus-4.8-1m", + "opus-4.8[1m]", + "opus-4-8-1m", + "claude-opus-4-8-1m", + "claude-opus-4-8[1m]", + "anthropic/claude-opus-4-8-1m", + "anthropic/claude-opus-4-8-api", + "opus-4.7", + "opus-4-7", + "opus-4.6", + "opus-4-6", + "claude-opus-4-6", + "claude-opus-4-7", + "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-6-api", + "anthropic/claude-opus-4-7", + "anthropic/claude-opus-4-7-api", + ], + displayName: "Claude Opus 4.8 1M", + family: "anthropic", + authTypes: ["cli-subscription"], + contextWindow: 1_000_000, + maxOutputTokens: 128_000, + capabilities: ALL_CAPS, + reasoningTiers: ["low", "medium", "high", "xhigh", "max", "ultracode"], + serviceTiers: ["fast"], + color: "#D97706", + providerRoute: "claude-cli", + providerModelId: "claude-opus-4-8", + cliCommand: "claude", + isCliWrapped: true, + inputPricePer1M: 5, + outputPricePer1M: 25, + costTier: "very_high", + }, { id: "anthropic/claude-opus-4-7-1m", shortId: "opus-1m", @@ -760,6 +788,7 @@ export type DynamicOpenCodeModelDescriptorOptions = { maxOutputTokens?: number; capabilities?: Partial; reasoningTiers?: string[]; + defaultReasoningEffort?: string; serviceTiers?: string[]; aliases?: string[]; color?: string; @@ -801,6 +830,7 @@ function normalizeAnthropicRuntimeAlias(modelId: string): { maxOutputTokens: number; capabilities: ModelCapabilities; reasoningTiers?: string[]; + defaultReasoningEffort?: string; serviceTiers?: string[]; wasAlias: boolean; } | null { @@ -821,6 +851,25 @@ function normalizeAnthropicRuntimeAlias(modelId: string): { wasAlias: normalized !== "claude-sonnet-5", }; } + if ( + normalized === "claude-opus-5" + || normalized === "opus-5" + || normalized === "opus-5.0" + || normalized === "opus-5-0" + || normalized === "opus" + ) { + return { + modelId: "claude-opus-5", + displayName: "Claude Opus 5", + contextWindow: 1_000_000, + maxOutputTokens: 128_000, + capabilities: ALL_CAPS, + reasoningTiers: ["low", "medium", "high", "xhigh", "max"], + defaultReasoningEffort: "high", + serviceTiers: ["fast"], + wasAlias: normalized !== "claude-opus-5", + }; + } if ( normalized === "claude-opus-4-8" || normalized === "claude-opus-4-7" @@ -830,7 +879,6 @@ function normalizeAnthropicRuntimeAlias(modelId: string): { || normalized === "claude-opus-4-6-fast" || normalized === "opus-4-6" || normalized === "opus-4.6" - || normalized === "opus" ) { return { modelId: "claude-opus-4-8", @@ -949,6 +997,9 @@ export function createDynamicOpenCodeModelDescriptor( providerModelId, ...(usesPairedIds ? { openCodeProviderId: opPid, openCodeModelId: opMid } : {}), ...(reasoningTiers?.length ? { reasoningTiers: [...reasoningTiers] } : {}), + ...((options?.defaultReasoningEffort ?? anthropicRuntime?.defaultReasoningEffort) + ? { defaultReasoningEffort: options?.defaultReasoningEffort ?? anthropicRuntime?.defaultReasoningEffort } + : {}), ...(serviceTiers?.length ? { serviceTiers: [...serviceTiers] } : {}), ...(aliases.length ? { aliases } : {}), ...(isLocal || options?.harnessProfile ? { harnessProfile: options?.harnessProfile ?? "guarded" } : {}), @@ -1219,6 +1270,7 @@ function normalizeDroidEffortLabel(value: string): string { const KNOWN_DROID_COMPACT_DISPLAY_NAMES: Record = { "claude-fable-5": "Fable 5", + "claude-opus-5": "Opus 5", "claude-opus-4-8": "Opus 4.8 1M", "claude-opus-4-5-20251101": "Opus 4.5 (2x)", "claude-opus-4-6": "Opus 4.6 (2x)", @@ -1294,6 +1346,7 @@ export function createDynamicDroidCliModelDescriptor( options?: { customProxy?: boolean; reasoningTiers?: string[]; + defaultReasoningEffort?: string; serviceTiers?: string[]; contextWindow?: number; maxOutputTokens?: number; @@ -1316,9 +1369,17 @@ export function createDynamicDroidCliModelDescriptor( const reasoningTiers = options?.reasoningTiers?.length ? options.reasoningTiers : canonicalDroid?.reasoningTiers; - const serviceTiers = options?.serviceTiers?.length - ? options.serviceTiers - : canonicalDroid?.serviceTiers; + const canonicalDroidModelId = normalizeAnthropicRuntimeAlias( + rawProviderModelId.replace(/^custom:/i, ""), + )?.modelId; + // Droid's SDK and CLI launch contracts do not consume a Fast toggle. Keep + // Opus 5 reasoning metadata, but never surface an inert service-tier control + // for built-in aliases, direct ids, or custom-proxy ids. + const serviceTiers = canonicalDroidModelId === "claude-opus-5" + ? undefined + : options?.serviceTiers?.length + ? options.serviceTiers + : canonicalDroid?.serviceTiers; return { id, shortId: trimmedProviderModelId, @@ -1337,6 +1398,9 @@ export function createDynamicDroidCliModelDescriptor( providerModelId: trimmedProviderModelId, cliCommand: "droid", ...(reasoningTiers?.length ? { reasoningTiers: [...reasoningTiers] } : {}), + ...((options?.defaultReasoningEffort ?? canonicalDroid?.defaultReasoningEffort) + ? { defaultReasoningEffort: options?.defaultReasoningEffort ?? canonicalDroid?.defaultReasoningEffort } + : {}), ...(serviceTiers?.length ? { serviceTiers: [...serviceTiers] } : {}), isCliWrapped: true, ...(options?.customProxy ? { customProxy: true } : {}), diff --git a/apps/ios/ADE/Resources/VoiceGlossary.json b/apps/ios/ADE/Resources/VoiceGlossary.json index c650c9077..6a52c6104 100644 --- a/apps/ios/ADE/Resources/VoiceGlossary.json +++ b/apps/ios/ADE/Resources/VoiceGlossary.json @@ -12,6 +12,7 @@ "Sonnet", "Haiku", "Fable", + "Claude Opus 5", "Claude Opus 4.8", "Claude Sonnet 5", "Claude Haiku 4.5", @@ -30,7 +31,6 @@ "OpenAI", "Google", "Gemini", - "Mistral", "DeepSeek", "xAI", "Groq", diff --git a/apps/ios/ADE/Views/Components/ADEDesignSystem.swift b/apps/ios/ADE/Views/Components/ADEDesignSystem.swift index eb1ce9394..fc3a7d4ef 100644 --- a/apps/ios/ADE/Views/Components/ADEDesignSystem.swift +++ b/apps/ios/ADE/Views/Components/ADEDesignSystem.swift @@ -121,6 +121,9 @@ enum ADEColor { "anthropic/claude-fable-5-api": 0xD97706, "claude-fable-5": 0xD97706, "fable": 0xD97706, + "anthropic/claude-opus-5": 0xD97706, + "claude-opus-5": 0xD97706, + "opus": 0xD97706, "anthropic/claude-opus-4-8": 0xD97706, "claude-opus-4-8": 0xD97706, "anthropic/claude-opus-4-7-1m": 0xB45309, @@ -202,12 +205,17 @@ enum ADEColor { append("anthropic/claude-fable-5-api") append("claude-fable-5") append("fable") - case "anthropic/claude-opus-4-8", "claude-opus-4-8", + case "opus", "opus-5", "opus-5.0", "opus-5-0", + "anthropic/claude-opus-5", "anthropic/claude-opus-5-api", "claude-opus-5": + append("anthropic/claude-opus-5") + append("claude-opus-5") + append("opus") + case "anthropic/claude-opus-4-8", "anthropic/claude-opus-4-8-api", "claude-opus-4-8", "opus-4.8", "opus-4-8", "opus-4.8-1m", "opus-4.8[1m]", "opus-4-8-1m", "anthropic/claude-opus-4-8-1m", "claude-opus-4-8-1m", "claude-opus-4-8[1m]": append("anthropic/claude-opus-4-8") append("claude-opus-4-8") - case "opus", "anthropic/claude-opus-4-7", "claude-opus-4-7": + case "anthropic/claude-opus-4-7", "anthropic/claude-opus-4-7-api", "claude-opus-4-7": append("anthropic/claude-opus-4-8") append("claude-opus-4-8") case "opus[1m]", "opus-1m", "anthropic/claude-opus-4-7-1m", "claude-opus-4-7-1m", "claude-opus-4-7[1m]": @@ -265,6 +273,9 @@ enum ADEColor { "anthropic/claude-fable-5-api": ["low", "medium", "high", "xhigh", "max", "ultracode"], "claude-fable-5": ["low", "medium", "high", "xhigh", "max", "ultracode"], "fable": ["low", "medium", "high", "xhigh", "max", "ultracode"], + "anthropic/claude-opus-5": ["low", "medium", "high", "xhigh", "max"], + "claude-opus-5": ["low", "medium", "high", "xhigh", "max"], + "opus": ["low", "medium", "high", "xhigh", "max"], "anthropic/claude-opus-4-8": ["low", "medium", "high", "xhigh", "max", "ultracode"], "claude-opus-4-8": ["low", "medium", "high", "xhigh", "max", "ultracode"], "anthropic/claude-opus-4-7-1m": ["low", "medium", "high", "xhigh", "max"], diff --git a/apps/ios/ADE/Views/Work/WorkChatComposerAndInputViews.swift b/apps/ios/ADE/Views/Work/WorkChatComposerAndInputViews.swift index e3b677809..0a7a3cfff 100644 --- a/apps/ios/ADE/Views/Work/WorkChatComposerAndInputViews.swift +++ b/apps/ios/ADE/Views/Work/WorkChatComposerAndInputViews.swift @@ -212,11 +212,14 @@ private func workModelRefsLookFastCapable(_ rawRefs: [String?]) -> Bool { "gpt-5.4", "gpt-5.4-codex", "openai/gpt-5.4", "openai/gpt-5.4-codex", "opencode/openai/gpt-5.4", "fable", "claude-fable-5", "anthropic/claude-fable-5", "opencode/anthropic/claude-fable-5", - "opus", "claude-opus-4-8", "anthropic/claude-opus-4-8", "opencode/anthropic/claude-opus-4-8", - "opus-4-7", "claude-opus-4-7", "anthropic/claude-opus-4-7", "opencode/anthropic/claude-opus-4-7", + "opus", "opus-5", "opus-5.0", "opus-5-0", "claude-opus-5", + "anthropic/claude-opus-5", "anthropic/claude-opus-5-api", "opencode/anthropic/claude-opus-5", + "opus-4-7", "claude-opus-4-7", "anthropic/claude-opus-4-7", + "anthropic/claude-opus-4-7-api", "opencode/anthropic/claude-opus-4-7", "opencode/anthropic/opus", "opus-1m", "opus[1m]", "claude-opus-4-7-1m", "anthropic/claude-opus-4-7-1m", "opus-4.8", "opus-4-8", "claude-opus-4-8", "anthropic/claude-opus-4-8", + "anthropic/claude-opus-4-8-api", "opencode/anthropic/claude-opus-4-8", ] return refs.contains { fastRefs.contains($0) || $0.hasSuffix("-fast") } diff --git a/apps/ios/ADE/Views/Work/WorkModelCatalog.swift b/apps/ios/ADE/Views/Work/WorkModelCatalog.swift index 524c88738..79f36aba0 100644 --- a/apps/ios/ADE/Views/Work/WorkModelCatalog.swift +++ b/apps/ios/ADE/Views/Work/WorkModelCatalog.swift @@ -6,7 +6,7 @@ import SwiftUI /// keeps the picker-facing tier, reasoning, and service metadata needed to /// render a branded row and send a valid selection to the paired runtime. struct WorkModelOption: Identifiable, Hashable { - /// Stable sync-contract id the host accepts (e.g. "claude-opus-4-8"). + /// Stable sync-contract id the host accepts (e.g. "claude-opus-5"). let id: String let displayName: String let tier: Tier @@ -230,6 +230,16 @@ struct WorkModelCatalogGroupLegacyView: Identifiable, Hashable { private let workModelGroupOrder = ["claude", "codex", "cursor", "droid", "opencode", "ollama", "lmstudio"] +private func workClaudeOpus5ReasoningEfforts() -> [AgentChatModelReasoningEffort] { + [ + AgentChatModelReasoningEffort(effort: "low", description: "Fast answers with light reasoning"), + AgentChatModelReasoningEffort(effort: "medium", description: "Balanced speed and reasoning"), + AgentChatModelReasoningEffort(effort: "high", description: "Recommended for complex agentic work"), + AgentChatModelReasoningEffort(effort: "xhigh", description: "Extended reasoning for difficult work"), + AgentChatModelReasoningEffort(effort: "max", description: "Maximum reasoning depth for the hardest problems"), + ] +} + private func workCodex56ReasoningEfforts(includeUltra: Bool) -> [AgentChatModelReasoningEffort] { var efforts = [ AgentChatModelReasoningEffort(effort: "low", description: "Fast answers with light reasoning"), @@ -348,9 +358,19 @@ private func workCuratedModelCatalogGroups() -> [WorkModelCatalogGroup] { displayName: "Anthropic", models: [ WorkModelOption(id: "claude-fable-5", displayName: "Claude Fable 5", tier: .flagship, tagline: "Flagship · 1M context", provider: "claude", serviceTiers: ["fast"]), - WorkModelOption(id: "claude-opus-4-8", displayName: "Claude Opus 4.8 1M", tier: .flagship, tagline: "Flagship · 1M context", provider: "claude", serviceTiers: ["fast"]), + WorkModelOption( + id: "claude-opus-5", + displayName: "Claude Opus 5", + tier: .flagship, + tagline: "Agentic coding · 1M context", + provider: "claude", + reasoningEfforts: workClaudeOpus5ReasoningEfforts(), + defaultReasoningEffort: "high", + serviceTiers: ["fast"] + ), WorkModelOption(id: "claude-sonnet-5", displayName: "Claude Sonnet 5", tier: .balanced, tagline: "Balanced · 1M context", provider: "claude"), WorkModelOption(id: "claude-haiku-4-5", displayName: "Claude Haiku 4.5", tier: .fast, tagline: "Fastest · cheapest", provider: "claude"), + WorkModelOption(id: "claude-opus-4-8", displayName: "Claude Opus 4.8 1M", tier: .flagship, tagline: "Previous Opus · 1M context", provider: "claude", serviceTiers: ["fast"]), WorkModelOption(id: "claude-opus-4-7-1m", displayName: "Claude Opus 4.7 1M", tier: .flagship, tagline: "1M-token context window", provider: "claude", serviceTiers: ["fast"]), ] ) @@ -445,6 +465,15 @@ private func workCuratedModelCatalogGroups() -> [WorkModelCatalogGroup] { key: "anthropic", displayName: "Anthropic (Droid)", models: [ + WorkModelOption( + id: "claude-opus-5", + displayName: "Opus 5", + tier: .flagship, + tagline: "Agentic coding · 1M context", + provider: "claude", + reasoningEfforts: workClaudeOpus5ReasoningEfforts(), + defaultReasoningEffort: "high" + ), WorkModelOption(id: "claude-opus-4-6", displayName: "Opus 4.6 (2x)", tier: .flagship, tagline: "Flagship reasoning · 2x usage", provider: "claude"), WorkModelOption(id: "claude-opus-4-6-fast", displayName: "Opus 4.6 Fast Mode (12x)", tier: .flagship, tagline: "Faster Opus · 12x usage", provider: "claude"), WorkModelOption(id: "claude-opus-4-5-20251101", displayName: "Opus 4.5 (2x)", tier: .flagship, tagline: "Prior-gen Opus", provider: "claude"), @@ -497,9 +526,19 @@ private func workCuratedModelCatalogGroups() -> [WorkModelCatalogGroup] { displayName: "Anthropic", models: [ WorkModelOption(id: "opencode/anthropic/claude-fable-5", displayName: "Claude Fable 5", tier: .flagship, tagline: "Flagship · 1M context", provider: "claude", serviceTiers: ["fast"]), - WorkModelOption(id: "opencode/anthropic/claude-opus-4-8", displayName: "Claude Opus 4.8 1M", tier: .flagship, tagline: "Flagship reasoning · 1M context", provider: "claude", serviceTiers: ["fast"]), + WorkModelOption( + id: "opencode/anthropic/claude-opus-5", + displayName: "Claude Opus 5", + tier: .flagship, + tagline: "Agentic coding · 1M context", + provider: "claude", + reasoningEfforts: workClaudeOpus5ReasoningEfforts(), + defaultReasoningEffort: "high", + serviceTiers: ["fast"] + ), WorkModelOption(id: "opencode/anthropic/claude-sonnet-5", displayName: "Claude Sonnet 5", tier: .balanced, tagline: "Balanced coder · 1M context", provider: "claude"), WorkModelOption(id: "opencode/anthropic/claude-haiku-4-5", displayName: "Claude Haiku 4.5", tier: .fast, tagline: "Fastest Anthropic", provider: "claude"), + WorkModelOption(id: "opencode/anthropic/claude-opus-4-8", displayName: "Claude Opus 4.8 1M", tier: .flagship, tagline: "Previous Opus · 1M context", provider: "claude", serviceTiers: ["fast"]), WorkModelOption(id: "opencode/anthropic/claude-opus-4-7-1m", displayName: "Claude Opus 4.7 1M", tier: .flagship, tagline: "1M-token context window", provider: "claude", serviceTiers: ["fast"]), ] ), @@ -769,11 +808,15 @@ private func workCanonicalClaudeRegistryId(for raw: String) -> String? { switch raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { case "fable", "claude-fable-5", "anthropic/claude-fable-5", "anthropic/claude-fable-5-api": return "anthropic/claude-fable-5" - case "claude-opus-4-8", "anthropic/claude-opus-4-8", + case "opus", "opus-5", "opus-5.0", "opus-5-0", + "claude-opus-5", "anthropic/claude-opus-5", "anthropic/claude-opus-5-api", + "opencode/anthropic/opus", "opencode/anthropic/claude-opus-5": + return "anthropic/claude-opus-5" + case "claude-opus-4-8", "anthropic/claude-opus-4-8", "anthropic/claude-opus-4-8-api", "opus-4.8", "opus-4-8", "opus-4.8-1m", "opus-4.8[1m]", "opus-4-8-1m", "claude-opus-4-8-1m", "claude-opus-4-8[1m]", "anthropic/claude-opus-4-8-1m": return "anthropic/claude-opus-4-8" - case "opus", "claude-opus-4-7", "anthropic/claude-opus-4-7", + case "claude-opus-4-7", "anthropic/claude-opus-4-7", "anthropic/claude-opus-4-7-api", "opus-4.6", "opus-4-6", "claude-opus-4-6", "anthropic/claude-opus-4-6": return "anthropic/claude-opus-4-8" case "opus[1m]", "opus-1m", "claude-opus-4-7-1m", "claude-opus-4-7[1m]", "anthropic/claude-opus-4-7-1m", @@ -793,11 +836,15 @@ private func workClaudeRuntimeModelId(for raw: String) -> String? { switch raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { case "fable", "claude-fable-5", "anthropic/claude-fable-5", "anthropic/claude-fable-5-api": return "claude-fable-5" - case "claude-opus-4-8", "anthropic/claude-opus-4-8", + case "opus", "opus-5", "opus-5.0", "opus-5-0", + "claude-opus-5", "anthropic/claude-opus-5", "anthropic/claude-opus-5-api", + "opencode/anthropic/opus", "opencode/anthropic/claude-opus-5": + return "claude-opus-5" + case "claude-opus-4-8", "anthropic/claude-opus-4-8", "anthropic/claude-opus-4-8-api", "opus-4.8", "opus-4-8", "opus-4.8-1m", "opus-4.8[1m]", "opus-4-8-1m", "claude-opus-4-8-1m", "claude-opus-4-8[1m]", "anthropic/claude-opus-4-8-1m": return "claude-opus-4-8" - case "opus", "claude-opus-4-7", "anthropic/claude-opus-4-7", + case "claude-opus-4-7", "anthropic/claude-opus-4-7", "anthropic/claude-opus-4-7-api", "opus-4.6", "opus-4-6", "claude-opus-4-6", "anthropic/claude-opus-4-6": return "claude-opus-4-8" case "opus[1m]", "opus-1m", "claude-opus-4-7-1m", "claude-opus-4-7[1m]", "anthropic/claude-opus-4-7-1m", @@ -873,11 +920,15 @@ func workKnownModelDisplayName(_ raw: String?) -> String? { switch raw?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() ?? "" { case "fable", "anthropic/claude-fable-5", "claude-fable-5", "opencode/anthropic/claude-fable-5": return "Claude Fable 5" - case "anthropic/claude-opus-4-8", "claude-opus-4-8", + case "opus", "opus-5", "opus-5.0", "opus-5-0", + "anthropic/claude-opus-5", "anthropic/claude-opus-5-api", "claude-opus-5", + "opencode/anthropic/opus", "opencode/anthropic/claude-opus-5": + return "Claude Opus 5" + case "anthropic/claude-opus-4-8", "anthropic/claude-opus-4-8-api", "claude-opus-4-8", "opus-4.8", "opus-4-8", "opus-4.8-1m", "opus-4.8[1m]", "opus-4-8-1m", "anthropic/claude-opus-4-8-1m", "claude-opus-4-8-1m", "claude-opus-4-8[1m]": return "Claude Opus 4.8 1M" - case "opus", "anthropic/claude-opus-4-7", "claude-opus-4-7", + case "anthropic/claude-opus-4-7", "anthropic/claude-opus-4-7-api", "claude-opus-4-7", "opus-4.6", "opus-4-6", "anthropic/claude-opus-4-6", "claude-opus-4-6": return "Claude Opus 4.8 1M" case "opus[1m]", "opus-1m", "anthropic/claude-opus-4-7-1m", "claude-opus-4-7-1m", "claude-opus-4-7[1m]", diff --git a/apps/ios/ADETests/ADETests.swift b/apps/ios/ADETests/ADETests.swift index 2fcca140b..eff26f9e9 100644 --- a/apps/ios/ADETests/ADETests.swift +++ b/apps/ios/ADETests/ADETests.swift @@ -14280,6 +14280,16 @@ final class ADETests: XCTestCase { model: "opus", status: "idle" ) + let claudeOpus5 = makeAgentChatSessionSummary( + provider: "claude", + model: "claude-opus-5", + status: "idle" + ) + let claudeOpus48 = makeAgentChatSessionSummary( + provider: "claude", + model: "claude-opus-4-8", + status: "idle" + ) let claude = makeAgentChatSessionSummary( provider: "claude", model: "sonnet", @@ -14299,6 +14309,8 @@ final class ADETests: XCTestCase { XCTAssertTrue(workChatComposerSupportsFastMode(codex)) XCTAssertFalse(workChatComposerSupportsFastMode(codexMini)) XCTAssertTrue(workChatComposerSupportsFastMode(claudeOpus)) + XCTAssertTrue(workChatComposerSupportsFastMode(claudeOpus5)) + XCTAssertTrue(workChatComposerSupportsFastMode(claudeOpus48)) XCTAssertFalse(workChatComposerSupportsFastMode(claude)) XCTAssertTrue(workChatComposerSupportsFastMode(openCode)) XCTAssertTrue(workChatComposerSupportsFastMode(openCodeLegacyOpus)) @@ -15703,19 +15715,56 @@ final class ADETests: XCTestCase { let claudeGroup = groups.first(where: { $0.key == "claude" }) let anthropicProvider = claudeGroup?.providers.first(where: { $0.key == "anthropic" }) let fable = anthropicProvider?.models.first(where: { $0.id == "claude-fable-5" }) + let opus5 = anthropicProvider?.models.first(where: { $0.id == "claude-opus-5" }) let opus48 = anthropicProvider?.models.first(where: { $0.id == "claude-opus-4-8" }) + let openCodeAnthropic = groups + .first(where: { $0.key == "opencode" })? + .providers + .first(where: { $0.key == "anthropic" }) + let droidAnthropic = groups + .first(where: { $0.key == "droid" })? + .providers + .first(where: { $0.key == "anthropic" }) + let droidOpus5 = droidAnthropic?.models.first(where: { $0.id == "claude-opus-5" }) let codexGroup = groups.first(where: { $0.key == "codex" }) let openAIProvider = codexGroup?.providers.first(where: { $0.key == "openai" }) let gpt55 = openAIProvider?.models.first(where: { $0.id == "gpt-5.5" }) - XCTAssertEqual(anthropicProvider?.models.first?.id, "claude-fable-5") + XCTAssertEqual(anthropicProvider?.models.map(\.id), [ + "claude-fable-5", + "claude-opus-5", + "claude-sonnet-5", + "claude-haiku-4-5", + "claude-opus-4-8", + "claude-opus-4-7-1m", + ]) + XCTAssertEqual(openCodeAnthropic?.models.map(\.id), [ + "opencode/anthropic/claude-fable-5", + "opencode/anthropic/claude-opus-5", + "opencode/anthropic/claude-sonnet-5", + "opencode/anthropic/claude-haiku-4-5", + "opencode/anthropic/claude-opus-4-8", + "opencode/anthropic/claude-opus-4-7-1m", + ]) + XCTAssertEqual(workDefaultCatalogModelId(provider: "claude"), "claude-fable-5") XCTAssertEqual(fable?.displayName, "Claude Fable 5") XCTAssertEqual(fable?.tier, .flagship) XCTAssertEqual(fable?.tagline, "Flagship · 1M context") XCTAssertNotNil(ADEColor.modelBrand(for: "claude-fable-5")) + XCTAssertEqual(opus5?.displayName, "Claude Opus 5") + XCTAssertEqual(opus5?.tagline, "Agentic coding · 1M context") + XCTAssertEqual(opus5?.reasoningEfforts.map(\.effort), ["low", "medium", "high", "xhigh", "max"]) + XCTAssertEqual(opus5?.defaultReasoningEffort, "high") + XCTAssertTrue(opus5?.supportsCodexFastMode == true) + XCTAssertNotNil(ADEColor.modelBrand(for: "claude-opus-5")) + XCTAssertEqual(droidAnthropic?.models.first?.id, "claude-opus-5") + XCTAssertEqual(droidOpus5?.displayName, "Opus 5") + XCTAssertEqual(droidOpus5?.reasoningEfforts.map(\.effort), ["low", "medium", "high", "xhigh", "max"]) + XCTAssertEqual(droidOpus5?.defaultReasoningEffort, "high") + XCTAssertFalse(droidOpus5?.supportsCodexFastMode == true) XCTAssertEqual(opus48?.displayName, "Claude Opus 4.8 1M") XCTAssertEqual(opus48?.tier, .flagship) - XCTAssertEqual(opus48?.tagline, "Flagship · 1M context") + XCTAssertEqual(opus48?.tagline, "Previous Opus · 1M context") XCTAssertNotNil(ADEColor.modelBrand(for: "claude-opus-4-8")) XCTAssertEqual(gpt55?.displayName, "GPT-5.5") XCTAssertEqual(gpt55?.tier, .flagship) @@ -15768,11 +15817,14 @@ final class ADETests: XCTestCase { XCTAssertEqual(ADEColor.reasoningTiers(for: "anthropic/claude-fable-5-api"), ["low", "medium", "high", "xhigh", "max", "ultracode"]) XCTAssertEqual(ADEColor.reasoningTiers(for: "claude-fable-5"), ["low", "medium", "high", "xhigh", "max", "ultracode"]) XCTAssertEqual(ADEColor.reasoningTiers(for: "fable"), ["low", "medium", "high", "xhigh", "max", "ultracode"]) + XCTAssertEqual(ADEColor.reasoningTiers(for: "anthropic/claude-opus-5"), ["low", "medium", "high", "xhigh", "max"]) + XCTAssertEqual(ADEColor.reasoningTiers(for: "claude-opus-5"), ["low", "medium", "high", "xhigh", "max"]) + XCTAssertEqual(ADEColor.reasoningTiers(for: "opus"), ["low", "medium", "high", "xhigh", "max"]) XCTAssertEqual(ADEColor.reasoningTiers(for: "anthropic/claude-opus-4-8"), ["low", "medium", "high", "xhigh", "max", "ultracode"]) + XCTAssertEqual(ADEColor.reasoningTiers(for: "anthropic/claude-opus-4-8-api"), ["low", "medium", "high", "xhigh", "max", "ultracode"]) XCTAssertEqual(ADEColor.reasoningTiers(for: "claude-opus-4-8"), ["low", "medium", "high", "xhigh", "max", "ultracode"]) XCTAssertEqual(ADEColor.reasoningTiers(for: "anthropic/claude-opus-4-7"), ["low", "medium", "high", "xhigh", "max", "ultracode"]) XCTAssertEqual(ADEColor.reasoningTiers(for: "claude-opus-4-7"), ["low", "medium", "high", "xhigh", "max", "ultracode"]) - XCTAssertEqual(ADEColor.reasoningTiers(for: "opus"), ["low", "medium", "high", "xhigh", "max", "ultracode"]) XCTAssertEqual(ADEColor.reasoningTiers(for: "opus[1m]"), ["low", "medium", "high", "xhigh", "max"]) XCTAssertEqual(ADEColor.reasoningTiers(for: "anthropic/claude-sonnet-5"), ["low", "medium", "high", "max"]) XCTAssertNil(ADEColor.reasoningTiers(for: "claude-haiku-4-5")) @@ -16078,8 +16130,15 @@ final class ADETests: XCTestCase { XCTAssertEqual(prettyWorkChatModelName("openai/gpt-5.5"), "GPT-5.5") } - func testWorkModelCatalogTreatsOpus46AliasesAsMigratedRows() { + func testWorkModelCatalogMapsCurrentAndMigratedOpusAliases() { + XCTAssertTrue(workModelIdsEquivalent("opus", "claude-opus-5")) + XCTAssertTrue(workModelIdsEquivalent("anthropic/claude-opus-5-api", "claude-opus-5")) + XCTAssertTrue(workModelIdsEquivalent("opencode/anthropic/opus", "claude-opus-5")) + XCTAssertTrue(workModelIdsEquivalent("opencode/anthropic/claude-opus-5", "claude-opus-5")) + XCTAssertEqual(workKnownModelDisplayName("anthropic/claude-opus-5-api"), "Claude Opus 5") + XCTAssertEqual(workKnownModelDisplayName("opencode/anthropic/opus"), "Claude Opus 5") XCTAssertTrue(workModelIdsEquivalent("claude-opus-4-6", "claude-opus-4-8")) + XCTAssertTrue(workModelIdsEquivalent("anthropic/claude-opus-4-8-api", "claude-opus-4-8")) XCTAssertTrue(workModelIdsEquivalent("anthropic/claude-opus-4-6", "anthropic/claude-opus-4-8")) XCTAssertTrue(workModelIdsEquivalent("opus-4-6", "claude-opus-4-8")) XCTAssertTrue(workModelIdsEquivalent("opus-4.6", "claude-opus-4-8")) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 138eeebc5..5fbd8428b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -510,10 +510,10 @@ Agent tools are split by domain: `apps/desktop/src/shared/modelRegistry.ts` + `apps/desktop/src/shared/modelProfiles.ts`: -- `MODEL_REGISTRY` — static CLI-wrapped entries + dynamically populated API-key/local entries. The OpenAI/Codex block is ordered GPT-5.6 Sol, Terra, Luna, then the retained GPT-5.5 and older rows; `pickDefaultCodexModel` chooses the newest Sol row, so new Codex sessions default to GPT-5.6 Sol. All three GPT-5.6 descriptors have a 372k context window and advertise Fast. Sol and Terra expose `low | medium | high | xhigh | max | ultra`; Luna exposes `low | medium | high | xhigh | max`. The product labels those tiers Light, Medium, High, Extra High, Max, and (for Sol/Terra) Ultra. Runtime app-server ladders pass through in their advertised order, including Max. `defaultReasoningEffort` is `low` for Sol and `medium` for Terra/Luna and is honored by desktop, TUI, iOS, CTO, review, and handoff pickers. The Claude block is ordered for every picker as Fable 5, Opus 4.8 1M, Sonnet 5, Haiku 4.5, then Opus 4.7 1M. Sonnet 5 uses provider model `claude-sonnet-5` (1,000,000 context / 128,000 max output); removed Sonnet 4.6 and basic Opus 4.7 ids resolve forward as compatibility aliases but do not appear as selectable rows. Opus 4.7 1M remains available as `anthropic/claude-opus-4-7-1m` with aliases `opus[1m]` / `claude-opus-4-7[1m]`. `ModelDescriptor.serviceTiers?: string[]` advertises optional service tiers (today: `"fast"`, set on Fable/Opus, GPT-5.6 and older fast-capable Codex entries, and dynamic Cursor SDK/CLI rows) that the UI's Fast Mode toggle keys off. Codex maps it to the JSON-RPC `serviceTier` argument; Cursor SDK maps it through discovered model parameters, and Cursor CLI launches use the matching fast model alias when present. +- `MODEL_REGISTRY` — static CLI-wrapped entries + dynamically populated API-key/local entries. The OpenAI/Codex block is ordered GPT-5.6 Sol, Terra, Luna, then the retained GPT-5.5 and older rows; `pickDefaultCodexModel` chooses the newest Sol row, so new Codex sessions default to GPT-5.6 Sol. All three GPT-5.6 descriptors have a 372k context window and advertise Fast. Sol and Terra expose `low | medium | high | xhigh | max | ultra`; Luna exposes `low | medium | high | xhigh | max`. The product labels those tiers Light, Medium, High, Extra High, Max, and (for Sol/Terra) Ultra. Runtime app-server ladders pass through in their advertised order, including Max. `defaultReasoningEffort` is `low` for Sol and `medium` for Terra/Luna and is honored by desktop, TUI, iOS, CTO, review, and handoff pickers. The Claude block is ordered for every picker as Fable 5, Opus 5, Sonnet 5, Haiku 4.5, Opus 4.8 1M, then Opus 4.7 1M. Opus 5 uses provider model `claude-opus-5` (1,000,000 context / 128,000 max output), defaults to `high` effort, and advertises Fast. Sonnet 5 uses provider model `claude-sonnet-5` (1,000,000 context / 128,000 max output); removed Sonnet 4.6 and basic Opus 4.7 ids resolve forward as compatibility aliases but do not appear as selectable rows. The generic `opus` alias resolves to Opus 5. Opus 4.8 remains selectable after Haiku, while Opus 4.7 1M remains available as `anthropic/claude-opus-4-7-1m` with aliases `opus[1m]` / `claude-opus-4-7[1m]`. `ModelDescriptor.serviceTiers?: string[]` advertises optional service tiers (today: `"fast"`, set on Fable/Opus, GPT-5.6 and older fast-capable Codex entries, and dynamic Cursor SDK/CLI rows) that the UI's Fast Mode toggle keys off. Codex maps it to the JSON-RPC `serviceTier` argument; Cursor SDK maps it through discovered model parameters, and Cursor CLI launches use the matching fast model alias when present. - `ModelProviderGroup` = `"claude" | "codex" | "opencode" | "cursor" | "droid"`. Cursor and Droid each have their own top-level provider group used by the model picker, identity routing, and tracked CLI provider catalog. - Helpers: `getModelById`, `getModelPricing`, `updateModelPricingInRegistry`, `replaceDynamicOpenCodeModelDescriptors`, `resolveProviderGroupForModel`, `resolveModelDescriptorForProvider`, `getRuntimeModelRefForDescriptor`, `modelSupportsServiceTier(descriptor, tier)` / `modelSupportsFastMode(descriptor)`. -- Reasoning tier passthrough (`providerOptions.ts`) maps tier strings directly to each provider's native config (`thinking.type`, `reasoningEffort`, `thinkingConfig.thinkingLevel`, etc.) — no arbitrary token budgets. Claude Fable and Opus 4.8 rows advertise `low | medium | high | xhigh | max | ultracode`; Sonnet 5 advertises `low | medium | high | max`. +- Reasoning tier passthrough (`providerOptions.ts`) maps tier strings directly to each provider's native config (`thinking.type`, `reasoningEffort`, `thinkingConfig.thinkingLevel`, etc.) — no arbitrary token budgets. Claude Opus 5 and Opus 4.7 advertise `low | medium | high | xhigh | max`; Fable and Opus 4.8 advertise `low | medium | high | xhigh | max | ultracode`; Sonnet 5 advertises `low | medium | high | max`. Interactive chat (Terminals, Work), CTO delegation, and automation-launched agent sessions flow through the unified executor with the same permission plumbing. Related feature docs: [Chat](./features/chat/README.md), [Agents](./features/agents/README.md), [CTO](./features/cto/README.md), and [Automations](./features/automations/README.md). diff --git a/docs/features/chat/README.md b/docs/features/chat/README.md index b46f1e753..7c38f3918 100644 --- a/docs/features/chat/README.md +++ b/docs/features/chat/README.md @@ -63,9 +63,9 @@ for its separate RPC, sync, storage, and UI contracts. | `apps/desktop/src/main/services/chat/droidSdkWorker.ts` | Node worker that hosts `@factory/droid-sdk`. Streams SDK events back to the main process and forwards permission / ask-user prompts back through the JSON-line protocol. | | `apps/desktop/src/main/services/chat/droidSdkProtocol.ts` | Worker IPC types: `DroidSdkSessionSettings` (autonomy level, interaction mode, reasoning effort), `DroidSdkReasoningEffort`, `DroidSdkPermissionRequest`/`Decision`, `DroidSdkAskUserRequest`/`Response`, `DroidSdkReady` (handshake with `availableModels`), and `DroidSdkSendPrompt`. | | `apps/desktop/src/main/services/chat/droidSdkEventMapper.ts` | Per-session `DroidSdkEventMapperState` + `mapDroidSdkMessageToChatEvents` / `mapDroidSdkRunResultToDoneEvent`. Tracks streaming text/thinking/image item ids, maps tool calls and results, maps `mission_worker_started` / `mission_worker_completed` notifications to provider-neutral subagent lifecycle events keyed by worker session id, surfaces image content as compact generation rows, and reports token usage. Replaces the deleted `droidAcpPool.ts` + `droidAcpEventMapper` path. | -| `apps/desktop/src/main/services/chat/droidModelsDiscovery.ts` | SDK-driven model probe (`listDroidModelsFromSdk`) plus the `~/.factory/config.json` custom-proxy merge. Normalizes retired factory Claude ids (Sonnet 4.6 -> Sonnet 5, basic Opus 4.7 -> Opus 4.8) before descriptors reach desktop, mobile, or TUI model pickers. Exposes `discoverDroidSdkModelDescriptors` (alias for the legacy `discoverDroidCliModelDescriptors` while callers migrate). | +| `apps/desktop/src/main/services/chat/droidModelsDiscovery.ts` | SDK-driven model probe (`listDroidModelsFromSdk`) plus the `~/.factory/config.json` custom-proxy merge. Normalizes the generic `opus` row to Opus 5 with its `high` default reasoning effort and Fast capability, while retired factory Claude ids still resolve forward (Sonnet 4.6 -> Sonnet 5, basic Opus 4.7 -> Opus 4.8) before descriptors reach desktop, mobile, or TUI model pickers. Exposes `discoverDroidSdkModelDescriptors` (alias for the legacy `discoverDroidCliModelDescriptors` while callers migrate). | | `apps/desktop/src/main/services/opencode/openCodeBinaryManager.ts` | Resolves the OpenCode CLI: PATH first, then the bundled `node_modules/.bin/opencode`. Cache entries are re-validated with `canRunBinaryCandidate` on every lookup so user installs after launch are picked up; missing-binary lookups are intentionally not cached. `clearOpenCodeBinaryCache()` is wired into the AI integration's full cache reset. | -| `apps/desktop/src/main/services/opencode/openCodeInventory.ts` | OpenCode provider/model probe. Now classifies model variants into `reasoningTiers` + `serviceTiers` (alias map covering `minimal`/`mini`/`med`/`xhigh`/`extra-high`), reads `capabilities` (tools/vision/reasoning) into descriptor capabilities, and tracks both `modelIds` (connected providers only) and `catalogModelIds` (the full browseable catalog). Anthropic rows normalize retired Sonnet 4.6 / basic Opus 4.7 ids to Sonnet 5 / Opus 4.8 so runtime catalogs cannot reintroduce removed picker rows. `OpenCodeProviderInfo.availableModelCount` exposes the connected count separately from `modelCount`. **Cross-launch persistence:** `persistOpenCodeInventory(projectRoot, providers)` writes each successful probe's provider list (keyed by project root, with `savedAt`) to `opencode-inventory-cache.json` under Electron `userData` (override via `ADE_OPENCODE_INVENTORY_CACHE_FILE`); on a cold start the Settings page reloads that persisted list flagged stale (`opencodeProvidersStale`) so the ~160-provider chip cloud renders immediately instead of blanking until the first live probe (stale-while-revalidate). Writes are best-effort and never break the probe. | +| `apps/desktop/src/main/services/opencode/openCodeInventory.ts` | OpenCode provider/model probe. Now classifies model variants into `reasoningTiers` + `serviceTiers` (alias map covering `minimal`/`mini`/`med`/`xhigh`/`extra-high`), reads `capabilities` (tools/vision/reasoning) into descriptor capabilities, and tracks both `modelIds` (connected providers only) and `catalogModelIds` (the full browseable catalog). Anthropic rows normalize generic `opus` to Opus 5 with its `high` default reasoning effort and Fast capability; retired Sonnet 4.6 / basic Opus 4.7 ids still resolve to Sonnet 5 / Opus 4.8 so runtime catalogs cannot reintroduce removed picker rows. `OpenCodeProviderInfo.availableModelCount` exposes the connected count separately from `modelCount`. **Cross-launch persistence:** `persistOpenCodeInventory(projectRoot, providers)` writes each successful probe's provider list (keyed by project root, with `savedAt`) to `opencode-inventory-cache.json` under Electron `userData` (override via `ADE_OPENCODE_INVENTORY_CACHE_FILE`); on a cold start the Settings page reloads that persisted list flagged stale (`opencodeProvidersStale`) so the ~160-provider chip cloud renders immediately instead of blanking until the first live probe (stale-while-revalidate). Writes are best-effort and never break the probe. | | `apps/desktop/src/main/services/opencode/openCodeAuthService.ts` | Drives the managed OpenCode server's auth API for subscription connect + API-key seeding, reusing the shared inventory server lease (never spawning its own process). `listAuthMethods` reads `GET /provider/auth`; `startOAuth` authorizes (`POST /provider/{id}/oauth/authorize`), opens the returned URL, and polls `provider.list().connected` every 2s until connected or a 5-min timeout, re-probing inventory on success; `cancelOAuth` stops the poller; `setProviderKey` does `PUT /auth/{id}` and mirrors the key into ADE's `apiKeyStore` so it is re-injected on future launches. One flow per `providerId` at a time (a new start supersedes the prior). Transitions are published through `addOpenCodeOAuthStatusListener` (`pending`/`connected`/`cancelled`/`timeout`/`failed`), a multi-sink fan-out so the same event reaches desktop windows and the remote/web runtime event buffer. Seeded credentials land in ADE's isolated managed OpenCode dir (XDG roots under `userData/opencode-runtime/xdg-v*`), never the user's `~/.local/share/opencode`. | | `apps/desktop/src/shared/chatTranscript.ts` | Pure JSON-lines parser for `AgentChatEventEnvelope` values. Used by both the main process and the renderer. | | `apps/desktop/src/shared/chatSubagents.ts` | Cross-target subagent helpers: `normalizeSubagentLifecycleEvent` (canonicalizes legacy `subagent_*` and dotted `subagent.*` envelopes), the stable `groupPaneSectionItems` partition and pane caps, `buildSubagentPaneRows`, tagged pane click targets, `buildSubagentTranscriptEvents`, `isLifecycleEventForSnapshot`, plus the `latestPlan` derivation. The partition keeps source order, forces pinned rows into the active cap, and excludes visually cleared Completed ids. It also owns the shared subagent-vs-background classification (`isBackgroundShellCommand`, `isRealSubagent`, `isNonAgentTaskRun`, `subagentAgentKey`) — `isNonAgentTaskRun` flags a `task_type` `other` run with no agent metadata (a plain Claude Code task, not a subagent) so both the idle-turn and foreground paths keep it out of the roster. Claude's raw `local_bash` kind is normalized only after explicit background evidence (`background_tasks_changed`, `is_backgrounded`, or `run_in_background`) because foreground Bash emits the same kind. The file also owns summary-quality helpers and `deriveSubagentTimelineRows` → `SubagentTimelineRow` (`spawn` / `result` / `background_chip`). Desktop consumes the partition directly; ADE Code consumes the expanded row model; iOS mirrors the same predicates and caps. | diff --git a/docs/features/chat/agent-routing.md b/docs/features/chat/agent-routing.md index adcd9f94c..80e4dea1b 100644 --- a/docs/features/chat/agent-routing.md +++ b/docs/features/chat/agent-routing.md @@ -94,14 +94,20 @@ defaults to `guarded` unless explicitly whitelisted. ### Reasoning tiers (Claude) Claude's global quick-pick vocabulary is `low | medium | high | max` -(`CLAUDE_THINKING_LEVELS` in `shared/modelProfiles.ts`), while Opus -registry rows can advertise the richer `low|medium|high|xhigh|max` -set for model-specific pickers. The Claude registry is ordered as -Fable 5, Opus 4.8 1M, Sonnet 5, Haiku 4.5, then Opus 4.7 1M. +(`CLAUDE_THINKING_LEVELS` in `shared/modelProfiles.ts`), while model +descriptors advertise their provider-native ladders to model-specific +pickers. Opus 5 and Opus 4.7 expose `low|medium|high|xhigh|max`; Fable +and Opus 4.8 add `ultracode`; Sonnet 5 exposes +`low|medium|high|max`; Haiku 4.5 has no reasoning control. The Claude +registry is ordered as +Fable 5, Opus 5, Sonnet 5, Haiku 4.5, Opus 4.8 1M, then Opus 4.7 1M. +Opus 5 selects provider model `claude-opus-5`, defaults to `high` +effort, and exposes `low|medium|high|xhigh|max` plus Fast Mode. Sonnet 5 selects provider model `claude-sonnet-5`; retired Sonnet 4.6 ids resolve forward for compatibility and no longer appear as picker rows. The basic Opus 4.7 row is also removed; its old aliases resolve -to Opus 4.8, while `opus[1m]` / `opus-1m` still target Opus 4.7 1M. +to Opus 4.8, while the generic `opus` alias selects Opus 5 and +`opus[1m]` / `opus-1m` still target Opus 4.7 1M. Passthrough to the provider config is unchanged (the tier string is forwarded directly to the CLI / SDK, with no synthesized token budgets). @@ -281,12 +287,12 @@ clients. Codex forwards Fast as `serviceTier: "fast" | null` on every `turn/start` and `thread/start` JSON-RPC call (an explicit `null` clears -any app-server default). Claude Opus descriptors advertise +any app-server default). Claude Fable and Opus descriptors advertise `serviceTiers: ["fast"]`; Claude chat sends the effective flag through the Agent SDK `settings.fastMode` layer, and Claude CLI launches/resumes pass `--settings '{"fastMode":true|false}'` so ADE can explicitly override user/project Claude settings when the chip is on or off. Claude -non-Opus rows do not advertise Fast, and ADE leaves Claude's native +Sonnet and Haiku rows do not advertise Fast, and ADE leaves Claude's native `/fast` slash command to the runtime instead of intercepting it. Cursor SDK sessions resolve the flag through `cursorModelsDiscovery` @@ -304,9 +310,11 @@ pre-marks GPT-5.6 and older fast-capable Codex CLI entries. Cursor discovery populates `serviceTiers` from SDK/CLI parameters and folds CLI `*-fast` rows into their base descriptors as aliases. OpenCode maps Fast to the provider variant `fast` for both chat and Work CLI launches. -Droid does not populate `serviceTiers`: Factory exposes fast choices as -concrete model IDs such as `claude-opus-4-6-fast`, so ADE launches that -selected model instead of showing an independent Fast toggle. +Droid preserves Factory's concrete fast model IDs when they are reported, +and its canonical Anthropic normalization also publishes `serviceTiers: +["fast"]` for fast-capable rows such as Opus 5. The former launch as the +selected concrete model; the latter use the same independent Fast toggle as +the other provider surfaces. Codex plan mode uses the native app-server planning flow. ADE passes its runtime guidance as an ordinary system-context input item and keeps diff --git a/docs/features/sync-and-multi-device/ios-companion.md b/docs/features/sync-and-multi-device/ios-companion.md index c2559ab38..fff5c687f 100644 --- a/docs/features/sync-and-multi-device/ios-companion.md +++ b/docs/features/sync-and-multi-device/ios-companion.md @@ -1976,10 +1976,11 @@ different machine's cached limits. (`WorkModelCatalog.swift`, mirroring desktop's `resolveCliProviderForModel`), so adding a provider means updating both the runtime registry and the phone's model-catalog grouping - together; the Claude picker order mirrors desktop (Fable 5, Opus - 4.8 1M, Sonnet 5, Haiku 4.5, Opus 4.7 1M) and legacy Sonnet 4.6 / + together; the Claude picker order mirrors desktop (Fable 5, Opus 5, + Sonnet 5, Haiku 4.5, Opus 4.8 1M, Opus 4.7 1M) and legacy Sonnet 4.6 / basic Opus 4.7 selections normalize forward instead of appearing as - rows. The OpenAI picker always promotes GPT-5.6 Sol, Terra, Luna in that + rows, while the generic `opus` alias resolves to Opus 5. The OpenAI picker + always promotes GPT-5.6 Sol, Terra, Luna in that order even when a host returns another order; Sol is the fallback default and GPT-5.5 remains below them. The phone prefers host-advertised reasoning tiers/defaults in their original order and falls back to Light / Medium /