refactor: migrated thinking configuration to centralized pi-ai module
- Extracted thinking module with ThinkingEffort, ThinkingLevel, and ThinkingMode types to centralize reasoning configuration across packages. - Migrated ThinkingLevel type from pi-agent-core to pi-ai package with new validation functions parseThinkingLevel() and getAvailableThinkingLevel(). - Consolidated thinking level constants and descriptions into reusable exports (ALL_THINKING_LEVELS, THINKING_MODE_DESCRIPTIONS) for consistent UI display. - Removed local thinking-effort-label utility and replaced formatThinkingEffortLabel() with centralized formatThinking() function from pi-ai. - Refactored thinking mode handling to distinguish ThinkingSelector (user-facing with 'off' option) from ThinkingEffort (provider-level).
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
/**
|
||||
* Agent class that uses the agent-loop directly.
|
||||
/** Agent class that uses the agent-loop directly.
|
||||
* No transport abstraction - calls streamSimple via the loop.
|
||||
*/
|
||||
import {
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
streamSimple,
|
||||
type TextContent,
|
||||
type ThinkingBudgets,
|
||||
type ThinkingLevel,
|
||||
type ToolChoice,
|
||||
type ToolResultMessage,
|
||||
} from "@oh-my-pi/pi-ai";
|
||||
@@ -27,7 +27,6 @@ import type {
|
||||
AgentTool,
|
||||
AgentToolContext,
|
||||
StreamFn,
|
||||
ThinkingLevel,
|
||||
ToolCallContext,
|
||||
} from "./types";
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
SimpleStreamOptions,
|
||||
streamSimple,
|
||||
TextContent,
|
||||
ThinkingLevel,
|
||||
Tool,
|
||||
ToolChoice,
|
||||
ToolResultMessage,
|
||||
@@ -139,12 +140,6 @@ export interface ToolCallContext {
|
||||
toolCalls: Array<{ id: string; name: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thinking/reasoning level for models that support it.
|
||||
* Note: "xhigh" is only supported by OpenAI gpt-5.1-codex-max, gpt-5.2, gpt-5.2-codex, gpt-5.3, and gpt-5.3-codex models.
|
||||
*/
|
||||
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
|
||||
/**
|
||||
* Extensible interface for custom app messages.
|
||||
* Apps can extend via declaration merging:
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
|
||||
- Exported new thinking module with `ThinkingEffort`, `ThinkingLevel`, and `ThinkingMode` types for managing reasoning effort levels
|
||||
- Added `getAvailableThinkingEffort()` function to determine supported thinking effort levels based on model capabilities
|
||||
- Added `parseThinkingEffort()`, `parseThinkingLevel()`, and `parseThinkingMode()` functions for parsing thinking configuration strings
|
||||
- Added `THINKING_LEVELS`, `ALL_THINKING_LEVELS`, and `ALL_THINKING_MODES` constants for iterating over available thinking options
|
||||
- Added `THINKING_MODE_DESCRIPTIONS` and `THINKING_MODE_LABELS` for displaying thinking modes in user interfaces
|
||||
- Added `formatThinking()` function to format thinking modes as compact display labels
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactored thinking level handling to distinguish between `ThinkingEffort` (provider-level, no "off") and `ThinkingLevel` (user-facing, includes "off")
|
||||
- Updated `ThinkingBudgets` type to use `ThinkingEffort` instead of `ThinkingLevel` for more precise token budget configuration
|
||||
- Improved reasoning option handling to explicitly support "off" value for disabling reasoning across all providers
|
||||
- Simplified thinking effort mapping logic by centralizing provider-specific clamping behavior
|
||||
|
||||
## [13.7.8] - 2026-03-04
|
||||
|
||||
@@ -9,6 +24,7 @@
|
||||
- Added ZenMux provider support with mixed API routing: Anthropic-owned models discovered from `https://zenmux.ai/api/v1/models` now use the Anthropic transport (`https://zenmux.ai/api/anthropic`), while other ZenMux models use the OpenAI-compatible transport.
|
||||
|
||||
## [13.7.7] - 2026-03-04
|
||||
|
||||
### Changed
|
||||
|
||||
- Modified response ID normalization to preserve existing item ID prefixes when truncating oversized IDs
|
||||
@@ -19,6 +35,7 @@
|
||||
- Fixed handling of reasoning item IDs to remain untouched during response normalization while function call IDs are properly normalized
|
||||
|
||||
## [13.7.2] - 2026-03-04
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for Kagi API key authentication via `login kagi` command
|
||||
@@ -31,6 +48,7 @@
|
||||
- Tool schema compilation is now cached per schema identity, eliminating redundant recompilation on every tool call
|
||||
|
||||
## [13.6.0] - 2026-03-03
|
||||
|
||||
### Added
|
||||
|
||||
- Added Anthropic Foundry gateway mode controlled by `CLAUDE_CODE_USE_FOUNDRY`, with support for `FOUNDRY_BASE_URL`, `ANTHROPIC_FOUNDRY_API_KEY`, `ANTHROPIC_CUSTOM_HEADERS`, and optional mTLS material (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS`)
|
||||
@@ -43,6 +61,7 @@
|
||||
- Anthropic auth base-URL fallback now prefers `FOUNDRY_BASE_URL` when `CLAUDE_CODE_USE_FOUNDRY` is enabled
|
||||
|
||||
## [13.5.8] - 2026-03-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed schema compatibility issue where patternProperties in tool parameters caused failures when converting to legacy Antigravity format
|
||||
@@ -59,6 +78,7 @@
|
||||
- Anthropic cache-control normalization now removes later `ttl: "1h"` entries when a default/5m block has already appeared earlier in evaluation order
|
||||
|
||||
## [13.5.3] - 2026-03-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed tool argument coercion to handle malformed JSON with trailing wrapper braces by parsing leading JSON containers
|
||||
|
||||
@@ -21,6 +21,7 @@ export * from "./providers/openai-responses";
|
||||
export * from "./providers/synthetic";
|
||||
export * from "./rate-limit-utils";
|
||||
export * from "./stream";
|
||||
export * from "./thinking";
|
||||
export * from "./types";
|
||||
export * from "./usage";
|
||||
export * from "./usage/claude";
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { $env } from "@oh-my-pi/pi-utils";
|
||||
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
||||
import { calculateCost } from "../models";
|
||||
import type { ThinkingEffort, ThinkingLevel } from "../thinking";
|
||||
import type {
|
||||
Api,
|
||||
AssistantMessage,
|
||||
@@ -35,7 +36,6 @@ import type {
|
||||
TextContent,
|
||||
ThinkingBudgets,
|
||||
ThinkingContent,
|
||||
ThinkingLevel,
|
||||
Tool,
|
||||
ToolCall,
|
||||
ToolResultMessage,
|
||||
@@ -622,14 +622,15 @@ function buildAdditionalModelRequestFields(
|
||||
model: Model<"bedrock-converse-stream">,
|
||||
options: BedrockOptions,
|
||||
): Record<string, any> | undefined {
|
||||
if (!options.reasoning || !model.reasoning) {
|
||||
const reasoning = options.reasoning;
|
||||
if (!reasoning || !model.reasoning || reasoning === "off") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (model.id.includes("anthropic.claude")) {
|
||||
// Opus 4.6+ / Sonnet 4.6+ uses adaptive thinking with effort levels
|
||||
if (supportsAdaptiveThinking(model.id)) {
|
||||
let effort = mapThinkingLevelToEffort(options.reasoning);
|
||||
let effort = mapThinkingLevelToEffort(reasoning);
|
||||
// "max" effort is only supported on Opus 4.6; clamp to "high" for Sonnet 4.6
|
||||
const supportsMax = model.id.includes("opus-4-6") || model.id.includes("opus-4.6");
|
||||
if (effort === "max" && !supportsMax) {
|
||||
@@ -642,7 +643,7 @@ function buildAdditionalModelRequestFields(
|
||||
return result;
|
||||
}
|
||||
|
||||
const defaultBudgets: Record<ThinkingLevel, number> = {
|
||||
const defaultBudgets: Record<ThinkingEffort, number> = {
|
||||
minimal: 1024,
|
||||
low: 2048,
|
||||
medium: 8192,
|
||||
@@ -651,8 +652,8 @@ function buildAdditionalModelRequestFields(
|
||||
};
|
||||
|
||||
// Custom budgets override defaults (xhigh not in ThinkingBudgets, use high)
|
||||
const level = options.reasoning === "xhigh" ? "high" : options.reasoning;
|
||||
const budget = options.thinkingBudgets?.[level] ?? defaultBudgets[options.reasoning];
|
||||
const level = reasoning === "xhigh" ? "high" : reasoning;
|
||||
const budget = options.thinkingBudgets?.[level] ?? defaultBudgets[level];
|
||||
|
||||
const result: Record<string, any> = {
|
||||
thinking: {
|
||||
|
||||
+43
-58
@@ -21,6 +21,7 @@ import { streamOpenAICodexResponses } from "./providers/openai-codex-responses";
|
||||
import { type OpenAICompletionsOptions, streamOpenAICompletions } from "./providers/openai-completions";
|
||||
import { streamOpenAIResponses } from "./providers/openai-responses";
|
||||
import { isSyntheticModel, streamSynthetic } from "./providers/synthetic";
|
||||
import { getAvailableThinkingEffort, type ThinkingEffort } from "./thinking";
|
||||
import type {
|
||||
Api,
|
||||
AssistantMessage,
|
||||
@@ -31,7 +32,6 @@ import type {
|
||||
SimpleStreamOptions,
|
||||
StreamOptions,
|
||||
ThinkingBudgets,
|
||||
ThinkingLevel,
|
||||
ToolChoice,
|
||||
} from "./types";
|
||||
|
||||
@@ -303,7 +303,7 @@ const MIN_OUTPUT_TOKENS = 1024;
|
||||
export const OUTPUT_FALLBACK_BUFFER = 4000;
|
||||
const ANTHROPIC_USE_INTERLEAVED_THINKING = true;
|
||||
|
||||
export const ANTHROPIC_THINKING: Record<ThinkingLevel, number> = {
|
||||
export const ANTHROPIC_THINKING: Record<ThinkingEffort, number> = {
|
||||
minimal: 1024,
|
||||
low: 4096,
|
||||
medium: 8192,
|
||||
@@ -311,7 +311,7 @@ export const ANTHROPIC_THINKING: Record<ThinkingLevel, number> = {
|
||||
xhigh: 32768,
|
||||
};
|
||||
|
||||
const GOOGLE_THINKING: Record<ThinkingLevel, number> = {
|
||||
const GOOGLE_THINKING: Record<ThinkingEffort, number> = {
|
||||
minimal: 1024,
|
||||
low: 4096,
|
||||
medium: 8192,
|
||||
@@ -319,7 +319,7 @@ const GOOGLE_THINKING: Record<ThinkingLevel, number> = {
|
||||
xhigh: 24575,
|
||||
};
|
||||
|
||||
const BEDROCK_CLAUDE_THINKING: Record<ThinkingLevel, number> = {
|
||||
const BEDROCK_CLAUDE_THINKING: Record<ThinkingEffort, number> = {
|
||||
minimal: 1024,
|
||||
low: 2048,
|
||||
medium: 8192,
|
||||
@@ -330,8 +330,8 @@ const BEDROCK_CLAUDE_THINKING: Record<ThinkingLevel, number> = {
|
||||
function resolveBedrockThinkingBudget(
|
||||
model: Model<"bedrock-converse-stream">,
|
||||
options?: SimpleStreamOptions,
|
||||
): { budget: number; level: ThinkingLevel } | null {
|
||||
if (!options?.reasoning || !model.reasoning) return null;
|
||||
): { budget: number; level: ThinkingEffort } | null {
|
||||
if (!options?.reasoning || !model.reasoning || options.reasoning === "off") return null;
|
||||
if (!model.id.includes("anthropic.claude")) return null;
|
||||
const level = options.reasoning === "xhigh" ? "high" : options.reasoning;
|
||||
const budget = options.thinkingBudgets?.[level] ?? BEDROCK_CLAUDE_THINKING[level];
|
||||
@@ -358,7 +358,7 @@ export function mapAnthropicToolChoice(choice?: ToolChoice): AnthropicOptions["t
|
||||
/**
|
||||
* Map ThinkingLevel to Anthropic effort levels for adaptive thinking (Opus 4.6+)
|
||||
*/
|
||||
function mapThinkingLevelToAnthropicEffort(level: ThinkingLevel): AnthropicOptions["effort"] {
|
||||
function mapThinkingLevelToAnthropicEffort(level: ThinkingEffort, supportsXhigh: boolean): AnthropicOptions["effort"] {
|
||||
switch (level) {
|
||||
case "minimal":
|
||||
return "low";
|
||||
@@ -369,7 +369,7 @@ function mapThinkingLevelToAnthropicEffort(level: ThinkingLevel): AnthropicOptio
|
||||
case "high":
|
||||
return "high";
|
||||
case "xhigh":
|
||||
return "max";
|
||||
return supportsXhigh ? "max" : "high";
|
||||
default:
|
||||
return "high";
|
||||
}
|
||||
@@ -429,14 +429,11 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
execHandlers: options?.execHandlers,
|
||||
};
|
||||
|
||||
// Helper to clamp xhigh to high for providers that don't support it
|
||||
const clampReasoning = (effort: ThinkingLevel | undefined) => (effort === "xhigh" ? "high" : effort);
|
||||
|
||||
switch (model.api) {
|
||||
case "anthropic-messages": {
|
||||
// Explicitly disable thinking when reasoning is not specified
|
||||
const reasoning = options?.reasoning;
|
||||
if (!reasoning) {
|
||||
if (!reasoning || reasoning === "off") {
|
||||
return {
|
||||
...base,
|
||||
thinkingEnabled: false,
|
||||
@@ -462,9 +459,7 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
model.id.includes("sonnet-4.6")
|
||||
) {
|
||||
const supportsMaxEffort = model.id.includes("opus-4-6") || model.id.includes("opus-4.6");
|
||||
const effort = mapThinkingLevelToAnthropicEffort(
|
||||
supportsMaxEffort ? reasoning : (clampReasoning(reasoning) ?? reasoning),
|
||||
);
|
||||
const effort = mapThinkingLevelToAnthropicEffort(reasoning, supportsMaxEffort);
|
||||
return {
|
||||
...base,
|
||||
thinkingEnabled: true,
|
||||
@@ -535,28 +530,28 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
case "openai-completions":
|
||||
return {
|
||||
...base,
|
||||
reasoningEffort: supportsXhigh(model) ? options?.reasoning : clampReasoning(options?.reasoning),
|
||||
reasoningEffort: getAvailableThinkingEffort(supportsXhigh(model)),
|
||||
toolChoice: mapOpenAiToolChoice(options?.toolChoice),
|
||||
} as OptionsForApi<TApi>;
|
||||
|
||||
case "openai-responses":
|
||||
return {
|
||||
...base,
|
||||
reasoningEffort: supportsXhigh(model) ? options?.reasoning : clampReasoning(options?.reasoning),
|
||||
reasoningEffort: getAvailableThinkingEffort(supportsXhigh(model)),
|
||||
toolChoice: mapOpenAiToolChoice(options?.toolChoice),
|
||||
} as OptionsForApi<TApi>;
|
||||
|
||||
case "azure-openai-responses":
|
||||
return {
|
||||
...base,
|
||||
reasoningEffort: supportsXhigh(model) ? options?.reasoning : clampReasoning(options?.reasoning),
|
||||
reasoningEffort: getAvailableThinkingEffort(supportsXhigh(model)),
|
||||
toolChoice: mapOpenAiToolChoice(options?.toolChoice),
|
||||
} as OptionsForApi<TApi>;
|
||||
|
||||
case "openai-codex-responses":
|
||||
return {
|
||||
...base,
|
||||
reasoningEffort: supportsXhigh(model) ? options?.reasoning : clampReasoning(options?.reasoning),
|
||||
reasoningEffort: getAvailableThinkingEffort(supportsXhigh(model)),
|
||||
toolChoice: mapOpenAiToolChoice(options?.toolChoice),
|
||||
preferWebsockets: options?.preferWebsockets,
|
||||
} as OptionsForApi<TApi>;
|
||||
@@ -564,7 +559,8 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
case "google-generative-ai": {
|
||||
// Explicitly disable thinking when reasoning is not specified
|
||||
// This is needed because Gemini has "dynamic thinking" enabled by default
|
||||
if (!options?.reasoning) {
|
||||
const reasoning = options?.reasoning;
|
||||
if (!reasoning || reasoning === "off") {
|
||||
return {
|
||||
...base,
|
||||
thinking: { enabled: false },
|
||||
@@ -573,7 +569,7 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
}
|
||||
|
||||
const googleModel = model as Model<"google-generative-ai">;
|
||||
const effort = clampReasoning(options.reasoning)!;
|
||||
const effort = reasoning === "xhigh" ? "high" : reasoning;
|
||||
|
||||
// Gemini 3+ models use thinkingLevel exclusively instead of thinkingBudget.
|
||||
// https://ai.google.dev/gemini-api/docs/thinking#set-budget
|
||||
@@ -599,7 +595,8 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
}
|
||||
|
||||
case "google-gemini-cli": {
|
||||
if (!options?.reasoning) {
|
||||
const reasoning = options?.reasoning;
|
||||
if (!reasoning || reasoning === "off") {
|
||||
return {
|
||||
...base,
|
||||
thinking: { enabled: false },
|
||||
@@ -607,7 +604,7 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
} as OptionsForApi<TApi>;
|
||||
}
|
||||
|
||||
const effort = clampReasoning(options.reasoning)!;
|
||||
const effort = reasoning === "xhigh" ? "high" : reasoning;
|
||||
|
||||
// Gemini 3+ models use thinkingLevel instead of thinkingBudget
|
||||
if (isGemini3ProModelId(model.id) || isGemini3FlashModelId(model.id)) {
|
||||
@@ -650,7 +647,8 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
|
||||
case "google-vertex": {
|
||||
// Explicitly disable thinking when reasoning is not specified
|
||||
if (!options?.reasoning) {
|
||||
const reasoning = options?.reasoning;
|
||||
if (!reasoning || reasoning === "off") {
|
||||
return {
|
||||
...base,
|
||||
thinking: { enabled: false },
|
||||
@@ -659,7 +657,7 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
}
|
||||
|
||||
const vertexModel = model as Model<"google-vertex">;
|
||||
const effort = clampReasoning(options.reasoning)!;
|
||||
const effort = reasoning === "xhigh" ? "high" : reasoning;
|
||||
const geminiModel = vertexModel as unknown as Model<"google-generative-ai">;
|
||||
|
||||
if (isGemini3ProModel(geminiModel) || isGemini3FlashModel(geminiModel)) {
|
||||
@@ -698,8 +696,6 @@ function mapOptionsForApi<TApi extends Api>(
|
||||
}
|
||||
}
|
||||
|
||||
type ClampedThinkingLevel = Exclude<ThinkingLevel, "xhigh">;
|
||||
|
||||
function isGemini3ProModelId(modelId: string): boolean {
|
||||
return /3(?:\.\d+)?-pro/.test(modelId);
|
||||
}
|
||||
@@ -718,18 +714,14 @@ function isGemini3FlashModel(model: Model<"google-generative-ai">): boolean {
|
||||
return isGemini3FlashModelId(model.id);
|
||||
}
|
||||
|
||||
function getGemini3ThinkingLevel(
|
||||
effort: ClampedThinkingLevel,
|
||||
model: Model<"google-generative-ai">,
|
||||
): GoogleThinkingLevel {
|
||||
function getGemini3ThinkingLevel(effort: ThinkingEffort, model: Model<"google-generative-ai">): GoogleThinkingLevel {
|
||||
if (isGemini3ProModel(model)) {
|
||||
// Gemini 3 Pro only supports LOW/HIGH (for now)
|
||||
switch (effort) {
|
||||
case "minimal":
|
||||
case "low":
|
||||
return "LOW";
|
||||
case "medium":
|
||||
case "high":
|
||||
default:
|
||||
return "HIGH";
|
||||
}
|
||||
}
|
||||
@@ -741,20 +733,19 @@ function getGemini3ThinkingLevel(
|
||||
return "LOW";
|
||||
case "medium":
|
||||
return "MEDIUM";
|
||||
case "high":
|
||||
default:
|
||||
return "HIGH";
|
||||
}
|
||||
}
|
||||
|
||||
function getGeminiCliThinkingLevel(effort: ClampedThinkingLevel, modelId: string): GoogleThinkingLevel {
|
||||
function getGeminiCliThinkingLevel(effort: ThinkingEffort, modelId: string): GoogleThinkingLevel {
|
||||
if (isGemini3ProModelId(modelId)) {
|
||||
// Gemini 3 Pro only supports LOW/HIGH (for now)
|
||||
switch (effort) {
|
||||
case "minimal":
|
||||
case "low":
|
||||
return "LOW";
|
||||
case "medium":
|
||||
case "high":
|
||||
default:
|
||||
return "HIGH";
|
||||
}
|
||||
}
|
||||
@@ -766,41 +757,35 @@ function getGeminiCliThinkingLevel(effort: ClampedThinkingLevel, modelId: string
|
||||
return "LOW";
|
||||
case "medium":
|
||||
return "MEDIUM";
|
||||
case "high":
|
||||
default:
|
||||
return "HIGH";
|
||||
}
|
||||
}
|
||||
|
||||
function getGoogleBudget(
|
||||
model: Model<"google-generative-ai">,
|
||||
effort: ClampedThinkingLevel,
|
||||
effort: ThinkingEffort,
|
||||
customBudgets?: ThinkingBudgets,
|
||||
): number {
|
||||
effort = effort === "xhigh" ? "high" : effort;
|
||||
|
||||
// Custom budgets take precedence if provided for this level
|
||||
if (customBudgets?.[effort] !== undefined) {
|
||||
return customBudgets[effort]!;
|
||||
}
|
||||
|
||||
// See https://ai.google.dev/gemini-api/docs/thinking#set-budget
|
||||
if (model.id.includes("2.5-pro")) {
|
||||
const budgets: Record<ClampedThinkingLevel, number> = {
|
||||
minimal: 128,
|
||||
low: 2048,
|
||||
medium: 8192,
|
||||
high: 32768,
|
||||
};
|
||||
return budgets[effort];
|
||||
}
|
||||
|
||||
if (model.id.includes("2.5-flash")) {
|
||||
// Covers 2.5-flash-lite as well
|
||||
const budgets: Record<ClampedThinkingLevel, number> = {
|
||||
minimal: 128,
|
||||
low: 2048,
|
||||
medium: 8192,
|
||||
high: 24576,
|
||||
};
|
||||
return budgets[effort];
|
||||
if (model.id.includes("2.5-")) {
|
||||
switch (effort) {
|
||||
case "minimal":
|
||||
return 128;
|
||||
case "low":
|
||||
return 2048;
|
||||
case "medium":
|
||||
return 8192;
|
||||
default:
|
||||
return model.id.includes("2.5-flash") ? 24576 : 32768;
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown model - use dynamic
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/** Provider-level thinking levels (no "off"), ordered least to most. */
|
||||
export type ThinkingEffort = "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
|
||||
/**
|
||||
* ThinkingLevel extended with "off" to disable reasoning entirely.
|
||||
* Used in UI, config, session state, and CLI args.
|
||||
* "off" is never sent to providers — callers strip it before streaming.
|
||||
*/
|
||||
export type ThinkingLevel = ThinkingEffort | "off";
|
||||
|
||||
/**
|
||||
* ThinkingSelector extended with "inherit" to indicate the role should
|
||||
* use the session-level default rather than an explicit choice.
|
||||
* Used in per-role model assignment UI.
|
||||
*/
|
||||
export type ThinkingMode = ThinkingLevel | "inherit";
|
||||
|
||||
/** Provider-level thinking levels (no "off"), ordered least to most. */
|
||||
export const THINKING_LEVELS = [
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
] as const satisfies readonly ThinkingEffort[];
|
||||
|
||||
/** All selectable thinking levels including "off", ordered none to maximum. */
|
||||
export const ALL_THINKING_LEVELS = [
|
||||
"off",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
] as const satisfies readonly ThinkingLevel[];
|
||||
|
||||
/** All thinking modes including "inherit", ordered inherit → none → maximum. */
|
||||
export const ALL_THINKING_MODES = [
|
||||
"inherit",
|
||||
"off",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
] as const satisfies readonly ThinkingMode[];
|
||||
|
||||
/** Human-readable descriptions for every thinking mode. */
|
||||
export const THINKING_MODE_DESCRIPTIONS: Record<ThinkingMode, string> = {
|
||||
inherit: "Inherit session default",
|
||||
off: "No reasoning",
|
||||
minimal: "Very brief reasoning (~1k tokens)",
|
||||
low: "Light reasoning (~2k tokens)",
|
||||
medium: "Moderate reasoning (~8k tokens)",
|
||||
high: "Deep reasoning (~16k tokens)",
|
||||
xhigh: "Maximum reasoning (~32k tokens)",
|
||||
};
|
||||
|
||||
/** Compact display labels for every thinking mode. */
|
||||
export const THINKING_MODE_LABELS: Record<ThinkingMode, string> = {
|
||||
inherit: "inherit",
|
||||
off: "off",
|
||||
minimal: "min",
|
||||
low: "low",
|
||||
medium: "medium",
|
||||
high: "high",
|
||||
xhigh: "xhigh",
|
||||
};
|
||||
|
||||
const F_LEVEL = 3;
|
||||
const F_SEL = 2;
|
||||
const F_MODE = 1;
|
||||
|
||||
const F_THINKING: Record<string, number> = {
|
||||
inherit: F_MODE,
|
||||
off: F_SEL,
|
||||
minimal: F_LEVEL,
|
||||
low: F_LEVEL,
|
||||
medium: F_LEVEL,
|
||||
high: F_LEVEL,
|
||||
xhigh: F_LEVEL,
|
||||
};
|
||||
|
||||
// Parses an unknown value and returns a ThinkingLevel if valid, otherwise undefined.
|
||||
export function parseThinkingEffort(level: string | null | undefined): ThinkingEffort | undefined {
|
||||
return level && (F_THINKING[level] ?? 0) >= F_LEVEL ? (level as ThinkingEffort) : undefined;
|
||||
}
|
||||
|
||||
// Parses an unknown value and returns a ThinkingSelector if valid, otherwise undefined.
|
||||
export function parseThinkingLevel(level: string | null | undefined): ThinkingLevel | undefined {
|
||||
return level && (F_THINKING[level] ?? 0) >= F_SEL ? (level as ThinkingLevel) : undefined;
|
||||
}
|
||||
|
||||
// Parses an unknown value and returns a ThinkingMode if valid, otherwise undefined.
|
||||
export function parseThinkingMode(level: string | null | undefined): ThinkingMode | undefined {
|
||||
return level && (F_THINKING[level] ?? 0) >= F_MODE ? (level as ThinkingMode) : undefined;
|
||||
}
|
||||
|
||||
/** Format a thinking mode as a compact display label. */
|
||||
export function formatThinking(mode: ThinkingMode): string {
|
||||
return THINKING_MODE_LABELS[mode];
|
||||
}
|
||||
|
||||
const REG_LVL: readonly ThinkingLevel[] = ["off", "minimal", "low", "medium", "high"];
|
||||
const XHI_LVL: readonly ThinkingLevel[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
||||
|
||||
/** Returns the available thinking modes for a model based on whether it supports xhigh. */
|
||||
export function getAvailableThinkingLevel(hasXhigh: boolean): ReadonlyArray<ThinkingLevel> {
|
||||
return hasXhigh ? XHI_LVL : REG_LVL;
|
||||
}
|
||||
|
||||
const REG_EFF: readonly ThinkingEffort[] = ["minimal", "low", "medium", "high"];
|
||||
const XHI_EFF: readonly ThinkingEffort[] = ["minimal", "low", "medium", "high", "xhigh"];
|
||||
|
||||
export function getAvailableThinkingEffort(hasXhigh: boolean): ReadonlyArray<ThinkingEffort> {
|
||||
return hasXhigh ? XHI_EFF : REG_EFF;
|
||||
}
|
||||
@@ -109,10 +109,10 @@ export type KnownProvider =
|
||||
| "lm-studio";
|
||||
export type Provider = KnownProvider | string;
|
||||
|
||||
export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
import type { ThinkingEffort, ThinkingLevel } from "./thinking";
|
||||
|
||||
/** Token budgets for each thinking level (token-based providers only) */
|
||||
export type ThinkingBudgets = { [key in ThinkingLevel]?: number };
|
||||
export type ThinkingBudgets = { [key in ThinkingEffort]?: number };
|
||||
|
||||
export type MessageAttribution = "user" | "agent";
|
||||
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
### Changed
|
||||
|
||||
- Moved `ThinkingLevel` type from `@oh-my-pi/pi-agent-core` to `@oh-my-pi/pi-ai` for centralized thinking level definitions
|
||||
- Replaced local thinking level validation with `parseThinkingLevel()` and `ALL_THINKING_LEVELS` from `@oh-my-pi/pi-ai`
|
||||
- Updated thinking level option providers to use `THINKING_MODE_DESCRIPTIONS` from `@oh-my-pi/pi-ai` for consistent descriptions
|
||||
- Renamed `RoleThinkingMode` type to `ThinkingMode` and changed default value from `'default'` to `'inherit'` for clarity
|
||||
- Replaced `formatThinkingEffortLabel()` utility with `formatThinking()` from `@oh-my-pi/pi-ai`
|
||||
- Renamed `extractExplicitThinkingLevel()` to `extractExplicitThinkingSelector()` in model resolver
|
||||
- Updated thinking level clamping to use `getAvailableThinkingLevel()` from `@oh-my-pi/pi-ai`
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `thinking-effort-label.ts` utility file (functionality moved to `@oh-my-pi/pi-ai`)
|
||||
- Removed local `VALID_THINKING_LEVELS` constant definitions across multiple files
|
||||
- Removed `isValidThinkingLevel()` function (replaced by `parseThinkingLevel()` from `@oh-my-pi/pi-ai`)
|
||||
- Removed `parseThinkingLevel()` helper from discovery module (now uses centralized version from `@oh-my-pi/pi-ai`)
|
||||
|
||||
## [13.8.0] - 2026-03-04
|
||||
### Added
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* CLI argument parsing and help display
|
||||
*/
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { ALL_THINKING_LEVELS, parseThinkingLevel, type ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import { APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils";
|
||||
import chalk from "chalk";
|
||||
import { BUILTIN_TOOLS } from "../tools";
|
||||
@@ -48,12 +48,6 @@ export interface Args {
|
||||
unknownFlags: Map<string, boolean | string>;
|
||||
}
|
||||
|
||||
const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
||||
|
||||
export function isValidThinkingLevel(level: string): level is ThinkingLevel {
|
||||
return VALID_THINKING_LEVELS.includes(level as ThinkingLevel);
|
||||
}
|
||||
|
||||
export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "boolean" | "string" }>): Args {
|
||||
const result: Args = {
|
||||
messages: [],
|
||||
@@ -127,13 +121,13 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
|
||||
}
|
||||
result.tools = validTools;
|
||||
} else if (arg === "--thinking" && i + 1 < args.length) {
|
||||
const level = args[++i];
|
||||
if (isValidThinkingLevel(level)) {
|
||||
result.thinking = level;
|
||||
const thinking = parseThinkingLevel(args[++i]);
|
||||
if (thinking !== undefined) {
|
||||
result.thinking = thinking;
|
||||
} else {
|
||||
logger.warn("Invalid thinking level passed to --thinking", {
|
||||
level,
|
||||
validThinkingLevels: [...VALID_THINKING_LEVELS],
|
||||
level: thinking,
|
||||
validThinkingLevels: [...ALL_THINKING_LEVELS],
|
||||
});
|
||||
}
|
||||
} else if (arg === "--print" || arg === "-p") {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Root command for the coding agent CLI.
|
||||
*/
|
||||
|
||||
import { ALL_THINKING_LEVELS } from "@oh-my-pi/pi-ai";
|
||||
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
||||
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
||||
import { parseArgs } from "../cli/args";
|
||||
@@ -86,7 +87,7 @@ export default class Index extends Command {
|
||||
}),
|
||||
thinking: Flags.string({
|
||||
description: "Set thinking level: off, minimal, low, medium, high, xhigh",
|
||||
options: ["off", "minimal", "low", "medium", "high", "xhigh"],
|
||||
options: [...ALL_THINKING_LEVELS],
|
||||
}),
|
||||
hook: Flags.string({
|
||||
description: "Load a hook/extension file (can be used multiple times)",
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
/**
|
||||
* Model resolution, scoping, and initial selection
|
||||
*/
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { type Api, DEFAULT_MODEL_PER_PROVIDER, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
||||
import {
|
||||
type Api,
|
||||
DEFAULT_MODEL_PER_PROVIDER,
|
||||
type KnownProvider,
|
||||
type Model,
|
||||
modelsAreEqual,
|
||||
parseThinkingLevel,
|
||||
type ThinkingLevel,
|
||||
} from "@oh-my-pi/pi-ai";
|
||||
import chalk from "chalk";
|
||||
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
||||
import { fuzzyMatch } from "../utils/fuzzy";
|
||||
import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
|
||||
import type { Settings } from "./settings";
|
||||
|
||||
const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
||||
|
||||
function isValidThinkingLevel(level: string): level is ThinkingLevel {
|
||||
return VALID_THINKING_LEVELS.includes(level as ThinkingLevel);
|
||||
}
|
||||
/** Default model IDs for each known provider */
|
||||
export const defaultModelPerProvider: Record<KnownProvider, string> = DEFAULT_MODEL_PER_PROVIDER;
|
||||
|
||||
@@ -38,8 +40,9 @@ export function parseModelString(
|
||||
const colonIdx = id.lastIndexOf(":");
|
||||
if (colonIdx !== -1) {
|
||||
const suffix = id.slice(colonIdx + 1);
|
||||
if (isValidThinkingLevel(suffix)) {
|
||||
return { provider, id: id.slice(0, colonIdx), thinkingLevel: suffix };
|
||||
const thinkingLevel = parseThinkingLevel(suffix);
|
||||
if (thinkingLevel) {
|
||||
return { provider, id: id.slice(0, colonIdx), thinkingLevel };
|
||||
}
|
||||
}
|
||||
return { provider, id };
|
||||
@@ -246,7 +249,7 @@ function parseModelPatternWithContext(
|
||||
pattern: string,
|
||||
availableModels: Model<Api>[],
|
||||
context: ModelPreferenceContext,
|
||||
options?: { allowInvalidThinkingLevelFallback?: boolean },
|
||||
options?: { allowInvalidThinkingSelectorFallback?: boolean },
|
||||
): ParsedModelResult {
|
||||
// Try exact match first
|
||||
const exactMatch = tryMatchModel(pattern, availableModels, context);
|
||||
@@ -264,7 +267,8 @@ function parseModelPatternWithContext(
|
||||
const prefix = pattern.substring(0, lastColonIndex);
|
||||
const suffix = pattern.substring(lastColonIndex + 1);
|
||||
|
||||
if (isValidThinkingLevel(suffix)) {
|
||||
const parsedThinkingLevel = parseThinkingLevel(suffix);
|
||||
if (parsedThinkingLevel) {
|
||||
// Valid thinking level - recurse on prefix and use this level
|
||||
const result = parseModelPatternWithContext(prefix, availableModels, context, options);
|
||||
if (result.model) {
|
||||
@@ -272,7 +276,7 @@ function parseModelPatternWithContext(
|
||||
const explicitThinkingLevel = !result.warning;
|
||||
return {
|
||||
model: result.model,
|
||||
thinkingLevel: explicitThinkingLevel ? suffix : undefined,
|
||||
thinkingLevel: explicitThinkingLevel ? parsedThinkingLevel : undefined,
|
||||
warning: result.warning,
|
||||
explicitThinkingLevel,
|
||||
};
|
||||
@@ -280,7 +284,7 @@ function parseModelPatternWithContext(
|
||||
return result;
|
||||
}
|
||||
|
||||
const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true;
|
||||
const allowFallback = options?.allowInvalidThinkingSelectorFallback ?? true;
|
||||
if (!allowFallback) {
|
||||
return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
||||
}
|
||||
@@ -302,7 +306,7 @@ export function parseModelPattern(
|
||||
pattern: string,
|
||||
availableModels: Model<Api>[],
|
||||
preferences?: ModelMatchPreferences,
|
||||
options?: { allowInvalidThinkingLevelFallback?: boolean },
|
||||
options?: { allowInvalidThinkingSelectorFallback?: boolean },
|
||||
): ParsedModelResult {
|
||||
const context = buildPreferenceContext(availableModels, preferences);
|
||||
return parseModelPatternWithContext(pattern, availableModels, context, options);
|
||||
@@ -361,7 +365,7 @@ export function resolveModelRoleValue(
|
||||
|
||||
const lastColonIndex = normalized.lastIndexOf(":");
|
||||
const hasThinkingSuffix =
|
||||
lastColonIndex > PREFIX_MODEL_ROLE.length && isValidThinkingLevel(normalized.slice(lastColonIndex + 1));
|
||||
lastColonIndex > PREFIX_MODEL_ROLE.length && parseThinkingLevel(normalized.slice(lastColonIndex + 1));
|
||||
const aliasCandidate = hasThinkingSuffix ? normalized.slice(0, lastColonIndex) : normalized;
|
||||
const effectivePattern = expandRoleAlias(aliasCandidate, options?.settings);
|
||||
const patternWithSuffix = hasThinkingSuffix
|
||||
@@ -376,7 +380,7 @@ export function resolveModelRoleValue(
|
||||
return { model, thinkingLevel, explicitThinkingLevel, warning };
|
||||
}
|
||||
|
||||
export function extractExplicitThinkingLevel(
|
||||
export function extractExplicitThinkingSelector(
|
||||
value: string | undefined,
|
||||
settings?: Settings,
|
||||
): ThinkingLevel | undefined {
|
||||
@@ -390,7 +394,7 @@ export function extractExplicitThinkingLevel(
|
||||
visited.add(current);
|
||||
const lastColonIndex = current.lastIndexOf(":");
|
||||
const hasThinkingSuffix =
|
||||
lastColonIndex > PREFIX_MODEL_ROLE.length && isValidThinkingLevel(current.slice(lastColonIndex + 1));
|
||||
lastColonIndex > PREFIX_MODEL_ROLE.length && parseThinkingLevel(current.slice(lastColonIndex + 1));
|
||||
if (hasThinkingSuffix) {
|
||||
return current.slice(lastColonIndex + 1) as ThinkingLevel;
|
||||
}
|
||||
@@ -493,8 +497,9 @@ export async function resolveModelScope(
|
||||
|
||||
if (colonIdx !== -1) {
|
||||
const suffix = pattern.substring(colonIdx + 1);
|
||||
if (isValidThinkingLevel(suffix)) {
|
||||
thinkingLevel = suffix;
|
||||
const parsedThinkingLevel = parseThinkingLevel(suffix);
|
||||
if (parsedThinkingLevel) {
|
||||
thinkingLevel = parsedThinkingLevel;
|
||||
explicitThinkingLevel = true;
|
||||
globPattern = pattern.substring(0, colonIdx);
|
||||
}
|
||||
@@ -621,7 +626,7 @@ export function resolveCliModel(options: {
|
||||
|
||||
const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
|
||||
const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, preferences, {
|
||||
allowInvalidThinkingLevelFallback: false,
|
||||
allowInvalidThinkingSelectorFallback: false,
|
||||
});
|
||||
|
||||
if (!model) {
|
||||
@@ -658,7 +663,7 @@ export async function findInitialModel(options: {
|
||||
isContinuing: boolean;
|
||||
defaultProvider?: string;
|
||||
defaultModelId?: string;
|
||||
defaultThinkingLevel?: ThinkingLevel;
|
||||
defaultThinkingSelector?: ThinkingLevel;
|
||||
modelRegistry: ModelRegistry;
|
||||
}): Promise<InitialModelResult> {
|
||||
const {
|
||||
@@ -668,7 +673,7 @@ export async function findInitialModel(options: {
|
||||
isContinuing,
|
||||
defaultProvider,
|
||||
defaultModelId,
|
||||
defaultThinkingLevel,
|
||||
defaultThinkingSelector,
|
||||
modelRegistry,
|
||||
} = options;
|
||||
|
||||
@@ -688,10 +693,10 @@ export async function findInitialModel(options: {
|
||||
// 2. Use first model from scoped models (skip if continuing/resuming)
|
||||
if (scopedModels.length > 0 && !isContinuing) {
|
||||
const scoped = scopedModels[0];
|
||||
const scopedThinkingLevel = scoped.thinkingLevel ?? defaultThinkingLevel ?? "off";
|
||||
const scopedThinkingSelector = scoped.thinkingLevel ?? defaultThinkingSelector ?? "off";
|
||||
return {
|
||||
model: scoped.model,
|
||||
thinkingLevel: scopedThinkingLevel,
|
||||
thinkingLevel: scopedThinkingSelector,
|
||||
fallbackMessage: undefined,
|
||||
};
|
||||
}
|
||||
@@ -701,8 +706,8 @@ export async function findInitialModel(options: {
|
||||
const found = modelRegistry.find(defaultProvider, defaultModelId);
|
||||
if (found) {
|
||||
model = found;
|
||||
if (defaultThinkingLevel) {
|
||||
thinkingLevel = defaultThinkingLevel;
|
||||
if (defaultThinkingSelector) {
|
||||
thinkingLevel = defaultThinkingSelector;
|
||||
}
|
||||
return { model, thinkingLevel, fallbackMessage: undefined };
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/**
|
||||
import { ALL_THINKING_LEVELS } from "@oh-my-pi/pi-ai";
|
||||
|
||||
/** Unified settings schema - single source of truth for all settings.
|
||||
* Unified settings schema - single source of truth for all settings.
|
||||
*
|
||||
* Each setting is defined once here with:
|
||||
@@ -190,7 +192,7 @@ export const SETTINGS_SCHEMA = {
|
||||
},
|
||||
defaultThinkingLevel: {
|
||||
type: "enum",
|
||||
values: ["off", "minimal", "low", "medium", "high", "xhigh"] as const,
|
||||
values: ALL_THINKING_LEVELS,
|
||||
default: "high",
|
||||
ui: {
|
||||
tab: "agent",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import { parseThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import { FileType, glob } from "@oh-my-pi/pi-natives";
|
||||
import { CONFIG_DIR_NAME, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { readFile } from "../capability/fs";
|
||||
@@ -9,8 +10,6 @@ import type { Skill, SkillFrontmatter } from "../capability/skill";
|
||||
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
|
||||
const VALID_THINKING_LEVELS: readonly string[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
||||
|
||||
/**
|
||||
* Standard paths for each config source.
|
||||
*/
|
||||
@@ -100,18 +99,6 @@ export function createSourceMeta(provider: string, filePath: string, level: "use
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse thinking level from frontmatter.
|
||||
* Supports keys: thinkingLevel, thinking-level, thinking
|
||||
*/
|
||||
export function parseThinkingLevel(frontmatter: Record<string, unknown>): ThinkingLevel | undefined {
|
||||
const raw = frontmatter.thinkingLevel ?? frontmatter["thinking-level"] ?? frontmatter.thinking;
|
||||
if (typeof raw === "string" && VALID_THINKING_LEVELS.includes(raw)) {
|
||||
return raw as ThinkingLevel;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function parseBoolean(value: unknown): boolean | undefined {
|
||||
if (typeof value === "boolean") return value;
|
||||
if (typeof value === "string") {
|
||||
@@ -247,10 +234,10 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
|
||||
}
|
||||
|
||||
const output = frontmatter.output !== undefined ? frontmatter.output : undefined;
|
||||
const thinkingLevel =
|
||||
typeof frontmatter.thinkingLevel === "string" ? parseThinkingLevel(frontmatter.thinkingLevel) : undefined;
|
||||
const model = parseModelList(frontmatter.model);
|
||||
const thinkingLevel = parseThinkingLevel(frontmatter);
|
||||
const blocking = parseBoolean(frontmatter.blocking);
|
||||
|
||||
return { name, description, tools, spawns, model, output, thinkingLevel, blocking };
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
import type * as fs1 from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
|
||||
import type { ImageContent, Model, TextContent, ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
||||
import type { KeyId } from "@oh-my-pi/pi-tui";
|
||||
import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* - Register commands, keyboard shortcuts, and CLI flags
|
||||
* - Interact with the user via UI primitives
|
||||
*/
|
||||
import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import type {
|
||||
Api,
|
||||
AssistantMessageEvent,
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
OAuthLoginCallbacks,
|
||||
SimpleStreamOptions,
|
||||
TextContent,
|
||||
ThinkingLevel,
|
||||
ToolResultMessage,
|
||||
} from "@oh-my-pi/pi-ai";
|
||||
import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { type Model, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
|
||||
import {
|
||||
formatThinking,
|
||||
getAvailableThinkingLevel,
|
||||
type Model,
|
||||
modelsAreEqual,
|
||||
supportsXhigh,
|
||||
type ThinkingMode,
|
||||
} from "@oh-my-pi/pi-ai";
|
||||
import { Container, Input, matchesKey, Spacer, type Tab, TabBar, Text, type TUI, visibleWidth } from "@oh-my-pi/pi-tui";
|
||||
import { MODEL_ROLE_IDS, MODEL_ROLES, type ModelRegistry, type ModelRole } from "../../config/model-registry";
|
||||
import { resolveModelRoleValue } from "../../config/model-resolver";
|
||||
import type { Settings } from "../../config/settings";
|
||||
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
||||
import { fuzzyFilter } from "../../utils/fuzzy";
|
||||
import { formatThinkingEffortLabel } from "../../utils/thinking-effort-label";
|
||||
import { getTabBarTheme } from "../shared";
|
||||
import { DynamicBorder } from "./dynamic-border";
|
||||
|
||||
@@ -16,11 +21,6 @@ function makeInvertedBadge(label: string, color: ThemeColor): string {
|
||||
return `${bgAnsi}\x1b[30m ${label} \x1b[39m\x1b[49m`;
|
||||
}
|
||||
|
||||
function formatRoleThinkingModeLabel(thinkingMode: RoleThinkingMode): string {
|
||||
if (thinkingMode === "default") return "inherit";
|
||||
return formatThinkingEffortLabel(thinkingMode);
|
||||
}
|
||||
|
||||
interface ModelItem {
|
||||
provider: string;
|
||||
id: string;
|
||||
@@ -32,14 +32,12 @@ interface ScopedModelItem {
|
||||
thinkingLevel: string;
|
||||
}
|
||||
|
||||
type RoleThinkingMode = "default" | ThinkingLevel;
|
||||
|
||||
interface RoleAssignment {
|
||||
model: Model;
|
||||
thinkingMode: RoleThinkingMode;
|
||||
thinkingMode: ThinkingMode;
|
||||
}
|
||||
|
||||
type RoleSelectCallback = (model: Model, role: ModelRole | null, thinkingMode?: RoleThinkingMode) => void;
|
||||
type RoleSelectCallback = (model: Model, role: ModelRole | null, thinkingMode?: ThinkingMode) => void;
|
||||
type CancelCallback = () => void;
|
||||
interface MenuRoleAction {
|
||||
label: string;
|
||||
@@ -55,7 +53,6 @@ const MENU_ROLE_ACTIONS: MenuRoleAction[] = MODEL_ROLE_IDS.map(role => {
|
||||
};
|
||||
});
|
||||
|
||||
const THINKING_MODE_OPTIONS: RoleThinkingMode[] = ["default", "off", "minimal", "low", "medium", "high"];
|
||||
const ALL_TAB = "ALL";
|
||||
|
||||
/**
|
||||
@@ -100,7 +97,7 @@ export class ModelSelectorComponent extends Container {
|
||||
settings: Settings,
|
||||
modelRegistry: ModelRegistry,
|
||||
scopedModels: ReadonlyArray<ScopedModelItem>,
|
||||
onSelect: (model: Model, role: ModelRole | null, thinkingMode?: RoleThinkingMode) => void,
|
||||
onSelect: (model: Model, role: ModelRole | null, thinkingMode?: ThinkingMode) => void,
|
||||
onCancel: () => void,
|
||||
options?: { temporaryOnly?: boolean; initialSearchInput?: string },
|
||||
) {
|
||||
@@ -195,7 +192,7 @@ export class ModelSelectorComponent extends Container {
|
||||
if (model) {
|
||||
this.#roles[role] = {
|
||||
model,
|
||||
thinkingMode: explicitThinkingLevel && thinkingLevel !== undefined ? thinkingLevel : "default",
|
||||
thinkingMode: explicitThinkingLevel && thinkingLevel !== undefined ? thinkingLevel : "inherit",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -412,7 +409,7 @@ export class ModelSelectorComponent extends Container {
|
||||
if (!tag || !assigned || !modelsAreEqual(assigned.model, item.model)) continue;
|
||||
|
||||
const badge = makeInvertedBadge(tag, color ?? "success");
|
||||
const thinkingLabel = formatRoleThinkingModeLabel(assigned.thinkingMode);
|
||||
const thinkingLabel = formatThinking(assigned.thinkingMode);
|
||||
roleBadgeTokens.push(`${badge} ${theme.fg("dim", `(${thinkingLabel})`)}`);
|
||||
}
|
||||
const badgeText = roleBadgeTokens.length > 0 ? ` ${roleBadgeTokens.join(" ")}` : "";
|
||||
@@ -459,16 +456,12 @@ export class ModelSelectorComponent extends Container {
|
||||
this.#listContainer.addChild(new Text(theme.fg("muted", ` Model Name: ${selected.model.name}`), 0, 0));
|
||||
}
|
||||
}
|
||||
#getThinkingModesForModel(model: Model): RoleThinkingMode[] {
|
||||
const thinkingModes = [...THINKING_MODE_OPTIONS];
|
||||
if (supportsXhigh(model)) {
|
||||
thinkingModes.push("xhigh");
|
||||
}
|
||||
return thinkingModes;
|
||||
#getThinkingModesForModel(model: Model): ReadonlyArray<ThinkingMode> {
|
||||
return ["inherit", ...getAvailableThinkingLevel(supportsXhigh(model))];
|
||||
}
|
||||
|
||||
#getCurrentRoleThinkingMode(role: ModelRole): RoleThinkingMode {
|
||||
return this.#roles[role]?.thinkingMode ?? "default";
|
||||
#getCurrentRoleThinkingMode(role: ModelRole): ThinkingMode {
|
||||
return this.#roles[role]?.thinkingMode ?? "inherit";
|
||||
}
|
||||
|
||||
#getThinkingPreselectIndex(role: ModelRole, model: Model): number {
|
||||
@@ -507,7 +500,7 @@ export class ModelSelectorComponent extends Container {
|
||||
const optionLines = showingThinking
|
||||
? thinkingOptions.map((thinkingMode, index) => {
|
||||
const prefix = index === this.#menuSelectedIndex ? ` ${theme.nav.cursor} ` : " ";
|
||||
const label = formatRoleThinkingModeLabel(thinkingMode);
|
||||
const label = formatThinking(thinkingMode);
|
||||
return `${prefix}${label}`;
|
||||
})
|
||||
: MENU_ROLE_ACTIONS.map((action, index) => {
|
||||
@@ -664,12 +657,12 @@ export class ModelSelectorComponent extends Container {
|
||||
}
|
||||
}
|
||||
|
||||
#formatRoleModelValue(model: Model, thinkingMode: RoleThinkingMode): string {
|
||||
#formatRoleModelValue(model: Model, thinkingMode: ThinkingMode): string {
|
||||
const modelKey = `${model.provider}/${model.id}`;
|
||||
if (thinkingMode === "default") return modelKey;
|
||||
if (thinkingMode === "inherit") return modelKey;
|
||||
return `${modelKey}:${thinkingMode}`;
|
||||
}
|
||||
#handleSelect(model: Model, role: ModelRole | null, thinkingMode?: RoleThinkingMode): void {
|
||||
#handleSelect(model: Model, role: ModelRole | null, thinkingMode?: ThinkingMode): void {
|
||||
// For temporary role, don't save to settings - just notify caller
|
||||
if (role === null) {
|
||||
this.#onSelectCallback(model, null);
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* 1. Add it to settings-schema.ts with a `ui` field
|
||||
* 2. That's it - it appears in the UI automatically
|
||||
*/
|
||||
|
||||
import { ALL_THINKING_LEVELS, THINKING_MODE_DESCRIPTIONS } from "@oh-my-pi/pi-ai";
|
||||
import { TERMINAL } from "@oh-my-pi/pi-tui";
|
||||
import {
|
||||
getDefault,
|
||||
@@ -222,12 +224,11 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
||||
],
|
||||
// Default thinking level
|
||||
defaultThinkingLevel: [
|
||||
{ value: "off", label: "off", description: "No reasoning" },
|
||||
{ value: "minimal", label: "minimal", description: "Very brief (~1k tokens)" },
|
||||
{ value: "low", label: "low", description: "Light (~2k tokens)" },
|
||||
{ value: "medium", label: "medium", description: "Moderate (~8k tokens)" },
|
||||
{ value: "high", label: "high", description: "Deep (~16k tokens)" },
|
||||
{ value: "xhigh", label: "xhigh", description: "Maximum (~32k tokens)" },
|
||||
...ALL_THINKING_LEVELS.map(level => ({
|
||||
value: level,
|
||||
label: level,
|
||||
description: THINKING_MODE_DESCRIPTIONS[level],
|
||||
})),
|
||||
],
|
||||
// Temperature
|
||||
temperature: [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import {
|
||||
Container,
|
||||
matchesKey,
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { THINKING_MODE_DESCRIPTIONS, type ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
|
||||
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
||||
import { getSelectListTheme } from "../../modes/theme/theme";
|
||||
import { DynamicBorder } from "./dynamic-border";
|
||||
|
||||
const LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {
|
||||
off: "No reasoning",
|
||||
minimal: "Very brief reasoning (~1k tokens)",
|
||||
low: "Light reasoning (~2k tokens)",
|
||||
medium: "Moderate reasoning (~8k tokens)",
|
||||
high: "Deep reasoning (~16k tokens)",
|
||||
xhigh: "Maximum reasoning (~32k tokens)",
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that renders a thinking level selector with borders
|
||||
*/
|
||||
@@ -29,7 +21,7 @@ export class ThinkingSelectorComponent extends Container {
|
||||
const thinkingLevels: SelectItem[] = availableLevels.map(level => ({
|
||||
value: level,
|
||||
label: level,
|
||||
description: LEVEL_DESCRIPTIONS[level],
|
||||
description: THINKING_MODE_DESCRIPTIONS[level],
|
||||
}));
|
||||
|
||||
// Add top border
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai";
|
||||
import { getOAuthProviders, type OAuthProvider, type ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
||||
import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
*
|
||||
* Spawns the agent in RPC mode and provides a typed API for all operations.
|
||||
*/
|
||||
import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentEvent, AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import { isRecord, ptree, readJsonl } from "@oh-my-pi/pi-utils";
|
||||
import type { BashResult } from "../../exec/bash-executor";
|
||||
import type { SessionStats } from "../../session/agent-session";
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* Commands are sent as JSON lines on stdin.
|
||||
* Responses and events are emitted as JSON lines on stdout.
|
||||
*/
|
||||
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Model, ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import type { BashResult } from "../../exec/bash-executor";
|
||||
import type { SessionStats } from "../../session/agent-session";
|
||||
import type { CompactionResult } from "../../session/compaction";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import {
|
||||
detectMacOSAppearance,
|
||||
type HighlightColors as NativeHighlightColors,
|
||||
@@ -1219,7 +1220,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":
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import {
|
||||
Agent,
|
||||
type AgentEvent,
|
||||
type AgentMessage,
|
||||
type AgentTool,
|
||||
INTENT_FIELD,
|
||||
type ThinkingLevel,
|
||||
} from "@oh-my-pi/pi-agent-core";
|
||||
import { type Message, type Model, supportsXhigh } from "@oh-my-pi/pi-ai";
|
||||
import { Agent, type AgentEvent, type AgentMessage, type AgentTool, INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
|
||||
import { type Message, type Model, supportsXhigh, type ThinkingSelector } from "@oh-my-pi/pi-ai";
|
||||
|
||||
import { prewarmOpenAICodexResponses } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { $env, getAgentDbPath, getAgentDir, getProjectDir, logger, postmortem } from "@oh-my-pi/pi-utils";
|
||||
@@ -124,9 +118,9 @@ export interface CreateAgentSessionOptions {
|
||||
* Used when model lookup is deferred because extension-provided models aren't registered yet. */
|
||||
modelPattern?: string;
|
||||
/** Thinking level. Default: from settings, else 'off' (clamped to model capabilities) */
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
thinkingLevel?: ThinkingSelector;
|
||||
/** Models available for cycling (Ctrl+P in interactive mode) */
|
||||
scopedModels?: Array<{ model: Model; thinkingLevel: ThinkingLevel }>;
|
||||
scopedModels?: Array<{ model: Model; thinkingLevel: ThinkingSelector }>;
|
||||
|
||||
/** System prompt. String replaces default, function receives default and returns final. */
|
||||
systemPrompt?: string | ((defaultPrompt: string) => string);
|
||||
@@ -702,7 +696,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
|
||||
// If session has data and includes a thinking entry, restore it
|
||||
if (thinkingLevel === undefined && hasExistingSession && hasThinkingEntry) {
|
||||
thinkingLevel = existingSession.thinkingLevel as ThinkingLevel;
|
||||
thinkingLevel = existingSession.thinkingLevel as ThinkingSelector;
|
||||
}
|
||||
|
||||
if (thinkingLevel === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) {
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
type AgentState,
|
||||
type AgentTool,
|
||||
INTENT_FIELD,
|
||||
type ThinkingLevel,
|
||||
} from "@oh-my-pi/pi-agent-core";
|
||||
import type {
|
||||
AssistantMessage,
|
||||
@@ -33,6 +32,7 @@ import type {
|
||||
Model,
|
||||
ProviderSessionState,
|
||||
TextContent,
|
||||
ThinkingLevel,
|
||||
ToolCall,
|
||||
ToolChoice,
|
||||
Usage,
|
||||
@@ -40,6 +40,7 @@ import type {
|
||||
} from "@oh-my-pi/pi-ai";
|
||||
import {
|
||||
calculateRateLimitBackoffMs,
|
||||
getAvailableThinkingLevel,
|
||||
isContextOverflow,
|
||||
modelsAreEqual,
|
||||
parseRateLimitReason,
|
||||
@@ -49,7 +50,7 @@ import { abortableSleep, getAgentDbPath, isEnoent, logger } from "@oh-my-pi/pi-u
|
||||
import type { AsyncJob, AsyncJobManager } from "../async";
|
||||
import type { Rule } from "../capability/rule";
|
||||
import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "../config/model-registry";
|
||||
import { extractExplicitThinkingLevel, parseModelString, resolveModelRoleValue } from "../config/model-resolver";
|
||||
import { extractExplicitThinkingSelector, parseModelString, resolveModelRoleValue } from "../config/model-resolver";
|
||||
import { expandPromptTemplate, type PromptTemplate, renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { Settings, SkillsSettings } from "../config/settings";
|
||||
import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
|
||||
@@ -252,10 +253,6 @@ export interface HandoffResult {
|
||||
// ============================================================================
|
||||
|
||||
/** Standard thinking levels */
|
||||
const THINKING_LEVELS: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high"];
|
||||
|
||||
/** Thinking levels including xhigh (for supported models) */
|
||||
const THINKING_LEVELS_WITH_XHIGH: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
||||
|
||||
const noOpUIContext: ExtensionUIContext = {
|
||||
select: async (_title, _options, _dialogOptions) => undefined,
|
||||
@@ -295,7 +292,6 @@ export class AgentSession {
|
||||
readonly settings: Settings;
|
||||
|
||||
#asyncJobManager: AsyncJobManager | undefined = undefined;
|
||||
|
||||
#scopedModels: Array<{ model: Model; thinkingLevel: ThinkingLevel }>;
|
||||
#promptTemplates: PromptTemplate[];
|
||||
#slashCommands: FileSlashCommand[];
|
||||
@@ -2864,9 +2860,9 @@ export class AgentSession {
|
||||
* Get available thinking levels for current model.
|
||||
* The provider will clamp to what the specific model supports internally.
|
||||
*/
|
||||
getAvailableThinkingLevels(): ThinkingLevel[] {
|
||||
getAvailableThinkingLevels(): ReadonlyArray<ThinkingLevel> {
|
||||
if (!this.supportsThinking()) return ["off"];
|
||||
return this.supportsXhighThinking() ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS;
|
||||
return getAvailableThinkingLevel(this.supportsXhighThinking());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2883,8 +2879,8 @@ export class AgentSession {
|
||||
return !!this.model?.reasoning;
|
||||
}
|
||||
|
||||
#clampThinkingLevel(level: ThinkingLevel, availableLevels: ThinkingLevel[]): ThinkingLevel {
|
||||
const ordered = THINKING_LEVELS_WITH_XHIGH;
|
||||
#clampThinkingLevel(level: ThinkingLevel, availableLevels: ReadonlyArray<ThinkingLevel>): ThinkingLevel {
|
||||
const ordered = getAvailableThinkingLevel(this.supportsXhighThinking());
|
||||
const available = new Set(availableLevels);
|
||||
const requestedIndex = ordered.indexOf(level);
|
||||
if (requestedIndex === -1) {
|
||||
@@ -3592,7 +3588,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
||||
const existingRoleValue = this.settings.getModelRole(role);
|
||||
if (!existingRoleValue) return modelKey;
|
||||
|
||||
const thinkingLevel = extractExplicitThinkingLevel(existingRoleValue, this.settings);
|
||||
const thinkingLevel = extractExplicitThinkingSelector(existingRoleValue, this.settings);
|
||||
if (thinkingLevel === undefined) return modelKey;
|
||||
return `${modelKey}:${thinkingLevel}`;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
|
||||
*/
|
||||
import path from "node:path";
|
||||
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, Model, ToolChoice } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, Model, ThinkingSelector, ToolChoice } from "@oh-my-pi/pi-ai";
|
||||
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import type { TSchema } from "@sinclair/typebox";
|
||||
import Ajv, { type ValidateFunction } from "ajv";
|
||||
@@ -143,7 +143,7 @@ export interface ExecutorOptions {
|
||||
index: number;
|
||||
id: string;
|
||||
modelOverride?: string | string[];
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
thinkingLevel?: ThinkingSelector;
|
||||
outputSchema?: unknown;
|
||||
/** Parent task recursion depth (0 = top-level, 1 = first child, etc.) */
|
||||
taskDepth?: number;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Usage } from "@oh-my-pi/pi-ai";
|
||||
import type { ThinkingLevel, Usage } from "@oh-my-pi/pi-ai";
|
||||
import { $env } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import type { NestedRepoPatch } from "./worktree";
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
|
||||
const COMPACT_THINKING_EFFORT_LABEL: Record<ThinkingLevel, string> = {
|
||||
off: "off",
|
||||
minimal: "min",
|
||||
low: "low",
|
||||
medium: "medium",
|
||||
high: "high",
|
||||
xhigh: "xhigh",
|
||||
};
|
||||
|
||||
export function formatThinkingEffortLabel(level: ThinkingLevel): string {
|
||||
return COMPACT_THINKING_EFFORT_LABEL[level];
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { getBundledModel, type Model } from "@oh-my-pi/pi-ai";
|
||||
import { Agent } from "@oh-my-pi/pi-agent-core";
|
||||
import { getBundledModel, type Model, type ThinkingLevel } from "@oh-my-pi/pi-ai";
|
||||
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
||||
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { formatThinkingEffortLabel } from "@oh-my-pi/pi-coding-agent/utils/thinking-effort-label";
|
||||
|
||||
describe("formatThinkingEffortLabel", () => {
|
||||
test("formats minimal as min", () => {
|
||||
expect(formatThinkingEffortLabel("minimal")).toBe("min");
|
||||
});
|
||||
|
||||
test("formats medium as medium", () => {
|
||||
expect(formatThinkingEffortLabel("medium")).toBe("medium");
|
||||
});
|
||||
|
||||
test("formats xhigh as xhigh", () => {
|
||||
expect(formatThinkingEffortLabel("xhigh")).toBe("xhigh");
|
||||
});
|
||||
});
|
||||
@@ -11,15 +11,13 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { parseArgs } from "node:util";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { ALL_THINKING_LEVELS, type ThinkingSelector } from "@oh-my-pi/pi-ai";
|
||||
import { padding } from "@oh-my-pi/pi-tui";
|
||||
import { TempDir } from "@oh-my-pi/pi-utils";
|
||||
import { generateJsonReport, generateReport } from "./report";
|
||||
import { type BenchmarkConfig, type ProgressEvent, runBenchmark } from "./runner";
|
||||
import { type EditTask, loadTasksFromDir, validateFixturesFromDir } from "./tasks";
|
||||
|
||||
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
||||
|
||||
function generateReportFilename(config: BenchmarkConfig, format: "markdown" | "json"): string {
|
||||
const modelName = config.model
|
||||
.split("/")
|
||||
@@ -209,14 +207,14 @@ async function main(): Promise<void> {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let thinkingLevel: ThinkingLevel = "low";
|
||||
let thinkingLevel: ThinkingSelector = "low";
|
||||
if (values.thinking) {
|
||||
if (!THINKING_LEVELS.includes(values.thinking as ThinkingLevel)) {
|
||||
if (!ALL_THINKING_LEVELS.includes(values.thinking as ThinkingSelector)) {
|
||||
console.error(`Invalid thinking level: ${values.thinking}`);
|
||||
console.error(`Valid levels: ${THINKING_LEVELS.join(", ")}`);
|
||||
console.error(`Valid levels: ${ALL_THINKING_LEVELS.join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
thinkingLevel = values.thinking as ThinkingLevel;
|
||||
thinkingLevel = values.thinking as ThinkingSelector;
|
||||
}
|
||||
|
||||
const runsPerTask = parseInt(values.runs!, 10);
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
/// <reference types="./bun-imports.d.ts" />
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ThinkingSelector } from "@oh-my-pi/pi-ai";
|
||||
|
||||
import { computeLineHash, RpcClient, renderPromptTemplate } from "@oh-my-pi/pi-coding-agent";
|
||||
import { Snowflake } from "@oh-my-pi/pi-utils";
|
||||
import { diffLines } from "diff";
|
||||
@@ -30,7 +31,7 @@ function makeTempDir(pre?: string): string {
|
||||
export interface BenchmarkConfig {
|
||||
provider: string;
|
||||
model: string;
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
thinkingLevel?: ThinkingSelector;
|
||||
runsPerTask: number;
|
||||
timeout: number;
|
||||
maxTurns?: number;
|
||||
|
||||
Reference in New Issue
Block a user