@oh-my-pi/pi-coding-agent 13.11.1 → 13.12.0
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 +58 -0
- package/package.json +7 -7
- package/src/capability/rule.ts +4 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/config-cli.ts +8 -3
- package/src/cli/shell-cli.ts +1 -1
- package/src/commands/config.ts +1 -1
- package/src/config/model-registry.ts +63 -10
- package/src/config/model-resolver.ts +84 -21
- package/src/config/settings-schema.ts +803 -637
- package/src/discovery/helpers.ts +8 -2
- package/src/exec/bash-executor.ts +62 -25
- package/src/extensibility/custom-tools/types.ts +2 -3
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/extensibility/hooks/types.ts +2 -0
- package/src/index.ts +6 -6
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/memories/index.ts +20 -7
- package/src/memories/storage.ts +46 -32
- package/src/modes/components/agent-dashboard.ts +23 -35
- package/src/modes/components/assistant-message.ts +25 -2
- package/src/modes/components/btw-panel.ts +104 -0
- package/src/modes/components/settings-defs.ts +1 -1
- package/src/modes/components/settings-selector.ts +6 -6
- package/src/modes/controllers/btw-controller.ts +193 -0
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +4 -0
- package/src/modes/controllers/input-controller.ts +10 -1
- package/src/modes/interactive-mode.ts +22 -0
- package/src/modes/prompt-action-autocomplete.ts +17 -3
- package/src/modes/rpc/rpc-client.ts +30 -19
- package/src/modes/theme/theme.ts +28 -36
- package/src/modes/types.ts +4 -0
- package/src/modes/utils/ui-helpers.ts +3 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/sdk.ts +17 -25
- package/src/session/agent-session.ts +65 -37
- package/src/session/blob-store.ts +32 -0
- package/src/session/compaction/compaction.ts +27 -6
- package/src/session/history-storage.ts +2 -2
- package/src/session/session-manager.ts +116 -44
- package/src/slash-commands/builtin-registry.ts +11 -0
- package/src/system-prompt.ts +4 -17
- package/src/task/agents.ts +1 -1
- package/src/task/index.ts +9 -8
- package/src/tools/browser.ts +11 -0
- package/src/tools/output-meta.ts +96 -3
- package/src/utils/title-generator.ts +70 -92
- package/src/utils/tools-manager.ts +1 -1
- package/src/web/scrapers/index.ts +7 -7
- package/src/web/scrapers/utils.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.12.0] - 2026-03-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added per-rule TTSR interrupt mode override via `interruptMode` field in rule frontmatter to allow fine-grained control over when TTSR interrupts stream processing
|
|
10
|
+
- Added `task` model role to allow configuring a dedicated model for subtask execution via `modelRoles.task` setting
|
|
11
|
+
- Added `moveCursorToMessageEnd` and `moveCursorToMessageStart` prompt actions to navigate to the beginning and end of the entire message
|
|
12
|
+
- Added support for provider-level `compat` configuration to apply OpenAI compatibility settings across all models from a provider
|
|
13
|
+
- Added `reasoningEffortMap` configuration option to map reasoning effort levels to provider-specific values
|
|
14
|
+
- Added support for `supportsUsageInStreaming`, `requiresToolResultName`, `requiresAssistantAfterToolResult`, `requiresThinkingAsText`, `thinkingFormat`, and `supportsStrictMode` OpenAI compatibility options
|
|
15
|
+
- Added support for provider-configurable `OpenAICompat.extraBody` to inject request-body fields for custom gateway/proxy routing
|
|
16
|
+
- Added `close()` method to SessionManager for properly closing persistent writers after flushing pending data
|
|
17
|
+
- Added `omp config init-xdg` command to initialize XDG Base Directory structure on Linux
|
|
18
|
+
- Added `getHistoryDbPath()`, `getModelDbPath()`, `getMemoriesDir()`, `getTerminalSessionsDir()` path helpers
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Path resolution on Linux redirects to XDG locations when `XDG_DATA_HOME` / `XDG_STATE_HOME` / `XDG_CACHE_HOME` environment variables are set
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Changed TTSR interrupt logic to respect per-rule `interruptMode` settings, falling back to global `ttsr.interruptMode` when rule-level override is not specified
|
|
27
|
+
- Reorganized settings tabs from 12 tabs (display, agent, input, tools, config, services, bash, lsp, ttsr, status) to 8 focused tabs (appearance, model, interaction, context, editing, tools, tasks, providers) for improved discoverability
|
|
28
|
+
- Consolidated status line settings into the Appearance tab instead of a separate Status tab
|
|
29
|
+
- Reorganized sampling parameters (temperature, topP, topK, minP, presencePenalty, repetitionPenalty) into the Model tab
|
|
30
|
+
- Moved edit tool settings (mode, fuzzyMatch, fuzzyThreshold, streamingAbort) to the Editing tab
|
|
31
|
+
- Moved read tool settings (readLineNumbers, readHashLines, read.defaultLimit) to the Editing tab
|
|
32
|
+
- Moved LSP settings (lsp.enabled, lsp.formatOnWrite, lsp.diagnosticsOnWrite, lsp.diagnosticsOnEdit) to the Editing tab
|
|
33
|
+
- Moved bash interceptor settings to the Editing tab
|
|
34
|
+
- Moved Python settings (python.toolMode, python.kernelMode, python.sharedGateway) to the Editing tab
|
|
35
|
+
- Moved task delegation settings (task.isolation.*, task.eager, task.maxConcurrency, task.maxRecursionDepth) to the Tasks tab
|
|
36
|
+
- Moved skill and command settings to the Tasks tab
|
|
37
|
+
- Moved provider selection settings (providers.webSearch, providers.codeSearch, providers.image, etc.) to the Providers tab
|
|
38
|
+
- Moved Exa settings to the Providers tab
|
|
39
|
+
- Moved secret handling settings to the Providers tab
|
|
40
|
+
- Moved speech-to-text settings to the Interaction tab
|
|
41
|
+
- Moved context promotion, compaction, branch summary, memories, and TTSR settings to the Context tab
|
|
42
|
+
- Updated tab icon symbols across unicode, nerd, and ASCII presets to match new tab structure
|
|
43
|
+
- Changed default agent model from `default` to `pi/task` to enable independent model configuration for subtasks
|
|
44
|
+
- Changed agent model resolution to support single-pattern inheritance fallback, allowing `pi/task` agents to inherit the active session model when the task role is unconfigured
|
|
45
|
+
- Changed system prompt to use ISO 8601 date format (YYYY-MM-DD) instead of locale-specific formatting
|
|
46
|
+
- Changed system prompt template to use `{{date}}` instead of `{{dateTime}}` for current date display
|
|
47
|
+
- Changed tool download timeout from 15 seconds to 120 seconds to accommodate slower network conditions
|
|
48
|
+
- Changed working directory paths in system prompt to use forward slashes for consistency across platforms
|
|
49
|
+
- Modified bash executor to fall back to one-shot shell execution after a persistent session hard timeout, preventing subsequent commands from hanging
|
|
50
|
+
|
|
51
|
+
### Removed
|
|
52
|
+
|
|
53
|
+
- Removed bash executor hard timeout recovery test file (functionality already documented in existing entries)
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- Fixed bash execution to fall back to one-shot shell runs after a persistent session hard timeout, preventing later commands from hanging until restart
|
|
58
|
+
- Fixed timeout handling in RpcClient to properly clear timeouts and prevent resource leaks
|
|
59
|
+
- Fixed AgentSession disposal to call SessionManager's `close()` method when available, ensuring proper cleanup of persistent writers
|
|
60
|
+
- Removed redundant `path.join()` call wrapping `getHistoryDbPath()` in history-storage.ts
|
|
61
|
+
|
|
5
62
|
## [13.11.1] - 2026-03-13
|
|
6
63
|
|
|
7
64
|
### Added
|
|
@@ -46,6 +103,7 @@
|
|
|
46
103
|
- Added `buildNamedToolChoice` utility function to build provider-aware tool choice constraints for named tools
|
|
47
104
|
- Support for comma/space-separated path lists in `find`, `grep`, `ast_grep`, and `ast_edit` tools (e.g., `apps/,packages/,phases/` or `apps/ packages/ phases/`)
|
|
48
105
|
- New `resolveMultiSearchPath` and `resolveMultiFindPattern` functions to handle multi-path search inputs with automatic common base path detection
|
|
106
|
+
- Added `display.showTokenUsage` setting to show per-turn token usage (input, output, cache) on assistant messages
|
|
49
107
|
|
|
50
108
|
### Changed
|
|
51
109
|
|
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.
|
|
4
|
+
"version": "13.12.0",
|
|
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.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.12.0",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.12.0",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.12.0",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.12.0",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.12.0",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.12.0",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
package/src/capability/rule.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface RuleFrontmatter {
|
|
|
20
20
|
condition?: string | string[];
|
|
21
21
|
/** New key for TTSR stream scope. */
|
|
22
22
|
scope?: string | string[];
|
|
23
|
+
/** Per-rule TTSR interrupt mode override. */
|
|
24
|
+
interruptMode?: "never" | "prose-only" | "tool-only" | "always";
|
|
23
25
|
[key: string]: unknown;
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -43,6 +45,8 @@ export interface Rule {
|
|
|
43
45
|
condition?: string[];
|
|
44
46
|
/** Optional stream scope tokens (for example: text, thinking, tool:edit(*.ts)). */
|
|
45
47
|
scope?: string[];
|
|
48
|
+
/** Per-rule TTSR interrupt mode override (falls back to global ttsr.interruptMode). */
|
|
49
|
+
interruptMode?: "never" | "prose-only" | "tool-only" | "always";
|
|
46
50
|
/** Source metadata */
|
|
47
51
|
_source: SourceMeta;
|
|
48
52
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
const APP_NAME = "omp";
|
|
6
|
+
|
|
7
|
+
export async function initXdg(): Promise<void> {
|
|
8
|
+
if (process.platform !== "linux") {
|
|
9
|
+
console.error("XDG directory setup is only supported on Linux.");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const dataHome = process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local/share");
|
|
14
|
+
const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local/state");
|
|
15
|
+
const cacheHome = process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
16
|
+
|
|
17
|
+
const dirs = [path.join(dataHome, APP_NAME), path.join(stateHome, APP_NAME), path.join(cacheHome, APP_NAME)];
|
|
18
|
+
|
|
19
|
+
for (const dir of dirs) {
|
|
20
|
+
await fs.mkdir(dir, { recursive: true });
|
|
21
|
+
console.log(`Created ${dir.replace(os.homedir(), "~")}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log("\nXDG directories initialized.");
|
|
25
|
+
console.log("Ensure XDG_DATA_HOME, XDG_STATE_HOME, and XDG_CACHE_HOME");
|
|
26
|
+
console.log("are set in your shell profile for omp to use them.");
|
|
27
|
+
}
|
package/src/cli/config-cli.ts
CHANGED
|
@@ -19,12 +19,13 @@ import {
|
|
|
19
19
|
} from "../config/settings";
|
|
20
20
|
import { SETTINGS_SCHEMA } from "../config/settings-schema";
|
|
21
21
|
import { theme } from "../modes/theme/theme";
|
|
22
|
+
import { initXdg } from "./commands/init-xdg";
|
|
22
23
|
|
|
23
24
|
// =============================================================================
|
|
24
25
|
// Types
|
|
25
26
|
// =============================================================================
|
|
26
27
|
|
|
27
|
-
export type ConfigAction = "list" | "get" | "set" | "reset" | "path";
|
|
28
|
+
export type ConfigAction = "list" | "get" | "set" | "reset" | "path" | "init-xdg";
|
|
28
29
|
|
|
29
30
|
export interface ConfigCommandArgs {
|
|
30
31
|
action: ConfigAction;
|
|
@@ -34,7 +35,6 @@ export interface ConfigCommandArgs {
|
|
|
34
35
|
json?: boolean;
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
|
-
|
|
38
38
|
// =============================================================================
|
|
39
39
|
// Setting Filtering
|
|
40
40
|
// =============================================================================
|
|
@@ -73,7 +73,7 @@ function getSettingValues(def: CliSettingDef): readonly string[] | undefined {
|
|
|
73
73
|
// Argument Parser
|
|
74
74
|
// =============================================================================
|
|
75
75
|
|
|
76
|
-
const VALID_ACTIONS: ConfigAction[] = ["list", "get", "set", "reset", "path"];
|
|
76
|
+
const VALID_ACTIONS: ConfigAction[] = ["list", "get", "set", "reset", "path", "init-xdg"];
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Parse config subcommand arguments.
|
|
@@ -251,6 +251,9 @@ export async function runConfigCommand(cmd: ConfigCommandArgs): Promise<void> {
|
|
|
251
251
|
case "path":
|
|
252
252
|
handlePath();
|
|
253
253
|
break;
|
|
254
|
+
case "init-xdg":
|
|
255
|
+
await initXdg();
|
|
256
|
+
break;
|
|
254
257
|
}
|
|
255
258
|
}
|
|
256
259
|
|
|
@@ -394,6 +397,7 @@ ${chalk.bold("Commands:")}
|
|
|
394
397
|
set <key> <value> Set a setting value
|
|
395
398
|
reset <key> Reset a setting to its default value
|
|
396
399
|
path Print the config directory path
|
|
400
|
+
init-xdg Initialize XDG Base Directory structure (Linux only)
|
|
397
401
|
|
|
398
402
|
${chalk.bold("Options:")}
|
|
399
403
|
--json Output as JSON
|
|
@@ -406,6 +410,7 @@ ${chalk.bold("Examples:")}
|
|
|
406
410
|
${APP_NAME} config set defaultThinkingLevel medium
|
|
407
411
|
${APP_NAME} config reset steeringMode
|
|
408
412
|
${APP_NAME} config list --json
|
|
413
|
+
${APP_NAME} config init-xdg
|
|
409
414
|
|
|
410
415
|
${chalk.bold("Boolean Values:")}
|
|
411
416
|
true, false, yes, no, on, off, 1, 0
|
package/src/cli/shell-cli.ts
CHANGED
package/src/commands/config.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
|
5
5
|
import { type ConfigAction, type ConfigCommandArgs, runConfigCommand } from "../cli/config-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
|
8
|
-
const ACTIONS: ConfigAction[] = ["list", "get", "set", "reset", "path"];
|
|
8
|
+
const ACTIONS: ConfigAction[] = ["list", "get", "set", "reset", "path", "init-xdg"];
|
|
9
9
|
|
|
10
10
|
export default class Config extends Command {
|
|
11
11
|
static description = "Manage configuration settings";
|
|
@@ -37,7 +37,7 @@ export function isAuthenticated(apiKey: string | undefined | null): apiKey is st
|
|
|
37
37
|
return Boolean(apiKey) && apiKey !== kNoAuth;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export type ModelRole = "default" | "smol" | "slow" | "vision" | "plan" | "commit";
|
|
40
|
+
export type ModelRole = "default" | "smol" | "slow" | "vision" | "plan" | "commit" | "task";
|
|
41
41
|
|
|
42
42
|
export interface ModelRoleInfo {
|
|
43
43
|
tag?: string;
|
|
@@ -52,9 +52,10 @@ export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
|
|
|
52
52
|
vision: { tag: "VISION", name: "Vision", color: "error" },
|
|
53
53
|
plan: { tag: "PLAN", name: "Architect", color: "muted" },
|
|
54
54
|
commit: { tag: "COMMIT", name: "Commit", color: "dim" },
|
|
55
|
+
task: { tag: "TASK", name: "Subtask", color: "muted" },
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "vision", "plan", "commit"];
|
|
58
|
+
export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "vision", "plan", "commit", "task"];
|
|
58
59
|
|
|
59
60
|
const OpenRouterRoutingSchema = Type.Object({
|
|
60
61
|
only: Type.Optional(Type.Array(Type.String())),
|
|
@@ -68,13 +69,36 @@ const VercelGatewayRoutingSchema = Type.Object({
|
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
// Schema for OpenAI compatibility settings
|
|
72
|
+
const ReasoningEffortMapSchema = Type.Object({
|
|
73
|
+
minimal: Type.Optional(Type.String()),
|
|
74
|
+
low: Type.Optional(Type.String()),
|
|
75
|
+
medium: Type.Optional(Type.String()),
|
|
76
|
+
high: Type.Optional(Type.String()),
|
|
77
|
+
xhigh: Type.Optional(Type.String()),
|
|
78
|
+
});
|
|
79
|
+
|
|
71
80
|
const OpenAICompatSchema = Type.Object({
|
|
72
81
|
supportsStore: Type.Optional(Type.Boolean()),
|
|
73
82
|
supportsDeveloperRole: Type.Optional(Type.Boolean()),
|
|
74
83
|
supportsReasoningEffort: Type.Optional(Type.Boolean()),
|
|
84
|
+
reasoningEffortMap: Type.Optional(ReasoningEffortMapSchema),
|
|
75
85
|
maxTokensField: Type.Optional(Type.Union([Type.Literal("max_completion_tokens"), Type.Literal("max_tokens")])),
|
|
86
|
+
supportsUsageInStreaming: Type.Optional(Type.Boolean()),
|
|
87
|
+
requiresToolResultName: Type.Optional(Type.Boolean()),
|
|
88
|
+
requiresAssistantAfterToolResult: Type.Optional(Type.Boolean()),
|
|
89
|
+
requiresThinkingAsText: Type.Optional(Type.Boolean()),
|
|
90
|
+
thinkingFormat: Type.Optional(
|
|
91
|
+
Type.Union([
|
|
92
|
+
Type.Literal("openai"),
|
|
93
|
+
Type.Literal("zai"),
|
|
94
|
+
Type.Literal("qwen"),
|
|
95
|
+
Type.Literal("qwen-chat-template"),
|
|
96
|
+
]),
|
|
97
|
+
),
|
|
76
98
|
openRouterRouting: Type.Optional(OpenRouterRoutingSchema),
|
|
77
99
|
vercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema),
|
|
100
|
+
extraBody: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
101
|
+
supportsStrictMode: Type.Optional(Type.Boolean()),
|
|
78
102
|
});
|
|
79
103
|
|
|
80
104
|
const EffortSchema = Type.Union([
|
|
@@ -180,6 +204,7 @@ const ProviderConfigSchema = Type.Object({
|
|
|
180
204
|
]),
|
|
181
205
|
),
|
|
182
206
|
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
207
|
+
compat: Type.Optional(OpenAICompatSchema),
|
|
183
208
|
authHeader: Type.Optional(Type.Boolean()),
|
|
184
209
|
auth: Type.Optional(ProviderAuthSchema),
|
|
185
210
|
discovery: Type.Optional(ProviderDiscoverySchema),
|
|
@@ -212,6 +237,7 @@ interface ProviderValidationConfig {
|
|
|
212
237
|
auth?: ProviderAuthMode;
|
|
213
238
|
oauthConfigured?: boolean;
|
|
214
239
|
discovery?: ProviderDiscovery;
|
|
240
|
+
compat?: Model<Api>["compat"];
|
|
215
241
|
modelOverrides?: Record<string, unknown>;
|
|
216
242
|
models: ProviderValidationModel[];
|
|
217
243
|
}
|
|
@@ -227,9 +253,9 @@ function validateProviderConfiguration(
|
|
|
227
253
|
if (models.length === 0) {
|
|
228
254
|
if (mode === "models-config") {
|
|
229
255
|
const hasModelOverrides = config.modelOverrides && Object.keys(config.modelOverrides).length > 0;
|
|
230
|
-
if (!config.baseUrl && !hasModelOverrides && !config.discovery) {
|
|
256
|
+
if (!config.baseUrl && !config.compat && !hasModelOverrides && !config.discovery) {
|
|
231
257
|
throw new Error(
|
|
232
|
-
`Provider ${providerName}: must specify "baseUrl", "modelOverrides", "discovery", or "models"
|
|
258
|
+
`Provider ${providerName}: must specify "baseUrl", "compat", "modelOverrides", "discovery", or "models"`,
|
|
233
259
|
);
|
|
234
260
|
}
|
|
235
261
|
}
|
|
@@ -288,6 +314,7 @@ export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsCon
|
|
|
288
314
|
api: providerConfig.api as Api | undefined,
|
|
289
315
|
auth: (providerConfig.auth ?? "apiKey") as ProviderAuthMode,
|
|
290
316
|
discovery: providerConfig.discovery as ProviderDiscovery | undefined,
|
|
317
|
+
compat: providerConfig.compat,
|
|
291
318
|
modelOverrides: providerConfig.modelOverrides,
|
|
292
319
|
models: (providerConfig.models ?? []) as ProviderValidationModel[],
|
|
293
320
|
},
|
|
@@ -297,11 +324,12 @@ export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsCon
|
|
|
297
324
|
},
|
|
298
325
|
);
|
|
299
326
|
|
|
300
|
-
/** Provider override config (baseUrl, headers, apiKey) without custom models */
|
|
327
|
+
/** Provider override config (baseUrl, headers, apiKey, compat) without custom models */
|
|
301
328
|
interface ProviderOverride {
|
|
302
329
|
baseUrl?: string;
|
|
303
330
|
headers?: Record<string, string>;
|
|
304
331
|
apiKey?: string;
|
|
332
|
+
compat?: Model<Api>["compat"];
|
|
305
333
|
}
|
|
306
334
|
|
|
307
335
|
interface DiscoveryProviderConfig {
|
|
@@ -309,6 +337,7 @@ interface DiscoveryProviderConfig {
|
|
|
309
337
|
api: Api;
|
|
310
338
|
baseUrl?: string;
|
|
311
339
|
headers?: Record<string, string>;
|
|
340
|
+
compat?: Model<Api>["compat"];
|
|
312
341
|
discovery: ProviderDiscovery;
|
|
313
342
|
optional?: boolean;
|
|
314
343
|
}
|
|
@@ -397,12 +426,18 @@ function mergeCompat(
|
|
|
397
426
|
const base = baseCompat ?? {};
|
|
398
427
|
const override = overrideCompat;
|
|
399
428
|
const merged: NonNullable<Model<Api>["compat"]> = { ...base, ...override };
|
|
429
|
+
if (baseCompat?.reasoningEffortMap || overrideCompat.reasoningEffortMap) {
|
|
430
|
+
merged.reasoningEffortMap = { ...baseCompat?.reasoningEffortMap, ...overrideCompat.reasoningEffortMap };
|
|
431
|
+
}
|
|
400
432
|
if (baseCompat?.openRouterRouting || overrideCompat.openRouterRouting) {
|
|
401
433
|
merged.openRouterRouting = { ...baseCompat?.openRouterRouting, ...overrideCompat.openRouterRouting };
|
|
402
434
|
}
|
|
403
435
|
if (baseCompat?.vercelGatewayRouting || overrideCompat.vercelGatewayRouting) {
|
|
404
436
|
merged.vercelGatewayRouting = { ...baseCompat?.vercelGatewayRouting, ...overrideCompat.vercelGatewayRouting };
|
|
405
437
|
}
|
|
438
|
+
if (baseCompat?.extraBody || overrideCompat.extraBody) {
|
|
439
|
+
merged.extraBody = { ...baseCompat?.extraBody, ...overrideCompat.extraBody };
|
|
440
|
+
}
|
|
406
441
|
return merged;
|
|
407
442
|
}
|
|
408
443
|
|
|
@@ -475,6 +510,7 @@ function buildCustomModel(
|
|
|
475
510
|
providerHeaders: Record<string, string> | undefined,
|
|
476
511
|
providerApiKey: string | undefined,
|
|
477
512
|
authHeader: boolean | undefined,
|
|
513
|
+
providerCompat: Model<Api>["compat"] | undefined,
|
|
478
514
|
modelDef: CustomModelDefinitionLike,
|
|
479
515
|
options: CustomModelBuildOptions,
|
|
480
516
|
): Model<Api> | undefined {
|
|
@@ -496,7 +532,7 @@ function buildCustomModel(
|
|
|
496
532
|
contextWindow: modelDef.contextWindow ?? (withDefaults ? 128000 : undefined),
|
|
497
533
|
maxTokens: modelDef.maxTokens ?? (withDefaults ? 16384 : undefined),
|
|
498
534
|
headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
|
|
499
|
-
compat: modelDef.compat,
|
|
535
|
+
compat: mergeCompat(providerCompat, modelDef.compat),
|
|
500
536
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
501
537
|
premiumMultiplier: modelDef.premiumMultiplier,
|
|
502
538
|
} as Model<Api>);
|
|
@@ -630,6 +666,7 @@ export class ModelRegistry {
|
|
|
630
666
|
...model,
|
|
631
667
|
baseUrl: providerOverride.baseUrl ?? model.baseUrl,
|
|
632
668
|
headers: providerOverride.headers ? { ...model.headers, ...providerOverride.headers } : model.headers,
|
|
669
|
+
compat: mergeCompat(model.compat, providerOverride.compat),
|
|
633
670
|
};
|
|
634
671
|
}
|
|
635
672
|
const modelOverride = perModelOverrides?.get(m.id);
|
|
@@ -669,7 +706,10 @@ export class ModelRegistry {
|
|
|
669
706
|
});
|
|
670
707
|
continue;
|
|
671
708
|
}
|
|
672
|
-
const models = this.#applyProviderModelOverrides(
|
|
709
|
+
const models = this.#applyProviderModelOverrides(
|
|
710
|
+
providerConfig.provider,
|
|
711
|
+
this.#applyProviderCompat(providerConfig.compat, cache.models),
|
|
712
|
+
);
|
|
673
713
|
cachedModels.push(...models);
|
|
674
714
|
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
675
715
|
provider: providerConfig.provider,
|
|
@@ -683,6 +723,11 @@ export class ModelRegistry {
|
|
|
683
723
|
return cachedModels;
|
|
684
724
|
}
|
|
685
725
|
|
|
726
|
+
#applyProviderCompat(compat: Model<Api>["compat"] | undefined, models: Model<Api>[]): Model<Api>[] {
|
|
727
|
+
if (!compat) return models;
|
|
728
|
+
return models.map(model => ({ ...model, compat: mergeCompat(model.compat, compat) }));
|
|
729
|
+
}
|
|
730
|
+
|
|
686
731
|
#addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
|
|
687
732
|
if (!configuredProviders.has("ollama")) {
|
|
688
733
|
this.#discoverableProviders.push({
|
|
@@ -752,12 +797,13 @@ export class ModelRegistry {
|
|
|
752
797
|
const configuredProviders = new Set(Object.keys(value.providers));
|
|
753
798
|
|
|
754
799
|
for (const [providerName, providerConfig] of Object.entries(value.providers)) {
|
|
755
|
-
// Always set overrides when baseUrl/headers present
|
|
756
|
-
if (providerConfig.baseUrl || providerConfig.headers || providerConfig.apiKey) {
|
|
800
|
+
// Always set overrides when baseUrl/headers/apiKey/compat are present
|
|
801
|
+
if (providerConfig.baseUrl || providerConfig.headers || providerConfig.apiKey || providerConfig.compat) {
|
|
757
802
|
overrides.set(providerName, {
|
|
758
803
|
baseUrl: providerConfig.baseUrl,
|
|
759
804
|
headers: providerConfig.headers,
|
|
760
805
|
apiKey: providerConfig.apiKey,
|
|
806
|
+
compat: providerConfig.compat,
|
|
761
807
|
});
|
|
762
808
|
}
|
|
763
809
|
|
|
@@ -772,6 +818,7 @@ export class ModelRegistry {
|
|
|
772
818
|
api: providerConfig.api as Api,
|
|
773
819
|
baseUrl: providerConfig.baseUrl,
|
|
774
820
|
headers: providerConfig.headers,
|
|
821
|
+
compat: providerConfig.compat,
|
|
775
822
|
discovery: providerConfig.discovery,
|
|
776
823
|
optional: false,
|
|
777
824
|
});
|
|
@@ -906,7 +953,10 @@ export class ModelRegistry {
|
|
|
906
953
|
if (discoveryError) {
|
|
907
954
|
this.#warnProviderDiscoveryFailure(providerConfig, discoveryError);
|
|
908
955
|
}
|
|
909
|
-
return this.#applyProviderModelOverrides(
|
|
956
|
+
return this.#applyProviderModelOverrides(
|
|
957
|
+
providerId,
|
|
958
|
+
this.#applyProviderCompat(providerConfig.compat, result.models),
|
|
959
|
+
);
|
|
910
960
|
}
|
|
911
961
|
|
|
912
962
|
#discoverModelsByProviderType(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
@@ -1302,6 +1352,7 @@ export class ModelRegistry {
|
|
|
1302
1352
|
providerConfig.headers,
|
|
1303
1353
|
providerConfig.apiKey,
|
|
1304
1354
|
providerConfig.authHeader,
|
|
1355
|
+
providerConfig.compat,
|
|
1305
1356
|
modelDef as CustomModelDefinitionLike,
|
|
1306
1357
|
{ useDefaults: true },
|
|
1307
1358
|
);
|
|
@@ -1463,6 +1514,7 @@ export class ModelRegistry {
|
|
|
1463
1514
|
config.headers,
|
|
1464
1515
|
config.apiKey,
|
|
1465
1516
|
config.authHeader,
|
|
1517
|
+
config.compat,
|
|
1466
1518
|
modelDef as CustomModelDefinitionLike,
|
|
1467
1519
|
{ useDefaults: false },
|
|
1468
1520
|
);
|
|
@@ -1506,6 +1558,7 @@ export interface ProviderConfigInput {
|
|
|
1506
1558
|
api?: Api;
|
|
1507
1559
|
streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
1508
1560
|
headers?: Record<string, string>;
|
|
1561
|
+
compat?: Model<Api>["compat"];
|
|
1509
1562
|
authHeader?: boolean;
|
|
1510
1563
|
oauth?: {
|
|
1511
1564
|
name: string;
|
|
@@ -318,16 +318,41 @@ export function parseModelPattern(
|
|
|
318
318
|
const PREFIX_MODEL_ROLE = "pi/";
|
|
319
319
|
const DEFAULT_MODEL_ROLE = "default";
|
|
320
320
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
value = value.slice(PREFIX_MODEL_ROLE.length);
|
|
321
|
+
function getModelRoleAlias(value: string): ModelRole | undefined {
|
|
322
|
+
const normalized = value.trim();
|
|
323
|
+
if (!normalized.startsWith(PREFIX_MODEL_ROLE)) return undefined;
|
|
324
|
+
|
|
325
|
+
const candidate = normalized.slice(PREFIX_MODEL_ROLE.length);
|
|
326
|
+
for (const role of MODEL_ROLE_IDS) {
|
|
327
|
+
if (candidate === role) return role;
|
|
329
328
|
}
|
|
330
|
-
return
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function normalizeModelPatternList(value: string | string[] | undefined): string[] {
|
|
333
|
+
if (!value) return [];
|
|
334
|
+
const patterns = Array.isArray(value) ? value : value.split(",");
|
|
335
|
+
return patterns.map(pattern => pattern.trim()).filter(Boolean);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function isSessionInheritedAgentPattern(value: string): boolean {
|
|
339
|
+
return value === DEFAULT_MODEL_ROLE || value === `${PREFIX_MODEL_ROLE}${DEFAULT_MODEL_ROLE}` || value === "pi/task";
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function resolveConfiguredRolePattern(value: string, settings?: Settings): string | undefined {
|
|
343
|
+
const normalized = value.trim();
|
|
344
|
+
if (!normalized) return undefined;
|
|
345
|
+
|
|
346
|
+
const lastColonIndex = normalized.lastIndexOf(":");
|
|
347
|
+
const thinkingLevel =
|
|
348
|
+
lastColonIndex > PREFIX_MODEL_ROLE.length ? parseThinkingLevel(normalized.slice(lastColonIndex + 1)) : undefined;
|
|
349
|
+
const aliasCandidate = thinkingLevel ? normalized.slice(0, lastColonIndex) : normalized;
|
|
350
|
+
const role = getModelRoleAlias(aliasCandidate);
|
|
351
|
+
if (!role) return normalized;
|
|
352
|
+
|
|
353
|
+
const configured = settings?.getModelRole(role)?.trim();
|
|
354
|
+
if (!configured) return undefined;
|
|
355
|
+
return thinkingLevel ? `${configured}:${thinkingLevel}` : configured;
|
|
331
356
|
}
|
|
332
357
|
|
|
333
358
|
/**
|
|
@@ -335,11 +360,48 @@ export function isDefaultModelAlias(value: string | string[] | undefined): boole
|
|
|
335
360
|
*/
|
|
336
361
|
export function expandRoleAlias(value: string, settings?: Settings): string {
|
|
337
362
|
const normalized = value.trim();
|
|
338
|
-
if (normalized ===
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
363
|
+
if (normalized === DEFAULT_MODEL_ROLE) {
|
|
364
|
+
return settings?.getModelRole("default") ?? value;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const resolved = resolveConfiguredRolePattern(value, settings);
|
|
368
|
+
return resolved ?? value;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function resolveConfiguredModelPatterns(value: string | string[] | undefined, settings?: Settings): string[] {
|
|
372
|
+
const patterns = normalizeModelPatternList(value);
|
|
373
|
+
return patterns.flatMap(pattern => {
|
|
374
|
+
const resolved = resolveConfiguredRolePattern(pattern, settings);
|
|
375
|
+
return resolved ? [resolved] : [];
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export interface AgentModelPatternResolutionOptions {
|
|
380
|
+
settingsOverride?: string | string[];
|
|
381
|
+
agentModel?: string | string[];
|
|
382
|
+
settings?: Settings;
|
|
383
|
+
activeModelPattern?: string;
|
|
384
|
+
fallbackModelPattern?: string;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function resolveAgentModelPatterns(options: AgentModelPatternResolutionOptions): string[] {
|
|
388
|
+
const { settingsOverride, agentModel, settings, activeModelPattern, fallbackModelPattern } = options;
|
|
389
|
+
|
|
390
|
+
const overridePatterns = resolveConfiguredModelPatterns(settingsOverride, settings);
|
|
391
|
+
if (overridePatterns.length > 0) return overridePatterns;
|
|
392
|
+
|
|
393
|
+
const normalizedAgentPatterns = normalizeModelPatternList(agentModel);
|
|
394
|
+
const configuredAgentPatterns = resolveConfiguredModelPatterns(agentModel, settings);
|
|
395
|
+
const singleAgentPattern = normalizedAgentPatterns.length === 1 ? normalizedAgentPatterns[0] : undefined;
|
|
396
|
+
const agentInheritsSessionModel = singleAgentPattern ? isSessionInheritedAgentPattern(singleAgentPattern) : false;
|
|
397
|
+
if (configuredAgentPatterns.length > 0) {
|
|
398
|
+
if (!agentInheritsSessionModel) return configuredAgentPatterns;
|
|
399
|
+
if (singleAgentPattern === "pi/task") return configuredAgentPatterns;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const fallback =
|
|
403
|
+
activeModelPattern?.trim() || fallbackModelPattern?.trim() || settings?.getModelRole("default")?.trim() || "";
|
|
404
|
+
return resolveConfiguredModelPatterns(fallback, settings);
|
|
343
405
|
}
|
|
344
406
|
|
|
345
407
|
/**
|
|
@@ -367,13 +429,14 @@ export function resolveModelRoleValue(
|
|
|
367
429
|
}
|
|
368
430
|
|
|
369
431
|
const lastColonIndex = normalized.lastIndexOf(":");
|
|
370
|
-
const
|
|
371
|
-
lastColonIndex > PREFIX_MODEL_ROLE.length
|
|
372
|
-
const aliasCandidate =
|
|
373
|
-
const effectivePattern =
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
432
|
+
const thinkingSelector =
|
|
433
|
+
lastColonIndex > PREFIX_MODEL_ROLE.length ? parseThinkingLevel(normalized.slice(lastColonIndex + 1)) : undefined;
|
|
434
|
+
const aliasCandidate = thinkingSelector ? normalized.slice(0, lastColonIndex) : normalized;
|
|
435
|
+
const effectivePattern = resolveConfiguredRolePattern(aliasCandidate, options?.settings);
|
|
436
|
+
if (!effectivePattern) {
|
|
437
|
+
return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
|
|
438
|
+
}
|
|
439
|
+
const patternWithSuffix = thinkingSelector ? `${effectivePattern}:${thinkingSelector}` : effectivePattern;
|
|
377
440
|
const { model, thinkingLevel, warning, explicitThinkingLevel } = parseModelPattern(
|
|
378
441
|
patternWithSuffix,
|
|
379
442
|
availableModels,
|