@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.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 +54 -0
- package/README.md +1 -1
- package/dist/cli.js +4659 -4261
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +316 -47
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
- package/dist/types/live/controller.d.ts +40 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +48 -0
- package/dist/types/live/visualizer.d.ts +33 -0
- package/dist/types/lsp/client.d.ts +16 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +7 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +314 -0
- package/dist/types/session/agent-session.d.ts +118 -755
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +49 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +22 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +143 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +5 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +14 -0
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/types.d.ts +94 -0
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +2 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/usage-cli.ts +10 -1
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/model-registry.ts +10 -2
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +209 -51
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +89 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/audio-worklet.txt +59 -0
- package/src/live/browser-runtime.txt +221 -0
- package/src/live/controller.ts +464 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +490 -0
- package/src/live/visualizer.ts +234 -0
- package/src/lsp/client.ts +29 -9
- package/src/lsp/index.ts +19 -4
- package/src/main.ts +8 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +1 -1
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +23 -1
- package/src/modes/components/settings-selector.ts +169 -1
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.ts +11 -2
- package/src/modes/controllers/command-controller.ts +56 -15
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/live-command-controller.ts +178 -0
- package/src/modes/controllers/mcp-command-controller.ts +10 -1
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +18 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +157 -38
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +210 -136
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +334 -0
- package/src/session/agent-session.ts +1892 -12226
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +212 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +263 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.ts +6 -3
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +0 -46
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +943 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +92 -11
- package/src/slash-commands/helpers/usage-report.ts +22 -3
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +285 -70
- package/src/task/index.ts +98 -105
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +1 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +26 -0
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +9 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +7 -1
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +67 -3
- package/src/tts/streaming-player.ts +62 -4
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +3 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
5
|
+
import {
|
|
6
|
+
formatModelSelectorValue,
|
|
7
|
+
formatModelString,
|
|
8
|
+
formatModelStringWithRouting,
|
|
9
|
+
parseModelString,
|
|
10
|
+
} from "../config/model-resolver";
|
|
11
|
+
import type { Settings } from "../config/settings";
|
|
12
|
+
import { type ConfiguredThinkingLevel, concreteThinkingLevel } from "../thinking";
|
|
13
|
+
|
|
14
|
+
/** Configured fallback chains keyed by role or model selector. */
|
|
15
|
+
export type RetryFallbackChains = Record<string, string[]>;
|
|
16
|
+
|
|
17
|
+
/** Policy controlling restoration of a fallback chain's primary model. */
|
|
18
|
+
export type RetryFallbackRevertPolicy = "never" | "cooldown-expiry";
|
|
19
|
+
|
|
20
|
+
/** Parsed model selector used by retry fallback resolution. */
|
|
21
|
+
export interface RetryFallbackSelector {
|
|
22
|
+
raw: string;
|
|
23
|
+
provider: string;
|
|
24
|
+
id: string;
|
|
25
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Minimal model lookup needed by fallback-chain resolution. */
|
|
29
|
+
export interface RetryFallbackModelLookup {
|
|
30
|
+
find(provider: string, id: string): Model | undefined;
|
|
31
|
+
hasProvider(provider: string): boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Inputs shared by startup (sdk) and runtime (turn-recovery) fallback-chain
|
|
36
|
+
* resolution. `chains` is pre-expanded so callers can apply the default chain
|
|
37
|
+
* to roles beyond the configured model roles (e.g. a subagent fallback role).
|
|
38
|
+
*/
|
|
39
|
+
export interface RetryFallbackResolutionContext {
|
|
40
|
+
chains: RetryFallbackChains;
|
|
41
|
+
getModelRole(role: string): string | undefined;
|
|
42
|
+
modelLookup: RetryFallbackModelLookup;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Active retry fallback state retained until the primary can be restored. */
|
|
46
|
+
export interface ActiveRetryFallbackState {
|
|
47
|
+
/** Chain key that produced this fallback: a model-role name or a model-selector key. */
|
|
48
|
+
role: string;
|
|
49
|
+
originalSelector: string;
|
|
50
|
+
originalThinkingLevel: ConfiguredThinkingLevel | undefined;
|
|
51
|
+
lastAppliedFallbackThinkingLevel: ConfiguredThinkingLevel | undefined;
|
|
52
|
+
pinned: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const RETRY_BACKOFF_MAX_DELAY_MS = 8_000;
|
|
56
|
+
const RETRY_BACKOFF_JITTER_RATIO = 0.25;
|
|
57
|
+
|
|
58
|
+
/** Calculates capped exponential retry delay with downward jitter. */
|
|
59
|
+
export function calculateRetryBackoffDelayMs(baseDelayMs: number, attempt: number): number {
|
|
60
|
+
const cappedDelayMs = Math.min(Math.max(0, baseDelayMs) * 2 ** Math.max(0, attempt - 1), RETRY_BACKOFF_MAX_DELAY_MS);
|
|
61
|
+
const jitter = 1 - Math.random() * RETRY_BACKOFF_JITTER_RATIO;
|
|
62
|
+
return cappedDelayMs * jitter;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Parses a configured retry fallback selector. */
|
|
66
|
+
export function parseRetryFallbackSelector(
|
|
67
|
+
selector: string,
|
|
68
|
+
modelLookup?: Pick<RetryFallbackModelLookup, "find">,
|
|
69
|
+
): RetryFallbackSelector | undefined {
|
|
70
|
+
const trimmed = selector.trim();
|
|
71
|
+
if (!trimmed) return undefined;
|
|
72
|
+
const parsed = parseModelString(trimmed, {
|
|
73
|
+
allowMaxSuffix: true,
|
|
74
|
+
allowAutoAlias: true,
|
|
75
|
+
isLiteralModelId: (provider, id) => modelLookup?.find(provider, id) !== undefined,
|
|
76
|
+
});
|
|
77
|
+
if (!parsed) return undefined;
|
|
78
|
+
return {
|
|
79
|
+
raw: trimmed,
|
|
80
|
+
provider: parsed.provider,
|
|
81
|
+
id: parsed.id,
|
|
82
|
+
thinkingLevel: concreteThinkingLevel(parsed.thinkingLevel),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Whether a fallback-chain key is a model selector rather than a role. */
|
|
87
|
+
export function isRetryFallbackModelKey(key: string): boolean {
|
|
88
|
+
return key.includes("/");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Whether a fallback-chain key or entry is a provider wildcard. */
|
|
92
|
+
export function isRetryFallbackWildcardKey(key: string): boolean {
|
|
93
|
+
return key.endsWith("/*");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Splits a wildcard selector into provider and optional model-id prefix. */
|
|
97
|
+
export function parseRetryFallbackWildcard(
|
|
98
|
+
key: string,
|
|
99
|
+
isKnownProvider: (provider: string) => boolean,
|
|
100
|
+
): { provider: string; idPrefix: string | undefined } {
|
|
101
|
+
const template = key.slice(0, -2);
|
|
102
|
+
const slash = template.indexOf("/");
|
|
103
|
+
if (slash < 0 || isKnownProvider(template)) return { provider: template, idPrefix: undefined };
|
|
104
|
+
return { provider: template.slice(0, slash), idPrefix: template.slice(slash + 1) };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Formats a concrete model and thinking level as a fallback selector. */
|
|
108
|
+
export function formatRetryFallbackSelector(model: Model, thinkingLevel: ThinkingLevel | undefined): string {
|
|
109
|
+
return formatModelSelectorValue(formatModelStringWithRouting(model), thinkingLevel);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Formats the model-only portion of a parsed fallback selector. */
|
|
113
|
+
function formatRetryFallbackBaseSelector(selector: RetryFallbackSelector): string {
|
|
114
|
+
return `${selector.provider}/${selector.id}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Whether a provider is registered or configured for discovery. */
|
|
118
|
+
export function isKnownProvider(modelRegistry: ModelRegistry, provider: string): boolean {
|
|
119
|
+
return modelRegistry.hasProvider(provider);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Apply the configured default chain to roles without their own chain. */
|
|
123
|
+
export function expandDefaultRetryFallbackChains(
|
|
124
|
+
configuredChains: RetryFallbackChains,
|
|
125
|
+
roleNames: readonly string[],
|
|
126
|
+
): RetryFallbackChains {
|
|
127
|
+
const chains: RetryFallbackChains = { ...configuredChains };
|
|
128
|
+
const defaultChain = chains.default;
|
|
129
|
+
if (!Array.isArray(defaultChain)) return chains;
|
|
130
|
+
for (const role of roleNames) {
|
|
131
|
+
if (role !== "default" && chains[role] === undefined) chains[role] = defaultChain;
|
|
132
|
+
}
|
|
133
|
+
return chains;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Resolves configured fallback chains, applying the default chain to named roles. */
|
|
137
|
+
export function getRetryFallbackChains(settings: Settings): RetryFallbackChains {
|
|
138
|
+
const configuredChains = settings.get("retry.fallbackChains");
|
|
139
|
+
if (!configuredChains || typeof configuredChains !== "object") return {};
|
|
140
|
+
return expandDefaultRetryFallbackChains(configuredChains, Object.keys(settings.getModelRoles()));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Validates configured fallback chains and reports each warning. */
|
|
144
|
+
export function validateRetryFallbackChains(
|
|
145
|
+
settings: Settings,
|
|
146
|
+
modelRegistry: ModelRegistry,
|
|
147
|
+
warn: (message: string) => void,
|
|
148
|
+
): void {
|
|
149
|
+
const configuredChains = settings.get("retry.fallbackChains");
|
|
150
|
+
if (configuredChains === undefined) return;
|
|
151
|
+
const report = (message: string) => {
|
|
152
|
+
logger.warn(message);
|
|
153
|
+
warn(message);
|
|
154
|
+
};
|
|
155
|
+
if (!configuredChains || typeof configuredChains !== "object" || Array.isArray(configuredChains)) {
|
|
156
|
+
report("retry.fallbackChains must be a mapping of role names or model selectors to selector arrays.");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
for (const key in configuredChains) {
|
|
161
|
+
const chain = configuredChains[key];
|
|
162
|
+
const keyKind = isRetryFallbackModelKey(key) ? "model" : "role";
|
|
163
|
+
if (keyKind === "model") {
|
|
164
|
+
if (isRetryFallbackWildcardKey(key)) {
|
|
165
|
+
const { provider } = parseRetryFallbackWildcard(key, candidate =>
|
|
166
|
+
isKnownProvider(modelRegistry, candidate),
|
|
167
|
+
);
|
|
168
|
+
if (!isKnownProvider(modelRegistry, provider)) {
|
|
169
|
+
report(`retry.fallbackChains wildcard key references unknown provider: ${key}`);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
const parsedKey = parseRetryFallbackSelector(key, modelRegistry);
|
|
173
|
+
if (!parsedKey) {
|
|
174
|
+
report(`Invalid model selector key in retry.fallbackChains: ${key}`);
|
|
175
|
+
} else if (!modelRegistry.find(parsedKey.provider, parsedKey.id)) {
|
|
176
|
+
report(`retry.fallbackChains key references unknown model: ${key}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!Array.isArray(chain)) {
|
|
181
|
+
report(`Fallback chain for ${keyKind} '${key}' must be an array of selector strings.`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
for (const selectorStr of chain) {
|
|
185
|
+
if (typeof selectorStr !== "string") {
|
|
186
|
+
report(`Fallback chain for ${keyKind} '${key}' contains a non-string selector.`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (isRetryFallbackWildcardKey(selectorStr)) {
|
|
190
|
+
const { provider } = parseRetryFallbackWildcard(selectorStr, candidate =>
|
|
191
|
+
isKnownProvider(modelRegistry, candidate),
|
|
192
|
+
);
|
|
193
|
+
if (!isKnownProvider(modelRegistry, provider)) {
|
|
194
|
+
report(`Fallback chain for ${keyKind} '${key}' references unknown provider: ${selectorStr}`);
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const parsed = parseRetryFallbackSelector(selectorStr, modelRegistry);
|
|
199
|
+
if (!parsed) {
|
|
200
|
+
report(`Invalid fallback selector format in ${keyKind} '${key}': ${selectorStr}`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (!modelRegistry.find(parsed.provider, parsed.id)) {
|
|
204
|
+
report(`Fallback chain for ${keyKind} '${key}' references unknown model: ${selectorStr}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Returns the configured fallback-primary restoration policy. */
|
|
211
|
+
export function getRetryFallbackRevertPolicy(settings: Settings): RetryFallbackRevertPolicy {
|
|
212
|
+
return settings.get("retry.fallbackRevertPolicy") === "never" ? "never" : "cooldown-expiry";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Resolves the primary selector represented by a fallback-chain key. */
|
|
216
|
+
function getRetryFallbackPrimarySelector(
|
|
217
|
+
context: RetryFallbackResolutionContext,
|
|
218
|
+
chainKey: string,
|
|
219
|
+
): RetryFallbackSelector | undefined {
|
|
220
|
+
if (isRetryFallbackWildcardKey(chainKey)) return undefined;
|
|
221
|
+
if (isRetryFallbackModelKey(chainKey)) return parseRetryFallbackSelector(chainKey, context.modelLookup);
|
|
222
|
+
const configuredSelector = context.getModelRole(chainKey);
|
|
223
|
+
return configuredSelector ? parseRetryFallbackSelector(configuredSelector, context.modelLookup) : undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function selectorMatchesCurrent(
|
|
227
|
+
primary: RetryFallbackSelector | undefined,
|
|
228
|
+
currentSelector: string,
|
|
229
|
+
currentBaseSelector: string,
|
|
230
|
+
currentPlainSelector: string | undefined,
|
|
231
|
+
currentPlainBaseSelector: string | undefined,
|
|
232
|
+
): boolean {
|
|
233
|
+
if (!primary) return false;
|
|
234
|
+
if (primary.raw === currentSelector || (currentPlainSelector && primary.raw === currentPlainSelector)) return true;
|
|
235
|
+
const base = formatRetryFallbackBaseSelector(primary);
|
|
236
|
+
return base === currentBaseSelector || (!!currentPlainBaseSelector && base === currentPlainBaseSelector);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Resolve the chain key for a concrete selector by specificity: exact model,
|
|
241
|
+
* longest matching wildcard, hinted/configured role, then default.
|
|
242
|
+
*/
|
|
243
|
+
export function resolveRetryFallbackChainKey(
|
|
244
|
+
context: RetryFallbackResolutionContext,
|
|
245
|
+
currentSelector: string,
|
|
246
|
+
currentModel?: Model | null,
|
|
247
|
+
roleHint?: string,
|
|
248
|
+
): string | undefined {
|
|
249
|
+
const parsedConfigured = parseRetryFallbackSelector(currentSelector, context.modelLookup);
|
|
250
|
+
const currentPlainSelector = currentModel
|
|
251
|
+
? formatModelSelectorValue(formatModelString(currentModel), parsedConfigured?.thinkingLevel)
|
|
252
|
+
: undefined;
|
|
253
|
+
const parsedCurrent =
|
|
254
|
+
parsedConfigured ??
|
|
255
|
+
(currentPlainSelector ? parseRetryFallbackSelector(currentPlainSelector, context.modelLookup) : undefined);
|
|
256
|
+
if (!parsedCurrent) {
|
|
257
|
+
if (roleHint && Array.isArray(context.chains[roleHint])) return roleHint;
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
const currentBaseSelector = formatRetryFallbackBaseSelector(parsedCurrent);
|
|
261
|
+
const currentPlainBaseSelector =
|
|
262
|
+
currentPlainSelector && currentPlainSelector !== currentSelector
|
|
263
|
+
? formatRetryFallbackBaseSelector(parseRetryFallbackSelector(currentPlainSelector) ?? parsedCurrent)
|
|
264
|
+
: undefined;
|
|
265
|
+
|
|
266
|
+
// 1. Exact model-selector keys — most specific.
|
|
267
|
+
for (const key in context.chains) {
|
|
268
|
+
if (isRetryFallbackModelKey(key) && !isRetryFallbackWildcardKey(key)) {
|
|
269
|
+
if (
|
|
270
|
+
selectorMatchesCurrent(
|
|
271
|
+
getRetryFallbackPrimarySelector(context, key),
|
|
272
|
+
currentSelector,
|
|
273
|
+
currentBaseSelector,
|
|
274
|
+
currentPlainSelector,
|
|
275
|
+
currentPlainBaseSelector,
|
|
276
|
+
)
|
|
277
|
+
) {
|
|
278
|
+
return key;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 2. Provider wildcards — an id-prefixed key (`openrouter/google/*`)
|
|
284
|
+
// beats the plain `provider/*` key for ids under its prefix.
|
|
285
|
+
let wildcardMatch: string | undefined;
|
|
286
|
+
let wildcardPrefixLength = -1;
|
|
287
|
+
for (const key in context.chains) {
|
|
288
|
+
if (!isRetryFallbackWildcardKey(key) || !Array.isArray(context.chains[key])) continue;
|
|
289
|
+
const { provider, idPrefix } = parseRetryFallbackWildcard(key, provider =>
|
|
290
|
+
context.modelLookup.hasProvider(provider),
|
|
291
|
+
);
|
|
292
|
+
if (provider !== parsedCurrent.provider) continue;
|
|
293
|
+
if (idPrefix !== undefined && !parsedCurrent.id.startsWith(`${idPrefix}/`)) continue;
|
|
294
|
+
const prefixLength = idPrefix?.length ?? 0;
|
|
295
|
+
if (prefixLength > wildcardPrefixLength) {
|
|
296
|
+
wildcardMatch = key;
|
|
297
|
+
wildcardPrefixLength = prefixLength;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (wildcardMatch) return wildcardMatch;
|
|
301
|
+
|
|
302
|
+
// 3. The hinted role, then role keys matched by their assigned model.
|
|
303
|
+
if (roleHint && Array.isArray(context.chains[roleHint])) return roleHint;
|
|
304
|
+
for (const key in context.chains) {
|
|
305
|
+
if (isRetryFallbackModelKey(key)) continue;
|
|
306
|
+
if (
|
|
307
|
+
selectorMatchesCurrent(
|
|
308
|
+
getRetryFallbackPrimarySelector(context, key),
|
|
309
|
+
currentSelector,
|
|
310
|
+
currentBaseSelector,
|
|
311
|
+
currentPlainSelector,
|
|
312
|
+
currentPlainBaseSelector,
|
|
313
|
+
)
|
|
314
|
+
) {
|
|
315
|
+
return key;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 4. The default chain, when default has no explicit role primary.
|
|
320
|
+
const defaultChain = context.chains.default;
|
|
321
|
+
if (
|
|
322
|
+
Array.isArray(defaultChain) &&
|
|
323
|
+
defaultChain.length > 0 &&
|
|
324
|
+
getRetryFallbackPrimarySelector(context, "default") === undefined
|
|
325
|
+
) {
|
|
326
|
+
return "default";
|
|
327
|
+
}
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Parse one configured chain entry. A `provider/*` entry keeps the failing
|
|
333
|
+
* model's id and swaps the provider (google-antigravity/x → google/x); an
|
|
334
|
+
* id-prefixed `provider/prefix/*` entry re-prefixes the failing model's
|
|
335
|
+
* bare id instead (openrouter/google/* : google-antigravity/x →
|
|
336
|
+
* openrouter/google/x). Ids the target provider lacks are skipped by the
|
|
337
|
+
* candidate loop's registry lookup.
|
|
338
|
+
*/
|
|
339
|
+
function parseRetryFallbackChainEntry(
|
|
340
|
+
context: RetryFallbackResolutionContext,
|
|
341
|
+
entry: string,
|
|
342
|
+
current: RetryFallbackSelector | undefined,
|
|
343
|
+
): RetryFallbackSelector | undefined {
|
|
344
|
+
if (!isRetryFallbackWildcardKey(entry)) return parseRetryFallbackSelector(entry, context.modelLookup);
|
|
345
|
+
if (!current) return undefined;
|
|
346
|
+
const { provider, idPrefix } = parseRetryFallbackWildcard(entry, candidate =>
|
|
347
|
+
context.modelLookup.hasProvider(candidate),
|
|
348
|
+
);
|
|
349
|
+
const bareId = current.id.slice(current.id.lastIndexOf("/") + 1);
|
|
350
|
+
let id: string;
|
|
351
|
+
if (idPrefix !== undefined) {
|
|
352
|
+
id = `${idPrefix}/${bareId}`;
|
|
353
|
+
} else if (
|
|
354
|
+
bareId !== current.id &&
|
|
355
|
+
!context.modelLookup.find(provider, current.id) &&
|
|
356
|
+
context.modelLookup.find(provider, bareId)
|
|
357
|
+
) {
|
|
358
|
+
// Aggregator → direct: the failing id carries a vendor prefix the
|
|
359
|
+
// target provider does not use (openrouter/google/x → google-vertex/x).
|
|
360
|
+
id = bareId;
|
|
361
|
+
} else {
|
|
362
|
+
id = current.id;
|
|
363
|
+
}
|
|
364
|
+
return { raw: `${provider}/${id}`, provider, id, thinkingLevel: undefined };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Builds a fallback chain beginning with its effective primary selector. */
|
|
368
|
+
function getRetryFallbackEffectiveChain(
|
|
369
|
+
context: RetryFallbackResolutionContext,
|
|
370
|
+
chainKey: string,
|
|
371
|
+
currentSelector: string,
|
|
372
|
+
currentModel: Model | null | undefined,
|
|
373
|
+
allowMissingPrimary: boolean,
|
|
374
|
+
): RetryFallbackSelector[] {
|
|
375
|
+
const parsedConfigured = parseRetryFallbackSelector(currentSelector, context.modelLookup);
|
|
376
|
+
const parsedCurrent =
|
|
377
|
+
parsedConfigured ??
|
|
378
|
+
(currentModel
|
|
379
|
+
? parseRetryFallbackSelector(
|
|
380
|
+
formatModelSelectorValue(formatModelString(currentModel), undefined),
|
|
381
|
+
context.modelLookup,
|
|
382
|
+
)
|
|
383
|
+
: undefined);
|
|
384
|
+
const seen = new Set<string>();
|
|
385
|
+
const chain: RetryFallbackSelector[] = [];
|
|
386
|
+
if (isRetryFallbackWildcardKey(chainKey)) {
|
|
387
|
+
// A wildcard key has no fixed primary: the active model is the
|
|
388
|
+
// primary, followed by the configured provider-level fallbacks.
|
|
389
|
+
if (parsedCurrent) {
|
|
390
|
+
chain.push(parsedCurrent);
|
|
391
|
+
seen.add(parsedCurrent.raw);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
const primarySelector = getRetryFallbackPrimarySelector(context, chainKey);
|
|
395
|
+
if (primarySelector) {
|
|
396
|
+
chain.push(primarySelector);
|
|
397
|
+
seen.add(primarySelector.raw);
|
|
398
|
+
} else if ((chainKey === "default" || allowMissingPrimary) && parsedCurrent) {
|
|
399
|
+
chain.push(parsedCurrent);
|
|
400
|
+
seen.add(parsedCurrent.raw);
|
|
401
|
+
} else if (!allowMissingPrimary) {
|
|
402
|
+
return [];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
for (const selector of context.chains[chainKey] ?? []) {
|
|
406
|
+
const parsed = parseRetryFallbackChainEntry(context, selector, parsedCurrent);
|
|
407
|
+
if (!parsed || seen.has(parsed.raw)) continue;
|
|
408
|
+
seen.add(parsed.raw);
|
|
409
|
+
chain.push(parsed);
|
|
410
|
+
}
|
|
411
|
+
return chain;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Return the candidates after the current selector in an effective chain. */
|
|
415
|
+
export function findRetryFallbackCandidates(
|
|
416
|
+
context: RetryFallbackResolutionContext,
|
|
417
|
+
chainKey: string,
|
|
418
|
+
currentSelector: string,
|
|
419
|
+
currentModel?: Model | null,
|
|
420
|
+
options?: { allowMissingPrimary?: boolean },
|
|
421
|
+
): RetryFallbackSelector[] {
|
|
422
|
+
const chain = getRetryFallbackEffectiveChain(
|
|
423
|
+
context,
|
|
424
|
+
chainKey,
|
|
425
|
+
currentSelector,
|
|
426
|
+
currentModel,
|
|
427
|
+
options?.allowMissingPrimary === true,
|
|
428
|
+
);
|
|
429
|
+
const parsedConfigured = parseRetryFallbackSelector(currentSelector, context.modelLookup);
|
|
430
|
+
const currentPlainSelector = currentModel
|
|
431
|
+
? formatModelSelectorValue(formatModelString(currentModel), parsedConfigured?.thinkingLevel)
|
|
432
|
+
: undefined;
|
|
433
|
+
const parsedCurrent =
|
|
434
|
+
parsedConfigured ??
|
|
435
|
+
(currentPlainSelector ? parseRetryFallbackSelector(currentPlainSelector, context.modelLookup) : undefined);
|
|
436
|
+
if (!parsedCurrent) return chain;
|
|
437
|
+
if (chain.length <= 1) return [];
|
|
438
|
+
const currentBaseSelector = formatRetryFallbackBaseSelector(parsedCurrent);
|
|
439
|
+
const currentPlainBaseSelector =
|
|
440
|
+
parsedCurrent && currentPlainSelector && currentPlainSelector !== currentSelector
|
|
441
|
+
? formatRetryFallbackBaseSelector(parseRetryFallbackSelector(currentPlainSelector) ?? parsedCurrent)
|
|
442
|
+
: undefined;
|
|
443
|
+
const exactIndex = chain.findIndex(
|
|
444
|
+
selector => selector.raw === currentSelector || selector.raw === currentPlainSelector,
|
|
445
|
+
);
|
|
446
|
+
if (exactIndex >= 0) return chain.slice(exactIndex + 1);
|
|
447
|
+
const baseIndex = currentBaseSelector
|
|
448
|
+
? chain.findIndex(selector => {
|
|
449
|
+
const selectorBase = formatRetryFallbackBaseSelector(selector);
|
|
450
|
+
return selectorBase === currentBaseSelector || selectorBase === currentPlainBaseSelector;
|
|
451
|
+
})
|
|
452
|
+
: -1;
|
|
453
|
+
if (baseIndex >= 0) return chain.slice(baseIndex + 1);
|
|
454
|
+
return chain.slice(1);
|
|
455
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
4
|
+
import {
|
|
5
|
+
extractExplicitThinkingSelector,
|
|
6
|
+
formatModelSelectorValue,
|
|
7
|
+
getModelMatchPreferences,
|
|
8
|
+
parseModelString,
|
|
9
|
+
type ResolvedModelRoleValue,
|
|
10
|
+
resolveModelRoleValue,
|
|
11
|
+
} from "../config/model-resolver";
|
|
12
|
+
import type { Settings } from "../config/settings";
|
|
13
|
+
|
|
14
|
+
/** Formats a role assignment while preserving its explicit thinking selector. */
|
|
15
|
+
export function formatRoleModelValue(
|
|
16
|
+
settings: Settings,
|
|
17
|
+
modelRegistry: ModelRegistry,
|
|
18
|
+
role: string,
|
|
19
|
+
model: Model,
|
|
20
|
+
selectorOverride?: string,
|
|
21
|
+
thinkingLevelOverride?: ThinkingLevel,
|
|
22
|
+
): string {
|
|
23
|
+
const modelKey = selectorOverride ?? `${model.provider}/${model.id}`;
|
|
24
|
+
if (thinkingLevelOverride !== undefined) return formatModelSelectorValue(modelKey, thinkingLevelOverride);
|
|
25
|
+
const existingRoleValue = settings.getModelRole(role);
|
|
26
|
+
if (!existingRoleValue) return modelKey;
|
|
27
|
+
const thinkingLevel = extractExplicitThinkingSelector(existingRoleValue, settings, {
|
|
28
|
+
isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
|
|
29
|
+
});
|
|
30
|
+
return formatModelSelectorValue(modelKey, thinkingLevel);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Resolves a configured model target relative to the current provider. */
|
|
34
|
+
export function resolveConfiguredModelTarget(
|
|
35
|
+
configuredTarget: string | undefined,
|
|
36
|
+
currentModel: Model,
|
|
37
|
+
availableModels: Model[],
|
|
38
|
+
): Model | undefined {
|
|
39
|
+
const trimmedTarget = configuredTarget?.trim();
|
|
40
|
+
if (!trimmedTarget) return undefined;
|
|
41
|
+
const parsed = parseModelString(trimmedTarget, {
|
|
42
|
+
allowMaxSuffix: true,
|
|
43
|
+
allowAutoAlias: true,
|
|
44
|
+
isLiteralModelId: (provider, id) => availableModels.some(model => model.provider === provider && model.id === id),
|
|
45
|
+
});
|
|
46
|
+
if (parsed) {
|
|
47
|
+
const explicitModel = availableModels.find(model => model.provider === parsed.provider && model.id === parsed.id);
|
|
48
|
+
if (explicitModel) return explicitModel;
|
|
49
|
+
}
|
|
50
|
+
return availableModels.find(model => model.provider === currentModel.provider && model.id === trimmedTarget);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Resolves a model's configured context-promotion target. */
|
|
54
|
+
export function resolveContextPromotionConfiguredTarget(
|
|
55
|
+
currentModel: Model,
|
|
56
|
+
availableModels: Model[],
|
|
57
|
+
): Model | undefined {
|
|
58
|
+
return resolveConfiguredModelTarget(currentModel.contextPromotionTarget, currentModel, availableModels);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Resolves a model's configured compaction target. */
|
|
62
|
+
export function resolveCompactionConfiguredTarget(currentModel: Model, availableModels: Model[]): Model | undefined {
|
|
63
|
+
return resolveConfiguredModelTarget(currentModel.compactionModel, currentModel, availableModels);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Resolves a model role and its explicit thinking selection. */
|
|
67
|
+
export function resolveRoleModelFull(
|
|
68
|
+
settings: Settings,
|
|
69
|
+
role: string,
|
|
70
|
+
availableModels: Model[],
|
|
71
|
+
currentModel: Model | undefined,
|
|
72
|
+
): ResolvedModelRoleValue {
|
|
73
|
+
const roleModelStr =
|
|
74
|
+
role === "default"
|
|
75
|
+
? (settings.getModelRole("default") ??
|
|
76
|
+
(currentModel ? `${currentModel.provider}/${currentModel.id}` : undefined))
|
|
77
|
+
: settings.getModelRole(role);
|
|
78
|
+
if (!roleModelStr) {
|
|
79
|
+
return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
|
|
80
|
+
}
|
|
81
|
+
return resolveModelRoleValue(roleModelStr, availableModels, {
|
|
82
|
+
settings,
|
|
83
|
+
matchPreferences: getModelMatchPreferences(settings),
|
|
84
|
+
});
|
|
85
|
+
}
|