@oh-my-pi/pi-coding-agent 16.1.23 → 16.2.1
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 +82 -0
- package/dist/cli.js +3785 -3695
- package/dist/types/advisor/__tests__/emission-guard.test.d.ts +1 -0
- package/dist/types/advisor/emission-guard.d.ts +73 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/runtime.d.ts +7 -0
- package/dist/types/advisor/watchdog.d.ts +13 -0
- package/dist/types/cli/flag-tables.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +1 -1
- package/dist/types/collab/display-name.d.ts +3 -0
- package/dist/types/collab/host.d.ts +0 -2
- package/dist/types/commands/worktree.d.ts +0 -3
- package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
- package/dist/types/config/model-discovery.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +6 -2
- package/dist/types/config/model-resolver.d.ts +12 -0
- package/dist/types/config/models-config-schema.d.ts +29 -2
- package/dist/types/config/models-config.d.ts +22 -1
- package/dist/types/config/settings-schema.d.ts +91 -21
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/dap/config.d.ts +1 -1
- package/dist/types/edit/hashline/filesystem.d.ts +4 -2
- package/dist/types/edit/renderer.d.ts +4 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -17
- package/dist/types/extensibility/hooks/types.d.ts +10 -10
- package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
- package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
- package/dist/types/internal-urls/router.d.ts +1 -1
- package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
- package/dist/types/internal-urls/types.d.ts +19 -2
- package/dist/types/irc/bus.d.ts +6 -0
- package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -0
- package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/components/move-overlay.d.ts +23 -0
- package/dist/types/modes/components/plugin-selector.d.ts +2 -1
- package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
- package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +4 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/show-images-selector.d.ts +2 -1
- package/dist/types/modes/components/status-line/component.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/theme-selector.d.ts +2 -1
- package/dist/types/modes/components/thinking-selector.d.ts +2 -1
- package/dist/types/modes/controllers/command-controller.d.ts +10 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -1
- package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
- package/dist/types/modes/running-subagent-badge.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +9 -1
- package/dist/types/sdk.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +8 -1
- package/dist/types/session/blob-store.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +13 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
- package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
- package/dist/types/ssh/connection-manager.d.ts +2 -0
- package/dist/types/ssh/file-transfer.d.ts +79 -0
- package/dist/types/ssh/utils.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +3 -0
- package/dist/types/task/executor.d.ts +16 -0
- package/dist/types/tiny/text.d.ts +1 -1
- package/dist/types/tools/builtin-names.d.ts +5 -1
- package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
- package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tools/path-utils.d.ts +29 -0
- package/dist/types/tools/read.d.ts +2 -2
- package/dist/types/tools/render-utils.d.ts +8 -0
- package/dist/types/tools/renderers.d.ts +11 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo.d.ts +0 -16
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/active-repo-context.d.ts +8 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/utils/markit-cache.d.ts +23 -0
- package/dist/types/utils/markit.d.ts +5 -1
- package/dist/types/utils/prompt-path.d.ts +1 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
- package/dist/types/web/search/providers/xai.d.ts +13 -0
- package/dist/types/web/search/types.d.ts +18 -2
- package/package.json +30 -15
- package/scripts/build-binary.ts +9 -9
- package/scripts/bundle-dist.ts +2 -2
- package/src/advisor/__tests__/advisor.test.ts +61 -4
- package/src/advisor/__tests__/emission-guard.test.ts +147 -0
- package/src/advisor/advise-tool.ts +1 -1
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +11 -0
- package/src/advisor/watchdog.ts +27 -1
- package/src/cli/args.ts +5 -2
- package/src/cli/auth-broker-cli.ts +17 -0
- package/src/cli/config-cli.ts +4 -0
- package/src/cli/flag-tables.ts +7 -4
- package/src/cli/gallery-cli.ts +14 -2
- package/src/cli/gallery-fixtures/edit.ts +60 -0
- package/src/cli/gallery-fixtures/fs.ts +17 -17
- package/src/cli/gallery-fixtures/search.ts +4 -4
- package/src/cli/usage-cli.ts +20 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +5 -1
- package/src/collab/host.ts +2 -13
- package/src/commands/worktree.ts +6 -0
- package/src/config/inline-tool-descriptors-mode.ts +27 -0
- package/src/config/model-discovery.ts +77 -8
- package/src/config/model-registry.ts +89 -11
- package/src/config/model-resolver.ts +39 -1
- package/src/config/models-config-schema.ts +41 -5
- package/src/config/models-config.ts +4 -1
- package/src/config/settings-schema.ts +85 -27
- package/src/config/settings.ts +172 -1
- package/src/cursor.ts +1 -1
- package/src/dap/config.ts +152 -8
- package/src/dap/session.ts +1 -1
- package/src/discovery/claude-plugins.ts +3 -2
- package/src/discovery/helpers.ts +3 -1
- package/src/edit/hashline/diff.ts +14 -3
- package/src/edit/hashline/execute.ts +42 -6
- package/src/edit/hashline/filesystem.ts +38 -2
- package/src/edit/index.ts +1 -0
- package/src/edit/modes/patch.ts +11 -1
- package/src/edit/renderer.ts +140 -13
- package/src/eval/__tests__/agent-bridge.test.ts +101 -0
- package/src/eval/agent-bridge.ts +26 -3
- package/src/eval/js/tool-bridge.ts +2 -2
- package/src/export/html/index.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/export/html/tool-views.generated.js +20 -20
- package/src/extensibility/extensions/types.ts +22 -22
- package/src/extensibility/hooks/types.ts +11 -11
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +8 -3
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +25 -10
- package/src/extensibility/plugins/marketplace/manager.ts +22 -0
- package/src/extensibility/plugins/marketplace/types.ts +1 -0
- package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
- package/src/internal-urls/docs-index.generated.txt +2 -2
- package/src/internal-urls/docs-index.ts +2 -3
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/registry-helpers.ts +19 -4
- package/src/internal-urls/router.ts +5 -3
- package/src/internal-urls/ssh-protocol.ts +367 -0
- package/src/internal-urls/types.ts +19 -2
- package/src/irc/bus.ts +11 -3
- package/src/mcp/tool-bridge.ts +32 -2
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-agent.ts +14 -1
- package/src/modes/acp/acp-event-mapper.ts +91 -27
- package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
- package/src/modes/components/agent-hub.ts +3 -0
- package/src/modes/components/agent-transcript-viewer.ts +9 -7
- package/src/modes/components/custom-editor.ts +90 -5
- package/src/modes/components/move-overlay.ts +282 -0
- package/src/modes/components/plan-review-overlay.ts +35 -35
- package/src/modes/components/plugin-selector.ts +6 -1
- package/src/modes/components/queue-mode-selector.ts +6 -1
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/session-selector.ts +11 -10
- package/src/modes/components/settings-defs.ts +14 -1
- package/src/modes/components/settings-selector.ts +196 -29
- package/src/modes/components/show-images-selector.ts +6 -1
- package/src/modes/components/status-line/component.ts +108 -28
- package/src/modes/components/status-line/segments.ts +5 -1
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/theme-selector.ts +6 -1
- package/src/modes/components/thinking-selector.ts +6 -1
- package/src/modes/components/tool-execution.ts +25 -9
- package/src/modes/components/tree-selector.ts +5 -5
- package/src/modes/controllers/command-controller.ts +140 -47
- package/src/modes/controllers/event-controller.ts +59 -3
- package/src/modes/controllers/input-controller.ts +68 -2
- package/src/modes/controllers/selector-controller.ts +5 -2
- package/src/modes/controllers/streaming-reveal.ts +17 -0
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +116 -50
- package/src/modes/internal-url-autocomplete.ts +17 -2
- package/src/modes/prompt-action-autocomplete.ts +3 -1
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
- package/src/modes/setup-wizard/scenes/providers.ts +2 -1
- package/src/modes/setup-wizard/scenes/theme.ts +6 -12
- package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
- package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
- package/src/modes/theme/theme.ts +9 -1
- package/src/modes/types.ts +9 -1
- package/src/modes/utils/interactive-context-helpers.ts +1 -1
- package/src/priority.json +15 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/context-files.md +8 -0
- package/src/prompts/advisor/system.md +21 -7
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/plan-mode-active.md +3 -3
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +6 -6
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/{find.md → glob.md} +1 -1
- package/src/prompts/tools/{search.md → grep.md} +3 -3
- package/src/prompts/tools/read.md +4 -2
- package/src/sdk.ts +65 -25
- package/src/session/agent-session.ts +519 -132
- package/src/session/blob-store.ts +24 -0
- package/src/session/session-history-format.ts +2 -2
- package/src/session/session-manager.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +138 -20
- package/src/slash-commands/helpers/usage-report.ts +23 -2
- package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
- package/src/ssh/connection-manager.ts +12 -15
- package/src/ssh/file-transfer.ts +209 -0
- package/src/ssh/utils.ts +24 -0
- package/src/system-prompt.ts +56 -28
- package/src/task/executor.ts +96 -36
- package/src/task/index.ts +3 -3
- package/src/task/render.ts +14 -13
- package/src/task/worktree.ts +38 -1
- package/src/tiny/text.ts +49 -4
- package/src/tiny/worker.ts +3 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/builtin-names.ts +26 -2
- package/src/tools/{find.ts → glob.ts} +48 -42
- package/src/tools/{search.ts → grep.ts} +311 -129
- package/src/tools/index.ts +13 -14
- package/src/tools/irc.ts +6 -2
- package/src/tools/path-utils.ts +64 -1
- package/src/tools/read.ts +35 -6
- package/src/tools/render-utils.ts +14 -0
- package/src/tools/renderers.ts +15 -4
- package/src/tools/ssh.ts +17 -2
- package/src/tools/todo.ts +4 -26
- package/src/tools/write.ts +25 -9
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/image-resize.ts +88 -7
- package/src/utils/lang-from-path.ts +3 -3
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +86 -18
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/title-generator.ts +1 -1
- package/src/web/search/provider.ts +54 -34
- package/src/web/search/providers/duckduckgo.ts +140 -0
- package/src/web/search/providers/firecrawl.ts +144 -0
- package/src/web/search/providers/tinyfish.ts +159 -0
- package/src/web/search/providers/xai.ts +292 -0
- package/src/web/search/providers/zai.ts +142 -56
- package/src/web/search/types.ts +6 -2
package/src/collab/guest.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { SessionEntry } from "../session/session-entries";
|
|
|
24
24
|
import { shouldDisableReasoning, toReasoningEffort } from "../thinking";
|
|
25
25
|
import { setSessionTerminalTitle } from "../utils/title-generator";
|
|
26
26
|
import { importRoomKey } from "./crypto";
|
|
27
|
-
import { collabDisplayName } from "./
|
|
27
|
+
import { collabDisplayName } from "./display-name";
|
|
28
28
|
import {
|
|
29
29
|
type AgentSnapshot,
|
|
30
30
|
COLLAB_PROTO,
|
|
@@ -276,6 +276,7 @@ export class CollabGuestLink {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
this.#ctx.collabGuest = this;
|
|
279
|
+
this.#ctx.syncRunningSubagentBadge();
|
|
279
280
|
}
|
|
280
281
|
|
|
281
282
|
/** User-initiated leave (or post-disconnect cleanup): restore the previous session. */
|
|
@@ -357,6 +358,7 @@ export class CollabGuestLink {
|
|
|
357
358
|
this.#applyHostState(pending.state);
|
|
358
359
|
this.#ctx.resetObserverRegistry();
|
|
359
360
|
this.#applyAgentSnapshots(pending.agents);
|
|
361
|
+
this.#ctx.syncRunningSubagentBadge();
|
|
360
362
|
this.#assistantStreamSynced = false;
|
|
361
363
|
setSessionTerminalTitle(pending.state.sessionName ?? pending.header.title, pending.state.cwd);
|
|
362
364
|
this.#ctx.chatContainer.clear();
|
|
@@ -440,6 +442,7 @@ export class CollabGuestLink {
|
|
|
440
442
|
break;
|
|
441
443
|
case "agents":
|
|
442
444
|
this.#applyAgentSnapshots(frame.agents);
|
|
445
|
+
this.#ctx.syncRunningSubagentBadge();
|
|
443
446
|
break;
|
|
444
447
|
case "transcript": {
|
|
445
448
|
const resolve = this.#pendingTranscripts.get(frame.reqId);
|
|
@@ -573,6 +576,7 @@ export class CollabGuestLink {
|
|
|
573
576
|
this.#ctx.statusLine.setCollabStatus(null);
|
|
574
577
|
this.#flushPendingTranscripts();
|
|
575
578
|
this.#clearAgentMirror();
|
|
579
|
+
this.#ctx.syncRunningSubagentBadge();
|
|
576
580
|
this.#ctx.resetObserverRegistry();
|
|
577
581
|
this.#clearTransientUi();
|
|
578
582
|
// Replica file stays on disk: it is a valid session file outside the
|
package/src/collab/host.ts
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
import { timingSafeEqual } from "node:crypto";
|
|
13
13
|
import * as fs from "node:fs/promises";
|
|
14
|
-
import * as os from "node:os";
|
|
15
14
|
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
16
15
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
17
16
|
import type { BusChannel, AgentEvent as WireAgentEvent, SessionEntry as WireSessionEntry } from "@oh-my-pi/pi-wire";
|
|
@@ -21,8 +20,9 @@ import { type AgentRef, AgentRegistry } from "../registry/agent-registry";
|
|
|
21
20
|
import type { AgentSessionEvent } from "../session/agent-session";
|
|
22
21
|
import { stripImagesFromMessage, USER_INTERRUPT_LABEL } from "../session/messages";
|
|
23
22
|
import type { SessionEntry as StoredSessionEntry } from "../session/session-entries";
|
|
24
|
-
import { TASK_SUBAGENT_LIFECYCLE_CHANNEL, TASK_SUBAGENT_PROGRESS_CHANNEL } from "../task";
|
|
23
|
+
import { TASK_SUBAGENT_LIFECYCLE_CHANNEL, TASK_SUBAGENT_PROGRESS_CHANNEL } from "../task/types";
|
|
25
24
|
import { generateRoomKey, generateWriteToken, importRoomKey } from "./crypto";
|
|
25
|
+
import { collabDisplayName } from "./display-name";
|
|
26
26
|
import {
|
|
27
27
|
type AgentSnapshot,
|
|
28
28
|
COLLAB_PROMPT_MESSAGE_TYPE,
|
|
@@ -102,17 +102,6 @@ const TRANSCRIPT_READ_CAP = 4 * 1024 * 1024;
|
|
|
102
102
|
*/
|
|
103
103
|
const SNAPSHOT_CHUNK_BYTES = 512 * 1024;
|
|
104
104
|
|
|
105
|
-
/** Display name for this process's user in collab sessions. */
|
|
106
|
-
export function collabDisplayName(ctx: InteractiveModeContext): string {
|
|
107
|
-
const configured = (ctx.settings.get("collab.displayName") ?? "").trim();
|
|
108
|
-
if (configured) return configured;
|
|
109
|
-
try {
|
|
110
|
-
return os.userInfo().username;
|
|
111
|
-
} catch {
|
|
112
|
-
return "anonymous";
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
105
|
export class CollabHost {
|
|
117
106
|
#ctx: InteractiveModeContext;
|
|
118
107
|
#socket: CollabSocket | null = null;
|
package/src/commands/worktree.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* List and clean up agent-managed git worktrees under `~/.omp/wt`.
|
|
3
3
|
*/
|
|
4
|
+
import { getProjectDir } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
6
|
import { clearWorktrees, listWorktrees } from "../cli/worktree-cli";
|
|
7
|
+
import { Settings } from "../config/settings";
|
|
6
8
|
|
|
7
9
|
export default class Worktree extends Command {
|
|
8
10
|
static description = "List or clear agent-managed git worktrees (~/.omp/wt)";
|
|
@@ -43,6 +45,10 @@ export default class Worktree extends Command {
|
|
|
43
45
|
|
|
44
46
|
async run(): Promise<void> {
|
|
45
47
|
const { args, flags } = await this.parse(Worktree);
|
|
48
|
+
// Load settings so the `worktree.base` override is applied before we scan
|
|
49
|
+
// — otherwise this command would inspect ~/.omp/wt while the agent created
|
|
50
|
+
// its worktrees under the configured base.
|
|
51
|
+
await Settings.init({ cwd: getProjectDir() });
|
|
46
52
|
if (args.action === "clear") {
|
|
47
53
|
await clearWorktrees({
|
|
48
54
|
all: flags.all ?? false,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { modelFamilyToken } from "@oh-my-pi/pi-catalog/identity";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves whether full tool descriptors should be inlined into the system
|
|
5
|
+
* prompt (and stripped from provider tool schemas) for a given model and
|
|
6
|
+
* setting.
|
|
7
|
+
*
|
|
8
|
+
* `auto` enforces a per-model policy: inline for Gemini models, off otherwise.
|
|
9
|
+
* Gemini benefits from descriptors in-prompt; other providers keep them in the
|
|
10
|
+
* tool schemas. `on`/`off` are explicit user overrides.
|
|
11
|
+
*
|
|
12
|
+
* @param modelId Canonical model id (e.g. `gemini-3-pro`); resolve aliases via
|
|
13
|
+
* `ModelRegistry.getCanonicalId` before calling so `auto` classifies correctly.
|
|
14
|
+
*/
|
|
15
|
+
export function shouldInlineToolDescriptors(
|
|
16
|
+
setting: "auto" | "on" | "off" | undefined,
|
|
17
|
+
modelId: string | undefined,
|
|
18
|
+
): boolean {
|
|
19
|
+
switch (setting ?? "auto") {
|
|
20
|
+
case "on":
|
|
21
|
+
return true;
|
|
22
|
+
case "off":
|
|
23
|
+
return false;
|
|
24
|
+
default:
|
|
25
|
+
return modelId !== undefined && modelFamilyToken(modelId) === "gemini";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -6,14 +6,19 @@
|
|
|
6
6
|
* discovery lives in pi-catalog's provider-models.
|
|
7
7
|
*/
|
|
8
8
|
import { type ApiKey, type FetchImpl, withAuth } from "@oh-my-pi/pi-ai";
|
|
9
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai/types";
|
|
9
|
+
import type { Api, Model, RemoteCompactionConfig } from "@oh-my-pi/pi-ai/types";
|
|
10
10
|
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
|
11
11
|
import {
|
|
12
12
|
getBundledModelReferenceIndex,
|
|
13
13
|
resolveModelReference,
|
|
14
14
|
stripBracketedModelIdAffixes,
|
|
15
15
|
} from "@oh-my-pi/pi-catalog/identity";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
fetchLiteLLMRichModels,
|
|
18
|
+
fetchLmStudioNativeModelMetadata,
|
|
19
|
+
OPENAI_COMPAT_DISCOVERY_DEFAULT_CONTEXT_WINDOW,
|
|
20
|
+
OPENAI_COMPAT_DISCOVERY_DEFAULT_MAX_TOKENS,
|
|
21
|
+
} from "@oh-my-pi/pi-catalog/provider-models/openai-compat";
|
|
17
22
|
import type { ModelSpec } from "@oh-my-pi/pi-catalog/types";
|
|
18
23
|
import { isRecord } from "@oh-my-pi/pi-utils";
|
|
19
24
|
import type { ProviderDiscovery } from "./models-config-schema";
|
|
@@ -27,7 +32,8 @@ import type { ProviderDiscovery } from "./models-config-schema";
|
|
|
27
32
|
// mid-stream when models hit the cap on legitimate large tool calls (see
|
|
28
33
|
// issue #1528: `write` payloads >~5KB on deepseek-v4-pro surfaced as
|
|
29
34
|
// "socket connection was closed unexpectedly").
|
|
30
|
-
export const
|
|
35
|
+
export const DISCOVERY_DEFAULT_CONTEXT_WINDOW = OPENAI_COMPAT_DISCOVERY_DEFAULT_CONTEXT_WINDOW;
|
|
36
|
+
export const DISCOVERY_DEFAULT_MAX_TOKENS = OPENAI_COMPAT_DISCOVERY_DEFAULT_MAX_TOKENS;
|
|
31
37
|
|
|
32
38
|
const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
|
|
33
39
|
const OLLAMA_HOST_DEFAULT_PORT = "11434";
|
|
@@ -89,6 +95,7 @@ export interface DiscoveryProviderConfig {
|
|
|
89
95
|
baseUrl?: string;
|
|
90
96
|
headers?: Record<string, string>;
|
|
91
97
|
compat?: ModelSpec<Api>["compat"];
|
|
98
|
+
remoteCompaction?: RemoteCompactionConfig<Api>;
|
|
92
99
|
discovery: ProviderDiscovery;
|
|
93
100
|
optional?: boolean;
|
|
94
101
|
}
|
|
@@ -209,6 +216,8 @@ export function discoverModelsByProviderType(
|
|
|
209
216
|
return discoverOpenAIModelsList(providerConfig, ctx);
|
|
210
217
|
case "proxy":
|
|
211
218
|
return discoverProxyModels(providerConfig, ctx);
|
|
219
|
+
case "litellm":
|
|
220
|
+
return discoverLiteLLMModels(providerConfig, ctx);
|
|
212
221
|
}
|
|
213
222
|
}
|
|
214
223
|
|
|
@@ -302,7 +311,7 @@ export async function discoverOllamaModels(
|
|
|
302
311
|
input: metadata?.input ?? ["text"],
|
|
303
312
|
imageInputDecoder: "stb",
|
|
304
313
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
305
|
-
contextWindow: metadata?.contextWindow ??
|
|
314
|
+
contextWindow: metadata?.contextWindow ?? DISCOVERY_DEFAULT_CONTEXT_WINDOW,
|
|
306
315
|
maxTokens: Math.min(metadata?.contextWindow ?? Number.POSITIVE_INFINITY, DISCOVERY_DEFAULT_MAX_TOKENS),
|
|
307
316
|
headers: providerConfig.headers,
|
|
308
317
|
} as ModelSpec<Api>);
|
|
@@ -369,7 +378,7 @@ export async function discoverLlamaCppModels(
|
|
|
369
378
|
for (const item of models) {
|
|
370
379
|
const { id } = item;
|
|
371
380
|
if (!id) continue;
|
|
372
|
-
const contextWindow = item.contextWindow ?? serverMetadata?.contextWindow ??
|
|
381
|
+
const contextWindow = item.contextWindow ?? serverMetadata?.contextWindow ?? DISCOVERY_DEFAULT_CONTEXT_WINDOW;
|
|
373
382
|
discovered.push(
|
|
374
383
|
buildModel({
|
|
375
384
|
id,
|
|
@@ -467,7 +476,7 @@ export async function discoverOpenAIModelsList(
|
|
|
467
476
|
toPositiveNumberOrUndefined(item.max_model_len) ??
|
|
468
477
|
toPositiveNumberOrUndefined(item.context_length) ??
|
|
469
478
|
nativeMetadataForModel?.contextWindow ??
|
|
470
|
-
|
|
479
|
+
DISCOVERY_DEFAULT_CONTEXT_WINDOW;
|
|
471
480
|
discovered.push(
|
|
472
481
|
buildModel({
|
|
473
482
|
id,
|
|
@@ -493,6 +502,59 @@ export async function discoverOpenAIModelsList(
|
|
|
493
502
|
return discovered;
|
|
494
503
|
}
|
|
495
504
|
|
|
505
|
+
export async function discoverLiteLLMModels(
|
|
506
|
+
providerConfig: DiscoveryProviderConfig,
|
|
507
|
+
ctx: DiscoveryContext,
|
|
508
|
+
): Promise<Model<Api>[]> {
|
|
509
|
+
const baseUrl = normalizeLiteLLMDiscoveryBaseUrl(providerConfig.baseUrl);
|
|
510
|
+
const references = getBundledModelReferenceIndex();
|
|
511
|
+
const resolveReference = (id: string) => resolveModelReference(id, references) as ModelSpec<Api> | undefined;
|
|
512
|
+
const baseHeaders: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
513
|
+
let headers = baseHeaders;
|
|
514
|
+
const attempt = async (h: Record<string, string>) => {
|
|
515
|
+
headers = h;
|
|
516
|
+
let authError: (Error & { status: number }) | undefined;
|
|
517
|
+
const authAwareFetch: FetchImpl = async (input, init) => {
|
|
518
|
+
const response = await ctx.fetch(input, init);
|
|
519
|
+
if (response.status === 401) {
|
|
520
|
+
authError = new Error(`HTTP ${response.status} from ${String(input)}`) as Error & { status: number };
|
|
521
|
+
authError.status = response.status;
|
|
522
|
+
}
|
|
523
|
+
return response;
|
|
524
|
+
};
|
|
525
|
+
const models = await fetchLiteLLMRichModels({
|
|
526
|
+
api: providerConfig.api,
|
|
527
|
+
provider: providerConfig.provider,
|
|
528
|
+
baseUrl,
|
|
529
|
+
headers: h,
|
|
530
|
+
fetch: authAwareFetch,
|
|
531
|
+
referenceResolver: resolveReference,
|
|
532
|
+
signal: AbortSignal.timeout(10_000),
|
|
533
|
+
});
|
|
534
|
+
if (authError && models === null) {
|
|
535
|
+
throw authError;
|
|
536
|
+
}
|
|
537
|
+
return models;
|
|
538
|
+
};
|
|
539
|
+
const apiKey = await ctx.getBearerApiKeyResolver(providerConfig.provider);
|
|
540
|
+
let richModels: ModelSpec<Api>[] | null;
|
|
541
|
+
try {
|
|
542
|
+
richModels = apiKey
|
|
543
|
+
? await withAuth(apiKey, key => attempt({ ...baseHeaders, Authorization: `Bearer ${key}` }))
|
|
544
|
+
: await attempt(baseHeaders);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
const status = typeof error === "object" && error !== null && "status" in error ? error.status : undefined;
|
|
547
|
+
if (status !== 401) {
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
550
|
+
richModels = null;
|
|
551
|
+
}
|
|
552
|
+
if (!richModels || richModels.length === 0) {
|
|
553
|
+
return discoverOpenAIModelsList({ ...providerConfig, baseUrl }, ctx);
|
|
554
|
+
}
|
|
555
|
+
return richModels.map(spec => buildModel({ ...spec, headers }));
|
|
556
|
+
}
|
|
557
|
+
|
|
496
558
|
/**
|
|
497
559
|
* Discover models from an Anthropic+OpenAI-compatible reseller proxy that
|
|
498
560
|
* exposes both `/v1/messages` and `/v1/chat/completions`, advertising each
|
|
@@ -571,7 +633,10 @@ export async function discoverProxyModels(
|
|
|
571
633
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
572
634
|
// Prefer the context_length the API reports for this model; fall
|
|
573
635
|
// back to the bundled reference, then a sane default.
|
|
574
|
-
contextWindow:
|
|
636
|
+
contextWindow:
|
|
637
|
+
toPositiveNumberOrUndefined(item.context_length) ??
|
|
638
|
+
reference?.contextWindow ??
|
|
639
|
+
DISCOVERY_DEFAULT_CONTEXT_WINDOW,
|
|
575
640
|
maxTokens: reference?.maxTokens ?? discoveryDefaultMaxTokens(api),
|
|
576
641
|
headers,
|
|
577
642
|
// OpenAI-compat fields are no-ops on anthropic models; the
|
|
@@ -616,7 +681,11 @@ function toLlamaCppNativeBaseUrl(baseUrl: string): string {
|
|
|
616
681
|
}
|
|
617
682
|
}
|
|
618
683
|
|
|
619
|
-
function
|
|
684
|
+
export function normalizeLiteLLMDiscoveryBaseUrl(baseUrl?: string): string {
|
|
685
|
+
return normalizeOpenAIModelsListBaseUrl(baseUrl ?? "http://localhost:4000/v1");
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
export function normalizeOpenAIModelsListBaseUrl(baseUrl?: string): string {
|
|
620
689
|
const defaultBaseUrl = "http://127.0.0.1:1234/v1";
|
|
621
690
|
const raw = baseUrl || defaultBaseUrl;
|
|
622
691
|
try {
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { registerCustomApi, unregisterCustomApis } from "@oh-my-pi/pi-ai/api-registry";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
Api,
|
|
6
|
+
Context,
|
|
7
|
+
Model,
|
|
8
|
+
ModelSpec,
|
|
9
|
+
RemoteCompactionConfig,
|
|
10
|
+
SimpleStreamOptions,
|
|
11
|
+
ThinkingConfig,
|
|
12
|
+
} from "@oh-my-pi/pi-ai/types";
|
|
5
13
|
import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
|
|
6
14
|
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
|
7
15
|
import { isVertexExpressOpenAIUrl } from "@oh-my-pi/pi-catalog/hosts";
|
|
@@ -71,6 +79,7 @@ import {
|
|
|
71
79
|
discoverModelsByProviderType,
|
|
72
80
|
getImplicitOllamaBaseUrl,
|
|
73
81
|
getOllamaContextLengthOverride,
|
|
82
|
+
normalizeLiteLLMDiscoveryBaseUrl,
|
|
74
83
|
} from "./model-discovery";
|
|
75
84
|
import { ModelsConfigFile, type ProviderValidationModel, validateProviderConfiguration } from "./models-config";
|
|
76
85
|
import type { ModelOverride, ModelsConfig, ProviderAuthMode } from "./models-config-schema";
|
|
@@ -95,6 +104,7 @@ interface ProviderOverride {
|
|
|
95
104
|
apiKey?: string;
|
|
96
105
|
authHeader?: boolean;
|
|
97
106
|
compat?: ModelSpec<Api>["compat"];
|
|
107
|
+
remoteCompaction?: RemoteCompactionConfig<Api>;
|
|
98
108
|
transport?: Model<Api>["transport"];
|
|
99
109
|
}
|
|
100
110
|
|
|
@@ -127,7 +137,7 @@ interface ProviderOverride {
|
|
|
127
137
|
export function mergeDiscoveredModel<TApi extends Api>(
|
|
128
138
|
model: Model<TApi>,
|
|
129
139
|
existing: Model<Api> | undefined,
|
|
130
|
-
providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "transport">,
|
|
140
|
+
providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "remoteCompaction" | "transport">,
|
|
131
141
|
): Model<TApi> {
|
|
132
142
|
if (existing) {
|
|
133
143
|
const supportsTools = model.supportsTools ?? existing.supportsTools;
|
|
@@ -136,6 +146,10 @@ export function mergeDiscoveredModel<TApi extends Api>(
|
|
|
136
146
|
baseUrl: providerOverride?.baseUrl ?? model.baseUrl ?? existing.baseUrl,
|
|
137
147
|
headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
|
|
138
148
|
transport: providerOverride?.transport ?? existing.transport ?? model.transport,
|
|
149
|
+
remoteCompaction: mergeProviderRemoteCompactionConfig(
|
|
150
|
+
mergeRemoteCompactionConfig(existing.remoteCompaction, model.remoteCompaction),
|
|
151
|
+
providerOverride?.remoteCompaction,
|
|
152
|
+
),
|
|
139
153
|
...(supportsTools !== undefined ? { supportsTools } : {}),
|
|
140
154
|
compat: model.compatConfig,
|
|
141
155
|
} as ModelSpec<TApi>);
|
|
@@ -146,6 +160,10 @@ export function mergeDiscoveredModel<TApi extends Api>(
|
|
|
146
160
|
baseUrl: providerOverride.baseUrl ?? model.baseUrl,
|
|
147
161
|
headers: providerOverride.headers ? { ...model.headers, ...providerOverride.headers } : model.headers,
|
|
148
162
|
...(providerOverride.transport !== undefined ? { transport: providerOverride.transport } : {}),
|
|
163
|
+
remoteCompaction: mergeProviderRemoteCompactionConfig(
|
|
164
|
+
model.remoteCompaction,
|
|
165
|
+
providerOverride.remoteCompaction,
|
|
166
|
+
),
|
|
149
167
|
compat: model.compatConfig,
|
|
150
168
|
} as ModelSpec<TApi>);
|
|
151
169
|
}
|
|
@@ -353,6 +371,22 @@ function mergeCompat<TBase extends object, TOverride extends object>(
|
|
|
353
371
|
return merged as TBase & TOverride;
|
|
354
372
|
}
|
|
355
373
|
|
|
374
|
+
function mergeRemoteCompactionConfig(
|
|
375
|
+
baseConfig: RemoteCompactionConfig<Api> | undefined,
|
|
376
|
+
overrideConfig: RemoteCompactionConfig<Api> | undefined,
|
|
377
|
+
): RemoteCompactionConfig<Api> | undefined {
|
|
378
|
+
if (!baseConfig) return overrideConfig;
|
|
379
|
+
if (!overrideConfig) return baseConfig;
|
|
380
|
+
return { ...baseConfig, ...overrideConfig };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function mergeProviderRemoteCompactionConfig(
|
|
384
|
+
modelConfig: RemoteCompactionConfig<Api> | undefined,
|
|
385
|
+
providerConfig: RemoteCompactionConfig<Api> | undefined,
|
|
386
|
+
): RemoteCompactionConfig<Api> | undefined {
|
|
387
|
+
return mergeRemoteCompactionConfig(providerConfig, modelConfig);
|
|
388
|
+
}
|
|
389
|
+
|
|
356
390
|
/**
|
|
357
391
|
* Project a built model back to spec shape for the model-manager/cache
|
|
358
392
|
* boundary: sparse compat comes from `compatConfig`, never from the resolved
|
|
@@ -380,6 +414,8 @@ interface ModelPatch {
|
|
|
380
414
|
headers?: Record<string, string>;
|
|
381
415
|
compat?: ModelSpec<Api>["compat"];
|
|
382
416
|
contextPromotionTarget?: string;
|
|
417
|
+
compactionModel?: string;
|
|
418
|
+
remoteCompaction?: RemoteCompactionConfig<Api>;
|
|
383
419
|
premiumMultiplier?: number;
|
|
384
420
|
}
|
|
385
421
|
|
|
@@ -403,6 +439,10 @@ function applyModelPatch(base: Model<Api>, patch: ModelPatch, transport: ModelTr
|
|
|
403
439
|
if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
|
|
404
440
|
if (patch.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = patch.omitMaxOutputTokens;
|
|
405
441
|
if (patch.contextPromotionTarget !== undefined) result.contextPromotionTarget = patch.contextPromotionTarget;
|
|
442
|
+
if (patch.compactionModel !== undefined) result.compactionModel = patch.compactionModel;
|
|
443
|
+
if (patch.remoteCompaction !== undefined) {
|
|
444
|
+
result.remoteCompaction = mergeRemoteCompactionConfig(base.remoteCompaction, patch.remoteCompaction);
|
|
445
|
+
}
|
|
406
446
|
if (patch.premiumMultiplier !== undefined) result.premiumMultiplier = patch.premiumMultiplier;
|
|
407
447
|
if (patch.cost) {
|
|
408
448
|
result.cost = {
|
|
@@ -475,8 +515,6 @@ function mergeAuthHeader(
|
|
|
475
515
|
/**
|
|
476
516
|
* Decide whether a custom-yaml model should force OAuth-style request shaping.
|
|
477
517
|
* - Explicit `auth: oauth` → force on.
|
|
478
|
-
* - Explicit `auth: apiKey` / `auth: none` → leave unset (auto-detect by key prefix).
|
|
479
|
-
* - No `auth` specified and `api: anthropic-messages` → default on. Custom Anthropic
|
|
480
518
|
* endpoints are typically Claude-Code-style proxies (e.g. CLIProxyAPI) that expect
|
|
481
519
|
* the cloaked request shape regardless of how the proxy itself is authenticated.
|
|
482
520
|
* - Otherwise → unset.
|
|
@@ -497,6 +535,7 @@ function buildCustomModelOverlay(
|
|
|
497
535
|
authHeader: boolean | undefined,
|
|
498
536
|
providerCompat: ModelSpec<Api>["compat"] | undefined,
|
|
499
537
|
providerAuth: ProviderAuthMode | undefined,
|
|
538
|
+
providerRemoteCompaction: RemoteCompactionConfig<Api> | undefined,
|
|
500
539
|
modelDef: CustomModelDefinitionLike,
|
|
501
540
|
): CustomModelOverlay | undefined {
|
|
502
541
|
const api = modelDef.api ?? providerApi;
|
|
@@ -518,6 +557,8 @@ function buildCustomModelOverlay(
|
|
|
518
557
|
headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
|
|
519
558
|
compat: mergeCompat(providerCompat, modelDef.compat),
|
|
520
559
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
560
|
+
compactionModel: modelDef.compactionModel,
|
|
561
|
+
remoteCompaction: mergeRemoteCompactionConfig(providerRemoteCompaction, modelDef.remoteCompaction),
|
|
521
562
|
premiumMultiplier: modelDef.premiumMultiplier,
|
|
522
563
|
isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
|
|
523
564
|
};
|
|
@@ -558,6 +599,8 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
|
|
|
558
599
|
omitMaxOutputTokens: resolvedModel.omitMaxOutputTokens ?? reference?.omitMaxOutputTokens,
|
|
559
600
|
compat: mergeCompat(reference?.compatConfig, resolvedModel.compat),
|
|
560
601
|
contextPromotionTarget: resolvedModel.contextPromotionTarget,
|
|
602
|
+
compactionModel: resolvedModel.compactionModel,
|
|
603
|
+
remoteCompaction: resolvedModel.remoteCompaction,
|
|
561
604
|
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
562
605
|
isOAuth: resolvedModel.isOAuth,
|
|
563
606
|
} as ModelSpec<Api>);
|
|
@@ -1078,12 +1121,25 @@ export class ModelRegistry {
|
|
|
1078
1121
|
)
|
|
1079
1122
|
: models;
|
|
1080
1123
|
|
|
1124
|
+
const withRemoteCompaction = providerConfig.remoteCompaction
|
|
1125
|
+
? withDecoderMetadata.map(model =>
|
|
1126
|
+
buildModel({
|
|
1127
|
+
...model,
|
|
1128
|
+
remoteCompaction: mergeProviderRemoteCompactionConfig(
|
|
1129
|
+
model.remoteCompaction,
|
|
1130
|
+
providerConfig.remoteCompaction,
|
|
1131
|
+
),
|
|
1132
|
+
compat: model.compatConfig,
|
|
1133
|
+
} as ModelSpec<Api>),
|
|
1134
|
+
)
|
|
1135
|
+
: withDecoderMetadata;
|
|
1136
|
+
|
|
1081
1137
|
if (providerConfig.provider !== "ollama" || providerConfig.api !== "openai-responses") {
|
|
1082
|
-
return
|
|
1138
|
+
return withRemoteCompaction;
|
|
1083
1139
|
}
|
|
1084
1140
|
|
|
1085
1141
|
const contextLengthOverride = getOllamaContextLengthOverride();
|
|
1086
|
-
return
|
|
1142
|
+
return withRemoteCompaction.map(model => {
|
|
1087
1143
|
const normalized =
|
|
1088
1144
|
model.api === "openai-completions"
|
|
1089
1145
|
? buildModel({
|
|
@@ -1172,7 +1228,6 @@ export class ModelRegistry {
|
|
|
1172
1228
|
const discoverableProviders: DiscoveryProviderConfig[] = [];
|
|
1173
1229
|
const providerEntries = Object.entries(value.providers ?? {});
|
|
1174
1230
|
const configuredProviders = new Set(Object.keys(value.providers ?? {}));
|
|
1175
|
-
|
|
1176
1231
|
for (const [providerName, providerConfig] of providerEntries) {
|
|
1177
1232
|
const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
|
|
1178
1233
|
// Always set overrides when baseUrl/headers/apiKey/authHeader/compat/disableStrictTools/transport are present
|
|
@@ -1183,15 +1238,20 @@ export class ModelRegistry {
|
|
|
1183
1238
|
providerConfig.authHeader !== undefined ||
|
|
1184
1239
|
providerConfig.compat ||
|
|
1185
1240
|
providerConfig.disableStrictTools ||
|
|
1241
|
+
providerConfig.remoteCompaction ||
|
|
1186
1242
|
providerConfig.transport
|
|
1187
1243
|
) {
|
|
1188
1244
|
const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
|
|
1189
1245
|
overrides.set(providerName, {
|
|
1190
|
-
baseUrl:
|
|
1246
|
+
baseUrl:
|
|
1247
|
+
providerConfig.discovery?.type === "litellm"
|
|
1248
|
+
? normalizeLiteLLMDiscoveryBaseUrl(providerConfig.baseUrl)
|
|
1249
|
+
: providerConfig.baseUrl,
|
|
1191
1250
|
headers: resolvedProviderHeaders,
|
|
1192
1251
|
apiKey: providerConfig.apiKey,
|
|
1193
1252
|
authHeader: providerConfig.authHeader,
|
|
1194
1253
|
compat: mergeCompat(providerConfig.compat, disableStrictCompat),
|
|
1254
|
+
remoteCompaction: providerConfig.remoteCompaction,
|
|
1195
1255
|
transport: providerConfig.transport,
|
|
1196
1256
|
});
|
|
1197
1257
|
}
|
|
@@ -1212,6 +1272,7 @@ export class ModelRegistry {
|
|
|
1212
1272
|
baseUrl: providerConfig.baseUrl,
|
|
1213
1273
|
headers: resolvedProviderHeaders,
|
|
1214
1274
|
compat: mergeCompat(providerConfig.compat, disableStrictCompat),
|
|
1275
|
+
remoteCompaction: providerConfig.remoteCompaction,
|
|
1215
1276
|
discovery: providerConfig.discovery,
|
|
1216
1277
|
optional: false,
|
|
1217
1278
|
});
|
|
@@ -1302,6 +1363,9 @@ export class ModelRegistry {
|
|
|
1302
1363
|
if (providerConfig.discovery.type === "openai-models-list") {
|
|
1303
1364
|
return `${providerConfig.provider}:openai-models-list-context-v2`;
|
|
1304
1365
|
}
|
|
1366
|
+
if (providerConfig.discovery.type === "litellm") {
|
|
1367
|
+
return `${providerConfig.provider}:litellm-rich-v1`;
|
|
1368
|
+
}
|
|
1305
1369
|
return providerConfig.provider;
|
|
1306
1370
|
}
|
|
1307
1371
|
|
|
@@ -1599,12 +1663,18 @@ export class ModelRegistry {
|
|
|
1599
1663
|
authHeader: override.authHeader ?? baseOverride?.authHeader,
|
|
1600
1664
|
headers: override.headers ? { ...(baseOverride?.headers ?? {}), ...override.headers } : baseOverride?.headers,
|
|
1601
1665
|
compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
|
|
1666
|
+
remoteCompaction: mergeRemoteCompactionConfig(baseOverride?.remoteCompaction, override.remoteCompaction),
|
|
1602
1667
|
transport: override.transport ?? baseOverride?.transport,
|
|
1603
1668
|
};
|
|
1604
1669
|
}
|
|
1605
|
-
#applyProviderTransportOverride<
|
|
1670
|
+
#applyProviderTransportOverride<
|
|
1671
|
+
T extends { baseUrl?: string; headers?: Record<string, string>; remoteCompaction?: RemoteCompactionConfig<Api> },
|
|
1672
|
+
>(
|
|
1606
1673
|
entry: T,
|
|
1607
|
-
override: Pick<
|
|
1674
|
+
override: Pick<
|
|
1675
|
+
ProviderOverride,
|
|
1676
|
+
"baseUrl" | "headers" | "authHeader" | "apiKey" | "remoteCompaction" | "transport"
|
|
1677
|
+
>,
|
|
1608
1678
|
): T {
|
|
1609
1679
|
const headers = mergeAuthHeader(
|
|
1610
1680
|
override.headers ? { ...entry.headers, ...override.headers } : entry.headers,
|
|
@@ -1618,6 +1688,7 @@ export class ModelRegistry {
|
|
|
1618
1688
|
// Preserve the model's existing transport when the override omits one;
|
|
1619
1689
|
// providers without a `transport` field keep the default per-API dispatch.
|
|
1620
1690
|
...(override.transport !== undefined ? { transport: override.transport } : {}),
|
|
1691
|
+
remoteCompaction: mergeProviderRemoteCompactionConfig(entry.remoteCompaction, override.remoteCompaction),
|
|
1621
1692
|
};
|
|
1622
1693
|
}
|
|
1623
1694
|
#applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
|
|
@@ -1719,7 +1790,6 @@ export class ModelRegistry {
|
|
|
1719
1790
|
|
|
1720
1791
|
#parseModels(config: ModelsConfig): CustomModelOverlay[] {
|
|
1721
1792
|
const models: CustomModelOverlay[] = [];
|
|
1722
|
-
|
|
1723
1793
|
for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
|
|
1724
1794
|
const modelDefs = providerConfig.models ?? [];
|
|
1725
1795
|
if (modelDefs.length === 0) continue; // Override-only, no custom models
|
|
@@ -1740,6 +1810,7 @@ export class ModelRegistry {
|
|
|
1740
1810
|
providerConfig.authHeader,
|
|
1741
1811
|
providerCompat,
|
|
1742
1812
|
(providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
|
|
1813
|
+
providerConfig.remoteCompaction,
|
|
1743
1814
|
modelDef as CustomModelDefinitionLike,
|
|
1744
1815
|
);
|
|
1745
1816
|
if (!model) continue;
|
|
@@ -2147,6 +2218,7 @@ export class ModelRegistry {
|
|
|
2147
2218
|
config.authHeader,
|
|
2148
2219
|
config.compat,
|
|
2149
2220
|
undefined,
|
|
2221
|
+
config.remoteCompaction,
|
|
2150
2222
|
modelDef as CustomModelDefinitionLike,
|
|
2151
2223
|
);
|
|
2152
2224
|
if (!overlay) {
|
|
@@ -2214,6 +2286,7 @@ export class ModelRegistry {
|
|
|
2214
2286
|
providerAuthHeader,
|
|
2215
2287
|
providerCompat,
|
|
2216
2288
|
undefined,
|
|
2289
|
+
config.remoteCompaction,
|
|
2217
2290
|
modelDef as CustomModelDefinitionLike,
|
|
2218
2291
|
);
|
|
2219
2292
|
if (overlay) results.push(finalizeCustomModel(overlay, { useDefaults: true }));
|
|
@@ -2231,6 +2304,7 @@ export class ModelRegistry {
|
|
|
2231
2304
|
config.headers ||
|
|
2232
2305
|
config.apiKey ||
|
|
2233
2306
|
config.authHeader !== undefined ||
|
|
2307
|
+
config.remoteCompaction !== undefined ||
|
|
2234
2308
|
config.transport !== undefined
|
|
2235
2309
|
) {
|
|
2236
2310
|
const transportOverride = {
|
|
@@ -2238,6 +2312,7 @@ export class ModelRegistry {
|
|
|
2238
2312
|
headers: config.headers,
|
|
2239
2313
|
apiKey: config.apiKey,
|
|
2240
2314
|
authHeader: config.authHeader,
|
|
2315
|
+
remoteCompaction: config.remoteCompaction,
|
|
2241
2316
|
transport: config.transport,
|
|
2242
2317
|
};
|
|
2243
2318
|
const nextRuntimeOverride = this.#mergeProviderOverride(
|
|
@@ -2299,6 +2374,7 @@ export interface ProviderConfigInput {
|
|
|
2299
2374
|
streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
2300
2375
|
headers?: Record<string, string>;
|
|
2301
2376
|
compat?: ModelSpec<Api>["compat"];
|
|
2377
|
+
remoteCompaction?: RemoteCompactionConfig<Api>;
|
|
2302
2378
|
authHeader?: boolean;
|
|
2303
2379
|
/** Streaming transport override — see {@link Model.transport}. */
|
|
2304
2380
|
transport?: Model<Api>["transport"];
|
|
@@ -2333,6 +2409,8 @@ export interface ProviderConfigInput {
|
|
|
2333
2409
|
headers?: Record<string, string>;
|
|
2334
2410
|
compat?: ModelSpec<Api>["compat"];
|
|
2335
2411
|
contextPromotionTarget?: string;
|
|
2412
|
+
compactionModel?: string;
|
|
2413
|
+
remoteCompaction?: RemoteCompactionConfig<Api>;
|
|
2336
2414
|
premiumMultiplier?: number;
|
|
2337
2415
|
}>;
|
|
2338
2416
|
}
|
|
@@ -877,6 +877,23 @@ function shouldInheritDefaultBeforePriority(role: ModelRole): boolean {
|
|
|
877
877
|
return role === "smol" || role === "slow" || role === "designer";
|
|
878
878
|
}
|
|
879
879
|
|
|
880
|
+
/**
|
|
881
|
+
* Roles that have no priority.json chain of their own reuse another role's
|
|
882
|
+
* list. The advisor — a second-opinion reviewer — defaults to the `slow`
|
|
883
|
+
* reasoning chain, but (unlike the `slow` role, see
|
|
884
|
+
* {@link shouldInheritDefaultBeforePriority}) never inherits the primary's
|
|
885
|
+
* model, so it stays a distinct strong model out of the box.
|
|
886
|
+
*/
|
|
887
|
+
const ROLE_PRIORITY_ALIAS: Partial<Record<ModelRole, keyof typeof MODEL_PRIO>> = {
|
|
888
|
+
advisor: "slow",
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
/** Built-in priority patterns for a role, following {@link ROLE_PRIORITY_ALIAS}. */
|
|
892
|
+
function rolePriorityDefaults(role: ModelRole): string[] {
|
|
893
|
+
const key = ROLE_PRIORITY_ALIAS[role] ?? (role as keyof typeof MODEL_PRIO);
|
|
894
|
+
return normalizeModelPatternList(MODEL_PRIO[key]);
|
|
895
|
+
}
|
|
896
|
+
|
|
880
897
|
function resolveDefaultInheritedPatterns(
|
|
881
898
|
role: ModelRole,
|
|
882
899
|
configuredDefault: string | undefined,
|
|
@@ -939,7 +956,7 @@ function resolveConfiguredRolePattern(
|
|
|
939
956
|
|
|
940
957
|
const configured = settings?.getModelRole(role)?.trim();
|
|
941
958
|
const configuredDefault = settings?.getModelRole(DEFAULT_MODEL_ROLE)?.trim();
|
|
942
|
-
const roleDefaults =
|
|
959
|
+
const roleDefaults = rolePriorityDefaults(role);
|
|
943
960
|
const resolved = configured
|
|
944
961
|
? normalizeModelPatternList(configured)
|
|
945
962
|
: resolveDefaultInheritedPatterns(role, configuredDefault, roleDefaults, settings, visited);
|
|
@@ -1245,6 +1262,27 @@ export function resolveRoleSelection(
|
|
|
1245
1262
|
return undefined;
|
|
1246
1263
|
}
|
|
1247
1264
|
|
|
1265
|
+
/**
|
|
1266
|
+
* Resolve the model for the `advisor` role. A configured `modelRoles.advisor`
|
|
1267
|
+
* wins outright (a bad override surfaces as no model rather than silently
|
|
1268
|
+
* running something else); when unset it falls back to the `slow` priority
|
|
1269
|
+
* chain via {@link ROLE_PRIORITY_ALIAS} — a strong reasoning model that, unlike
|
|
1270
|
+
* the `slow` role itself, never inherits the primary's model. Returns undefined
|
|
1271
|
+
* only when no candidate in the resolved chain is available.
|
|
1272
|
+
*/
|
|
1273
|
+
export function resolveAdvisorRoleSelection(
|
|
1274
|
+
settings: Settings,
|
|
1275
|
+
availableModels: Model<Api>[],
|
|
1276
|
+
modelRegistry?: CanonicalModelRegistry,
|
|
1277
|
+
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
|
|
1278
|
+
const resolved = resolveModelRoleValue(`${PREFIX_MODEL_ROLE}advisor`, availableModels, {
|
|
1279
|
+
settings,
|
|
1280
|
+
matchPreferences: getModelMatchPreferences(settings),
|
|
1281
|
+
modelRegistry,
|
|
1282
|
+
});
|
|
1283
|
+
return resolved.model ? { model: resolved.model, thinkingLevel: resolved.thinkingLevel } : undefined;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1248
1286
|
function resolveExactCanonicalScopePattern(
|
|
1249
1287
|
pattern: string,
|
|
1250
1288
|
modelRegistry: Pick<ModelRegistry, "getCanonicalVariants">,
|