Add xhigh thinking level for OpenAI codex-max models

- Add 'xhigh' to ThinkingLevel type in ai and agent packages
- Map xhigh to reasoning_effort: 'max' for OpenAI providers
- Add thinkingXhigh color token to theme schema and built-in themes
- Show xhigh option only when using codex-max models
- Update CHANGELOG for both ai and coding-agent packages

closes #143
This commit is contained in:
Mario Zechner 2025-12-08 21:12:54 +01:00
parent 87a1a9ded4
commit 00370cab39
19 changed files with 300 additions and 54 deletions

View file

@ -29,7 +29,7 @@ import { transformMessages } from "./transorm-messages.js";
export interface OpenAICompletionsOptions extends StreamOptions {
toolChoice?: "auto" | "none" | "required" | { type: "function"; function: { name: string } };
reasoningEffort?: "minimal" | "low" | "medium" | "high";
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
}
export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (

View file

@ -32,7 +32,7 @@ import { transformMessages } from "./transorm-messages.js";
// OpenAI Responses-specific options
export interface OpenAIResponsesOptions extends StreamOptions {
reasoningEffort?: "minimal" | "low" | "medium" | "high";
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
reasoningSummary?: "auto" | "detailed" | "concise" | null;
}
@ -158,7 +158,10 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
else if (event.type === "response.content_part.added") {
if (currentItem && currentItem.type === "message") {
currentItem.content = currentItem.content || [];
currentItem.content.push(event.part);
// Filter out ReasoningText, only accept output_text and refusal
if (event.part.type === "output_text" || event.part.type === "refusal") {
currentItem.content.push(event.part);
}
}
} else if (event.type === "response.output_text.delta") {
if (currentItem && currentItem.type === "message" && currentBlock && currentBlock.type === "text") {

View file

@ -122,6 +122,9 @@ function mapOptionsForApi<TApi extends Api>(
apiKey: apiKey || options?.apiKey,
};
// Helper to clamp xhigh to high for providers that don't support it
const clampReasoning = (effort: ReasoningEffort | undefined) => (effort === "xhigh" ? "high" : effort);
switch (model.api) {
case "anthropic-messages": {
if (!options?.reasoning) return base satisfies AnthropicOptions;
@ -136,7 +139,7 @@ function mapOptionsForApi<TApi extends Api>(
return {
...base,
thinkingEnabled: true,
thinkingBudgetTokens: anthropicBudgets[options.reasoning],
thinkingBudgetTokens: anthropicBudgets[clampReasoning(options.reasoning)!],
} satisfies AnthropicOptions;
}
@ -155,7 +158,10 @@ function mapOptionsForApi<TApi extends Api>(
case "google-generative-ai": {
if (!options?.reasoning) return base as any;
const googleBudget = getGoogleBudget(model as Model<"google-generative-ai">, options.reasoning);
const googleBudget = getGoogleBudget(
model as Model<"google-generative-ai">,
clampReasoning(options.reasoning)!,
);
return {
...base,
thinking: {
@ -173,10 +179,12 @@ function mapOptionsForApi<TApi extends Api>(
}
}
function getGoogleBudget(model: Model<"google-generative-ai">, effort: ReasoningEffort): number {
type ClampedReasoningEffort = Exclude<ReasoningEffort, "xhigh">;
function getGoogleBudget(model: Model<"google-generative-ai">, effort: ClampedReasoningEffort): number {
// See https://ai.google.dev/gemini-api/docs/thinking#set-budget
if (model.id.includes("2.5-pro")) {
const budgets = {
const budgets: Record<ClampedReasoningEffort, number> = {
minimal: 128,
low: 2048,
medium: 8192,
@ -187,7 +195,7 @@ function getGoogleBudget(model: Model<"google-generative-ai">, effort: Reasoning
if (model.id.includes("2.5-flash")) {
// Covers 2.5-flash-lite as well
const budgets = {
const budgets: Record<ClampedReasoningEffort, number> = {
minimal: 128,
low: 2048,
medium: 8192,

View file

@ -29,7 +29,7 @@ export type OptionsForApi<TApi extends Api> = ApiOptionsMap[TApi];
export type KnownProvider = "anthropic" | "google" | "openai" | "xai" | "groq" | "cerebras" | "openrouter" | "zai";
export type Provider = KnownProvider | string;
export type ReasoningEffort = "minimal" | "low" | "medium" | "high";
export type ReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh";
// Base options all providers share
export interface StreamOptions {