mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-21 22:01:41 +00:00
fix: clean up Codex thinking level handling
- Remove per-thinking-level model variants (gpt-5.2-codex-high, etc.) - Remove thinkingLevels from Model type - Provider clamps reasoning effort internally - Omit reasoning field when thinking is off fixes #472
This commit is contained in:
parent
02b72b49d5
commit
0b9e3ada0c
11 changed files with 45 additions and 148 deletions
|
|
@ -8,7 +8,6 @@ import {
|
||||||
type ImageContent,
|
type ImageContent,
|
||||||
type Message,
|
type Message,
|
||||||
type Model,
|
type Model,
|
||||||
type ReasoningEffort,
|
|
||||||
streamSimple,
|
streamSimple,
|
||||||
type TextContent,
|
type TextContent,
|
||||||
} from "@mariozechner/pi-ai";
|
} from "@mariozechner/pi-ai";
|
||||||
|
|
@ -276,8 +275,7 @@ export class Agent {
|
||||||
this._state.streamMessage = null;
|
this._state.streamMessage = null;
|
||||||
this._state.error = undefined;
|
this._state.error = undefined;
|
||||||
|
|
||||||
const reasoning: ReasoningEffort | undefined =
|
const reasoning = this._state.thinkingLevel === "off" ? undefined : this._state.thinkingLevel;
|
||||||
this._state.thinkingLevel === "off" ? undefined : (this._state.thinkingLevel as ReasoningEffort);
|
|
||||||
|
|
||||||
const context: AgentContext = {
|
const context: AgentContext = {
|
||||||
systemPrompt: this._state.systemPrompt,
|
systemPrompt: this._state.systemPrompt,
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,19 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- OpenAI Codex models no longer have per-thinking-level variants (e.g., `gpt-5.2-codex-high`). Use the base model ID and set thinking level separately. The Codex provider clamps reasoning effort to what each model supports internally. (initial implementation by [@ben-vargas](https://github.com/ben-vargas) in [#472](https://github.com/badlogic/pi-mono/pull/472))
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Headless OAuth support for all callback-server providers (Google Gemini CLI, Antigravity, OpenAI Codex): paste redirect URL when browser callback is unreachable ([#428](https://github.com/badlogic/pi-mono/pull/428) by [@ben-vargas](https://github.com/ben-vargas), [#468](https://github.com/badlogic/pi-mono/pull/468) by [@crcatala](https://github.com/crcatala))
|
- Headless OAuth support for all callback-server providers (Google Gemini CLI, Antigravity, OpenAI Codex): paste redirect URL when browser callback is unreachable ([#428](https://github.com/badlogic/pi-mono/pull/428) by [@ben-vargas](https://github.com/ben-vargas), [#468](https://github.com/badlogic/pi-mono/pull/468) by [@crcatala](https://github.com/crcatala))
|
||||||
- Cancellable GitHub Copilot device code polling via AbortSignal
|
- Cancellable GitHub Copilot device code polling via AbortSignal
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Codex requests now omit the `reasoning` field entirely when thinking is off, letting the backend use its default instead of forcing a value. ([#472](https://github.com/badlogic/pi-mono/pull/472))
|
||||||
|
|
||||||
## [0.36.0] - 2026-01-05
|
## [0.36.0] - 2026-01-05
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -454,7 +454,6 @@ async function generateModels() {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: CODEX_BASE_URL,
|
baseUrl: CODEX_BASE_URL,
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["low", "medium", "high", "xhigh"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
contextWindow: CODEX_CONTEXT,
|
contextWindow: CODEX_CONTEXT,
|
||||||
|
|
@ -479,7 +478,6 @@ async function generateModels() {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: CODEX_BASE_URL,
|
baseUrl: CODEX_BASE_URL,
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["low", "medium", "high", "xhigh"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
contextWindow: CODEX_CONTEXT,
|
contextWindow: CODEX_CONTEXT,
|
||||||
|
|
@ -492,7 +490,6 @@ async function generateModels() {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: CODEX_BASE_URL,
|
baseUrl: CODEX_BASE_URL,
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["low", "medium", "high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
contextWindow: CODEX_CONTEXT,
|
contextWindow: CODEX_CONTEXT,
|
||||||
|
|
@ -505,7 +502,6 @@ async function generateModels() {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: CODEX_BASE_URL,
|
baseUrl: CODEX_BASE_URL,
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["medium", "high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
contextWindow: CODEX_CONTEXT,
|
contextWindow: CODEX_CONTEXT,
|
||||||
|
|
@ -518,7 +514,6 @@ async function generateModels() {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: CODEX_BASE_URL,
|
baseUrl: CODEX_BASE_URL,
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["medium", "high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
contextWindow: CODEX_CONTEXT,
|
contextWindow: CODEX_CONTEXT,
|
||||||
|
|
@ -531,7 +526,6 @@ async function generateModels() {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: CODEX_BASE_URL,
|
baseUrl: CODEX_BASE_URL,
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["medium", "high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
contextWindow: CODEX_CONTEXT,
|
contextWindow: CODEX_CONTEXT,
|
||||||
|
|
@ -544,7 +538,6 @@ async function generateModels() {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: CODEX_BASE_URL,
|
baseUrl: CODEX_BASE_URL,
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["low", "medium", "high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
contextWindow: CODEX_CONTEXT,
|
contextWindow: CODEX_CONTEXT,
|
||||||
|
|
@ -999,9 +992,6 @@ export const MODELS = {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
output += `\t\t\treasoning: ${model.reasoning},\n`;
|
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\tinput: [${model.input.map(i => `"${i}"`).join(", ")}],\n`;
|
||||||
output += `\t\t\tcost: {\n`;
|
output += `\t\t\tcost: {\n`;
|
||||||
output += `\t\t\t\tinput: ${model.cost.input},\n`;
|
output += `\t\t\t\tinput: ${model.cost.input},\n`;
|
||||||
|
|
|
||||||
|
|
@ -2781,7 +2781,6 @@ export const MODELS = {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: "https://chatgpt.com/backend-api",
|
baseUrl: "https://chatgpt.com/backend-api",
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["medium","high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 0,
|
input: 0,
|
||||||
|
|
@ -2816,7 +2815,6 @@ export const MODELS = {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: "https://chatgpt.com/backend-api",
|
baseUrl: "https://chatgpt.com/backend-api",
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["low","medium","high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 0,
|
input: 0,
|
||||||
|
|
@ -2834,7 +2832,6 @@ export const MODELS = {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: "https://chatgpt.com/backend-api",
|
baseUrl: "https://chatgpt.com/backend-api",
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["medium","high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 0,
|
input: 0,
|
||||||
|
|
@ -2920,7 +2917,6 @@ export const MODELS = {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: "https://chatgpt.com/backend-api",
|
baseUrl: "https://chatgpt.com/backend-api",
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["low","medium","high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 0,
|
input: 0,
|
||||||
|
|
@ -2938,7 +2934,6 @@ export const MODELS = {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: "https://chatgpt.com/backend-api",
|
baseUrl: "https://chatgpt.com/backend-api",
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["low","medium","high","xhigh"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 0,
|
input: 0,
|
||||||
|
|
@ -2956,7 +2951,6 @@ export const MODELS = {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: "https://chatgpt.com/backend-api",
|
baseUrl: "https://chatgpt.com/backend-api",
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["medium","high"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 0,
|
input: 0,
|
||||||
|
|
@ -2991,7 +2985,6 @@ export const MODELS = {
|
||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
baseUrl: "https://chatgpt.com/backend-api",
|
baseUrl: "https://chatgpt.com/backend-api",
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevels: ["low","medium","high","xhigh"],
|
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 0,
|
input: 0,
|
||||||
|
|
@ -4056,7 +4049,7 @@ export const MODELS = {
|
||||||
cacheWrite: 0,
|
cacheWrite: 0,
|
||||||
},
|
},
|
||||||
contextWindow: 256000,
|
contextWindow: 256000,
|
||||||
maxTokens: 32768,
|
maxTokens: 128000,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"meta-llama/llama-3-70b-instruct": {
|
"meta-llama/llama-3-70b-instruct": {
|
||||||
id: "meta-llama/llama-3-70b-instruct",
|
id: "meta-llama/llama-3-70b-instruct",
|
||||||
|
|
@ -6676,6 +6669,23 @@ export const MODELS = {
|
||||||
contextWindow: 163840,
|
contextWindow: 163840,
|
||||||
maxTokens: 65536,
|
maxTokens: 65536,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"tngtech/tng-r1t-chimera:free": {
|
||||||
|
id: "tngtech/tng-r1t-chimera:free",
|
||||||
|
name: "TNG: R1T Chimera (free)",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "openrouter",
|
||||||
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
reasoning: true,
|
||||||
|
input: ["text"],
|
||||||
|
cost: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 163840,
|
||||||
|
maxTokens: 65536,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"x-ai/grok-3": {
|
"x-ai/grok-3": {
|
||||||
id: "x-ai/grok-3",
|
id: "x-ai/grok-3",
|
||||||
name: "xAI: Grok 3",
|
name: "xAI: Grok 3",
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,13 @@
|
||||||
import { MODELS } from "./models.generated.js";
|
import { MODELS } from "./models.generated.js";
|
||||||
import type { Api, KnownProvider, Model, ReasoningEffort, Usage } from "./types.js";
|
import type { Api, KnownProvider, Model, Usage } from "./types.js";
|
||||||
|
|
||||||
const modelRegistry: Map<string, Map<string, Model<Api>>> = new Map();
|
const modelRegistry: Map<string, Map<string, Model<Api>>> = new Map();
|
||||||
|
|
||||||
const CODEX_THINKING_SUFFIXES = ["-none", "-minimal", "-low", "-medium", "-high", "-xhigh"];
|
|
||||||
const CODEX_THINKING_LEVELS: Record<string, ReasoningEffort[]> = {
|
|
||||||
"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<TApi extends Api>(model: Model<TApi>): Model<TApi> {
|
|
||||||
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
|
// Initialize registry from MODELS on module load
|
||||||
for (const [provider, models] of Object.entries(MODELS)) {
|
for (const [provider, models] of Object.entries(MODELS)) {
|
||||||
const providerModels = new Map<string, Model<Api>>();
|
const providerModels = new Map<string, Model<Api>>();
|
||||||
for (const [id, model] of Object.entries(models)) {
|
for (const [id, model] of Object.entries(models)) {
|
||||||
const typedModel = model as Model<Api>;
|
providerModels.set(id, model as Model<Api>);
|
||||||
if (provider === "openai-codex" && isCodexThinkingVariant(typedModel.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
providerModels.set(id, applyCodexThinkingLevels(typedModel));
|
|
||||||
}
|
}
|
||||||
modelRegistry.set(provider, providerModels);
|
modelRegistry.set(provider, providerModels);
|
||||||
}
|
}
|
||||||
|
|
@ -59,16 +22,7 @@ export function getModel<TProvider extends KnownProvider, TModelId extends keyof
|
||||||
modelId: TModelId,
|
modelId: TModelId,
|
||||||
): Model<ModelApi<TProvider, TModelId>> {
|
): Model<ModelApi<TProvider, TModelId>> {
|
||||||
const providerModels = modelRegistry.get(provider);
|
const providerModels = modelRegistry.get(provider);
|
||||||
const direct = providerModels?.get(modelId as string);
|
return providerModels?.get(modelId as string) as Model<ModelApi<TProvider, TModelId>>;
|
||||||
if (direct) return direct as Model<ModelApi<TProvider, TModelId>>;
|
|
||||||
if (provider === "openai-codex") {
|
|
||||||
const normalized = normalizeCodexModelId(modelId as string);
|
|
||||||
const normalizedModel = providerModels?.get(normalized);
|
|
||||||
if (normalizedModel) {
|
|
||||||
return normalizedModel as Model<ModelApi<TProvider, TModelId>>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return direct as unknown as Model<ModelApi<TProvider, TModelId>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProviders(): KnownProvider[] {
|
export function getProviders(): KnownProvider[] {
|
||||||
|
|
@ -96,12 +50,9 @@ const XHIGH_MODELS = new Set(["gpt-5.1-codex-max", "gpt-5.2", "gpt-5.2-codex"]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a model supports xhigh thinking level.
|
* Check if a model supports xhigh thinking level.
|
||||||
* Currently only certain OpenAI models support this.
|
* Currently only certain OpenAI Codex models support this.
|
||||||
*/
|
*/
|
||||||
export function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean {
|
export function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean {
|
||||||
if (model.thinkingLevels) {
|
|
||||||
return model.thinkingLevels.includes("xhigh");
|
|
||||||
}
|
|
||||||
return XHIGH_MODELS.has(model.id);
|
return XHIGH_MODELS.has(model.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import type {
|
||||||
KnownProvider,
|
KnownProvider,
|
||||||
Model,
|
Model,
|
||||||
OptionsForApi,
|
OptionsForApi,
|
||||||
ReasoningEffort,
|
|
||||||
SimpleStreamOptions,
|
SimpleStreamOptions,
|
||||||
|
ThinkingLevel,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
const VERTEX_ADC_CREDENTIALS_PATH = join(homedir(), ".config", "gcloud", "application_default_credentials.json");
|
const VERTEX_ADC_CREDENTIALS_PATH = join(homedir(), ".config", "gcloud", "application_default_credentials.json");
|
||||||
|
|
@ -180,7 +180,7 @@ function mapOptionsForApi<TApi extends Api>(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to clamp xhigh to high for providers that don't support it
|
// Helper to clamp xhigh to high for providers that don't support it
|
||||||
const clampReasoning = (effort: ReasoningEffort | undefined) => (effort === "xhigh" ? "high" : effort);
|
const clampReasoning = (effort: ThinkingLevel | undefined) => (effort === "xhigh" ? "high" : effort);
|
||||||
|
|
||||||
switch (model.api) {
|
switch (model.api) {
|
||||||
case "anthropic-messages": {
|
case "anthropic-messages": {
|
||||||
|
|
@ -286,7 +286,7 @@ function mapOptionsForApi<TApi extends Api>(
|
||||||
// Models using thinkingBudget (Gemini 2.x, Claude via Antigravity)
|
// Models using thinkingBudget (Gemini 2.x, Claude via Antigravity)
|
||||||
// Claude requires max_tokens > thinking.budget_tokens
|
// Claude requires max_tokens > thinking.budget_tokens
|
||||||
// So we need to ensure maxTokens accounts for both thinking and output
|
// So we need to ensure maxTokens accounts for both thinking and output
|
||||||
const budgets: Record<ClampedReasoningEffort, number> = {
|
const budgets: Record<ClampedThinkingLevel, number> = {
|
||||||
minimal: 1024,
|
minimal: 1024,
|
||||||
low: 2048,
|
low: 2048,
|
||||||
medium: 8192,
|
medium: 8192,
|
||||||
|
|
@ -350,7 +350,7 @@ function mapOptionsForApi<TApi extends Api>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClampedReasoningEffort = Exclude<ReasoningEffort, "xhigh">;
|
type ClampedThinkingLevel = Exclude<ThinkingLevel, "xhigh">;
|
||||||
|
|
||||||
function isGemini3ProModel(model: Model<"google-generative-ai">): boolean {
|
function isGemini3ProModel(model: Model<"google-generative-ai">): boolean {
|
||||||
// Covers gemini-3-pro, gemini-3-pro-preview, and possible other prefixed ids in the future
|
// Covers gemini-3-pro, gemini-3-pro-preview, and possible other prefixed ids in the future
|
||||||
|
|
@ -363,7 +363,7 @@ function isGemini3FlashModel(model: Model<"google-generative-ai">): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGemini3ThinkingLevel(
|
function getGemini3ThinkingLevel(
|
||||||
effort: ClampedReasoningEffort,
|
effort: ClampedThinkingLevel,
|
||||||
model: Model<"google-generative-ai">,
|
model: Model<"google-generative-ai">,
|
||||||
): GoogleThinkingLevel {
|
): GoogleThinkingLevel {
|
||||||
if (isGemini3ProModel(model)) {
|
if (isGemini3ProModel(model)) {
|
||||||
|
|
@ -390,7 +390,7 @@ function getGemini3ThinkingLevel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGeminiCliThinkingLevel(effort: ClampedReasoningEffort, modelId: string): GoogleThinkingLevel {
|
function getGeminiCliThinkingLevel(effort: ClampedThinkingLevel, modelId: string): GoogleThinkingLevel {
|
||||||
if (modelId.includes("3-pro")) {
|
if (modelId.includes("3-pro")) {
|
||||||
// Gemini 3 Pro only supports LOW/HIGH (for now)
|
// Gemini 3 Pro only supports LOW/HIGH (for now)
|
||||||
switch (effort) {
|
switch (effort) {
|
||||||
|
|
@ -415,10 +415,10 @@ function getGeminiCliThinkingLevel(effort: ClampedReasoningEffort, modelId: stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGoogleBudget(model: Model<"google-generative-ai">, effort: ClampedReasoningEffort): number {
|
function getGoogleBudget(model: Model<"google-generative-ai">, effort: ClampedThinkingLevel): number {
|
||||||
// See https://ai.google.dev/gemini-api/docs/thinking#set-budget
|
// See https://ai.google.dev/gemini-api/docs/thinking#set-budget
|
||||||
if (model.id.includes("2.5-pro")) {
|
if (model.id.includes("2.5-pro")) {
|
||||||
const budgets: Record<ClampedReasoningEffort, number> = {
|
const budgets: Record<ClampedThinkingLevel, number> = {
|
||||||
minimal: 128,
|
minimal: 128,
|
||||||
low: 2048,
|
low: 2048,
|
||||||
medium: 8192,
|
medium: 8192,
|
||||||
|
|
@ -429,7 +429,7 @@ function getGoogleBudget(model: Model<"google-generative-ai">, effort: ClampedRe
|
||||||
|
|
||||||
if (model.id.includes("2.5-flash")) {
|
if (model.id.includes("2.5-flash")) {
|
||||||
// Covers 2.5-flash-lite as well
|
// Covers 2.5-flash-lite as well
|
||||||
const budgets: Record<ClampedReasoningEffort, number> = {
|
const budgets: Record<ClampedThinkingLevel, number> = {
|
||||||
minimal: 128,
|
minimal: 128,
|
||||||
low: 2048,
|
low: 2048,
|
||||||
medium: 8192,
|
medium: 8192,
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export type KnownProvider =
|
||||||
| "mistral";
|
| "mistral";
|
||||||
export type Provider = KnownProvider | string;
|
export type Provider = KnownProvider | string;
|
||||||
|
|
||||||
export type ReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh";
|
export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||||
|
|
||||||
// Base options all providers share
|
// Base options all providers share
|
||||||
export interface StreamOptions {
|
export interface StreamOptions {
|
||||||
|
|
@ -68,7 +68,7 @@ export interface StreamOptions {
|
||||||
|
|
||||||
// Unified options with reasoning passed to streamSimple() and completeSimple()
|
// Unified options with reasoning passed to streamSimple() and completeSimple()
|
||||||
export interface SimpleStreamOptions extends StreamOptions {
|
export interface SimpleStreamOptions extends StreamOptions {
|
||||||
reasoning?: ReasoningEffort;
|
reasoning?: ThinkingLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic StreamFunction with typed options
|
// Generic StreamFunction with typed options
|
||||||
|
|
@ -210,8 +210,6 @@ export interface Model<TApi extends Api> {
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
reasoning: boolean;
|
reasoning: boolean;
|
||||||
/** Supported reasoning levels for this model (excluding "off"). */
|
|
||||||
thinkingLevels?: ReasoningEffort[];
|
|
||||||
input: ("text" | "image")[];
|
input: ("text" | "image")[];
|
||||||
cost: {
|
cost: {
|
||||||
input: number; // $/million tokens
|
input: number; // $/million tokens
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,7 @@
|
||||||
|
|
||||||
- OAuth login UI now uses dedicated dialog component with consistent borders
|
- 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)
|
- Assume truecolor support for all terminals except `dumb`, empty, or `linux` (fixes colors over SSH)
|
||||||
|
- OpenAI Codex clean-up: removed per-thinking-level model variants, thinking level is now set separately and the provider clamps to what each model supports internally (initial implementation in [#472](https://github.com/badlogic/pi-mono/pull/472) by [@ben-vargas](https://github.com/ben-vargas))
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Thinking level availability now reflects per-model supported reasoning levels.
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
@ -24,7 +21,6 @@
|
||||||
- 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))
|
- 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))
|
- 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))
|
- 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
|
## [0.36.0] - 2026-01-05
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1007,16 +1007,10 @@ export class AgentSession {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available thinking levels for current model.
|
* Get available thinking levels for current model.
|
||||||
|
* The provider will clamp to what the specific model supports internally.
|
||||||
*/
|
*/
|
||||||
getAvailableThinkingLevels(): ThinkingLevel[] {
|
getAvailableThinkingLevels(): ThinkingLevel[] {
|
||||||
if (!this.supportsThinking()) return ["off"];
|
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;
|
return this.supportsXhighThinking() ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,6 @@ import type { AuthStorage } from "./auth-storage.js";
|
||||||
|
|
||||||
const Ajv = (AjvModule as any).default || AjvModule;
|
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
|
// Schema for OpenAI compatibility settings
|
||||||
const OpenAICompatSchema = Type.Object({
|
const OpenAICompatSchema = Type.Object({
|
||||||
supportsStore: Type.Optional(Type.Boolean()),
|
supportsStore: Type.Optional(Type.Boolean()),
|
||||||
|
|
@ -50,7 +40,6 @@ const ModelDefinitionSchema = Type.Object({
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
reasoning: Type.Boolean(),
|
reasoning: Type.Boolean(),
|
||||||
thinkingLevels: Type.Optional(ThinkingLevelsSchema),
|
|
||||||
input: Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])),
|
input: Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])),
|
||||||
cost: Type.Object({
|
cost: Type.Object({
|
||||||
input: Type.Number(),
|
input: Type.Number(),
|
||||||
|
|
@ -118,14 +107,6 @@ function resolveApiKeyConfig(keyConfig: string): string | undefined {
|
||||||
return keyConfig;
|
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.
|
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
||||||
*/
|
*/
|
||||||
|
|
@ -349,7 +330,6 @@ export class ModelRegistry {
|
||||||
provider: providerName,
|
provider: providerName,
|
||||||
baseUrl: providerConfig.baseUrl!,
|
baseUrl: providerConfig.baseUrl!,
|
||||||
reasoning: modelDef.reasoning,
|
reasoning: modelDef.reasoning,
|
||||||
thinkingLevels: modelDef.thinkingLevels,
|
|
||||||
input: modelDef.input as ("text" | "image")[],
|
input: modelDef.input as ("text" | "image")[],
|
||||||
cost: modelDef.cost,
|
cost: modelDef.cost,
|
||||||
contextWindow: modelDef.contextWindow,
|
contextWindow: modelDef.contextWindow,
|
||||||
|
|
@ -383,15 +363,7 @@ export class ModelRegistry {
|
||||||
* Find a model by provider and ID.
|
* Find a model by provider and ID.
|
||||||
*/
|
*/
|
||||||
find(provider: string, modelId: string): Model<Api> | undefined {
|
find(provider: string, modelId: string): Model<Api> | undefined {
|
||||||
const exact = this.models.find((m) => m.provider === provider && m.id === modelId);
|
return 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -102,18 +102,6 @@ export interface ParsedModelResult {
|
||||||
warning: string | undefined;
|
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.
|
* Parse a pattern to extract model and thinking level.
|
||||||
* Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix).
|
* Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix).
|
||||||
|
|
@ -134,14 +122,6 @@ export function parseModelPattern(pattern: string, availableModels: Model<Api>[]
|
||||||
return { model: exactMatch, thinkingLevel: "off", warning: undefined };
|
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
|
// No match - try splitting on last colon if present
|
||||||
const lastColonIndex = pattern.lastIndexOf(":");
|
const lastColonIndex = pattern.lastIndexOf(":");
|
||||||
if (lastColonIndex === -1) {
|
if (lastColonIndex === -1) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue