diff --git a/package-lock.json b/package-lock.json index 1780b4d2..1ce7e759 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5185,7 +5185,6 @@ "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@lit/reactive-element": "^2.1.0", "lit-element": "^4.2.0", @@ -6386,7 +6385,6 @@ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -6415,8 +6413,7 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -6602,7 +6599,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -6682,7 +6678,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -7225,6 +7220,7 @@ }, "devDependencies": { "@types/diff": "^7.0.2", + "@types/ms": "^2.1.0", "@types/node": "^24.3.0", "@types/proper-lockfile": "^4.1.4", "typescript": "^5.7.3", diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 4a2a4ef6..e7d04463 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- `minimal` thinking level now maps to `minimal` reasoning effort instead of being treated as `low`. + ## [0.36.0] - 2026-01-05 ## [0.35.0] - 2026-01-05 diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index d040e9ec..a8c3eb2d 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -277,11 +277,7 @@ export class Agent { this._state.error = undefined; const reasoning: ReasoningEffort | undefined = - this._state.thinkingLevel === "off" - ? undefined - : this._state.thinkingLevel === "minimal" - ? "low" - : (this._state.thinkingLevel as ReasoningEffort); + this._state.thinkingLevel === "off" ? undefined : (this._state.thinkingLevel as ReasoningEffort); const context: AgentContext = { systemPrompt: this._state.systemPrompt, diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 57939ed1..6152b4db 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -454,54 +454,7 @@ async function generateModels() { provider: "openai-codex", baseUrl: CODEX_BASE_URL, reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.2-codex-low", - name: "gpt-5.2-codex-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.2-codex-medium", - name: "gpt-5.2-codex-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.2-codex-high", - name: "gpt-5.2-codex-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.2-codex-xhigh", - name: "gpt-5.2-codex-xhigh", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, + thinkingLevels: ["low", "medium", "high", "xhigh"], input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: CODEX_CONTEXT, @@ -519,66 +472,6 @@ async function generateModels() { contextWindow: CODEX_CONTEXT, maxTokens: CODEX_MAX_TOKENS, }, - { - id: "gpt-5.2-none", - name: "gpt-5.2-none", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.2-low", - name: "gpt-5.2-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.2-medium", - name: "gpt-5.2-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.2-high", - name: "gpt-5.2-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.2-xhigh", - name: "gpt-5.2-xhigh", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, { id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max", @@ -586,54 +479,7 @@ async function generateModels() { provider: "openai-codex", baseUrl: CODEX_BASE_URL, reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-codex-max-low", - name: "gpt-5.1-codex-max-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-codex-max-medium", - name: "gpt-5.1-codex-max-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-codex-max-high", - name: "gpt-5.1-codex-max-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-codex-max-xhigh", - name: "gpt-5.1-codex-max-xhigh", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, + thinkingLevels: ["low", "medium", "high", "xhigh"], input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: CODEX_CONTEXT, @@ -646,42 +492,7 @@ async function generateModels() { provider: "openai-codex", baseUrl: CODEX_BASE_URL, reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-codex-low", - name: "gpt-5.1-codex-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-codex-medium", - name: "gpt-5.1-codex-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-codex-high", - name: "gpt-5.1-codex-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, + thinkingLevels: ["low", "medium", "high"], input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: CODEX_CONTEXT, @@ -694,30 +505,7 @@ async function generateModels() { provider: "openai-codex", baseUrl: CODEX_BASE_URL, reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-codex-mini-medium", - name: "gpt-5.1-codex-mini-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-codex-mini-high", - name: "gpt-5.1-codex-mini-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, + thinkingLevels: ["medium", "high"], input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: CODEX_CONTEXT, @@ -730,6 +518,7 @@ async function generateModels() { provider: "openai-codex", baseUrl: CODEX_BASE_URL, reasoning: true, + thinkingLevels: ["medium", "high"], input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: CODEX_CONTEXT, @@ -742,30 +531,7 @@ async function generateModels() { provider: "openai-codex", baseUrl: CODEX_BASE_URL, reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5-codex-mini-medium", - name: "gpt-5-codex-mini-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5-codex-mini-high", - name: "gpt-5-codex-mini-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, + thinkingLevels: ["medium", "high"], input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: CODEX_CONTEXT, @@ -778,6 +544,7 @@ async function generateModels() { provider: "openai-codex", baseUrl: CODEX_BASE_URL, reasoning: true, + thinkingLevels: ["low", "medium", "high"], input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: CODEX_CONTEXT, @@ -795,54 +562,6 @@ async function generateModels() { contextWindow: CODEX_CONTEXT, maxTokens: CODEX_MAX_TOKENS, }, - { - id: "gpt-5.1-none", - name: "gpt-5.1-none", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-low", - name: "gpt-5.1-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-medium", - name: "gpt-5.1-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, - { - id: "gpt-5.1-high", - name: "gpt-5.1-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: CODEX_CONTEXT, - maxTokens: CODEX_MAX_TOKENS, - }, { id: "gpt-5.1-chat-latest", name: "gpt-5.1-chat-latest", @@ -1280,6 +999,9 @@ export const MODELS = { `; } output += `\t\t\treasoning: ${model.reasoning},\n`; + if (model.thinkingLevels) { + output += `\t\t\tthinkingLevels: ${JSON.stringify(model.thinkingLevels)},\n`; + } output += `\t\t\tinput: [${model.input.map(i => `"${i}"`).join(", ")}],\n`; output += `\t\t\tcost: {\n`; output += `\t\t\t\tinput: ${model.cost.input},\n`; diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index fc924b62..bc288087 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -2781,6 +2781,7 @@ export const MODELS = { provider: "openai-codex", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, + thinkingLevels: ["medium","high"], input: ["text", "image"], cost: { input: 0, @@ -2815,6 +2816,7 @@ export const MODELS = { provider: "openai-codex", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, + thinkingLevels: ["low","medium","high"], input: ["text", "image"], cost: { input: 0, @@ -2832,40 +2834,7 @@ export const MODELS = { provider: "openai-codex", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5-codex-mini-high": { - id: "gpt-5-codex-mini-high", - name: "gpt-5-codex-mini-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5-codex-mini-medium": { - id: "gpt-5-codex-mini-medium", - name: "gpt-5-codex-mini-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, + thinkingLevels: ["medium","high"], input: ["text", "image"], cost: { input: 0, @@ -2951,40 +2920,7 @@ export const MODELS = { provider: "openai-codex", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-codex-high": { - id: "gpt-5.1-codex-high", - name: "gpt-5.1-codex-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-codex-low": { - id: "gpt-5.1-codex-low", - name: "gpt-5.1-codex-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, + thinkingLevels: ["low","medium","high"], input: ["text", "image"], cost: { input: 0, @@ -3002,91 +2938,7 @@ export const MODELS = { provider: "openai-codex", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-codex-max-high": { - id: "gpt-5.1-codex-max-high", - name: "gpt-5.1-codex-max-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-codex-max-low": { - id: "gpt-5.1-codex-max-low", - name: "gpt-5.1-codex-max-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-codex-max-medium": { - id: "gpt-5.1-codex-max-medium", - name: "gpt-5.1-codex-max-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-codex-max-xhigh": { - id: "gpt-5.1-codex-max-xhigh", - name: "gpt-5.1-codex-max-xhigh", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-codex-medium": { - id: "gpt-5.1-codex-medium", - name: "gpt-5.1-codex-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, + thinkingLevels: ["low","medium","high","xhigh"], input: ["text", "image"], cost: { input: 0, @@ -3104,108 +2956,7 @@ export const MODELS = { provider: "openai-codex", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-codex-mini-high": { - id: "gpt-5.1-codex-mini-high", - name: "gpt-5.1-codex-mini-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-codex-mini-medium": { - id: "gpt-5.1-codex-mini-medium", - name: "gpt-5.1-codex-mini-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-high": { - id: "gpt-5.1-high", - name: "gpt-5.1-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-low": { - id: "gpt-5.1-low", - name: "gpt-5.1-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-medium": { - id: "gpt-5.1-medium", - name: "gpt-5.1-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.1-none": { - id: "gpt-5.1-none", - name: "gpt-5.1-none", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, + thinkingLevels: ["medium","high"], input: ["text", "image"], cost: { input: 0, @@ -3240,159 +2991,7 @@ export const MODELS = { provider: "openai-codex", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.2-codex-high": { - id: "gpt-5.2-codex-high", - name: "gpt-5.2-codex-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.2-codex-low": { - id: "gpt-5.2-codex-low", - name: "gpt-5.2-codex-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.2-codex-medium": { - id: "gpt-5.2-codex-medium", - name: "gpt-5.2-codex-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.2-codex-xhigh": { - id: "gpt-5.2-codex-xhigh", - name: "gpt-5.2-codex-xhigh", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.2-high": { - id: "gpt-5.2-high", - name: "gpt-5.2-high", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.2-low": { - id: "gpt-5.2-low", - name: "gpt-5.2-low", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.2-medium": { - id: "gpt-5.2-medium", - name: "gpt-5.2-medium", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.2-none": { - id: "gpt-5.2-none", - name: "gpt-5.2-none", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 400000, - maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, - "gpt-5.2-xhigh": { - id: "gpt-5.2-xhigh", - name: "gpt-5.2-xhigh", - api: "openai-codex-responses", - provider: "openai-codex", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, + thinkingLevels: ["low","medium","high","xhigh"], input: ["text", "image"], cost: { input: 0, diff --git a/packages/ai/src/models.ts b/packages/ai/src/models.ts index d2ab593d..1c6b2f51 100644 --- a/packages/ai/src/models.ts +++ b/packages/ai/src/models.ts @@ -1,13 +1,50 @@ import { MODELS } from "./models.generated.js"; -import type { Api, KnownProvider, Model, Usage } from "./types.js"; +import type { Api, KnownProvider, Model, ReasoningEffort, Usage } from "./types.js"; const modelRegistry: Map>> = new Map(); +const CODEX_THINKING_SUFFIXES = ["-none", "-minimal", "-low", "-medium", "-high", "-xhigh"]; +const CODEX_THINKING_LEVELS: Record = { + "gpt-5.2-codex": ["low", "medium", "high", "xhigh"], + "gpt-5.1-codex-max": ["low", "medium", "high", "xhigh"], + "gpt-5.1-codex": ["low", "medium", "high"], + "gpt-5.1-codex-mini": ["medium", "high"], + "codex-mini-latest": ["medium", "high"], + "gpt-5-codex-mini": ["medium", "high"], + "gpt-5-codex": ["low", "medium", "high"], +}; + +function isCodexThinkingVariant(modelId: string): boolean { + const normalized = modelId.toLowerCase(); + return CODEX_THINKING_SUFFIXES.some((suffix) => normalized.endsWith(suffix)); +} + +function normalizeCodexModelId(modelId: string): string { + const normalized = modelId.toLowerCase(); + for (const suffix of CODEX_THINKING_SUFFIXES) { + if (normalized.endsWith(suffix)) { + return modelId.slice(0, modelId.length - suffix.length); + } + } + return modelId; +} + +function applyCodexThinkingLevels(model: Model): Model { + if (model.provider !== "openai-codex") return model; + const thinkingLevels = CODEX_THINKING_LEVELS[model.id]; + if (!thinkingLevels) return model; + return { ...model, thinkingLevels }; +} + // Initialize registry from MODELS on module load for (const [provider, models] of Object.entries(MODELS)) { const providerModels = new Map>(); for (const [id, model] of Object.entries(models)) { - providerModels.set(id, model as Model); + const typedModel = model as Model; + if (provider === "openai-codex" && isCodexThinkingVariant(typedModel.id)) { + continue; + } + providerModels.set(id, applyCodexThinkingLevels(typedModel)); } modelRegistry.set(provider, providerModels); } @@ -21,7 +58,17 @@ export function getModel> { - return modelRegistry.get(provider)?.get(modelId as string) as Model>; + const providerModels = modelRegistry.get(provider); + const direct = providerModels?.get(modelId as string); + if (direct) return direct as Model>; + if (provider === "openai-codex") { + const normalized = normalizeCodexModelId(modelId as string); + const normalizedModel = providerModels?.get(normalized); + if (normalizedModel) { + return normalizedModel as Model>; + } + } + return direct as unknown as Model>; } export function getProviders(): KnownProvider[] { @@ -52,6 +99,9 @@ const XHIGH_MODELS = new Set(["gpt-5.1-codex-max", "gpt-5.2", "gpt-5.2-codex"]); * Currently only certain OpenAI models support this. */ export function supportsXhigh(model: Model): boolean { + if (model.thinkingLevels) { + return model.thinkingLevels.includes("xhigh"); + } return XHIGH_MODELS.has(model.id); } diff --git a/packages/ai/src/providers/openai-codex-responses.ts b/packages/ai/src/providers/openai-codex-responses.ts index 56db637d..d807c24d 100644 --- a/packages/ai/src/providers/openai-codex-responses.ts +++ b/packages/ai/src/providers/openai-codex-responses.ts @@ -126,10 +126,12 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses" context.systemPrompt, ); + const reasoningEffort = transformedBody.reasoning?.effort ?? null; const headers = createCodexHeaders(model.headers, accountId, apiKey, transformedBody.prompt_cache_key); logCodexDebug("codex request", { url, model: params.model, + reasoningEffort, headers: redactHeaders(headers), }); diff --git a/packages/ai/src/providers/openai-codex/request-transformer.ts b/packages/ai/src/providers/openai-codex/request-transformer.ts index 4774b89d..1decd3e6 100644 --- a/packages/ai/src/providers/openai-codex/request-transformer.ts +++ b/packages/ai/src/providers/openai-codex/request-transformer.ts @@ -308,11 +308,15 @@ export async function transformRequestBody( } } - const reasoningConfig = getReasoningConfig(normalizedModel, options); - body.reasoning = { - ...body.reasoning, - ...reasoningConfig, - }; + if (options.reasoningEffort !== undefined) { + const reasoningConfig = getReasoningConfig(normalizedModel, options); + body.reasoning = { + ...body.reasoning, + ...reasoningConfig, + }; + } else { + delete body.reasoning; + } body.text = { ...body.text, diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index 6551ed49..f578d256 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -210,6 +210,8 @@ export interface Model { provider: Provider; baseUrl: string; reasoning: boolean; + /** Supported reasoning levels for this model (excluding "off"). */ + thinkingLevels?: ReasoningEffort[]; input: ("text" | "image")[]; cost: { input: number; // $/million tokens diff --git a/packages/ai/test/abort.test.ts b/packages/ai/test/abort.test.ts index b4fc8fd5..0108c50c 100644 --- a/packages/ai/test/abort.test.ts +++ b/packages/ai/test/abort.test.ts @@ -145,12 +145,12 @@ describe("AI Providers Abort Tests", () => { describe("OpenAI Codex Provider Abort", () => { it.skipIf(!openaiCodexToken)("should abort mid-stream", { retry: 3 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testAbortSignal(llm, { apiKey: openaiCodexToken }); }); it.skipIf(!openaiCodexToken)("should handle immediate abort", { retry: 3 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testImmediateAbort(llm, { apiKey: openaiCodexToken }); }); }); diff --git a/packages/ai/test/context-overflow.test.ts b/packages/ai/test/context-overflow.test.ts index f32fdceb..85e8855a 100644 --- a/packages/ai/test/context-overflow.test.ts +++ b/packages/ai/test/context-overflow.test.ts @@ -271,9 +271,9 @@ describe("Context overflow error handling", () => { describe("OpenAI Codex (OAuth)", () => { it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should detect overflow via isContextOverflow", + "gpt-5.2-codex - should detect overflow via isContextOverflow", async () => { - const model = getModel("openai-codex", "gpt-5.2-xhigh"); + const model = getModel("openai-codex", "gpt-5.2-codex"); const result = await testContextOverflow(model, openaiCodexToken!); logResult(result); diff --git a/packages/ai/test/empty.test.ts b/packages/ai/test/empty.test.ts index 97b4efe2..95832215 100644 --- a/packages/ai/test/empty.test.ts +++ b/packages/ai/test/empty.test.ts @@ -577,37 +577,37 @@ describe("AI Providers Empty Message Tests", () => { describe("OpenAI Codex Provider Empty Messages", () => { it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should handle empty content array", + "gpt-5.2-codex - should handle empty content array", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testEmptyMessage(llm, { apiKey: openaiCodexToken }); }, ); it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should handle empty string content", + "gpt-5.2-codex - should handle empty string content", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testEmptyStringMessage(llm, { apiKey: openaiCodexToken }); }, ); it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should handle whitespace-only content", + "gpt-5.2-codex - should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testWhitespaceOnlyMessage(llm, { apiKey: openaiCodexToken }); }, ); it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should handle empty assistant message in conversation", + "gpt-5.2-codex - should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testEmptyAssistantMessage(llm, { apiKey: openaiCodexToken }); }, ); diff --git a/packages/ai/test/image-tool-result.test.ts b/packages/ai/test/image-tool-result.test.ts index 97cc3fd1..762e8495 100644 --- a/packages/ai/test/image-tool-result.test.ts +++ b/packages/ai/test/image-tool-result.test.ts @@ -398,19 +398,19 @@ describe("Tool Results with Images", () => { describe("OpenAI Codex Provider", () => { it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should handle tool result with only image", + "gpt-5.2-codex - should handle tool result with only image", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await handleToolWithImageResult(llm, { apiKey: openaiCodexToken }); }, ); it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should handle tool result with text and image", + "gpt-5.2-codex - should handle tool result with text and image", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await handleToolWithTextAndImageResult(llm, { apiKey: openaiCodexToken }); }, ); diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index b2ccb408..d8ff4265 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -879,8 +879,8 @@ describe("Generate E2E Tests", () => { }); }); - describe("OpenAI Codex Provider (gpt-5.2-xhigh)", () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + describe("OpenAI Codex Provider (gpt-5.2-codex)", () => { + const llm = getModel("openai-codex", "gpt-5.2-codex"); it.skipIf(!openaiCodexToken)("should complete basic text generation", { retry: 3 }, async () => { await basicTextGeneration(llm, { apiKey: openaiCodexToken }); @@ -895,7 +895,7 @@ describe("Generate E2E Tests", () => { }); it.skipIf(!openaiCodexToken)("should handle thinking", { retry: 3 }, async () => { - await handleThinking(llm, { apiKey: openaiCodexToken }); + await handleThinking(llm, { apiKey: openaiCodexToken, reasoningEffort: "high" }); }); it.skipIf(!openaiCodexToken)("should handle multi-turn with thinking and tools", { retry: 3 }, async () => { diff --git a/packages/ai/test/tokens.test.ts b/packages/ai/test/tokens.test.ts index 66d93ea9..f6b86f62 100644 --- a/packages/ai/test/tokens.test.ts +++ b/packages/ai/test/tokens.test.ts @@ -222,10 +222,10 @@ describe("Token Statistics on Abort", () => { describe("OpenAI Codex Provider", () => { it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should include token stats when aborted mid-stream", + "gpt-5.2-codex - should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testTokensOnAbort(llm, { apiKey: openaiCodexToken }); }, ); diff --git a/packages/ai/test/tool-call-without-result.test.ts b/packages/ai/test/tool-call-without-result.test.ts index 051f2cf7..854762a3 100644 --- a/packages/ai/test/tool-call-without-result.test.ts +++ b/packages/ai/test/tool-call-without-result.test.ts @@ -248,10 +248,10 @@ describe("Tool Call Without Result Tests", () => { describe("OpenAI Codex Provider", () => { it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should filter out tool calls without corresponding tool results", + "gpt-5.2-codex - should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => { - const model = getModel("openai-codex", "gpt-5.2-xhigh"); + const model = getModel("openai-codex", "gpt-5.2-codex"); await testToolCallWithoutResult(model, { apiKey: openaiCodexToken }); }, ); diff --git a/packages/ai/test/total-tokens.test.ts b/packages/ai/test/total-tokens.test.ts index 6dcdf25a..13b4afdf 100644 --- a/packages/ai/test/total-tokens.test.ts +++ b/packages/ai/test/total-tokens.test.ts @@ -541,10 +541,10 @@ describe("totalTokens field", () => { describe("OpenAI Codex (OAuth)", () => { it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should return totalTokens equal to sum of components", + "gpt-5.2-codex - should return totalTokens equal to sum of components", { retry: 3, timeout: 60000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); console.log(`\nOpenAI Codex / ${llm.id}:`); const { first, second } = await testTotalTokensWithCache(llm, { apiKey: openaiCodexToken }); diff --git a/packages/ai/test/unicode-surrogate.test.ts b/packages/ai/test/unicode-surrogate.test.ts index 0cdc0b3f..f848c15a 100644 --- a/packages/ai/test/unicode-surrogate.test.ts +++ b/packages/ai/test/unicode-surrogate.test.ts @@ -619,28 +619,28 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => { describe("OpenAI Codex Provider Unicode Handling", () => { it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should handle emoji in tool results", + "gpt-5.2-codex - should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testEmojiInToolResults(llm, { apiKey: openaiCodexToken }); }, ); it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should handle real-world LinkedIn comment data with emoji", + "gpt-5.2-codex - should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testRealWorldLinkedInData(llm, { apiKey: openaiCodexToken }); }, ); it.skipIf(!openaiCodexToken)( - "gpt-5.2-xhigh - should handle unpaired high surrogate (0xD83D) in tool results", + "gpt-5.2-codex - should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => { - const llm = getModel("openai-codex", "gpt-5.2-xhigh"); + const llm = getModel("openai-codex", "gpt-5.2-codex"); await testUnpairedHighSurrogate(llm, { apiKey: openaiCodexToken }); }, ); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 34583998..89abcddb 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -12,6 +12,10 @@ - OAuth login UI now uses dedicated dialog component with consistent borders - Assume truecolor support for all terminals except `dumb`, empty, or `linux` (fixes colors over SSH) +### Changed + +- Thinking level availability now reflects per-model supported reasoning levels. + ### Fixed - Managed binaries (`fd`, `rg`) now stored in `~/.pi/agent/bin/` instead of `tools/`, eliminating false deprecation warnings ([#470](https://github.com/badlogic/pi-mono/pull/470) by [@mcinteerj](https://github.com/mcinteerj)) @@ -20,6 +24,7 @@ - Migration warnings now ignore `fd.exe` and `rg.exe` in `tools/` on Windows ([#458](https://github.com/badlogic/pi-mono/pull/458) by [@carlosgtrz](https://github.com/carlosgtrz)) - CI: add `examples/extensions/with-deps` to workspaces to fix typecheck ([#467](https://github.com/badlogic/pi-mono/pull/467) by [@aliou](https://github.com/aliou)) - SDK: passing `extensions: []` now disables extension discovery as documented ([#465](https://github.com/badlogic/pi-mono/pull/465) by [@aliou](https://github.com/aliou)) +- Legacy Codex model IDs with thinking suffixes resolve to their base models. ## [0.36.0] - 2026-01-05 diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index 2dfdeceb..96b6e23f 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@types/diff": "^7.0.2", + "@types/ms": "^2.1.0", "@types/node": "^24.3.0", "@types/proper-lockfile": "^4.1.4", "typescript": "^5.7.3", diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 1bba813a..e5459e0b 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -978,16 +978,12 @@ export class AgentSession { /** * Set thinking level. - * Clamps to model capabilities: "off" if no reasoning, "high" if xhigh unsupported. + * Clamps to model capabilities based on available thinking levels. * Saves to session and settings. */ setThinkingLevel(level: ThinkingLevel): void { - let effectiveLevel = level; - if (!this.supportsThinking()) { - effectiveLevel = "off"; - } else if (level === "xhigh" && !this.supportsXhighThinking()) { - effectiveLevel = "high"; - } + const availableLevels = this.getAvailableThinkingLevels(); + const effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels); this.agent.setThinkingLevel(effectiveLevel); this.sessionManager.appendThinkingLevelChange(effectiveLevel); this.settingsManager.setDefaultThinkingLevel(effectiveLevel); @@ -1013,6 +1009,14 @@ export class AgentSession { * Get available thinking levels for current model. */ getAvailableThinkingLevels(): ThinkingLevel[] { + if (!this.supportsThinking()) return ["off"]; + + const modelLevels = this.model?.thinkingLevels; + if (modelLevels && modelLevels.length > 0) { + const withOff: ThinkingLevel[] = ["off", ...modelLevels]; + return THINKING_LEVELS_WITH_XHIGH.filter((level) => withOff.includes(level)); + } + return this.supportsXhighThinking() ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS; } @@ -1030,6 +1034,24 @@ export class AgentSession { return !!this.model?.reasoning; } + private _clampThinkingLevel(level: ThinkingLevel, availableLevels: ThinkingLevel[]): ThinkingLevel { + const ordered = THINKING_LEVELS_WITH_XHIGH; + const available = new Set(availableLevels); + const requestedIndex = ordered.indexOf(level); + if (requestedIndex === -1) { + return availableLevels[0] ?? "off"; + } + for (let i = requestedIndex; i < ordered.length; i++) { + const candidate = ordered[i]; + if (available.has(candidate)) return candidate; + } + for (let i = requestedIndex - 1; i >= 0; i--) { + const candidate = ordered[i]; + if (available.has(candidate)) return candidate; + } + return availableLevels[0] ?? "off"; + } + // ========================================================================= // Queue Mode Management // ========================================================================= diff --git a/packages/coding-agent/src/core/model-registry.ts b/packages/coding-agent/src/core/model-registry.ts index d6a314a4..cab368c6 100644 --- a/packages/coding-agent/src/core/model-registry.ts +++ b/packages/coding-agent/src/core/model-registry.ts @@ -18,6 +18,16 @@ import type { AuthStorage } from "./auth-storage.js"; const Ajv = (AjvModule as any).default || AjvModule; +const ThinkingLevelsSchema = Type.Array( + Type.Union([ + Type.Literal("minimal"), + Type.Literal("low"), + Type.Literal("medium"), + Type.Literal("high"), + Type.Literal("xhigh"), + ]), +); + // Schema for OpenAI compatibility settings const OpenAICompatSchema = Type.Object({ supportsStore: Type.Optional(Type.Boolean()), @@ -40,6 +50,7 @@ const ModelDefinitionSchema = Type.Object({ ]), ), reasoning: Type.Boolean(), + thinkingLevels: Type.Optional(ThinkingLevelsSchema), input: Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])), cost: Type.Object({ input: Type.Number(), @@ -107,6 +118,14 @@ function resolveApiKeyConfig(keyConfig: string): string | undefined { return keyConfig; } +function normalizeCodexModelId(modelId: string): string { + const suffixes = ["-none", "-minimal", "-low", "-medium", "-high", "-xhigh"]; + const normalized = modelId.toLowerCase(); + const matchedSuffix = suffixes.find((suffix) => normalized.endsWith(suffix)); + if (!matchedSuffix) return modelId; + return modelId.slice(0, modelId.length - matchedSuffix.length); +} + /** * Model registry - loads and manages models, resolves API keys via AuthStorage. */ @@ -330,6 +349,7 @@ export class ModelRegistry { provider: providerName, baseUrl: providerConfig.baseUrl!, reasoning: modelDef.reasoning, + thinkingLevels: modelDef.thinkingLevels, input: modelDef.input as ("text" | "image")[], cost: modelDef.cost, contextWindow: modelDef.contextWindow, @@ -363,7 +383,15 @@ export class ModelRegistry { * Find a model by provider and ID. */ find(provider: string, modelId: string): Model | undefined { - return this.models.find((m) => m.provider === provider && m.id === modelId) ?? undefined; + const exact = this.models.find((m) => m.provider === provider && m.id === modelId); + if (exact) return exact; + if (provider === "openai-codex") { + const normalized = normalizeCodexModelId(modelId); + if (normalized !== modelId) { + return this.models.find((m) => m.provider === provider && m.id === normalized) ?? undefined; + } + } + return undefined; } /** diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts index 98c8fd60..c6ed7ef3 100644 --- a/packages/coding-agent/src/core/model-resolver.ts +++ b/packages/coding-agent/src/core/model-resolver.ts @@ -102,6 +102,18 @@ export interface ParsedModelResult { warning: string | undefined; } +const THINKING_SUFFIXES = ["-none", "-minimal", "-low", "-medium", "-high", "-xhigh"]; + +function stripThinkingSuffix(pattern: string): string { + const normalized = pattern.toLowerCase(); + for (const suffix of THINKING_SUFFIXES) { + if (normalized.endsWith(suffix)) { + return pattern.slice(0, pattern.length - suffix.length); + } + } + return pattern; +} + /** * Parse a pattern to extract model and thinking level. * Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix). @@ -122,6 +134,14 @@ export function parseModelPattern(pattern: string, availableModels: Model[] return { model: exactMatch, thinkingLevel: "off", warning: undefined }; } + const normalizedPattern = stripThinkingSuffix(pattern); + if (normalizedPattern !== pattern) { + const normalizedMatch = tryMatchModel(normalizedPattern, availableModels); + if (normalizedMatch) { + return { model: normalizedMatch, thinkingLevel: "off", warning: undefined }; + } + } + // No match - try splitting on last colon if present const lastColonIndex = pattern.lastIndexOf(":"); if (lastColonIndex === -1) {