feat(claude-local): add Sonnet 5.5 and refresh the qualified Claude runtime (#14993)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Claude local adapter lists models for users with a Claude
subscription.
> - The list needs Claude Sonnet 5.5 and its supported effort levels.
> - Sonnet 5.5 needs Claude Code 2.1.284 or later on both execution
paths.
> - The qualified ACP runtime previously used Claude Code 2.1.280.
> - This change adds Sonnet 5.5 and pins Agent SDK 0.3.286, which
includes Claude Code 2.1.286.
> - Users can select the model and run it with a qualified runtime.

## Linked Issues or Issue Description

Refs #3936. This replaces #14816 because the maintainer integration
cannot write to the contributor fork.

Thank you to @SkilLab-Tech for the model support, runtime refresh,
tests, and platform digest verification. This branch preserves both
original commits: `8d2f3261af61a2ac1120e51e8a8618732ace543b` and
`e68d1d002a3ed745f016fb11c50ac5a3c5a9ff8d`.

Related work:

- #14917 added Claude model ordering. This branch includes that merged
change and resolves its conflicts with #14816.
- #14942 updates the other models and harnesses. It remains separate.
Its matching Sonnet effort and CLI-gate changes are identical. Both PRs
merge with master. The second PR will need a rebase after the first
merges because adjacent runtime-pin and test edits conflict.
- #14954 is another Sonnet 5.5 change. It overlaps with the model
additions but does not include the qualified runtime refresh.
- #14039 makes the per-task effort picker model-aware. #3937 is also
related to effort selection.

The original author checked the [Claude Code
changelog](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md)
and [effort
documentation](https://platform.claude.com/docs/en/build-with-claude/effort)
on 2026-10-01.

## What Changed

- Add the direct `claude-sonnet-5-5` model and Low, Medium, High,
X-High, and Max effort levels.
- Require Claude Code 2.1.284 or later for that model on the CLI path.
- Put Sonnet 5.5 after Opus 5.5 in the current-model group. Keep Sonnet
5 in the older-model group.
- Retain the Sonnet 5.5 assertions and the model-order assertions in the
server tests.
- Pin Agent SDK 0.3.286 and Claude Code 2.1.286 across overrides,
integrity digests, qualified profiles, Rust provider pins, and the
Daytona version check.
- Update the related adapter and runtime documentation.

## Verification

Local verification uses the resolved source tree and pnpm 9.15.4. Model
tests passed on Node 25.9.0. Runtime integrity tests use CI's Node
24.21.0.

- Five focused Claude test files pass: 62 tests. They cover model
defaults, model ordering, CLI gates, and remote execution probes.
- Server model-list and UI setup tests pass: 30 tests.
- The Claude adapter typecheck passes.
- Runner integrity and qualification tests pass on Node 24.21.0: 78
tests. Four descriptor-loader tests fail on Node 25.9.0; all four pass
on the CI version.
- The runner package contract passes: 10 tests.
- Full local typecheck stopped with exit 137 in the database package
under the container's 4 GB memory limit. The production build reached
the runner Rust build, then stopped because `cargo` is absent.
- The full stable local Vitest run was stopped after all current-head CI
test shards passed. It did not complete locally. The 180 focused tests
listed above passed.
- `git diff --check` passes. The branch changes 20 files against master.
It has no lockfile or workflow changes.
- The original author verified all three platform digests against
registry integrity and ran the Linux executable. Its version was
`2.1.286 (Claude Code)`. See #14816 for that evidence.
- Greptile reviewed head `6db3d3f1` and gave 5/5 with zero comments.
Both Superagent scans and Commitperclip pass. All current-head CI jobs
pass, including build, typecheck, Rust, test shards, browser tests, and
the canary dry run.

## Risks

- The controller and provider pack must use matching runtime pins.
Deploy them together.
- CI owns `pnpm-lock.yaml`. The master lockfile refresh must resolve the
SDK override. Refresh the Daytona lock digest with that lockfile.
- Images built with Claude Code older than 2.1.284 need a rebuild before
the CLI path can use Sonnet 5.5.
- The runtime remains at SDK 0.3.286. This PR does not take the later
0.3.287 patch.
- A live Sonnet 5.5 session and a Daytona image build are not part of
the local verification.

## Model Used

- Original work: Anthropic Claude Code, `claude-sonnet-5-5`. Review:
`claude-opus-5-5`. The author reported `xhigh` effort, tool use, and
code execution. The original context window was not reported.
- Merge repair and PR preparation: OpenAI Codex, based on GPT-6, with
tool use and code execution. The runtime does not expose the exact model
identifier or context window in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

## Squash Attribution

Keep these trailers in the squash commit to preserve the original author
and AI attribution:

```text
Co-Authored-By: Claude Code (Ivan) <SkilLab-Tech@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
```

---------

Co-authored-by: Claude Code (Ivan) <ivan@skillab.com.br>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
authored and GitHub committed 2026-10-05 11:03:20 -07:00
1 parent 17fa718675
commit cab4263dc9
20 files changed
+46 -29

No files matched your search

+1 -1
View File
@@ -140,7 +140,7 @@ RUN /bin/sh -lc 'set -eu; \
command -v "$command_name" >/dev/null || { echo "FATAL: $command_name not on PATH for daytona user" >&2; exit 1; }; \
done; \
test "$(codex --version)" = "codex-cli 0.156.0"; \
test "$(claude --version)" = "2.1.280 (Claude Code)"; \
test "$(claude --version)" = "2.1.286 (Claude Code)"; \
test "$(opencode --version)" = "1.18.32"; \
test "$(acpx --version)" = "0.13.1"; \
test "$(claude-agent-acp --version)" = "0.73.0"; \
+5 -5
View File
@@ -14,12 +14,12 @@ image that this Dockerfile can extend directly.
## Harness versions
The September 22, 2026 refresh pins Codex 0.156.0, Claude Agent SDK
0.3.280 (Claude Code 2.1.280), and OpenCode 1.18.32 in the shared provider
The October 1, 2026 refresh pins Codex 0.156.0, Claude Agent SDK
0.3.286 (Claude Code 2.1.286), and OpenCode 1.18.32 in the shared provider
pack. Claude Code 2.1.280 is the minimum for
[Opus 5.5](https://code.claude.com/docs/en/model-config); it also supports
Fable 5.1. Codex uses the current
[GPT-6 Sol and Luna model IDs](https://learn.chatgpt.com/docs/models).
[Opus 5.5](https://code.claude.com/docs/en/model-config) and 2.1.284 is the
minimum for Sonnet 5.5; the pinned runtime also supports Fable 5.1. Codex uses
the current [GPT-6 Sol and Luna model IDs](https://learn.chatgpt.com/docs/models).
Grok CLI 1.0.41 supports the current
[Grok 4.7](https://docs.x.ai/developers/grok-4-7) model family.
+1 -1
View File
@@ -122,7 +122,7 @@
},
"overrides": {
"@agentclientprotocol/codex-acp@1.6.2>@openai/codex": "0.156.0",
"@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.280",
"@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.286",
"rollup": ">=4.59.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
@@ -8,10 +8,16 @@ describe("Claude model defaults", () => {
expect(claudeLocalReasoningEffortsForModel(model)).toEqual(["low", "medium", "high", "xhigh", "max"]);
});
it.each(["claude-sonnet-5-5", "claude-sonnet-5-5[1m]"])("requires CLI 2.1.284 and offers all efforts for %s", (model) => {
expect(minimumClaudeCliVersionForModel(model)).toBe("2.1.284");
expect(claudeLocalReasoningEffortsForModel(model)).toEqual(["low", "medium", "high", "xhigh", "max"]);
});
it("keeps model-specific Claude reasoning limits", () => {
expect(claudeLocalReasoningEffortsForModel("claude-fable-5-1")).toContain("xhigh");
expect(claudeLocalReasoningEffortsForModel("claude-sonnet-5")).toContain("max");
expect(claudeLocalReasoningEffortsForModel("claude-sonnet-4-6")).toEqual(["low", "medium", "high", "max"]);
expect(claudeLocalReasoningEffortsForModel("claude-sonnet-4-5")).toEqual(["low", "medium", "high"]);
expect(claudeLocalReasoningEffortsForModel("claude-haiku-4-5")).toEqual([]);
expect(claudeLocalReasoningEffortsForModel("custom-model")).toEqual(["low", "medium", "high"]);
});
+5 -3
View File
@@ -26,7 +26,7 @@ export const type = "claude_local";
export function claudeLocalReasoningEffortsForModel(model: string): readonly string[] {
const id = model.trim().replace(/\[1m\]$/, "").replace(/^(?:(?:us|eu|apac|global)\.)?anthropic\./, "");
if (/^claude-haiku-/.test(id)) return [];
if (/^claude-(?:opus-5(?:-5)?|opus-4-[78]|sonnet-5|fable-5(?:-1)?)$/.test(id)) {
if (/^claude-(?:opus-5(?:-5)?|opus-4-[78]|sonnet-5(?:-5)?|fable-5(?:-1)?)$/.test(id)) {
return ["low", "medium", "high", "xhigh", "max"];
}
if (/^claude-(?:opus|sonnet)-4-6(?:-v1)?$/.test(id)) return ["low", "medium", "high", "max"];
@@ -43,13 +43,14 @@ export const models = [
{ id: "claude-fable-5-1", label: "Claude Fable 5.1" },
{ id: "claude-mythos-5", label: "Claude Mythos 5" },
{ id: "claude-opus-5-5", label: "Claude Opus 5.5" },
{ id: "claude-sonnet-5", label: "Claude Sonnet 5" },
{ id: "claude-sonnet-5-5", label: "Claude Sonnet 5.5" },
{ id: "claude-haiku-4-5", label: "Claude Haiku 4.5" },
{ id: "claude-fable-5", label: "Claude Fable 5" },
{ id: "claude-opus-5", label: "Claude Opus 5" },
{ id: "claude-opus-4-8", label: "Claude Opus 4.8" },
{ id: "claude-opus-4-7", label: "Claude Opus 4.7" },
{ id: "claude-opus-4-6", label: "Claude Opus 4.6" },
{ id: "claude-sonnet-5", label: "Claude Sonnet 5" },
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
{ id: "claude-sonnet-4-5", label: "Claude Sonnet 4.5" },
];
@@ -63,7 +64,7 @@ Core fields:
- cwd (string, optional): default absolute working directory fallback for the agent process (created if missing when possible)
- instructionsFilePath (string, optional): absolute path to a markdown instructions file injected at runtime
- model (string, optional): Claude model id. Missing or blank defaults to ${DEFAULT_CLAUDE_LOCAL_MODEL} in both CLI and ACP, including existing agents. Explicit model IDs and ANTHROPIC_MODEL overrides are preserved. Bedrock/Vertex without an explicit model retain their provider default.
- effort (string, optional): model-specific reasoning effort passed via --effort (low|medium|high; current Opus, Sonnet 5, and Fable models also support xhigh|max)
- effort (string, optional): model-specific reasoning effort passed via --effort (low|medium|high; current Opus, Sonnet 5/5.5, and Fable models also support xhigh|max)
- chrome (boolean, optional): pass --chrome when running Claude
- promptTemplate (string, optional): run prompt template
- maxTurnsPerRun (number, optional): max turns for one run
@@ -92,6 +93,7 @@ Operational fields:
Notes:
- Claude Opus 5.5 uses model ID \`claude-opus-5-5\` and requires Claude Code v2.1.280 or later.
- Claude Sonnet 5.5 uses model ID \`claude-sonnet-5-5\` and requires Claude Code v2.1.284 or later.
- filesystemScope and networkScope are spawn-level confinement and are orthogonal to Claude permission flags. Both require Bubblewrap on the host and explicit engine="cli"; default or explicit ACP is rejected because ACP confinement is not yet supported. networkScope="allowlist" injects HTTP_PROXY/HTTPS_PROXY for the CLI while its private network namespace blocks direct sockets, so every required provider/API hostname must be listed explicitly.
- The Claude ACP lane requires Node >=24.11.0 and @agentclientprotocol/claude-agent-acp to be installed with this adapter package. Missing prerequisites fail both default and explicit ACP runs with an actionable setup error; the adapter never switches engines automatically.
- For ACP runs, model selection is passed through ANTHROPIC_MODEL at ACP server startup; Paperclip-managed Claude permissions and ephemeral skill materialization are handled by the shared ACP engine.
@@ -38,6 +38,7 @@ function cacheKeyForTarget(command: string, target: AdapterExecutionTarget | nul
export function minimumClaudeCliVersionForModel(model: string): string | null {
const modelId = model.trim().replace(/\[1m\]$/, "").replace(/^(?:(?:us|eu|apac|global)\.)?anthropic\./, "");
if (modelId === "claude-opus-5-5") return "2.1.280";
if (modelId === "claude-sonnet-5-5") return "2.1.284";
return modelId === "claude-fable-5-1"
? CLAUDE_FABLE_5_1_MIN_CLI_VERSION
: null;
@@ -18,7 +18,7 @@ const {
signal: null,
timedOut: false,
stdout: args.includes("--version")
? "2.1.280 (Claude Code)\n"
? "2.1.284 (Claude Code)\n"
: [
JSON.stringify({ type: "system", subtype: "init", session_id: "claude-session-1", model: "claude-sonnet" }),
JSON.stringify({ type: "assistant", session_id: "claude-session-1", message: { content: [{ type: "text", text: "hello" }] } }),
@@ -467,7 +467,7 @@ describe("claude remote execution", () => {
return { args: call?.[2] ?? [], result };
}
it.each(["claude-fable-5-1", "claude-opus-5-5"])("passes %s as --model on the CLI lane", async (model) => {
it.each(["claude-fable-5-1", "claude-opus-5-5", "claude-sonnet-5-5"])("passes %s as --model on the CLI lane", async (model) => {
const { args } = await executeWithModel("paperclip-claude-model-direct-", {
model,
});
@@ -500,6 +500,7 @@ describe("claude remote execution", () => {
it.each([
["claude-fable-5-1", "2.1.251", "2.1.247"],
["claude-opus-5-5", "2.1.280", "2.1.279"],
["claude-sonnet-5-5", "2.1.284", "2.1.283"],
])("rejects %s before launch below CLI %s", async (model, minimumVersion, detectedVersion) => {
runChildProcess.mockResolvedValueOnce({
exitCode: 0,
@@ -642,6 +642,7 @@ describe("claude CLI local hello probe hardening", () => {
it.each([
["claude-fable-5-1", "2.1.251"],
["claude-opus-5-5", "2.1.280"],
["claude-sonnet-5-5", "2.1.284"],
])("warns without executing %s when runtime PATH selects a different executable", async (model, minimumVersion) => {
const runtimeDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-cli-runtime-path-"));
const runtimeClaudePath = path.join(runtimeDir, "claude");
@@ -144,6 +144,7 @@ describe("claude CLI model compatibility check", () => {
it.each([
["claude-fable-5-1", "2.1.251", "2.1.247"],
["claude-opus-5-5", "2.1.280", "2.1.279"],
["claude-sonnet-5-5", "2.1.284", "2.1.283"],
])("fails before the hello probe for %s below CLI %s", async (model, minimumVersion, detectedVersion) => {
probeResult.value = {
exitCode: 0,
@@ -153,7 +153,7 @@ impl AcpxProviderDescriptor {
"@agentclientprotocol/claude-agent-acp",
"0.73.0",
Some("@anthropic-ai/claude-agent-sdk"),
Some("0.3.280"),
Some("0.3.286"),
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
),
"codex" => (
@@ -2257,7 +2257,7 @@ mod tests {
"@agentclientprotocol/claude-agent-acp",
"0.73.0",
json!("@anthropic-ai/claude-agent-sdk"),
json!("0.3.280"),
json!("0.3.286"),
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
)
} else {
@@ -266,7 +266,7 @@ fn pending_acpx_runtime_request(
provider["agentServerPackage"] = json!("@agentclientprotocol/claude-agent-acp");
provider["agentServerVersion"] = json!("0.73.0");
provider["agentRuntimePackage"] = json!("@anthropic-ai/claude-agent-sdk");
provider["agentRuntimeVersion"] = json!("0.3.280");
provider["agentRuntimeVersion"] = json!("0.3.286");
provider["commandDigest"] = json!(digest);
provider["sidecarArgs"][3] = json!(digest);
} else {
@@ -127,7 +127,7 @@ function acpxExecution(
? "@openai/codex"
: "@anthropic-ai/claude-agent-sdk",
agentRuntimeVersion:
agent === "pi" ? "0.84.2" : agent === "codex" ? "0.156.0" : "0.3.280",
agent === "pi" ? "0.84.2" : agent === "codex" ? "0.156.0" : "0.3.286",
commandDigest:
agent === "codex"
? "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3"
@@ -449,7 +449,7 @@ describe("ACPX installation integrity", () => {
},
{
name: "@anthropic-ai/claude-agent-sdk",
version: "0.3.280",
version: "0.3.286",
directory: join(dependencyRoot, "claude-agent-sdk"),
},
{
@@ -48,13 +48,13 @@ const VERIFIED_PROVIDER_RUNTIME_TARGET_ENV =
const QUALIFIED_CLAUDE_LINUX_X64_RUNTIME = Object.freeze({
runtimePackageName: "@anthropic-ai/claude-agent-sdk",
runtimePackageVersion: "0.3.280",
runtimePackageVersion: "0.3.286",
packageName: "@anthropic-ai/claude-agent-sdk-linux-x64",
packageVersion: "0.3.280",
dependencyDeclaration: "0.3.280",
packageVersion: "0.3.286",
dependencyDeclaration: "0.3.286",
relativeExecutable: "claude",
executableDigest:
"sha256:1e08503dbdf3c2cb0d706d32f3408277388d1c76ef108673e8fe42c1b322925b",
"sha256:fe503f65c6289d59c23e5b21ae44f03583f997dd33a2cbfc75ab4f96fb8fc73f",
environmentVariable: "CLAUDE_CODE_EXECUTABLE",
});
@@ -62,12 +62,12 @@ const QUALIFIED_CLAUDE_DARWIN_RUNTIMES = {
arm64: Object.freeze({
...QUALIFIED_CLAUDE_LINUX_X64_RUNTIME,
packageName: "@anthropic-ai/claude-agent-sdk-darwin-arm64",
executableDigest: "sha256:387a5c5dcdbb815085edf0baf79591f9d8894efe922bceaf3d75b1b08055229d",
executableDigest: "sha256:75e3016e9d2570767b08e43a7467d4817a4f149232c169ca295f2c95fef21433",
}),
x64: Object.freeze({
...QUALIFIED_CLAUDE_LINUX_X64_RUNTIME,
packageName: "@anthropic-ai/claude-agent-sdk-darwin-x64",
executableDigest: "sha256:c1d32d87630482250633208ab77855429b24010ae3086a7ff7539b57b93168d4",
executableDigest: "sha256:53e6a936e89519d695230f9cc97943991286b72766674fba11bee845f0a7c047",
}),
};
@@ -95,8 +95,8 @@ const QUALIFIED_CLAUDE_PROVIDER_DEPENDENCIES = Object.freeze([
}),
Object.freeze({
packageName: "@anthropic-ai/claude-agent-sdk",
packageVersion: "0.3.280",
// The package's own package.json still declares 0.3.257 — 0.3.280 is
packageVersion: "0.3.286",
// The package's own package.json still declares 0.3.257 — 0.3.286 is
// only what pnpm resolves, forced by the
// "claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk" override in
// the workspace root. This field binds the declared string, not the
@@ -41,7 +41,7 @@ describe("qualified ACPX profiles", () => {
it("binds Claude ACP to the SDK and native CLI runtime it launches", () => {
expect(QUALIFIED_ACPX_PROFILES.claude).toMatchObject({
agentRuntimePackage: "@anthropic-ai/claude-agent-sdk",
agentRuntimeVersion: "0.3.280",
agentRuntimeVersion: "0.3.286",
});
});
});
@@ -89,7 +89,7 @@ export const QUALIFIED_ACPX_PROFILES: Readonly<
agentServerPackage: "@agentclientprotocol/claude-agent-acp",
agentServerVersion: "0.73.0",
agentRuntimePackage: "@anthropic-ai/claude-agent-sdk",
agentRuntimeVersion: "0.3.280",
agentRuntimeVersion: "0.3.286",
commandDigest:
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
qualificationModel: "claude-sonnet-5",
@@ -60,7 +60,7 @@ test("the runner pins every qualified ACPX production dependency", () => {
assert.equal(runnerPackage.dependencies["@openai/codex"], "0.156.0");
assert.equal(runnerPackage.dependencies["@anthropic-ai/claude-agent-sdk"], undefined);
assert.equal(rootPackage.pnpm.overrides["@agentclientprotocol/codex-acp@1.6.2>@openai/codex"], runnerPackage.dependencies["@openai/codex"]);
assert.equal(rootPackage.pnpm.overrides["@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk"], "0.3.280");
assert.equal(rootPackage.pnpm.overrides["@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk"], "0.3.286");
assert.equal(runnerPackage.optionalDependencies, undefined);
assert.equal(runnerPackage.dependencies.node, undefined);
assert.equal(runnerPackage.dependencies.acpx, "0.13.1");
+1 -1
View File
@@ -33,7 +33,7 @@ patchedDependencies:
# pnpm patches change package files, but overrides control dependency resolution.
overrides:
"@agentclientprotocol/codex-acp@1.6.2>@openai/codex": "0.156.0"
"@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.280"
"@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.286"
rollup: ">=4.59.0"
react: "^19.2.8"
react-dom: "^19.2.8"
@@ -72,6 +72,7 @@ describe("adapter model listing", () => {
// Opus 5 is a current GA flagship and must be offered even when live discovery is unavailable.
expect(models.some((model) => model.id === "claude-opus-5")).toBe(true);
expect(models).toContainEqual({ id: "claude-opus-5-5", label: "Claude Opus 5.5" });
expect(models).toContainEqual({ id: "claude-sonnet-5-5", label: "Claude Sonnet 5.5" });
expect(fetchSpy).not.toHaveBeenCalled();
});
@@ -95,6 +96,7 @@ describe("adapter model listing", () => {
expect(first.some((model) => model.id === "claude-opus-4-8-20260529")).toBe(true);
expect(first.some((model) => model.id === "claude-opus-4-8")).toBe(true);
expect(first.some((model) => model.id === "claude-opus-5-5")).toBe(true);
expect(first.some((model) => model.id === "claude-sonnet-5-5")).toBe(true);
// Discovered models take the curated order too: the API's order is not shown as-is.
const firstIds = first.map((model) => model.id);
expect(firstIds[0]).toBe("claude-fable-5-1");
@@ -141,6 +143,7 @@ describe("adapter model listing", () => {
it.each([
["claude-fable-5-1", "Claude Fable 5.1"],
["claude-opus-5-5", "Claude Opus 5.5"],
["claude-sonnet-5-5", "Claude Sonnet 5.5"],
])("does not duplicate %s when discovery returns the identical ID", async (id, displayName) => {
process.env.ANTHROPIC_API_KEY = "sk-ant-test";
vi.spyOn(globalThis, "fetch").mockResolvedValue({
+2
View File
@@ -5,6 +5,8 @@ import { setupEfforts } from "./agent-setup-fields";
describe("model-specific setup efforts", () => {
it("offers current Claude efforts without offering them on Haiku", () => {
expect(setupEfforts("claude_local", "claude-fable-5-1")).toEqual(["low", "medium", "high", "xhigh", "max"]);
expect(setupEfforts("claude_local", "claude-sonnet-5-5")).toEqual(["low", "medium", "high", "xhigh", "max"]);
expect(setupEfforts("claude_local", "claude-opus-5-5")).toEqual(["low", "medium", "high", "xhigh", "max"]);
expect(setupEfforts("claude_local", "claude-haiku-4-5")).toEqual([]);
});