feat(adapters): refresh current coding models and reasoning controls (#13829)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Its adapters supply model catalogs and reasoning controls to agent
setup.
> - Several provider releases are missing from the fallback catalogs.
> - Some newer models also have effort levels that the UI does not
offer.
> - Operators need the exact supported IDs and controls when discovery
is unavailable.
> - This pull request updates the existing adapters from current
provider documentation.
> - Operators can select current coding models without entering custom
IDs.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Model selection and reasoning controls across the existing coding-agent
adapters.

**Subsystem affected**

Claude, Codex, Grok, Gemini, Cursor, Kimi, and OpenCode adapters; model
discovery tests; agent creation and editing.

**Current behavior**

The catalogs omit Opus 5.5, GPT-6 Sol/Luna, Grok 4.7/4.6/4.5, current
Gemini Flash models, and several Cursor/Kimi choices. Bedrock has
obsolete IDs. The UI omits supported effort levels and saves Grok effort
under a key the runtime does not read.

**Proposed behavior**

Offer verified current model IDs and model-specific efforts. Remove
retired Gemini 2.0 choices. Keep configured defaults and saved model
IDs. Keep runtime discovery for account-specific choices.

**Reason and benefit**

Catch up with provider releases through September 22, 2026. Correct the
picker and runtime controls together.

**Breaking changes**

No database or API change. Gemini 2.0 options leave the picker after
their June 1 shutdown. Existing saved IDs remain unchanged. Corrected
Bedrock catalog IDs do not rewrite saved configuration.

**Additional context**

Supersedes the separate GPT-6 Sol PR #13830. Fable 5.1 was already
merged in #12730, and GPT-6 Astra in #12851. The Grok 4.6/4.5 proposal
#11324 was closed and parked by its author. This change retains the
default-sentinel fix from #12062. Related discovery proposals #13127 and
#13565 do not supply these catalog and effort updates. Searches found no
open PR for the additional model IDs.

See [the dated
audit](https://github.com/paperclipai/paperclip/blob/feat/claude-opus-5-5/doc/adapter-model-audit-2026-09-22.md)
for exact scope, primary sources, runtime observations, and
account-specific limits. This updates existing adapters and does not
duplicate planned core work.

## What Changed

- Add Opus 5.5 for direct Claude and Bedrock, with a Claude Code 2.1.280
gate. Correct and extend Bedrock model IDs.
- Add GPT-6 Sol/Luna and Fast mode. Offer Ultra for Astra/Sol and
GPT-5.6 Sol/Terra, and Max for both Luna generations.
- Add Grok 4.7/4.6/4.5, expose supported Extra High effort, and save
Grok edits under `reasoningEffort`.
- Add Gemini Flash 3.8/3.7/3.6/3.5, Flash Lite 3.5/3.1, and 3 Flash
Preview. Remove retired 2.0 choices.
- Add the current documented Cursor fallback models, including Fable
5.1, Composer 2.5, and Muse Spark 1.3.
- Refresh OpenCode fallback IDs used in remote environments from its
installed provider registry.
- Add Kimi K3 256K. Update the existing coding alias to K2.8 Preview and
enable its CLI effort settings.
- Use model-specific Claude/Grok efforts in creation and editing. Clear
unsupported effort when switching models.
- Add catalog, CLI/ACP forwarding, compatibility, and UI persistence
coverage. Record the audit and sources.

## Verification

- Latest head `6e63c9ef53b54ba869cd4fb431a8570bebe289f4`: 53 CI checks
passed, 2 skipped. This includes full workspace typecheck, build, and
all test shards. Greptile is 5/5 with zero unresolved threads. GitHub
reports no merge conflicts.
- 340 focused tests passed across adapter metadata, CLI/ACP arguments,
Claude version checks, Kimi effort, Grok execution, server model
discovery, and UI effort selection/persistence.
- `pnpm --filter @paperclipai/adapter-claude-local --filter
@paperclipai/adapter-codex-local --filter
@paperclipai/adapter-grok-local --filter
@paperclipai/adapter-gemini-local --filter
@paperclipai/adapter-kimi-local --filter
@paperclipai/adapter-cursor-local --filter
@paperclipai/adapter-opencode-local typecheck` — passed. The same
filters with `build` passed.
- `pnpm check:token-gates` and `git diff --check` — passed.
- Full workspace and UI typechecks were attempted locally. They stop on
existing missing `three` dependencies in `packages/shared/src/cliplab`.
- Full `pnpm test:run` and `pnpm build` were not run locally. Worktree
creation exhausted disk space, so a clean dependency install is not
feasible on this host. Focused checks reuse existing dependencies. CI
supplies full workspace verification.
- No provider inference was run. Account-specific runtime model lists
were inspected where available.
- Manual check: select the new models in agent setup and editing.
Confirm Luna has Max but no Ultra, Grok 4.7 has Extra High, and Fable
5.1 has Extra High/Max. Save Grok effort and confirm
`adapterConfig.reasoningEffort` contains the selection.

## Risks

- Catalog presence does not grant account access. Older CLIs and
restricted accounts can reject a model. Opus 5.5 has an explicit upgrade
check.
- Higher effort can increase cost and latency. Existing agent defaults
are unchanged.
- Cursor fallback IDs come from public model documentation; the local
account exposed no live catalog. Runtime discovery still adds
account-specific variants.
- Kimi effort remains supported only on its explicit CLI engine. This
does not add effort support to its default ACP engine.
- Saved obsolete Bedrock or retired Gemini IDs are not migrated
automatically.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, tool use, and code
execution. The exact deployment ID and context window are not exposed to
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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-09-22 16:56:00 -07:00
1 parent 7badae6981
commit cdf04a33fa
29 files changed
+379 -86

No files matched your search

@@ -1,7 +1,21 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_CLAUDE_LOCAL_MODEL, resolveClaudeModel } from "./index.js";
import { claudeLocalReasoningEffortsForModel, DEFAULT_CLAUDE_LOCAL_MODEL, resolveClaudeModel } from "./index.js";
import { minimumClaudeCliVersionForModel } from "./server/cli-capabilities.js";
describe("Claude model defaults", () => {
it.each(["claude-opus-5-5", "us.anthropic.claude-opus-5-5", "global.anthropic.claude-opus-5-5[1m]"])("requires a current CLI and offers all efforts for %s", (model) => {
expect(minimumClaudeCliVersionForModel(model)).toBe("2.1.280");
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-haiku-4-5")).toEqual([]);
expect(claudeLocalReasoningEffortsForModel("custom-model")).toEqual(["low", "medium", "high"]);
});
it.each([undefined, null, "", " "])("uses Opus 5 for an unset model (%j)", (model) => {
expect(DEFAULT_CLAUDE_LOCAL_MODEL).toBe("claude-opus-5");
expect(resolveClaudeModel(model)).toBe("claude-opus-5");
+13 -1
View File
@@ -22,6 +22,16 @@ export function resolveClaudeModel(
}
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)) {
return ["low", "medium", "high", "xhigh", "max"];
}
if (/^claude-(?:opus|sonnet)-4-6(?:-v1)?$/.test(id)) return ["low", "medium", "high", "max"];
return ["low", "medium", "high"];
}
export const label = "Claude Code";
export const SANDBOX_INSTALL_COMMAND = "npm install -g @anthropic-ai/claude-code";
@@ -32,6 +42,7 @@ export const models = [
{ id: "claude-fable-5-1", label: "Claude Fable 5.1" },
{ id: "claude-fable-5", label: "Claude Fable 5" },
{ id: "claude-mythos-5", label: "Claude Mythos 5" },
{ id: "claude-opus-5-5", label: "Claude Opus 5.5" },
{ id: "claude-opus-5", label: "Claude Opus 5" },
{ id: "claude-opus-4-7", label: "Claude Opus 4.7" },
{ id: "claude-opus-4-6", label: "Claude Opus 4.6" },
@@ -49,7 +60,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): reasoning effort passed via --effort (low|medium|high)
- effort (string, optional): model-specific reasoning effort passed via --effort (low|medium|high; current Opus, Sonnet 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
@@ -77,6 +88,7 @@ Operational fields:
- graceSec (number, optional): SIGTERM grace period in seconds
Notes:
- Claude Opus 5.5 uses model ID \`claude-opus-5-5\` and requires Claude Code v2.1.280 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.
@@ -6,11 +6,6 @@ const effortFlagSupportCache = new Map<string, Promise<boolean | null>>();
export const CLAUDE_FABLE_5_1_MIN_CLI_VERSION = "2.1.251";
const CLAUDE_FABLE_5_1_MODEL_IDS = new Set([
"claude-fable-5-1",
"us.anthropic.claude-fable-5-1",
]);
export function claudeCommandLooksLike(command: string, expected = "claude"): boolean {
const base = path.basename(command).toLowerCase();
return base === expected || base === `${expected}.cmd` || base === `${expected}.exe`;
@@ -41,7 +36,9 @@ function cacheKeyForTarget(command: string, target: AdapterExecutionTarget | nul
}
export function minimumClaudeCliVersionForModel(model: string): string | null {
return CLAUDE_FABLE_5_1_MODEL_IDS.has(model.trim())
const modelId = model.trim().replace(/\[1m\]$/, "").replace(/^(?:(?:us|eu|apac|global)\.)?anthropic\./, "");
if (modelId === "claude-opus-5-5") return "2.1.280";
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.251 (Claude Code)\n"
? "2.1.280 (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" }] } }),
@@ -466,14 +466,14 @@ describe("claude remote execution", () => {
return { args: call?.[2] ?? [], result };
}
it("passes the exact configured Fable 5.1 ID as --model on the CLI lane", async () => {
it.each(["claude-fable-5-1", "claude-opus-5-5"])("passes %s as --model on the CLI lane", async (model) => {
const { args } = await executeWithModel("paperclip-claude-model-direct-", {
model: "claude-fable-5-1",
model,
});
const modelFlag = args.indexOf("--model");
expect(modelFlag).toBeGreaterThanOrEqual(0);
expect(args[modelFlag + 1]).toBe("claude-fable-5-1");
expect(args[modelFlag + 1]).toBe(model);
});
it("passes the Bedrock-native Fable 5.1 ID as --model under Bedrock auth", async () => {
@@ -496,27 +496,30 @@ describe("claude remote execution", () => {
expect(args).not.toContain("--model");
});
it("rejects Fable 5.1 before launch when the CLI is older than 2.1.251", async () => {
it.each([
["claude-fable-5-1", "2.1.251", "2.1.247"],
["claude-opus-5-5", "2.1.280", "2.1.279"],
])("rejects %s before launch below CLI %s", async (model, minimumVersion, detectedVersion) => {
runChildProcess.mockResolvedValueOnce({
exitCode: 0,
signal: null,
timedOut: false,
stdout: "2.1.247 (Claude Code)\n",
stdout: `${detectedVersion} (Claude Code)\n`,
stderr: "",
pid: 123,
startedAt: new Date().toISOString(),
});
const { args, result } = await executeWithModel("paperclip-claude-model-old-cli-", {
model: "claude-fable-5-1",
model,
});
expect(args).toEqual([]);
expect(result.errorCode).toBe("claude_cli_version_incompatible");
expect(result.errorMessage).toContain("requires Claude Code 2.1.251 or newer");
expect(result.errorMessage).toContain(`${model} requires Claude Code ${minimumVersion} or newer`);
expect(result.resultJson).toMatchObject({
requiredClaudeCodeVersion: "2.1.251",
detectedClaudeCodeVersion: "2.1.247",
requiredClaudeCodeVersion: minimumVersion,
detectedClaudeCodeVersion: detectedVersion,
});
});
@@ -1286,7 +1286,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
? `detected ${detectedCliVersion}`
: "could not determine the installed version";
const errorMessage =
`Claude Fable 5.1 requires Claude Code ${minimumCliVersion} or newer on the CLI lane; ${detected}. ` +
`${model} requires Claude Code ${minimumCliVersion} or newer on the CLI lane; ${detected}. ` +
"Upgrade Claude Code or restore the default ACP lane before retrying.";
await onLog("stderr", `[paperclip] ${errorMessage}\n`);
return {
@@ -9,10 +9,15 @@ const ANTHROPIC_API_VERSION = "2023-06-01";
/** AWS Bedrock model IDs — region-qualified identifiers required by the Bedrock API. */
const BEDROCK_MODELS: AdapterModel[] = [
{ id: "us.anthropic.claude-opus-4-8-v1", label: "Bedrock Opus 4.8" },
{ id: "us.anthropic.claude-opus-4-8", label: "Bedrock Opus 4.8" },
{ id: "us.anthropic.claude-opus-5-5", label: "Bedrock Opus 5.5" },
{ id: "us.anthropic.claude-opus-5", label: "Bedrock Opus 5" },
{ id: "us.anthropic.claude-sonnet-5", label: "Bedrock Sonnet 5" },
// Fable 5.1's documented geo inference ID carries no -v1 suffix, unlike earlier entries.
{ id: "us.anthropic.claude-fable-5-1", label: "Bedrock Fable 5.1" },
{ id: "us.anthropic.claude-fable-5-v1", label: "Bedrock Fable 5" },
{ id: "us.anthropic.claude-fable-5", label: "Bedrock Fable 5" },
{ id: "us.anthropic.claude-opus-4-7", label: "Bedrock Opus 4.7" },
{ id: "us.anthropic.claude-sonnet-4-6", label: "Bedrock Sonnet 4.6" },
{ id: "us.anthropic.claude-opus-4-6-v1", label: "Bedrock Opus 4.6" },
{ id: "us.anthropic.claude-sonnet-4-5-20250929-v2:0", label: "Bedrock Sonnet 4.5" },
{ id: "us.anthropic.claude-haiku-4-5-20251001-v1:0", label: "Bedrock Haiku 4.5" },
@@ -639,14 +639,17 @@ describe("claude CLI local hello probe hardening", () => {
expect(JSON.stringify(spawnedEnv)).not.toContain("caller-proxy");
});
it("warns without executing when runtime PATH selects a different local Claude executable", async () => {
it.each([
["claude-fable-5-1", "2.1.251"],
["claude-opus-5-5", "2.1.280"],
])("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");
await writeFile(runtimeClaudePath, "#!/bin/sh\nexit 0\n");
await chmod(runtimeClaudePath, 0o755);
try {
probeResult.value = { exitCode: 0, stdout: "2.1.251 (Claude Code)\n", stderr: "" };
probeResult.value = { exitCode: 0, stdout: `${minimumVersion} (Claude Code)\n`, stderr: "" };
const result = await testEnvironment({
companyId: "company-1",
@@ -654,7 +657,7 @@ describe("claude CLI local hello probe hardening", () => {
config: {
engine: "cli",
command: "claude",
model: "claude-fable-5-1",
model,
env: { PATH: runtimeDir },
},
executionTarget: null,
@@ -664,6 +667,7 @@ describe("claude CLI local hello probe hardening", () => {
expect(result.status).toBe("warn");
expect(result.checks).toContainEqual(expect.objectContaining({
code: "claude_cli_version_probe_mismatch",
hint: `Ensure the runtime-selected Claude Code is ${minimumVersion} or newer. Execution will verify that exact executable before launch.`,
level: "warn",
}));
expect(runAdapterExecutionTargetProcess).not.toHaveBeenCalled();
@@ -141,10 +141,13 @@ describe("claude sandbox auth-missing check", () => {
});
describe("claude CLI model compatibility check", () => {
it("fails before the hello probe when Fable 5.1 is configured with an older CLI", async () => {
it.each([
["claude-fable-5-1", "2.1.251", "2.1.247"],
["claude-opus-5-5", "2.1.280", "2.1.279"],
])("fails before the hello probe for %s below CLI %s", async (model, minimumVersion, detectedVersion) => {
probeResult.value = {
exitCode: 0,
stdout: "2.1.247 (Claude Code)\n",
stdout: `${detectedVersion} (Claude Code)\n`,
stderr: "",
};
@@ -154,7 +157,7 @@ describe("claude CLI model compatibility check", () => {
config: {
engine: "cli",
command: "claude",
model: "claude-fable-5-1",
model,
},
executionTarget: sandboxTarget,
environmentName: "Daytona",
@@ -164,7 +167,8 @@ describe("claude CLI model compatibility check", () => {
expect(result.checks).toContainEqual(expect.objectContaining({
code: "claude_cli_version_incompatible",
level: "error",
detail: "Detected Claude Code 2.1.247.",
message: `${model} requires Claude Code ${minimumVersion} or newer on the CLI lane.`,
detail: `Detected Claude Code ${detectedVersion}.`,
}));
expect(runAdapterExecutionTargetProcess).toHaveBeenCalledTimes(1);
const versionCall = runAdapterExecutionTargetProcess.mock.calls[0] as unknown as [
@@ -266,9 +266,9 @@ export async function testEnvironment(
code: "claude_cli_version_probe_mismatch",
level: "warn",
message:
"Skipped Fable 5.1 readiness probing because the runtime PATH selects a different Claude executable than the trusted local Test probe.",
`Skipped ${configuredModel} readiness probing because the runtime PATH selects a different Claude executable than the trusted local Test probe.`,
hint:
"Ensure the runtime-selected Claude Code is 2.1.251 or newer. Execution will verify that exact executable before launch.",
`Ensure the runtime-selected Claude Code is ${minimumCliVersion} or newer. Execution will verify that exact executable before launch.`,
});
} else if (canRunProbe && minimumCliVersion && versionProbeCommand) {
const versionProbeEnv = localProbe?.env ?? env;
@@ -289,7 +289,7 @@ export async function testEnvironment(
checks.push({
code: "claude_cli_version_incompatible",
level: "error",
message: `Claude Fable 5.1 requires Claude Code ${minimumCliVersion} or newer on the CLI lane.`,
message: `${configuredModel} requires Claude Code ${minimumCliVersion} or newer on the CLI lane.`,
detail: detectedCliVersion
? `Detected Claude Code ${detectedCliVersion}.`
: "Could not determine the installed Claude Code version.",
@@ -14,21 +14,24 @@ describe("codex local adapter metadata", () => {
// Default to the concrete gpt-5.6-sol slug — Codex ships no metadata for the bare gpt-5.6
// alias, so it must not be advertised or used as the default (it triggers a fallback warning).
expect(DEFAULT_CODEX_LOCAL_MODEL).toBe("gpt-5.6-sol");
expect(modelIds.slice(0, 4)).toEqual([
expect(modelIds.slice(0, 6)).toEqual([
"gpt-5.6-sol",
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-terra",
"gpt-5.6-luna",
]);
expect(modelIds).not.toContain("gpt-5.6");
expect(isCodexLocalFastModeSupported(DEFAULT_CODEX_LOCAL_MODEL)).toBe(true);
expect(isCodexLocalFastModeSupported("gpt-6-astra")).toBe(true);
expect(isCodexLocalFastModeSupported("gpt-6-sol")).toBe(true);
expect(modelIds).not.toContain("gpt-5.3-codex");
expect(modelIds).not.toContain("gpt-5.3-codex-spark");
});
it("uses the reasoning efforts advertised for GPT-6 Astra", () => {
expect(codexLocalReasoningEffortsForModel("gpt-6-astra")).toEqual([
it.each(["gpt-6-astra", "gpt-6-sol", " gpt-6-sol ", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6"])("uses the reasoning efforts advertised for %s", (model) => {
expect(codexLocalReasoningEffortsForModel(model)).toEqual([
"low",
"medium",
"high",
@@ -36,7 +39,10 @@ describe("codex local adapter metadata", () => {
"max",
"ultra",
]);
expect(codexLocalReasoningEffortsForModel("gpt-5.6-sol")).toEqual([
});
it.each(["gpt-5.5", "custom-model"])("preserves legacy efforts for %s", (model) => {
expect(codexLocalReasoningEffortsForModel(model)).toEqual([
"minimal",
"low",
"medium",
@@ -45,6 +51,11 @@ describe("codex local adapter metadata", () => {
]);
});
it.each(["gpt-6-luna", "gpt-5.6-luna"])("caps %s at max and supports Fast mode", (model) => {
expect(codexLocalReasoningEffortsForModel(model)).toEqual(["low", "medium", "high", "xhigh", "max"]);
expect(isCodexLocalFastModeSupported(model)).toBe(true);
});
it("normalizes the legacy bare gpt-5.6 alias to the concrete gpt-5.6-sol slug", () => {
expect(normalizeCodexModel("gpt-5.6")).toBe("gpt-5.6-sol");
expect(normalizeCodexModel(" gpt-5.6 ")).toBe("gpt-5.6-sol");
+26 -8
View File
@@ -14,6 +14,8 @@ export const DEFAULT_CODEX_LOCAL_MODEL = PAPERCLIP_RUNNER_DEFAULT_MODELS.codex;
export const DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX = true;
export const CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS = [
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
@@ -41,18 +43,22 @@ const CODEX_LOCAL_DEFAULT_REASONING_EFFORTS = [
"xhigh",
] as const;
const CODEX_LOCAL_ASTRA_REASONING_EFFORTS = [
const CODEX_LOCAL_MAX_REASONING_EFFORTS = [
"low",
"medium",
"high",
"xhigh",
"max",
] as const;
const CODEX_LOCAL_ULTRA_REASONING_EFFORTS = [
...CODEX_LOCAL_MAX_REASONING_EFFORTS,
"ultra",
] as const;
export type CodexLocalReasoningEffort =
| (typeof CODEX_LOCAL_DEFAULT_REASONING_EFFORTS)[number]
| (typeof CODEX_LOCAL_ASTRA_REASONING_EFFORTS)[number];
| (typeof CODEX_LOCAL_ULTRA_REASONING_EFFORTS)[number];
export function normalizeCodexModel(model: string | null | undefined): string {
const normalizedModel = normalizeModelId(model);
@@ -62,9 +68,19 @@ export function normalizeCodexModel(model: string | null | undefined): string {
export function codexLocalReasoningEffortsForModel(
model: string | null | undefined,
): readonly CodexLocalReasoningEffort[] {
return normalizeCodexModel(model) === "gpt-6-astra"
? CODEX_LOCAL_ASTRA_REASONING_EFFORTS
: CODEX_LOCAL_DEFAULT_REASONING_EFFORTS;
const normalizedModel = normalizeCodexModel(model);
switch (normalizedModel) {
case "gpt-6-astra":
case "gpt-6-sol":
case "gpt-5.6-sol":
case "gpt-5.6-terra":
return CODEX_LOCAL_ULTRA_REASONING_EFFORTS;
case "gpt-6-luna":
case "gpt-5.6-luna":
return CODEX_LOCAL_MAX_REASONING_EFFORTS;
default:
return CODEX_LOCAL_DEFAULT_REASONING_EFFORTS;
}
}
export function isCodexLocalKnownModel(model: string | null | undefined): boolean {
@@ -94,6 +110,8 @@ export const models = [
// DEFAULT_CODEX_LOCAL_MODEL is gpt-5.6-sol, so it doubles as the first (default) 5.6 entry.
{ id: DEFAULT_CODEX_LOCAL_MODEL, label: DEFAULT_CODEX_LOCAL_MODEL },
{ id: "gpt-6-astra", label: "gpt-6-astra" },
{ id: "gpt-6-sol", label: "gpt-6-sol" },
{ id: "gpt-6-luna", label: "gpt-6-luna" },
{ id: "gpt-5.6-terra", label: "gpt-5.6-terra" },
{ id: "gpt-5.6-luna", label: "gpt-5.6-luna" },
{ id: "gpt-5.4", label: "gpt-5.4" },
@@ -116,10 +134,10 @@ 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 prepended to stdin prompt at runtime
- model (string, optional): Codex model id
- modelReasoningEffort (string, optional): reasoning effort override passed via -c model_reasoning_effort=...; GPT-6 Astra supports low|medium|high|xhigh|max|ultra
- modelReasoningEffort (string, optional): reasoning effort override passed via -c model_reasoning_effort=...; GPT-6 Astra/Sol and GPT-5.6 Sol/Terra support low|medium|high|xhigh|max|ultra; GPT-6 Luna and GPT-5.6 Luna support low|medium|high|xhigh|max
- promptTemplate (string, optional): run prompt template
- search (boolean, optional): run codex with --search
- fastMode (boolean, optional): enable Codex Fast mode; supported on GPT-6 Astra, GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and passed through for manual model IDs
- fastMode (boolean, optional): enable Codex Fast mode; supported on GPT-6 (astra/sol/luna), GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and passed through for manual model IDs
- dangerouslyBypassApprovalsAndSandbox (boolean, optional): run with bypass flag
- command (string, optional): defaults to "codex"
- extraArgs (string[], optional): additional CLI args
@@ -150,7 +168,7 @@ Notes:
- Paperclip injects desired local skills into the effective CODEX_HOME/skills/ directory at execution time so Codex can discover "$paperclip" and related skills without polluting the project working directory. For new and updated agents, Paperclip assigns an isolated managed home at ~/.paperclip/instances/<id>/companies/<companyId>/agents/<agentId>/codex-home/skills/; when CODEX_HOME is explicitly overridden in adapter config, that override is used instead.
- New and updated codex_local agents persist an empty OPENAI_API_KEY override by default so a host-level OPENAI_API_KEY cannot leak into Codex runs through process inheritance. Explicit CODEX_HOME overrides must not point at the shared company codex-home, $CODEX_HOME, or ~/.codex.
- Some model/tool combinations reject certain effort levels (for example minimal with web search enabled).
- Fast mode is supported on GPT-6 Astra, GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and manual model IDs. When enabled for those models, Paperclip applies \`service_tier="fast"\` and \`features.fast_mode=true\`.
- Fast mode is supported on GPT-6 (astra/sol/luna), GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and manual model IDs. When enabled for those models, Paperclip applies \`service_tier="fast"\` and \`features.fast_mode=true\`.
- When Paperclip realizes a workspace/runtime for a run, it injects PAPERCLIP_WORKSPACE_* and PAPERCLIP_RUNTIME_* env vars for agent-side tooling.
- The ACP engine keeps its workspace sandbox and enables network access on each turn. Explicit sandbox_workspace_write.network_access overrides in extraArgs (or env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS="false") disable it; execution-target network denial wins. The bundled ACP patch is needed because upstream mode presets override Codex config.toml on every turn.
- The CLI engine defaults to a writable workspace sandbox with network access for unattended work and Paperclip API calls. It does not enable the dangerous bypass flag. Explicit sandbox modes/profiles and network overrides in extraArgs retain their meaning. An execution-target network denial remains enforced.
@@ -508,15 +508,15 @@ describe("codex_local ACP lane", () => {
});
});
it("forwards GPT-6 Astra controls to the ACPX Codex target", () => {
it.each([["gpt-6-astra", "ultra"], ["gpt-6-sol", "ultra"], ["gpt-6-luna", "max"], ["gpt-5.6-sol", "ultra"], ["gpt-5.6-terra", "ultra"], ["gpt-5.6-luna", "max"]])("forwards %s controls to the ACPX Codex target", (model, effort) => {
expect(buildCodexAcpConfig({
engine: "acp",
model: "gpt-6-astra",
modelReasoningEffort: "ultra",
model,
modelReasoningEffort: effort,
fastMode: true,
})).toMatchObject({
model: "gpt-6-astra",
modelReasoningEffort: "ultra",
model,
modelReasoningEffort: effort,
fastMode: true,
});
});
@@ -9,14 +9,14 @@ describe("buildCodexExecArgs", () => {
if (resumeSessionId) expect(args.slice(-3)).toEqual(["resume", resumeSessionId, "-"]);
});
it("forwards GPT-6 Astra, its ultra reasoning effort, and fast mode", () => {
it.each([["gpt-6-astra", "ultra"], ["gpt-6-sol", "ultra"], ["gpt-6-luna", "max"], ["gpt-5.6-sol", "ultra"], ["gpt-5.6-terra", "ultra"], ["gpt-5.6-luna", "max"]])("forwards %s, its supported reasoning effort, and fast mode", (model, effort) => {
const result = buildCodexExecArgs({
model: "gpt-6-astra",
modelReasoningEffort: "ultra",
model,
modelReasoningEffort: effort,
fastMode: true,
});
expect(result.model).toBe("gpt-6-astra");
expect(result.model).toBe(model);
expect(result.fastModeApplied).toBe(true);
expect(result.fastModeIgnoredReason).toBeNull();
expect(result.args).toEqual([
@@ -24,9 +24,9 @@ describe("buildCodexExecArgs", () => {
"--json",
"--dangerously-bypass-approvals-and-sandbox",
"--model",
"gpt-6-astra",
model,
"-c",
'model_reasoning_effort="ultra"',
`model_reasoning_effort="${effort}"`,
"-c",
'service_tier="fast"',
"-c",
@@ -148,7 +148,7 @@ describe("buildCodexExecArgs", () => {
expect(result.fastModeRequested).toBe(true);
expect(result.fastModeApplied).toBe(false);
expect(result.fastModeIgnoredReason).toContain(
"currently only supported on gpt-6-astra, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4 or manually configured model IDs",
"currently only supported on gpt-6-astra, gpt-6-sol, gpt-6-luna, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4 or manually configured model IDs",
);
expect(result.args).toEqual([
"exec",
@@ -63,18 +63,18 @@ describe("buildCodexLocalConfig", () => {
});
});
it("persists the exact GPT-6 Astra model and supported controls", () => {
it.each([["gpt-6-astra", "ultra"], ["gpt-6-sol", "ultra"], ["gpt-6-luna", "max"], ["gpt-5.6-sol", "ultra"], ["gpt-5.6-terra", "ultra"], ["gpt-5.6-luna", "max"]])("persists the exact %s model and supported controls", (model, effort) => {
const config = buildCodexLocalConfig(
makeValues({
model: "gpt-6-astra",
thinkingEffort: "ultra",
model,
thinkingEffort: effort,
fastMode: true,
}),
);
expect(config).toMatchObject({
model: "gpt-6-astra",
modelReasoningEffort: "ultra",
model,
modelReasoningEffort: effort,
fastMode: true,
});
});
@@ -14,6 +14,18 @@ export const DEFAULT_CURSOR_LOCAL_MODEL = "auto";
const CURSOR_FALLBACK_MODEL_IDS = [
"auto",
"composer-2.5",
"claude-opus-5-5",
"claude-fable-5-1",
"claude-sonnet-5",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"gemini-3.8-flash",
"muse-spark-1.3",
"grok-4.7",
"grok-4.6",
"grok-4.5",
"composer-1.5",
"composer-1",
"gpt-5.3-codex-low",
+7 -2
View File
@@ -13,11 +13,16 @@ export const models = [
{ id: DEFAULT_GEMINI_LOCAL_MODEL, label: "Auto" },
{ id: "gemini-3.1-pro-preview", label: "Gemini 3.1 Pro Preview" },
{ id: "gemini-3.1-pro-preview-customtools", label: "Gemini 3.1 Pro Preview (Custom Tools)" },
{ id: "gemini-3.8-flash", label: "Gemini 3.8 Flash" },
{ id: "gemini-3.7-flash", label: "Gemini 3.7 Flash" },
{ id: "gemini-3.6-flash", label: "Gemini 3.6 Flash" },
{ id: "gemini-3.5-flash", label: "Gemini 3.5 Flash" },
{ id: "gemini-3.5-flash-lite", label: "Gemini 3.5 Flash Lite" },
{ id: "gemini-3.1-flash-lite", label: "Gemini 3.1 Flash Lite" },
{ id: "gemini-3-flash-preview", label: "Gemini 3 Flash Preview" },
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash" },
{ id: "gemini-2.5-flash-lite", label: "Gemini 2.5 Flash Lite" },
{ id: "gemini-2.0-flash", label: "Gemini 2.0 Flash" },
{ id: "gemini-2.0-flash-lite", label: "Gemini 2.0 Flash Lite" },
];
export const agentConfigurationDoc = `# gemini_local agent configuration
+10 -1
View File
@@ -5,8 +5,17 @@ export const DEFAULT_GROK_LOCAL_MODEL = "grok-build";
export const models = [
{ id: DEFAULT_GROK_LOCAL_MODEL, label: DEFAULT_GROK_LOCAL_MODEL },
{ id: "grok-4.7", label: "Grok 4.7" },
{ id: "grok-4.6", label: "Grok 4.6" },
{ id: "grok-4.5", label: "Grok 4.5" },
];
export function grokLocalReasoningEffortsForModel(model: string): readonly string[] {
return model.trim() === "grok-4.7" || model.trim() === "grok-4.6"
? ["low", "medium", "high", "xhigh"]
: ["low", "medium", "high"];
}
export const agentConfigurationDoc = `# grok_local agent configuration
Adapter: grok_local
@@ -27,7 +36,7 @@ Core fields:
- promptTemplate (string, optional): run prompt template
- model (string, optional): Grok model id. Defaults to grok-build.
- permissionMode (string, optional): Grok permission mode passed via \`--permission-mode\`. Unset by default: Grok >= 1.0 enforces \`dontAsk\` as deny-by-default and it overrides \`--always-approve\`, so unattended runs rely on \`--always-approve\` alone unless you explicitly need a mode
- reasoningEffort (string, optional): Grok reasoning effort passed via \`--reasoning-effort\`
- reasoningEffort (string, optional): Grok reasoning effort (low|medium|high; grok-4.7 and grok-4.6 also accept xhigh) passed via \`--reasoning-effort\`
- maxTurns (number, optional): maximum agent turns for the run
- command (string, optional): defaults to "grok"
- extraArgs (string[], optional): additional CLI args
@@ -160,6 +160,19 @@ async function makeCtx(runId: string, cwd: string): Promise<AdapterExecutionCont
}
describe("grok_local execute", () => {
it.each(["grok-4.7", "grok-4.6"])("forwards the explicit %s model and xhigh effort", async (model) => {
const root = await makeTempRoot();
const ctx = await makeCtx("model-selection", root);
ctx.config = { cwd: root, model, reasoningEffort: "xhigh" };
runProcessMock.mockResolvedValue(makeSuccessfulRunResult());
await execute(ctx);
const args = runProcessMock.mock.calls[0][3] as string[];
expect(args[args.indexOf("--model") + 1]).toBe(model);
expect(args[args.indexOf("--reasoning-effort") + 1]).toBe("xhigh");
});
beforeEach(() => {
mocks.state.isRemote = false;
mocks.state.prepareRuntimeResult = null;
+8 -3
View File
@@ -8,9 +8,10 @@ export const SANDBOX_INSTALL_COMMAND = buildSandboxNpmInstallCommand("@moonshot-
export const DEFAULT_KIMI_LOCAL_MODEL = "kimi-code/kimi-for-coding";
export const models = [
{ id: DEFAULT_KIMI_LOCAL_MODEL, label: "K2.7 Coding" },
{ id: DEFAULT_KIMI_LOCAL_MODEL, label: "K2.8 Preview" },
{ id: "kimi-code/kimi-for-coding-highspeed", label: "K2.7 Coding Highspeed" },
{ id: "kimi-code/k3", label: "K3" },
{ id: "kimi-code/k3-256k", label: "K3 (256K)" },
];
/**
@@ -26,7 +27,11 @@ export type KimiEffort = (typeof KIMI_SUPPORTED_EFFORTS)[number];
* Models that advertise `support_efforts` in Kimi's model catalog. Keep in
* sync with `models` above; only these accept KIMI_MODEL_THINKING_EFFORT.
*/
export const EFFORT_CAPABLE_MODELS = new Set<string>(["kimi-code/k3"]);
export const EFFORT_CAPABLE_MODELS = new Set<string>([
DEFAULT_KIMI_LOCAL_MODEL,
"kimi-code/k3",
"kimi-code/k3-256k",
]);
export function modelSupportsEffort(model: string): boolean {
return EFFORT_CAPABLE_MODELS.has(model.trim());
@@ -65,7 +70,7 @@ Core fields:
- instructionsFilePath (string, optional): absolute path to a markdown instructions file prepended to the run prompt. Sibling files in the same directory (HEARTBEAT.md, SOUL.md, TOOLS.md) are made readable via --add-dir for local runs.
- promptTemplate (string, optional): run prompt template
- model (string, optional): Kimi model alias (provider/model). Defaults to kimi-code/kimi-for-coding.
- effort (string, optional): thinking effort (low | medium | high | max). CLI lane only (engine=cli): forwarded as KIMI_MODEL_THINKING_EFFORT for effort-capable models (currently kimi-code/k3); "medium" maps to "high" since Kimi has no medium tier. Ignored for models without support_efforts, and NOT forwarded on the default ACP engine lane (Kimi ACP exposes a separate "thinking" option that is not wired yet) — pin engine=cli when effort control matters.
- effort (string, optional): thinking effort (low | medium | high | max). CLI lane only (engine=cli): forwarded as KIMI_MODEL_THINKING_EFFORT for effort-capable models (K2.8 Preview, K3, and K3 256K); "medium" maps to "high" since Kimi has no medium tier. Ignored for models without support_efforts, and NOT forwarded on the default ACP engine lane (Kimi ACP exposes a separate "thinking" option that is not wired yet) — pin engine=cli when effort control matters.
- command (string, optional): defaults to "kimi"
- extraArgs (string[], optional): additional CLI args
- env (object, optional): KEY=VALUE environment variables
@@ -333,7 +333,7 @@ describe("kimi_local execute", () => {
expect(seenEnv.TERM).toBe("xterm-256color");
});
it("forwards configured effort as KIMI_MODEL_THINKING_EFFORT for effort-capable models", async () => {
it.each(["kimi-code/k3", "kimi-code/k3-256k", "kimi-code/kimi-for-coding"])("forwards configured effort for %s", async (model) => {
const root = await makeTempRoot();
let seenEnv: Record<string, string> = {};
runProcessMock.mockImplementation(async (_runId, _target, _command, _args, options) => {
@@ -341,7 +341,7 @@ describe("kimi_local execute", () => {
return { exitCode: 0, signal: null, timedOut: false, stdout: KIMI_STDOUT, stderr: "" };
});
await execute(makeContext(root, { config: { cwd: root, model: "kimi-code/k3", effort: "high" } }));
await execute(makeContext(root, { config: { cwd: root, model, effort: "high" } }));
expect(seenEnv.KIMI_MODEL_THINKING_EFFORT).toBe("high");
});
@@ -368,7 +368,7 @@ describe("kimi_local execute", () => {
});
await execute(makeContext(root, {
config: { cwd: root, model: "kimi-code/kimi-for-coding", effort: "high" },
config: { cwd: root, model: "kimi-code/kimi-for-coding-highspeed", effort: "high" },
}));
expect(seenEnv.KIMI_MODEL_THINKING_EFFORT).toBeUndefined();
@@ -53,6 +53,18 @@ export function isValidOpenCodeModelId(value: unknown): value is string {
export const models: Array<{ id: string; label: string }> = [
{ id: DEFAULT_OPENCODE_LOCAL_MODEL, label: DEFAULT_OPENCODE_LOCAL_MODEL },
{ id: "openai/gpt-6-astra", label: "openai/gpt-6-astra" },
{ id: "openai/gpt-6-sol", label: "openai/gpt-6-sol" },
{ id: "openai/gpt-6-luna", label: "openai/gpt-6-luna" },
{ id: "openai/gpt-5.6-sol", label: "openai/gpt-5.6-sol" },
{ id: "openai/gpt-5.6-terra", label: "openai/gpt-5.6-terra" },
{ id: "openai/gpt-5.6-luna", label: "openai/gpt-5.6-luna" },
{ id: "anthropic/claude-opus-5-5", label: "anthropic/claude-opus-5-5" },
{ id: "anthropic/claude-opus-5", label: "anthropic/claude-opus-5" },
{ id: "anthropic/claude-fable-5-1", label: "anthropic/claude-fable-5-1" },
{ id: "anthropic/claude-sonnet-5", label: "anthropic/claude-sonnet-5" },
{ id: "google/gemini-3.8-flash", label: "google/gemini-3.8-flash" },
{ id: "xai/grok-4.7", label: "xai/grok-4.7" },
{ id: "openai/gpt-5.5", label: "openai/gpt-5.5" },
{ id: "openai/gpt-5.4", label: "openai/gpt-5.4" },
{ id: "openai/gpt-5.4-mini", label: "openai/gpt-5.4-mini" },