feat(ai): add max thinking level

This commit is contained in:
Mario Zechner
2026-07-09 22:30:53 +02:00
parent 8973ae28ab
commit fbdd46389c
61 changed files with 441 additions and 168 deletions
@@ -35,7 +35,8 @@ const THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {
low: "Light reasoning (~2k tokens)",
medium: "Moderate reasoning (~8k tokens)",
high: "Deep reasoning (~16k tokens)",
xhigh: "Maximum reasoning (~32k tokens)",
xhigh: "Extra-high reasoning (~32k tokens)",
max: "Maximum reasoning",
};
const DEFAULT_PROJECT_TRUST_LABELS: Record<DefaultProjectTrust, string> = {
@@ -14,7 +14,8 @@ const LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {
low: "Light reasoning (~2k tokens)",
medium: "Moderate reasoning (~8k tokens)",
high: "Deep reasoning (~16k tokens)",
xhigh: "Maximum reasoning (~32k tokens)",
xhigh: "Extra-high reasoning (~32k tokens)",
max: "Maximum reasoning",
};
/**
@@ -75,6 +75,7 @@
"thinkingMedium": "#81a2be",
"thinkingHigh": "#b294bb",
"thinkingXhigh": "#d183e8",
"thinkingMax": "#ff5fff",
"bashMode": "green"
},
@@ -74,6 +74,7 @@
"thinkingMedium": "teal",
"thinkingHigh": "#875f87",
"thinkingXhigh": "#8b008b",
"thinkingMax": "#af005f",
"bashMode": "green"
},
@@ -34,7 +34,7 @@
},
"colors": {
"type": "object",
"description": "Theme color definitions (all required)",
"description": "Theme color definitions (thinkingMax is optional and falls back to thinkingXhigh)",
"required": [
"accent",
"border",
@@ -287,7 +287,11 @@
},
"thinkingXhigh": {
"$ref": "#/$defs/colorValue",
"description": "Thinking level border: xhigh (OpenAI codex-max only)"
"description": "Thinking level border: xhigh"
},
"thinkingMax": {
"$ref": "#/$defs/colorValue",
"description": "Thinking level border: max (falls back to thinkingXhigh when omitted)"
},
"bashMode": {
"$ref": "#/$defs/colorValue",
@@ -1,5 +1,6 @@
import * as fs from "node:fs";
import * as path from "node:path";
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
import {
type EditorTheme,
getCapabilities,
@@ -88,6 +89,7 @@ const ThemeJsonSchema = Type.Object({
thinkingMedium: ColorValueSchema,
thinkingHigh: ColorValueSchema,
thinkingXhigh: ColorValueSchema,
thinkingMax: Type.Optional(ColorValueSchema),
// Bash Mode (1 color)
bashMode: ColorValueSchema,
}),
@@ -149,6 +151,7 @@ export type ThemeColor =
| "thinkingMedium"
| "thinkingHigh"
| "thinkingXhigh"
| "thinkingMax"
| "bashMode";
export type ThemeBg =
@@ -316,6 +319,10 @@ function resolveThemeColors<T extends Record<string, ColorValue>>(
return resolved as Record<keyof T, string | number>;
}
function withThemeColorFallbacks(colors: ThemeJson["colors"]): ThemeJson["colors"] & { thinkingMax: ColorValue } {
return { ...colors, thinkingMax: colors.thinkingMax ?? colors.thinkingXhigh };
}
// ============================================================================
// Theme Class
// ============================================================================
@@ -339,7 +346,8 @@ export class Theme {
this.sourceInfo = options.sourceInfo;
this.mode = mode;
this.fgColors = new Map();
for (const [key, value] of Object.entries(fgColors) as [ThemeColor, string | number][]) {
const colors = { ...fgColors, thinkingMax: fgColors.thinkingMax ?? fgColors.thinkingXhigh };
for (const [key, value] of Object.entries(colors) as [ThemeColor, string | number][]) {
this.fgColors.set(key, fgAnsi(value, mode));
}
this.bgColors = new Map();
@@ -396,7 +404,7 @@ export class Theme {
return this.mode;
}
getThinkingBorderColor(level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"): (str: string) => string {
getThinkingBorderColor(level: ThinkingLevel): (str: string) => string {
// Map thinking levels to dedicated theme colors
switch (level) {
case "off":
@@ -411,6 +419,8 @@ export class Theme {
return (str: string) => this.fg("thinkingHigh", str);
case "xhigh":
return (str: string) => this.fg("thinkingXhigh", str);
case "max":
return (str: string) => this.fg("thinkingMax", str);
default:
return (str: string) => this.fg("thinkingOff", str);
}
@@ -586,7 +596,7 @@ function loadThemeJson(name: string): ThemeJson {
function createTheme(themeJson: ThemeJson, mode?: ColorMode, sourcePath?: string): Theme {
const colorMode = mode ?? (getCapabilities().trueColor ? "truecolor" : "256color");
const resolvedColors = resolveThemeColors(themeJson.colors, themeJson.vars);
const resolvedColors = resolveThemeColors(withThemeColorFallbacks(themeJson.colors), themeJson.vars);
const fgColors: Record<ThemeColor, string | number> = {} as Record<ThemeColor, string | number>;
const bgColors: Record<ThemeBg, string | number> = {} as Record<ThemeBg, string | number>;
const bgColorKeys: Set<string> = new Set([
@@ -1013,7 +1023,7 @@ export function getResolvedThemeColors(themeName?: string): Record<string, strin
const name = themeName ?? currentThemeName ?? getDefaultTheme();
const isLight = name === "light";
const themeJson = loadThemeJson(name);
const resolved = resolveThemeColors(themeJson.colors, themeJson.vars);
const resolved = resolveThemeColors(withThemeColorFallbacks(themeJson.colors), themeJson.vars);
// Default text color for empty values (terminal uses default fg color)
const defaultText = isLight ? "#000000" : "#e5e5e7";