mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
fix(codex-local): resolve GPT-5.6 model metadata at source (#9780)
## Thinking Path > - Paperclip is the open source control plane people use to manage AI agents for work > - The `codex_local` adapter runs OpenAI's Codex CLI through direct CLI and ACP execution lanes > - The adapter defaulted to the bare `gpt-5.6` alias while the bundled ACP Codex version lacked GPT-5.6-family metadata > - Default and legacy-configured runs therefore emitted fallback-metadata warnings and could use generic context limits > - This pull request upgrades the bundled Codex ACP dependency, selects the concrete `gpt-5.6-sol` model, and normalizes the legacy alias in both execution lanes > - The benefit is correct model metadata without hiding genuine stderr or transcript warnings ## Linked Issues or Issue Description Related public PRs: Refs #9342, Refs #9352, and Refs #9382. This PR is narrower: it upgrades bundled Codex metadata and normalizes the legacy bare alias in both execution lanes. **Bug report** ### What happened Default `codex_local` runs, and agents still configured with the bare `gpt-5.6` model, print a model-metadata fallback warning and use generic context-window limits. Root cause: the ACP lane bundled a Codex release predating GPT-5.6-family metadata, while Paperclip's default and advertised model used the bare `gpt-5.6` alias for which Codex publishes no metadata. ### Expected behavior A default Codex run resolves to a concrete model slug with published metadata and does not emit a fallback-metadata warning. ### Deployment mode Self-hosted/local `codex_local` adapter. ## What Changed - Upgraded `@agentclientprotocol/codex-acp` from `^1.1.0` to `^1.1.4` - Changed `DEFAULT_CODEX_LOCAL_MODEL` from `gpt-5.6` to `gpt-5.6-sol` - Removed the bare alias from advertised models and listed concrete GPT-5.6 Fast-mode variants - Added `normalizeCodexModel()` and applied it in both CLI and ACP execution lanes - Updated adapter docs, Storybook fixtures, and regression tests - Preserved warning visibility; no stderr, transcript, or log filtering changed ## Verification - `pnpm --filter @paperclipai/adapter-codex-local typecheck` - `pnpm check:token-gates` - `cd packages/adapters/codex-local && pnpm exec vitest run` — 205 tests passed - `cd server && pnpm exec vitest run src/__tests__/adapter-models.test.ts` — 17 tests passed - Confirmed the PR diff excludes `pnpm-lock.yaml` and `.github/workflows/**` as required by repository policy - Confirmed `.github/workflows/pr.yml` regenerates and uploads the PR lockfile artifact before downstream `pnpm install --frozen-lockfile` steps ## Risks Low risk. The behavior change is scoped to `codex_local` model selection. Existing concrete model IDs pass through unchanged; only the legacy bare `gpt-5.6` alias is rewritten. Dependency resolution may select a newer compatible `codex-acp` release within the declared range, so CI remains the final compatibility gate. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Original implementation: Anthropic Claude Opus 4.8 (`claude-opus-4-8`, 1M context, tool use and code execution) - Conflict resolution and PR preparation: OpenAI GPT-5.5 (`gpt-5.5`, Codex CLI coding agent, high-reasoning tool use and code execution; host-managed context window) ## 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) - [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details — branch name is fixed by the assigned execution workspace and cannot be renamed in-place - [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:
1 parent
a90f816c89
commit
487e33b8b6
9 files changed
+82
-14
No files matched your search
@@ -52,7 +52,7 @@
|
||||
"probe:quota": "pnpm exec tsx src/cli/quota-probe.ts --json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/codex-acp": "^1.1.0",
|
||||
"@agentclientprotocol/codex-acp": "^1.1.4",
|
||||
"@paperclipai/adapter-utils": "workspace:*",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_CODEX_LOCAL_MODEL, isCodexLocalFastModeSupported, models } from "./index.js";
|
||||
import {
|
||||
DEFAULT_CODEX_LOCAL_MODEL,
|
||||
isCodexLocalFastModeSupported,
|
||||
models,
|
||||
normalizeCodexModel,
|
||||
} from "./index.js";
|
||||
|
||||
describe("codex local adapter metadata", () => {
|
||||
it("advertises current GPT-5.6 Codex-capable OpenAI models by default", () => {
|
||||
const modelIds = models.map((model) => model.id);
|
||||
|
||||
expect(DEFAULT_CODEX_LOCAL_MODEL).toBe("gpt-5.6");
|
||||
expect(modelIds.slice(0, 4)).toEqual([
|
||||
"gpt-5.6",
|
||||
// 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, 3)).toEqual([
|
||||
"gpt-5.6-sol",
|
||||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
]);
|
||||
expect(modelIds).not.toContain("gpt-5.6");
|
||||
expect(isCodexLocalFastModeSupported(DEFAULT_CODEX_LOCAL_MODEL)).toBe(true);
|
||||
expect(modelIds).not.toContain("gpt-5.3-codex");
|
||||
expect(modelIds).not.toContain("gpt-5.3-codex-spark");
|
||||
});
|
||||
|
||||
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");
|
||||
// Concrete slugs and unknown/manual model IDs pass through untouched.
|
||||
expect(normalizeCodexModel("gpt-5.6-sol")).toBe("gpt-5.6-sol");
|
||||
expect(normalizeCodexModel("gpt-5.5")).toBe("gpt-5.5");
|
||||
expect(normalizeCodexModel("future-model")).toBe("future-model");
|
||||
expect(normalizeCodexModel("")).toBe("");
|
||||
expect(normalizeCodexModel(null)).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -5,14 +5,36 @@ export const label = "Codex";
|
||||
|
||||
export const SANDBOX_INSTALL_COMMAND = "npm install -g @openai/codex";
|
||||
|
||||
export const DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.6";
|
||||
// Use the concrete `gpt-5.6-sol` slug (Codex's own default for the 5.6 family) rather than the
|
||||
// bare `gpt-5.6` alias: OpenAI ships no model metadata for the bare slug, so passing it makes the
|
||||
// Codex CLI warn ("Model metadata for `gpt-5.6` not found") and fall back to generic context limits.
|
||||
export const DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.6-sol";
|
||||
export const DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX = true;
|
||||
export const CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS = ["gpt-5.6", "gpt-5.5", "gpt-5.4"] as const;
|
||||
export const CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS = [
|
||||
"gpt-5.6-sol",
|
||||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-5.5",
|
||||
"gpt-5.4",
|
||||
] as const;
|
||||
|
||||
function normalizeModelId(model: string | null | undefined): string {
|
||||
return typeof model === "string" ? model.trim() : "";
|
||||
}
|
||||
|
||||
// Legacy model aliases with no OpenAI-published metadata are rewritten to the concrete slug the
|
||||
// Codex CLI actually knows. Without this, agents still configured with the bare `gpt-5.6` alias
|
||||
// (the old default) keep triggering "Model metadata for `gpt-5.6` not found" warnings and Codex
|
||||
// falls back to generic context-window limits, even on a Codex build that ships the 5.6 family.
|
||||
const CODEX_LOCAL_MODEL_ALIASES: Readonly<Record<string, string>> = {
|
||||
"gpt-5.6": "gpt-5.6-sol",
|
||||
};
|
||||
|
||||
export function normalizeCodexModel(model: string | null | undefined): string {
|
||||
const normalizedModel = normalizeModelId(model);
|
||||
return CODEX_LOCAL_MODEL_ALIASES[normalizedModel] ?? normalizedModel;
|
||||
}
|
||||
|
||||
export function isCodexLocalKnownModel(model: string | null | undefined): boolean {
|
||||
const normalizedModel = normalizeModelId(model);
|
||||
if (!normalizedModel) return false;
|
||||
@@ -37,8 +59,8 @@ export function isCodexLocalFastModeSupported(model: string | null | undefined):
|
||||
}
|
||||
|
||||
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-5.6-sol", label: "gpt-5.6-sol" },
|
||||
{ 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" },
|
||||
@@ -74,7 +96,7 @@ Core fields:
|
||||
- modelReasoningEffort (string, optional): reasoning effort override (minimal|low|medium|high|xhigh) passed via -c model_reasoning_effort=...
|
||||
- promptTemplate (string, optional): run prompt template
|
||||
- search (boolean, optional): run codex with --search
|
||||
- fastMode (boolean, optional): enable Codex Fast mode; supported on GPT-5.6, GPT-5.5, GPT-5.4 and passed through for manual model IDs
|
||||
- fastMode (boolean, optional): enable Codex Fast mode; supported on 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
|
||||
@@ -105,7 +127,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-5.6, 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-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.
|
||||
- Codex ACP is the preferred auto lane when Node >=22.13.0 and the Codex ACP server are available. It reuses shared ACP prompt/runtime guidance, selected skill materialization into CODEX_HOME/skills, model/reasoning/fast-mode session config, and existing quota-window reporting. Auto selection falls back to CLI when ACP prerequisites are unavailable; explicit engine="acp" fails loudly.
|
||||
`;
|
||||
@@ -469,6 +469,12 @@ describe("codex_local ACP lane", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes the legacy bare gpt-5.6 alias to gpt-5.6-sol", () => {
|
||||
expect(buildCodexAcpConfig({ engine: "acp", model: "gpt-5.6" })).toMatchObject({
|
||||
model: "gpt-5.6-sol",
|
||||
});
|
||||
});
|
||||
|
||||
it("checks the Node version required by the ACPX runtime", () => {
|
||||
setNodeVersion("v22.12.0");
|
||||
expect(nodeVersionMeetsCodexAcpMinimum()).toBe(false);
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
asString,
|
||||
parseObject,
|
||||
} from "@paperclipai/adapter-utils/server-utils";
|
||||
import { normalizeCodexModel } from "../index.js";
|
||||
import { classifyCodexAuthRefreshFailure } from "./parse.js";
|
||||
import { copyBackCodexAuth } from "./codex-auth-copyback.js";
|
||||
import { buildCodexAuthInboundProvision } from "./codex-auth-merge-scripts.js";
|
||||
@@ -145,6 +146,11 @@ export function buildCodexAcpConfig(config: Record<string, unknown>): Record<str
|
||||
config.warmHandleIdleMs ??
|
||||
config.acpWarmHandleIdleMs ??
|
||||
DEFAULT_ACP_ENGINE_WARM_HANDLE_IDLE_MS;
|
||||
// Rewrite legacy model aliases (e.g. bare gpt-5.6) to the concrete slug Codex has metadata for,
|
||||
// so the ACP session config matches the CLI lane and avoids the fallback-metadata warning.
|
||||
const normalizedModel = normalizeCodexModel(
|
||||
typeof config.model === "string" ? config.model : "",
|
||||
);
|
||||
|
||||
return {
|
||||
...config,
|
||||
@@ -153,6 +159,7 @@ export function buildCodexAcpConfig(config: Record<string, unknown>): Record<str
|
||||
permissionMode,
|
||||
nonInteractivePermissions,
|
||||
warmHandleIdleMs,
|
||||
...(normalizedModel ? { model: normalizedModel } : {}),
|
||||
...(agentCommand ? { agentCommand } : {}),
|
||||
...(stateDir ? { stateDir } : {}),
|
||||
};
|
||||
|
||||
@@ -2,6 +2,19 @@ import { describe, expect, it } from "vitest";
|
||||
import { buildCodexExecArgs } from "./codex-args.js";
|
||||
|
||||
describe("buildCodexExecArgs", () => {
|
||||
it("rewrites the legacy bare gpt-5.6 alias to gpt-5.6-sol and applies fast mode", () => {
|
||||
const result = buildCodexExecArgs({
|
||||
model: "gpt-5.6",
|
||||
fastMode: true,
|
||||
});
|
||||
|
||||
expect(result.model).toBe("gpt-5.6-sol");
|
||||
expect(result.args).toContain("gpt-5.6-sol");
|
||||
expect(result.args).not.toContain("gpt-5.6");
|
||||
expect(result.fastModeApplied).toBe(true);
|
||||
expect(result.fastModeIgnoredReason).toBeNull();
|
||||
});
|
||||
|
||||
it("enables Codex fast mode overrides for GPT-5.4", () => {
|
||||
const result = buildCodexExecArgs({
|
||||
model: "gpt-5.4",
|
||||
@@ -98,7 +111,7 @@ describe("buildCodexExecArgs", () => {
|
||||
expect(result.fastModeRequested).toBe(true);
|
||||
expect(result.fastModeApplied).toBe(false);
|
||||
expect(result.fastModeIgnoredReason).toContain(
|
||||
"currently only supported on gpt-5.6, gpt-5.5, gpt-5.4 or manually configured model IDs",
|
||||
"currently only supported on 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",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { asBoolean, asString, asStringArray } from "@paperclipai/adapter-utils/s
|
||||
import {
|
||||
CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS,
|
||||
isCodexLocalFastModeSupported,
|
||||
normalizeCodexModel,
|
||||
} from "../index.js";
|
||||
|
||||
export type BuildCodexExecArgsResult = {
|
||||
@@ -36,7 +37,7 @@ export function buildCodexExecArgs(
|
||||
} = {},
|
||||
): BuildCodexExecArgsResult {
|
||||
const record = asRecord(config);
|
||||
const model = asString(record.model, "").trim();
|
||||
const model = normalizeCodexModel(asString(record.model, ""));
|
||||
const modelReasoningEffort = asString(
|
||||
record.modelReasoningEffort,
|
||||
asString(record.reasoningEffort, ""),
|
||||
|
||||
@@ -48,7 +48,8 @@ describe("adapter model listing", () => {
|
||||
const models = await listAdapterModels("codex_local");
|
||||
|
||||
expect(models).toEqual(codexFallbackModels);
|
||||
expect(models.some((model) => model.id === "gpt-5.6")).toBe(true);
|
||||
// The bare gpt-5.6 alias is intentionally not advertised (Codex has no metadata for it).
|
||||
expect(models.some((model) => model.id === "gpt-5.6")).toBe(false);
|
||||
expect(models.some((model) => model.id === "gpt-5.6-sol")).toBe(true);
|
||||
expect(models.some((model) => model.id === "gpt-5.6-terra")).toBe(true);
|
||||
expect(models.some((model) => model.id === "gpt-5.6-luna")).toBe(true);
|
||||
|
||||
@@ -70,7 +70,7 @@ const modelOverrideIssue: Issue = {
|
||||
title: "Verify task-level model override provenance",
|
||||
assigneeAdapterOverrides: {
|
||||
adapterConfig: {
|
||||
model: "gpt-5.6",
|
||||
model: "gpt-5.6-sol",
|
||||
modelReasoningEffort: "high",
|
||||
},
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user