@oh-my-pi/pi-coding-agent 13.9.1 → 13.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -0
- package/examples/sdk/02-custom-model.ts +2 -1
- package/package.json +7 -7
- package/src/cli/args.ts +6 -5
- package/src/cli/list-models.ts +2 -2
- package/src/commands/launch.ts +3 -3
- package/src/config/model-registry.ts +85 -39
- package/src/config/model-resolver.ts +47 -21
- package/src/config/settings-schema.ts +56 -2
- package/src/discovery/helpers.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +37 -15
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/extensions/loader.ts +3 -2
- package/src/extensibility/extensions/types.ts +10 -7
- package/src/extensibility/hooks/types.ts +2 -0
- package/src/main.ts +5 -22
- package/src/memories/index.ts +7 -3
- package/src/modes/components/footer.ts +10 -8
- package/src/modes/components/model-selector.ts +33 -38
- package/src/modes/components/settings-defs.ts +31 -2
- package/src/modes/components/settings-selector.ts +16 -5
- package/src/modes/components/status-line/context-thresholds.ts +68 -0
- package/src/modes/components/status-line/segments.ts +11 -12
- package/src/modes/components/thinking-selector.ts +7 -7
- package/src/modes/components/tree-selector.ts +3 -2
- package/src/modes/controllers/command-controller.ts +11 -26
- package/src/modes/controllers/event-controller.ts +16 -3
- package/src/modes/controllers/input-controller.ts +4 -2
- package/src/modes/controllers/selector-controller.ts +20 -7
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/rpc/rpc-client.ts +5 -10
- package/src/modes/rpc/rpc-types.ts +5 -5
- package/src/modes/theme/theme.ts +8 -3
- package/src/patch/hashline.ts +26 -3
- package/src/priority.json +1 -0
- package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
- package/src/prompts/system/system-prompt.md +18 -2
- package/src/prompts/tools/hashline.md +139 -83
- package/src/sdk.ts +22 -14
- package/src/session/agent-session.ts +259 -117
- package/src/session/agent-storage.ts +14 -14
- package/src/session/compaction/compaction.ts +500 -13
- package/src/session/messages.ts +12 -1
- package/src/session/session-manager.ts +77 -19
- package/src/slash-commands/builtin-registry.ts +48 -0
- package/src/task/agents.ts +3 -2
- package/src/task/executor.ts +2 -2
- package/src/task/types.ts +2 -1
- package/src/thinking.ts +87 -0
- package/src/tools/browser.ts +15 -6
- package/src/tools/fetch.ts +118 -100
- package/src/web/search/providers/exa.ts +74 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,83 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.9.3] - 2026-03-07
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Changed `ThinkingLevel` type to be imported from `@oh-my-pi/pi-agent-core` instead of `@oh-my-pi/pi-ai`
|
|
10
|
+
- Changed thinking level representation from string literals to `Effort` enum values (e.g., `Effort.High` instead of `"high"`)
|
|
11
|
+
- Changed `getThinkingLevel()` return type to `ThinkingLevel | undefined` to support models without thinking support
|
|
12
|
+
- Changed model `reasoning` property to `thinking` property with `ThinkingConfig` for explicit effort level configuration
|
|
13
|
+
- Changed `thinkingLevel` in session context to be optional (`ThinkingLevel | undefined`) instead of always present
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Added `thinking.ts` module with `getThinkingLevelMetadata()` and `resolveThinkingLevelForModel()` utilities for thinking level handling
|
|
18
|
+
- Added `ThinkingConfig` support to model definitions for specifying supported thinking effort levels per model
|
|
19
|
+
- Added `enrichModelThinking()` function to apply thinking configuration to models during registry initialization
|
|
20
|
+
- Added `clampThinkingLevelForModel()` function to constrain thinking levels to model-supported ranges
|
|
21
|
+
- Added `getSupportedEfforts()` function to retrieve available thinking efforts for a model
|
|
22
|
+
- Added `Effort` enum import from `@oh-my-pi/pi-ai` for type-safe thinking level representation
|
|
23
|
+
- Added `/fast` slash command to toggle OpenAI service tier priority mode for faster response processing
|
|
24
|
+
- Added `serviceTier` setting to control OpenAI processing priority (none, auto, default, flex, scale, priority)
|
|
25
|
+
- Added `compaction.remoteEnabled` setting to control use of remote compaction endpoints
|
|
26
|
+
- Added remote compaction support for OpenAI and OpenAI Codex models with encrypted reasoning preservation
|
|
27
|
+
- Added fast mode indicator (⚡) to model segment in status line when priority service tier is active
|
|
28
|
+
- Added context usage threshold levels (normal, warning, purple, error) with token-aware thresholds for better context awareness
|
|
29
|
+
- Added `isFastModeEnabled()`, `setFastMode()`, and `toggleFastMode()` methods to AgentSession for fast mode control
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Changed credential deletion to disable credentials with persisted cause instead of permanent deletion
|
|
34
|
+
- Added `disabledCause` parameter to credential deletion methods to track reason for disabling
|
|
35
|
+
- Changed thinking level parsing to use `parseEffort()` from local thinking module instead of `parseThinkingLevel()` from pi-ai
|
|
36
|
+
- Changed model list display to show supported thinking efforts (e.g., "low,medium,high") instead of yes/no reasoning indicator
|
|
37
|
+
- Changed footer and status line to check `model.thinking` instead of `model.reasoning` for thinking level display
|
|
38
|
+
- Changed thinking selector to work with `Effort` type instead of `ThinkingLevel` for available levels
|
|
39
|
+
- Changed model resolver to return `undefined` for thinking level instead of `"off"` when no thinking is specified
|
|
40
|
+
- Changed compaction reasoning parameters to use `Effort` enum values instead of string literals
|
|
41
|
+
- Changed RPC types to use `Effort` for cycling thinking levels and `ThinkingLevel | undefined` for session state
|
|
42
|
+
- Changed theme thinking border color function to accept both `ThinkingLevel` and `Effort` types
|
|
43
|
+
- Changed context usage coloring in footer and status line to use token-aware thresholds instead of fixed percentages
|
|
44
|
+
- Changed compaction to preserve OpenAI remote compaction state and encrypted reasoning across sessions
|
|
45
|
+
- Changed compaction to skip emitting kept messages when using OpenAI remote compaction with preserved history
|
|
46
|
+
- Changed session context to include `serviceTier` field for tracking active service tier across session branches
|
|
47
|
+
- Changed `compact()` function to accept `remoteInstructions` option for custom remote compaction prompts
|
|
48
|
+
- Changed model registry to apply hardcoded policies (gpt-5.4 context window) consistently across all model loading paths
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- Fixed OpenAI remote compaction to correctly append incremental responses instead of replacing entire history
|
|
53
|
+
- Fixed thinking level display logic in main.ts to correctly check for undefined instead of "off"
|
|
54
|
+
- Fixed model registry to preserve explicit thinking configuration on runtime-registered models
|
|
55
|
+
- Fixed usage limit reset time calculation to use absolute `resetsAt` timestamps instead of deprecated `resetInMs` field
|
|
56
|
+
- Fixed compaction summary message creation to no longer be automatically added to chat during compaction (now handled by session manager)
|
|
57
|
+
|
|
58
|
+
## [13.9.2] - 2026-03-05
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- Support for Python code execution messages with output display and error handling
|
|
63
|
+
- Support for mode change entries in session exports
|
|
64
|
+
- Support for TTSR injection and session initialization entries in tree filtering
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- Updated label lookup to use `targetId` field instead of `parentId` for label references
|
|
69
|
+
- Changed model change entry display to use `model` field instead of separate `provider` and `modelId` fields
|
|
70
|
+
- Simplified model change rendering by removing OpenAI Codex bridge prompt display
|
|
71
|
+
- Updated searchable text extraction to include Python code from `pythonExecution` messages
|
|
72
|
+
|
|
73
|
+
### Removed
|
|
74
|
+
|
|
75
|
+
- Removed `codexInjectionInfo` from session data destructuring
|
|
76
|
+
- Removed OpenAI Codex-specific bridge prompt UI from model change entries
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- Auto-corrected off-by-one range start errors in hashline edits that would duplicate preceding lines
|
|
81
|
+
|
|
5
82
|
## [13.9.0] - 2026-03-05
|
|
6
83
|
### Added
|
|
7
84
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Shows how to select a specific model and thinking level.
|
|
5
5
|
*/
|
|
6
|
+
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
6
7
|
import { getModel } from "@oh-my-pi/pi-ai";
|
|
7
8
|
import { createAgentSession, discoverAuthStorage, discoverModels } from "@oh-my-pi/pi-coding-agent";
|
|
8
9
|
|
|
@@ -32,7 +33,7 @@ console.log(
|
|
|
32
33
|
if (available.length > 0) {
|
|
33
34
|
const { session } = await createAgentSession({
|
|
34
35
|
model: available[0],
|
|
35
|
-
thinkingLevel:
|
|
36
|
+
thinkingLevel: ThinkingLevel.Medium, // off, low, medium, high
|
|
36
37
|
authStorage,
|
|
37
38
|
modelRegistry,
|
|
38
39
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.9.
|
|
4
|
+
"version": "13.9.3",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mozilla/readability": "^0.6",
|
|
44
|
-
"@oh-my-pi/omp-stats": "13.9.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.9.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.9.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.9.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.9.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.9.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.9.3",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.9.3",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.9.3",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.9.3",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.9.3",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.9.3",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
package/src/cli/args.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI argument parsing and help display
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { type Effort, THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
+
import { parseEffort } from "../thinking";
|
|
7
8
|
import { BUILTIN_TOOLS } from "../tools";
|
|
8
9
|
|
|
9
10
|
export type Mode = "text" | "json" | "rpc";
|
|
@@ -19,7 +20,7 @@ export interface Args {
|
|
|
19
20
|
apiKey?: string;
|
|
20
21
|
systemPrompt?: string;
|
|
21
22
|
appendSystemPrompt?: string;
|
|
22
|
-
thinking?:
|
|
23
|
+
thinking?: Effort;
|
|
23
24
|
continue?: boolean;
|
|
24
25
|
resume?: string | true;
|
|
25
26
|
help?: boolean;
|
|
@@ -122,13 +123,13 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
|
|
|
122
123
|
result.tools = validTools;
|
|
123
124
|
} else if (arg === "--thinking" && i + 1 < args.length) {
|
|
124
125
|
const rawThinking = args[++i];
|
|
125
|
-
const thinking =
|
|
126
|
+
const thinking = parseEffort(rawThinking);
|
|
126
127
|
if (thinking !== undefined) {
|
|
127
128
|
result.thinking = thinking;
|
|
128
129
|
} else {
|
|
129
130
|
logger.warn("Invalid thinking level passed to --thinking", {
|
|
130
131
|
level: rawThinking,
|
|
131
|
-
validThinkingLevels:
|
|
132
|
+
validThinkingLevels: THINKING_EFFORTS,
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
135
|
} else if (arg === "--print" || arg === "-p") {
|
|
@@ -207,7 +208,7 @@ export function getExtraHelpText(): string {
|
|
|
207
208
|
MISTRAL_API_KEY - Mistral models
|
|
208
209
|
ZAI_API_KEY - z.ai models (ZhipuAI/GLM)
|
|
209
210
|
MINIMAX_API_KEY - MiniMax models
|
|
210
|
-
OPENCODE_API_KEY - OpenCode models
|
|
211
|
+
OPENCODE_API_KEY - OpenCode Zen/OpenCode Go models
|
|
211
212
|
CURSOR_ACCESS_TOKEN - Cursor AI models
|
|
212
213
|
AI_GATEWAY_API_KEY - Vercel AI Gateway
|
|
213
214
|
|
package/src/cli/list-models.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* List available models with optional fuzzy search
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type Api, getSupportedEfforts, type Model } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import type { ModelRegistry } from "../config/model-registry";
|
|
7
7
|
import { fuzzyFilter } from "../utils/fuzzy";
|
|
@@ -41,7 +41,7 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s
|
|
|
41
41
|
model: m.id,
|
|
42
42
|
context: formatNumber(m.contextWindow),
|
|
43
43
|
maxOut: formatNumber(m.maxTokens),
|
|
44
|
-
thinking: m.reasoning ? "yes" : "
|
|
44
|
+
thinking: m.thinking ? getSupportedEfforts(m).join(",") : m.reasoning ? "yes" : "-",
|
|
45
45
|
images: m.input.includes("image") ? "yes" : "no",
|
|
46
46
|
}));
|
|
47
47
|
|
package/src/commands/launch.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Root command for the coding agent CLI.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
8
8
|
import { parseArgs } from "../cli/args";
|
|
@@ -86,8 +86,8 @@ export default class Index extends Command {
|
|
|
86
86
|
description: "Comma-separated list of tools to enable (default: all)",
|
|
87
87
|
}),
|
|
88
88
|
thinking: Flags.string({
|
|
89
|
-
description: `Set thinking level: ${
|
|
90
|
-
options:
|
|
89
|
+
description: `Set thinking level: ${THINKING_EFFORTS.join(", ")}`,
|
|
90
|
+
options: [...THINKING_EFFORTS],
|
|
91
91
|
}),
|
|
92
92
|
hook: Flags.string({
|
|
93
93
|
description: "Load a hook/extension file (can be used multiple times)",
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type Context,
|
|
5
5
|
createModelManager,
|
|
6
6
|
DEFAULT_LOCAL_TOKEN,
|
|
7
|
+
enrichModelThinking,
|
|
7
8
|
getBundledModels,
|
|
8
9
|
getBundledProviders,
|
|
9
10
|
googleAntigravityModelManagerOptions,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
registerCustomApi,
|
|
19
20
|
registerOAuthProvider,
|
|
20
21
|
type SimpleStreamOptions,
|
|
22
|
+
type ThinkingConfig,
|
|
21
23
|
unregisterCustomApis,
|
|
22
24
|
unregisterOAuthProviders,
|
|
23
25
|
} from "@oh-my-pi/pi-ai";
|
|
@@ -72,6 +74,28 @@ const OpenAICompatSchema = Type.Object({
|
|
|
72
74
|
vercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema),
|
|
73
75
|
});
|
|
74
76
|
|
|
77
|
+
const EffortSchema = Type.Union([
|
|
78
|
+
Type.Literal("minimal"),
|
|
79
|
+
Type.Literal("low"),
|
|
80
|
+
Type.Literal("medium"),
|
|
81
|
+
Type.Literal("high"),
|
|
82
|
+
Type.Literal("xhigh"),
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
const ThinkingControlModeSchema = Type.Union([
|
|
86
|
+
Type.Literal("effort"),
|
|
87
|
+
Type.Literal("budget"),
|
|
88
|
+
Type.Literal("google-level"),
|
|
89
|
+
Type.Literal("anthropic-adaptive"),
|
|
90
|
+
Type.Literal("anthropic-budget-effort"),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
const ModelThinkingSchema = Type.Object({
|
|
94
|
+
minLevel: EffortSchema,
|
|
95
|
+
maxLevel: EffortSchema,
|
|
96
|
+
mode: ThinkingControlModeSchema,
|
|
97
|
+
});
|
|
98
|
+
|
|
75
99
|
// Schema for custom model definition
|
|
76
100
|
// Most fields are optional with sensible defaults for local models (Ollama, LM Studio, etc.)
|
|
77
101
|
const ModelDefinitionSchema = Type.Object({
|
|
@@ -88,7 +112,9 @@ const ModelDefinitionSchema = Type.Object({
|
|
|
88
112
|
Type.Literal("google-vertex"),
|
|
89
113
|
]),
|
|
90
114
|
),
|
|
115
|
+
baseUrl: Type.Optional(Type.String({ minLength: 1 })),
|
|
91
116
|
reasoning: Type.Optional(Type.Boolean()),
|
|
117
|
+
thinking: Type.Optional(ModelThinkingSchema),
|
|
92
118
|
input: Type.Optional(Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")]))),
|
|
93
119
|
cost: Type.Optional(
|
|
94
120
|
Type.Object({
|
|
@@ -110,6 +136,7 @@ const ModelDefinitionSchema = Type.Object({
|
|
|
110
136
|
const ModelOverrideSchema = Type.Object({
|
|
111
137
|
name: Type.Optional(Type.String({ minLength: 1 })),
|
|
112
138
|
reasoning: Type.Optional(Type.Boolean()),
|
|
139
|
+
thinking: Type.Optional(ModelThinkingSchema),
|
|
113
140
|
input: Type.Optional(Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")]))),
|
|
114
141
|
cost: Type.Optional(
|
|
115
142
|
Type.Object({
|
|
@@ -375,6 +402,7 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
|
|
|
375
402
|
const result = { ...model };
|
|
376
403
|
if (override.name !== undefined) result.name = override.name;
|
|
377
404
|
if (override.reasoning !== undefined) result.reasoning = override.reasoning;
|
|
405
|
+
if (override.thinking !== undefined) result.thinking = override.thinking as ThinkingConfig;
|
|
378
406
|
if (override.input !== undefined) result.input = override.input as ("text" | "image")[];
|
|
379
407
|
if (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;
|
|
380
408
|
if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
|
|
@@ -392,14 +420,16 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
|
|
|
392
420
|
result.headers = { ...model.headers, ...override.headers };
|
|
393
421
|
}
|
|
394
422
|
result.compat = mergeCompat(model.compat, override.compat);
|
|
395
|
-
return result;
|
|
423
|
+
return enrichModelThinking(result);
|
|
396
424
|
}
|
|
397
425
|
|
|
398
426
|
interface CustomModelDefinitionLike {
|
|
399
427
|
id: string;
|
|
400
428
|
name?: string;
|
|
401
429
|
api?: Api;
|
|
430
|
+
baseUrl?: string;
|
|
402
431
|
reasoning?: boolean;
|
|
432
|
+
thinking?: ThinkingConfig;
|
|
403
433
|
input?: ("text" | "image")[];
|
|
404
434
|
cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
405
435
|
contextWindow?: number;
|
|
@@ -445,13 +475,14 @@ function buildCustomModel(
|
|
|
445
475
|
const withDefaults = options.useDefaults;
|
|
446
476
|
const cost = modelDef.cost ?? (withDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
|
|
447
477
|
const input = modelDef.input ?? (withDefaults ? ["text"] : undefined);
|
|
448
|
-
return {
|
|
478
|
+
return enrichModelThinking({
|
|
449
479
|
id: modelDef.id,
|
|
450
480
|
name: modelDef.name ?? (withDefaults ? modelDef.id : undefined),
|
|
451
481
|
api,
|
|
452
482
|
provider: providerName,
|
|
453
|
-
baseUrl: providerBaseUrl,
|
|
483
|
+
baseUrl: modelDef.baseUrl ?? providerBaseUrl,
|
|
454
484
|
reasoning: modelDef.reasoning ?? (withDefaults ? false : undefined),
|
|
485
|
+
thinking: modelDef.thinking as ThinkingConfig | undefined,
|
|
455
486
|
input: input as ("text" | "image")[],
|
|
456
487
|
cost,
|
|
457
488
|
contextWindow: modelDef.contextWindow ?? (withDefaults ? 128000 : undefined),
|
|
@@ -460,7 +491,7 @@ function buildCustomModel(
|
|
|
460
491
|
compat: modelDef.compat,
|
|
461
492
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
462
493
|
premiumMultiplier: modelDef.premiumMultiplier,
|
|
463
|
-
} as Model<Api
|
|
494
|
+
} as Model<Api>);
|
|
464
495
|
}
|
|
465
496
|
|
|
466
497
|
/**
|
|
@@ -537,7 +568,7 @@ export class ModelRegistry {
|
|
|
537
568
|
const builtInModels = this.#loadBuiltInModels(overrides, modelOverrides);
|
|
538
569
|
const combined = this.#mergeCustomModels(builtInModels, customModels);
|
|
539
570
|
|
|
540
|
-
this.#models = combined;
|
|
571
|
+
this.#models = this.#applyHardcodedModelPolicies(combined);
|
|
541
572
|
}
|
|
542
573
|
|
|
543
574
|
/** Load built-in models, applying provider and per-model overrides */
|
|
@@ -716,7 +747,7 @@ export class ModelRegistry {
|
|
|
716
747
|
: model;
|
|
717
748
|
}),
|
|
718
749
|
);
|
|
719
|
-
this.#models = this.#applyModelOverrides(merged, this.#modelOverrides);
|
|
750
|
+
this.#models = this.#applyHardcodedModelPolicies(this.#applyModelOverrides(merged, this.#modelOverrides));
|
|
720
751
|
}
|
|
721
752
|
|
|
722
753
|
async #discoverProviderModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
@@ -853,19 +884,21 @@ export class ModelRegistry {
|
|
|
853
884
|
for (const item of models) {
|
|
854
885
|
const id = item.model || item.name;
|
|
855
886
|
if (!id) continue;
|
|
856
|
-
discovered.push(
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
887
|
+
discovered.push(
|
|
888
|
+
enrichModelThinking({
|
|
889
|
+
id,
|
|
890
|
+
name: item.name || id,
|
|
891
|
+
api: providerConfig.api,
|
|
892
|
+
provider: providerConfig.provider,
|
|
893
|
+
baseUrl: `${endpoint}/v1`,
|
|
894
|
+
reasoning: false,
|
|
895
|
+
input: ["text"],
|
|
896
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
897
|
+
contextWindow: 128000,
|
|
898
|
+
maxTokens: 8192,
|
|
899
|
+
headers: providerConfig.headers,
|
|
900
|
+
}),
|
|
901
|
+
);
|
|
869
902
|
}
|
|
870
903
|
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
871
904
|
} catch (error) {
|
|
@@ -907,24 +940,26 @@ export class ModelRegistry {
|
|
|
907
940
|
for (const item of models) {
|
|
908
941
|
const id = item.id;
|
|
909
942
|
if (!id) continue;
|
|
910
|
-
discovered.push(
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
943
|
+
discovered.push(
|
|
944
|
+
enrichModelThinking({
|
|
945
|
+
id,
|
|
946
|
+
name: id,
|
|
947
|
+
api: providerConfig.api,
|
|
948
|
+
provider: providerConfig.provider,
|
|
949
|
+
baseUrl,
|
|
950
|
+
reasoning: false,
|
|
951
|
+
input: ["text"],
|
|
952
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
953
|
+
contextWindow: 128000,
|
|
954
|
+
maxTokens: 8192,
|
|
955
|
+
headers,
|
|
956
|
+
compat: {
|
|
957
|
+
supportsStore: false,
|
|
958
|
+
supportsDeveloperRole: false,
|
|
959
|
+
supportsReasoningEffort: false,
|
|
960
|
+
},
|
|
961
|
+
}),
|
|
962
|
+
);
|
|
928
963
|
}
|
|
929
964
|
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
930
965
|
} catch (error) {
|
|
@@ -980,6 +1015,15 @@ export class ModelRegistry {
|
|
|
980
1015
|
});
|
|
981
1016
|
}
|
|
982
1017
|
|
|
1018
|
+
#applyHardcodedModelPolicies(models: Model<Api>[]): Model<Api>[] {
|
|
1019
|
+
return models.map(model => {
|
|
1020
|
+
if (model.id === "gpt-5.4") {
|
|
1021
|
+
return { ...model, contextWindow: 1_000_000 };
|
|
1022
|
+
}
|
|
1023
|
+
return model;
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
|
|
983
1027
|
#parseModels(config: ModelsConfig): Model<Api>[] {
|
|
984
1028
|
const models: Model<Api>[] = [];
|
|
985
1029
|
|
|
@@ -997,7 +1041,7 @@ export class ModelRegistry {
|
|
|
997
1041
|
providerConfig.headers,
|
|
998
1042
|
providerConfig.apiKey,
|
|
999
1043
|
providerConfig.authHeader,
|
|
1000
|
-
modelDef,
|
|
1044
|
+
modelDef as CustomModelDefinitionLike,
|
|
1001
1045
|
{ useDefaults: true },
|
|
1002
1046
|
);
|
|
1003
1047
|
if (!model) continue;
|
|
@@ -1150,7 +1194,7 @@ export class ModelRegistry {
|
|
|
1150
1194
|
config.headers,
|
|
1151
1195
|
config.apiKey,
|
|
1152
1196
|
config.authHeader,
|
|
1153
|
-
modelDef,
|
|
1197
|
+
modelDef as CustomModelDefinitionLike,
|
|
1154
1198
|
{ useDefaults: false },
|
|
1155
1199
|
);
|
|
1156
1200
|
if (!model) {
|
|
@@ -1205,7 +1249,9 @@ export interface ProviderConfigInput {
|
|
|
1205
1249
|
id: string;
|
|
1206
1250
|
name: string;
|
|
1207
1251
|
api?: Api;
|
|
1252
|
+
baseUrl?: string;
|
|
1208
1253
|
reasoning: boolean;
|
|
1254
|
+
thinking?: ThinkingConfig;
|
|
1209
1255
|
input: ("text" | "image")[];
|
|
1210
1256
|
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
1211
1257
|
contextWindow: number;
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Model resolution, scoping, and initial selection
|
|
3
3
|
*/
|
|
4
|
+
|
|
5
|
+
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
4
6
|
import {
|
|
5
7
|
type Api,
|
|
8
|
+
clampThinkingLevelForModel,
|
|
6
9
|
DEFAULT_MODEL_PER_PROVIDER,
|
|
10
|
+
type Effort,
|
|
7
11
|
type KnownProvider,
|
|
8
12
|
type Model,
|
|
9
13
|
modelsAreEqual,
|
|
10
|
-
parseThinkingLevel,
|
|
11
|
-
type ThinkingLevel,
|
|
12
14
|
} from "@oh-my-pi/pi-ai";
|
|
13
15
|
import chalk from "chalk";
|
|
14
16
|
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
17
|
+
import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking";
|
|
15
18
|
import { fuzzyMatch } from "../utils/fuzzy";
|
|
16
19
|
import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
|
|
17
20
|
import type { Settings } from "./settings";
|
|
@@ -377,7 +380,14 @@ export function resolveModelRoleValue(
|
|
|
377
380
|
options?.matchPreferences,
|
|
378
381
|
);
|
|
379
382
|
|
|
380
|
-
return {
|
|
383
|
+
return {
|
|
384
|
+
model,
|
|
385
|
+
thinkingLevel: explicitThinkingLevel
|
|
386
|
+
? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel)
|
|
387
|
+
: thinkingLevel,
|
|
388
|
+
explicitThinkingLevel,
|
|
389
|
+
warning,
|
|
390
|
+
};
|
|
381
391
|
}
|
|
382
392
|
|
|
383
393
|
export function extractExplicitThinkingSelector(
|
|
@@ -393,10 +403,10 @@ export function extractExplicitThinkingSelector(
|
|
|
393
403
|
while (!visited.has(current)) {
|
|
394
404
|
visited.add(current);
|
|
395
405
|
const lastColonIndex = current.lastIndexOf(":");
|
|
396
|
-
const
|
|
397
|
-
lastColonIndex > PREFIX_MODEL_ROLE.length
|
|
398
|
-
if (
|
|
399
|
-
return
|
|
406
|
+
const thinkingSelector =
|
|
407
|
+
lastColonIndex > PREFIX_MODEL_ROLE.length ? parseThinkingLevel(current.slice(lastColonIndex + 1)) : undefined;
|
|
408
|
+
if (thinkingSelector) {
|
|
409
|
+
return thinkingSelector;
|
|
400
410
|
}
|
|
401
411
|
const expanded = expandRoleAlias(current, settings).trim();
|
|
402
412
|
if (!expanded || expanded === current) break;
|
|
@@ -520,7 +530,13 @@ export async function resolveModelScope(
|
|
|
520
530
|
|
|
521
531
|
for (const model of matchingModels) {
|
|
522
532
|
if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
|
|
523
|
-
scopedModels.push({
|
|
533
|
+
scopedModels.push({
|
|
534
|
+
model,
|
|
535
|
+
thinkingLevel: explicitThinkingLevel
|
|
536
|
+
? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel)
|
|
537
|
+
: thinkingLevel,
|
|
538
|
+
explicitThinkingLevel,
|
|
539
|
+
});
|
|
524
540
|
}
|
|
525
541
|
}
|
|
526
542
|
continue;
|
|
@@ -543,7 +559,13 @@ export async function resolveModelScope(
|
|
|
543
559
|
|
|
544
560
|
// Avoid duplicates
|
|
545
561
|
if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
|
|
546
|
-
scopedModels.push({
|
|
562
|
+
scopedModels.push({
|
|
563
|
+
model,
|
|
564
|
+
thinkingLevel: explicitThinkingLevel
|
|
565
|
+
? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel)
|
|
566
|
+
: thinkingLevel,
|
|
567
|
+
explicitThinkingLevel,
|
|
568
|
+
});
|
|
547
569
|
}
|
|
548
570
|
}
|
|
549
571
|
|
|
@@ -644,7 +666,7 @@ export function resolveCliModel(options: {
|
|
|
644
666
|
|
|
645
667
|
export interface InitialModelResult {
|
|
646
668
|
model: Model<Api> | undefined;
|
|
647
|
-
thinkingLevel
|
|
669
|
+
thinkingLevel?: ThinkingLevel;
|
|
648
670
|
fallbackMessage: string | undefined;
|
|
649
671
|
}
|
|
650
672
|
|
|
@@ -663,7 +685,7 @@ export async function findInitialModel(options: {
|
|
|
663
685
|
isContinuing: boolean;
|
|
664
686
|
defaultProvider?: string;
|
|
665
687
|
defaultModelId?: string;
|
|
666
|
-
defaultThinkingSelector?:
|
|
688
|
+
defaultThinkingSelector?: Effort;
|
|
667
689
|
modelRegistry: ModelRegistry;
|
|
668
690
|
}): Promise<InitialModelResult> {
|
|
669
691
|
const {
|
|
@@ -678,7 +700,7 @@ export async function findInitialModel(options: {
|
|
|
678
700
|
} = options;
|
|
679
701
|
|
|
680
702
|
let model: Model<Api> | undefined;
|
|
681
|
-
let thinkingLevel:
|
|
703
|
+
let thinkingLevel: Effort | undefined;
|
|
682
704
|
|
|
683
705
|
// 1. CLI args take priority
|
|
684
706
|
if (cliProvider && cliModel) {
|
|
@@ -687,16 +709,22 @@ export async function findInitialModel(options: {
|
|
|
687
709
|
console.error(chalk.red(`Model ${cliProvider}/${cliModel} not found`));
|
|
688
710
|
process.exit(1);
|
|
689
711
|
}
|
|
690
|
-
return { model: found, thinkingLevel:
|
|
712
|
+
return { model: found, thinkingLevel: undefined, fallbackMessage: undefined };
|
|
691
713
|
}
|
|
692
714
|
|
|
693
715
|
// 2. Use first model from scoped models (skip if continuing/resuming)
|
|
694
716
|
if (scopedModels.length > 0 && !isContinuing) {
|
|
695
717
|
const scoped = scopedModels[0];
|
|
696
|
-
const scopedThinkingSelector =
|
|
718
|
+
const scopedThinkingSelector =
|
|
719
|
+
scoped.thinkingLevel === ThinkingLevel.Inherit
|
|
720
|
+
? defaultThinkingSelector
|
|
721
|
+
: (scoped.thinkingLevel ?? defaultThinkingSelector);
|
|
697
722
|
return {
|
|
698
723
|
model: scoped.model,
|
|
699
|
-
thinkingLevel:
|
|
724
|
+
thinkingLevel:
|
|
725
|
+
scopedThinkingSelector === ThinkingLevel.Off
|
|
726
|
+
? ThinkingLevel.Off
|
|
727
|
+
: clampThinkingLevelForModel(scoped.model, scopedThinkingSelector),
|
|
700
728
|
fallbackMessage: undefined,
|
|
701
729
|
};
|
|
702
730
|
}
|
|
@@ -706,9 +734,7 @@ export async function findInitialModel(options: {
|
|
|
706
734
|
const found = modelRegistry.find(defaultProvider, defaultModelId);
|
|
707
735
|
if (found) {
|
|
708
736
|
model = found;
|
|
709
|
-
|
|
710
|
-
thinkingLevel = defaultThinkingSelector;
|
|
711
|
-
}
|
|
737
|
+
thinkingLevel = clampThinkingLevelForModel(found, defaultThinkingSelector);
|
|
712
738
|
return { model, thinkingLevel, fallbackMessage: undefined };
|
|
713
739
|
}
|
|
714
740
|
}
|
|
@@ -722,16 +748,16 @@ export async function findInitialModel(options: {
|
|
|
722
748
|
const defaultId = defaultModelPerProvider[provider];
|
|
723
749
|
const match = availableModels.find(m => m.provider === provider && m.id === defaultId);
|
|
724
750
|
if (match) {
|
|
725
|
-
return { model: match, thinkingLevel:
|
|
751
|
+
return { model: match, thinkingLevel: undefined, fallbackMessage: undefined };
|
|
726
752
|
}
|
|
727
753
|
}
|
|
728
754
|
|
|
729
755
|
// If no default found, use first available
|
|
730
|
-
return { model: availableModels[0], thinkingLevel:
|
|
756
|
+
return { model: availableModels[0], thinkingLevel: undefined, fallbackMessage: undefined };
|
|
731
757
|
}
|
|
732
758
|
|
|
733
759
|
// 5. No model found
|
|
734
|
-
return { model: undefined, thinkingLevel:
|
|
760
|
+
return { model: undefined, thinkingLevel: undefined, fallbackMessage: undefined };
|
|
735
761
|
}
|
|
736
762
|
|
|
737
763
|
/**
|