@oh-my-pi/pi-coding-agent 17.0.7 → 17.0.9
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 +91 -0
- package/dist/cli.js +5025 -4930
- package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +28 -1
- package/dist/types/config/model-resolver.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +50 -3
- package/dist/types/dap/client.d.ts +19 -0
- package/dist/types/extensibility/extensions/runner.d.ts +4 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +1 -1
- package/dist/types/extensibility/typebox.d.ts +4 -0
- package/dist/types/hindsight/client.d.ts +10 -0
- package/dist/types/hindsight/config.d.ts +8 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +6 -2
- package/dist/types/mcp/render.d.ts +2 -2
- package/dist/types/mcp/tool-bridge.d.ts +16 -3
- package/dist/types/mcp/types.d.ts +6 -0
- package/dist/types/mnemopi/state.d.ts +2 -0
- package/dist/types/modes/components/oauth-selector.d.ts +8 -0
- package/dist/types/modes/components/settings-defs.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +25 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +8 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
- package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/types.d.ts +14 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +12 -8
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session-error-log.test.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +86 -3
- package/dist/types/session/session-loader.d.ts +6 -4
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/prewalk.d.ts +3 -0
- package/dist/types/task/types.d.ts +14 -0
- package/dist/types/tools/ask.d.ts +10 -0
- package/dist/types/tools/hub/jobs.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/report-tool-issue.d.ts +24 -9
- package/dist/types/tools/tool-timeouts.d.ts +8 -2
- package/dist/types/utils/changelog.d.ts +4 -6
- package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +1 -1
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/models-cli.ts +37 -17
- package/src/cli/update-cli.ts +14 -1
- package/src/cli/usage-cli.ts +1 -1
- package/src/config/__tests__/model-registry.test.ts +147 -0
- package/src/config/model-registry.ts +87 -24
- package/src/config/model-resolver.ts +56 -32
- package/src/config/settings-schema.ts +46 -3
- package/src/config/settings.ts +68 -5
- package/src/dap/client.ts +86 -7
- package/src/edit/diff.ts +4 -4
- package/src/eval/py/prelude.py +5 -5
- package/src/export/html/template.js +23 -9
- package/src/extensibility/extensions/runner.ts +9 -4
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +851 -310
- package/src/extensibility/typebox.ts +22 -9
- package/src/hindsight/client.test.ts +42 -0
- package/src/hindsight/client.ts +40 -3
- package/src/hindsight/config.ts +18 -0
- package/src/internal-urls/registry-helpers.ts +40 -0
- package/src/launch/broker.ts +31 -5
- package/src/lsp/client.ts +23 -0
- package/src/lsp/clients/biome-client.ts +3 -4
- package/src/lsp/index.ts +26 -8
- package/src/main.ts +16 -5
- package/src/mcp/manager.ts +39 -8
- package/src/mcp/render.ts +94 -35
- package/src/mcp/tool-bridge.ts +116 -11
- package/src/mcp/types.ts +7 -0
- package/src/mnemopi/state.ts +70 -3
- package/src/modes/components/agent-dashboard.ts +6 -2
- package/src/modes/components/agent-hub.ts +26 -9
- package/src/modes/components/chat-transcript-builder.ts +13 -2
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/oauth-selector.ts +24 -7
- package/src/modes/components/plan-review-overlay.ts +20 -4
- package/src/modes/components/settings-defs.ts +5 -2
- package/src/modes/components/settings-selector.ts +9 -5
- package/src/modes/components/tool-execution.test.ts +63 -2
- package/src/modes/components/user-message.ts +100 -1
- package/src/modes/controllers/command-controller.ts +36 -12
- package/src/modes/controllers/event-controller.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +18 -0
- package/src/modes/controllers/input-controller.ts +47 -12
- package/src/modes/controllers/mcp-command-controller.ts +70 -40
- package/src/modes/controllers/selector-controller.ts +82 -6
- package/src/modes/interactive-mode.ts +39 -4
- package/src/modes/rpc/rpc-client.ts +94 -3
- package/src/modes/rpc/rpc-frame.ts +164 -4
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +79 -7
- package/src/modes/rpc/rpc-types.ts +34 -2
- package/src/modes/setup-wizard/scenes/model.ts +5 -7
- package/src/modes/setup-wizard/scenes/providers.ts +3 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
- package/src/modes/setup-wizard/scenes/theme.ts +13 -3
- package/src/modes/setup-wizard/scenes/types.ts +9 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +6 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/types.ts +16 -3
- package/src/modes/utils/ui-helpers.ts +40 -20
- package/src/prompts/goals/guided-goal-system.md +24 -3
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/task.md +12 -2
- package/src/sdk.ts +179 -51
- package/src/session/agent-session-error-log.test.ts +59 -0
- package/src/session/agent-session.ts +351 -46
- package/src/session/session-context.test.ts +224 -1
- package/src/session/session-context.ts +41 -2
- package/src/session/session-loader.ts +10 -5
- package/src/slash-commands/helpers/usage-report.ts +3 -1
- package/src/system-prompt.ts +1 -2
- package/src/task/executor.ts +44 -27
- package/src/task/index.ts +63 -8
- package/src/task/prewalk.ts +6 -0
- package/src/task/structured-subagent.ts +3 -1
- package/src/task/types.ts +16 -0
- package/src/task/worktree.ts +25 -11
- package/src/tools/ask.ts +15 -0
- package/src/tools/bash.ts +14 -6
- package/src/tools/browser.ts +1 -1
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval.ts +4 -5
- package/src/tools/fetch.ts +1 -1
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +67 -8
- package/src/tools/hub/launch.ts +7 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/report-tool-issue.ts +79 -28
- package/src/tools/tool-timeouts.ts +10 -3
- package/src/tools/write.ts +31 -0
- package/src/utils/changelog.ts +54 -58
- package/src/utils/git.ts +57 -49
- package/src/web/search/providers/firecrawl.ts +46 -13
- package/src/web/search/types.ts +5 -1
package/src/sdk.ts
CHANGED
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
obfuscateProviderContext,
|
|
120
120
|
SecretObfuscator,
|
|
121
121
|
} from "./secrets";
|
|
122
|
-
import { AgentSession, type PlanYolo, type Prewalk } from "./session/agent-session";
|
|
122
|
+
import { AgentSession, type InitialRetryFallbackState, type PlanYolo, type Prewalk } from "./session/agent-session";
|
|
123
123
|
import { discoverAuthStorage as discoverAuthStorageFromConfig } from "./session/auth-broker-config";
|
|
124
124
|
import type { AuthStorage } from "./session/auth-storage";
|
|
125
125
|
import { createInterruptedTurnAbortMessage } from "./session/exit-diagnostics";
|
|
@@ -907,7 +907,7 @@ function registerEvalCleanup(): void {
|
|
|
907
907
|
postmortem.register("julia-cleanup", disposeAllJuliaKernelSessions);
|
|
908
908
|
}
|
|
909
909
|
|
|
910
|
-
function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
|
910
|
+
export function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
|
911
911
|
const definition: ToolDefinition & { [TOOL_DEFINITION_MARKER]: true } = {
|
|
912
912
|
name: tool.name,
|
|
913
913
|
label: tool.label,
|
|
@@ -917,6 +917,9 @@ function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
|
|
917
917
|
loadMode: defaultLoadModeForToolName(tool.name, tool.loadMode),
|
|
918
918
|
deferrable: tool.deferrable,
|
|
919
919
|
approval: typeof tool.approval === "function" ? tool.approval.bind(tool) : tool.approval,
|
|
920
|
+
// Preserved through RegisteredToolAdapter so MCP-backed tools' explicit
|
|
921
|
+
// `strict: false` (#4336/#4340) survives the custom-tool → definition bridge.
|
|
922
|
+
strict: tool.strict,
|
|
920
923
|
mcpServerName: tool.mcpServerName,
|
|
921
924
|
mcpToolName: tool.mcpToolName,
|
|
922
925
|
execute: (toolCallId, params, signal, onUpdate, ctx) =>
|
|
@@ -1387,6 +1390,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1387
1390
|
);
|
|
1388
1391
|
let model = options.model;
|
|
1389
1392
|
let modelFallbackMessage: string | undefined;
|
|
1393
|
+
let initialRetryFallback: InitialRetryFallbackState | undefined;
|
|
1390
1394
|
// Identify session model strings to restore in fallback order. We do an
|
|
1391
1395
|
// initial pass here so model-dependent setup (thinking-level resolution,
|
|
1392
1396
|
// host preconnect) can use the restored model; extension-registered
|
|
@@ -1692,6 +1696,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1692
1696
|
getAgentId: () => resolvedAgentId,
|
|
1693
1697
|
getToolByName: name => session?.getToolByName(name),
|
|
1694
1698
|
agentRegistry,
|
|
1699
|
+
// The global lifecycle releases through AgentRegistry.global(); wiring it
|
|
1700
|
+
// onto a caller-supplied registry would report a cancel while releasing an
|
|
1701
|
+
// unrelated global ref. With no lifecycle, hub cancel falls back to
|
|
1702
|
+
// dispose + unregister on the session's own registry.
|
|
1703
|
+
agentLifecycle: options.agentRegistry ? undefined : () => AgentLifecycleManager.global(),
|
|
1695
1704
|
getSessionSpawns: () => options.spawns ?? "*",
|
|
1696
1705
|
getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
|
|
1697
1706
|
getActiveModelString,
|
|
@@ -2022,8 +2031,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2022
2031
|
// the background.
|
|
2023
2032
|
await modelRegistry.refreshRuntimeProviders("offline");
|
|
2024
2033
|
// Continue runtime discovery in the background (cache-aware) so startup is
|
|
2025
|
-
// only blocked on local cache reads, not provider network fetches.
|
|
2026
|
-
|
|
2034
|
+
// only blocked on local cache reads, not provider network fetches. Stash
|
|
2035
|
+
// the promise so the deferred `--model` retry below can await it instead
|
|
2036
|
+
// of starting a second concurrent discovery pass (the unfiltered
|
|
2037
|
+
// `refresh()` also covers runtime model managers).
|
|
2038
|
+
const runtimeDiscoveryPromise = modelRegistry.refreshRuntimeProviders().catch(error => {
|
|
2027
2039
|
logger.warn("runtime provider discovery failed", {
|
|
2028
2040
|
error: error instanceof Error ? error.message : String(error),
|
|
2029
2041
|
});
|
|
@@ -2072,8 +2084,42 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2072
2084
|
// registered. Use the same CLI resolver as the immediate path so bare role
|
|
2073
2085
|
// names, exact model names, and provider selectors keep one precedence rule.
|
|
2074
2086
|
if (!model && deferredModelPatterns.length > 0) {
|
|
2075
|
-
|
|
2087
|
+
// Deferred `--model` patterns almost always failed at the immediate
|
|
2088
|
+
// path (main.ts:881) precisely because discovery-backed providers
|
|
2089
|
+
// hadn't populated yet. Await the in-flight runtime discovery
|
|
2090
|
+
// already kicked off above (stash + reuse avoids a second concurrent
|
|
2091
|
+
// `#refreshRuntimeDiscoveries` pass for the same runtime model
|
|
2092
|
+
// managers; it resolves instantly when no runtime managers are
|
|
2093
|
+
// registered). `refreshRuntimeProviders()` only covers runtime model
|
|
2094
|
+
// managers, not config-discovery providers (e.g. user-configured
|
|
2095
|
+
// ollama); fall back to a full cache-aware refresh only when the
|
|
2096
|
+
// runtime pass didn't surface a match AND config-discovery providers
|
|
2097
|
+
// exist to fetch from. By then runtime managers short-circuit on the
|
|
2098
|
+
// fresh cache written by the awaited pass, closing the double-fetch
|
|
2099
|
+
// window.
|
|
2100
|
+
await logger.time("resolveModelDiscoveryDeferredRetry", () => runtimeDiscoveryPromise);
|
|
2076
2101
|
const matchPreferences = getModelMatchPreferences(settings);
|
|
2102
|
+
const runtimeResolved = deferredModelPatterns.some(pattern =>
|
|
2103
|
+
pattern.split(",").some(selector => {
|
|
2104
|
+
const trimmedSelector = selector.trim();
|
|
2105
|
+
if (!trimmedSelector) return false;
|
|
2106
|
+
const resolved = resolveCliModel({
|
|
2107
|
+
cliModel: trimmedSelector,
|
|
2108
|
+
modelRegistry,
|
|
2109
|
+
settings,
|
|
2110
|
+
preferences: matchPreferences,
|
|
2111
|
+
});
|
|
2112
|
+
return Boolean(
|
|
2113
|
+
resolved.model || (resolved.configuredPatterns && resolved.configuredPatterns.length > 0),
|
|
2114
|
+
);
|
|
2115
|
+
}),
|
|
2116
|
+
);
|
|
2117
|
+
if (!runtimeResolved && modelRegistry.getDiscoverableProviders().length > 0) {
|
|
2118
|
+
await logger.time("resolveModelDiscoveryFallbackNonRuntime", () =>
|
|
2119
|
+
modelRegistry.refresh("online-if-uncached"),
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
const availableModels = modelRegistry.getAll();
|
|
2077
2123
|
const expandedModelPatterns = deferredModelPatterns.flatMap(pattern =>
|
|
2078
2124
|
pattern.split(",").flatMap(selector => {
|
|
2079
2125
|
const trimmedSelector = selector.trim();
|
|
@@ -2085,26 +2131,70 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2085
2131
|
preferences: matchPreferences,
|
|
2086
2132
|
});
|
|
2087
2133
|
if (resolved.configuredPatterns && resolved.configuredPatterns.length > 0) {
|
|
2088
|
-
|
|
2134
|
+
const primaryPatterns: Array<{
|
|
2135
|
+
pattern: string;
|
|
2136
|
+
retryFallback: InitialRetryFallbackState | undefined;
|
|
2137
|
+
}> = resolved.configuredPatterns.map(pattern => ({
|
|
2138
|
+
pattern,
|
|
2139
|
+
retryFallback: undefined,
|
|
2140
|
+
}));
|
|
2141
|
+
if (!resolved.configuredRole || !settings.get("retry.modelFallback")) {
|
|
2142
|
+
return primaryPatterns;
|
|
2143
|
+
}
|
|
2144
|
+
const fallbackChains = settings.get("retry.fallbackChains");
|
|
2145
|
+
const roleFallbacks =
|
|
2146
|
+
fallbackChains[resolved.configuredRole] ??
|
|
2147
|
+
(resolved.configuredRole === "default" ? undefined : fallbackChains.default);
|
|
2148
|
+
if (!Array.isArray(roleFallbacks)) return primaryPatterns;
|
|
2149
|
+
const originalSelector = resolved.configuredPatterns[0];
|
|
2150
|
+
const parsedOriginal = parseModelString(originalSelector, {
|
|
2151
|
+
allowMaxSuffix: true,
|
|
2152
|
+
allowAutoAlias: true,
|
|
2153
|
+
isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
|
|
2154
|
+
});
|
|
2155
|
+
const retryFallback: InitialRetryFallbackState = {
|
|
2156
|
+
role: resolved.configuredRole,
|
|
2157
|
+
originalSelector,
|
|
2158
|
+
originalThinkingLevel: parsedOriginal?.thinkingLevel,
|
|
2159
|
+
};
|
|
2160
|
+
return [
|
|
2161
|
+
...primaryPatterns,
|
|
2162
|
+
...roleFallbacks
|
|
2163
|
+
.filter((pattern): pattern is string => typeof pattern === "string")
|
|
2164
|
+
.map(pattern => ({ pattern, retryFallback })),
|
|
2165
|
+
];
|
|
2089
2166
|
}
|
|
2090
2167
|
if (resolved.model) {
|
|
2091
2168
|
return [
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2169
|
+
{
|
|
2170
|
+
pattern: formatModelSelectorValue(
|
|
2171
|
+
resolved.selector ?? formatModelStringWithRouting(resolved.model),
|
|
2172
|
+
resolved.thinkingLevel,
|
|
2173
|
+
),
|
|
2174
|
+
retryFallback: undefined,
|
|
2175
|
+
},
|
|
2096
2176
|
];
|
|
2097
2177
|
}
|
|
2098
|
-
return resolveConfiguredModelPatterns([trimmedSelector], settings)
|
|
2178
|
+
return resolveConfiguredModelPatterns([trimmedSelector], settings).map(pattern => ({
|
|
2179
|
+
pattern,
|
|
2180
|
+
retryFallback: undefined,
|
|
2181
|
+
}));
|
|
2099
2182
|
}),
|
|
2100
2183
|
);
|
|
2101
2184
|
for (let patternIndex = 0; patternIndex < expandedModelPatterns.length; patternIndex += 1) {
|
|
2102
|
-
const pattern = expandedModelPatterns[patternIndex];
|
|
2185
|
+
const { pattern, retryFallback } = expandedModelPatterns[patternIndex];
|
|
2103
2186
|
const primary = parseModelPattern(pattern, availableModels, matchPreferences);
|
|
2104
|
-
if (!primary.model) continue;
|
|
2187
|
+
if (!primary.model || (retryFallback && !hasModelAuth(primary.model))) continue;
|
|
2105
2188
|
let selectedModel = primary.model;
|
|
2106
2189
|
let selectedThinkingLevel = primary.thinkingLevel;
|
|
2107
2190
|
let selectedExplicitThinkingLevel = primary.explicitThinkingLevel;
|
|
2191
|
+
// A chain entry without its own `:level` suffix inherits the
|
|
2192
|
+
// unavailable primary's configured thinking level, matching
|
|
2193
|
+
// runtime fallback-chain semantics.
|
|
2194
|
+
if (retryFallback && !selectedExplicitThinkingLevel && retryFallback.originalThinkingLevel !== undefined) {
|
|
2195
|
+
selectedThinkingLevel = retryFallback.originalThinkingLevel;
|
|
2196
|
+
selectedExplicitThinkingLevel = true;
|
|
2197
|
+
}
|
|
2108
2198
|
let authFallbackUsed = false;
|
|
2109
2199
|
if (options.modelPatternAuthFallback) {
|
|
2110
2200
|
const primaryKey = await modelRegistry.getApiKey(primary.model);
|
|
@@ -2132,8 +2222,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2132
2222
|
);
|
|
2133
2223
|
const seenSelectors = new Set<string>([primarySelector]);
|
|
2134
2224
|
const fallbackSelectors: string[] = [];
|
|
2135
|
-
for (const
|
|
2136
|
-
const fallback = parseModelPattern(
|
|
2225
|
+
for (const fallbackEntry of expandedModelPatterns.slice(patternIndex + 1)) {
|
|
2226
|
+
const fallback = parseModelPattern(fallbackEntry.pattern, availableModels, matchPreferences);
|
|
2137
2227
|
if (!fallback.model) continue;
|
|
2138
2228
|
const fallbackSelector = formatModelSelectorValue(
|
|
2139
2229
|
formatModelStringWithRouting(fallback.model),
|
|
@@ -2174,6 +2264,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2174
2264
|
}
|
|
2175
2265
|
}
|
|
2176
2266
|
model = selectedModel;
|
|
2267
|
+
initialRetryFallback = retryFallback;
|
|
2177
2268
|
modelFallbackMessage = undefined;
|
|
2178
2269
|
if (selectedExplicitThinkingLevel) {
|
|
2179
2270
|
restoredSessionThinkingLevel = selectedThinkingLevel;
|
|
@@ -2202,48 +2293,84 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2202
2293
|
// path-scoped `enabledModels` allow-list when configured. Skip when the
|
|
2203
2294
|
// user explicitly requested a model via --model that wasn't found.
|
|
2204
2295
|
if (!model && deferredModelPatterns.length === 0) {
|
|
2205
|
-
//
|
|
2206
|
-
//
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
//
|
|
2210
|
-
//
|
|
2211
|
-
// `
|
|
2212
|
-
//
|
|
2213
|
-
//
|
|
2214
|
-
// `
|
|
2215
|
-
//
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2296
|
+
// Retry the configured default role against the current catalog,
|
|
2297
|
+
// setting `model` (+ thinking level) when it resolves. Extension
|
|
2298
|
+
// factories register providers AFTER the early `defaultRoleSpec`
|
|
2299
|
+
// resolution, and configured discovery providers may still be
|
|
2300
|
+
// mid-discovery, so a role pointing at such a model (an openai-compat
|
|
2301
|
+
// plugin's `posthog/claude-opus-4-8`, a models.yml `openai-models-list`
|
|
2302
|
+
// endpoint) returned `undefined` there. Without this retry the
|
|
2303
|
+
// `pickDefaultAvailableModel` fallback below happily replaces the
|
|
2304
|
+
// user's configured default with a bundled provider's default whenever
|
|
2305
|
+
// a stray `OPENAI_API_KEY`/`ANTHROPIC_API_KEY` is in the environment.
|
|
2306
|
+
// (issues #3569, #6162)
|
|
2307
|
+
const tryResolveDefaultRole = async (): Promise<boolean> => {
|
|
2308
|
+
if (hasExplicitModel) return false;
|
|
2309
|
+
// Re-resolve the allowed set: extension factories and discovery
|
|
2310
|
+
// refreshes above may have registered models not visible earlier.
|
|
2311
|
+
const fallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
|
|
2219
2312
|
const reResolvedRoleSpec = resolveModelRoleValue(settings.getModelRole("default"), fallbackCandidates, {
|
|
2220
2313
|
settings,
|
|
2221
2314
|
matchPreferences: modelMatchPreferences,
|
|
2222
2315
|
});
|
|
2223
|
-
if (reResolvedRoleSpec.model)
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
}
|
|
2316
|
+
if (!reResolvedRoleSpec.model) return false;
|
|
2317
|
+
defaultRoleSpec = reResolvedRoleSpec;
|
|
2318
|
+
const resolvedDefaultModel = reResolvedRoleSpec.model;
|
|
2319
|
+
model = resolvedDefaultModel;
|
|
2320
|
+
modelFallbackMessage = undefined;
|
|
2321
|
+
// Recompute the thinking level against the now-real model.
|
|
2322
|
+
// `pickInitialThinkingLevel` closes over `defaultRoleSpec`,
|
|
2323
|
+
// so the role's explicit selector (e.g. `:max`) now applies.
|
|
2324
|
+
thinkingLevel = pickInitialThinkingLevel(resolvedDefaultModel);
|
|
2325
|
+
autoThinking = thinkingLevel === AUTO_THINKING;
|
|
2326
|
+
effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
|
|
2327
|
+
effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
|
|
2328
|
+
autoThinking
|
|
2329
|
+
? resolveProvisionalAutoLevel(resolvedDefaultModel)
|
|
2330
|
+
: resolveThinkingLevelForModel(resolvedDefaultModel, effectiveThinkingLevel),
|
|
2331
|
+
);
|
|
2332
|
+
preconnectModelHost(resolvedDefaultModel.baseUrl);
|
|
2333
|
+
return true;
|
|
2334
|
+
};
|
|
2335
|
+
|
|
2336
|
+
await tryResolveDefaultRole();
|
|
2242
2337
|
|
|
2243
2338
|
if (!model) {
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
|
|
2339
|
+
const fallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
|
|
2340
|
+
let pick = pickDefaultAvailableModel(fallbackCandidates.filter(hasModelAuth));
|
|
2341
|
+
|
|
2342
|
+
// Cold-cache discovery race (issues #6114, #6162): a discovery
|
|
2343
|
+
// provider (models.yml `openai-models-list`, LM Studio/Ollama/
|
|
2344
|
+
// llama.cpp, or an openai-compat proxy) ships no static models, so
|
|
2345
|
+
// the static+cached catalog resolved nothing above. Background
|
|
2346
|
+
// discovery in main.ts fires only AFTER createAgentSession returns,
|
|
2347
|
+
// so on a cache-cold boot the configured default stays unresolved
|
|
2348
|
+
// and `pick` silently degrades to an unrelated authed provider's
|
|
2349
|
+
// default (#6162) or "No models available" (#6114) — even though
|
|
2350
|
+
// `omp models` (which awaits discovery) lists the model. Await one
|
|
2351
|
+
// cache-aware discovery pass and retry when a default role is
|
|
2352
|
+
// configured (must win over `pick`) or nothing resolved at all.
|
|
2353
|
+
// The common path — role already resolved, or a `pick` with no
|
|
2354
|
+
// configured default — never pays for it.
|
|
2355
|
+
const defaultRoleConfigured = Boolean(settings.getModelRole("default"));
|
|
2356
|
+
if (
|
|
2357
|
+
!hasExplicitModel &&
|
|
2358
|
+
(defaultRoleConfigured || !pick) &&
|
|
2359
|
+
modelRegistry.getDiscoverableProviders().length > 0
|
|
2360
|
+
) {
|
|
2361
|
+
await logger.time("resolveModelDiscoveryFallback", () => modelRegistry.refresh("online-if-uncached"));
|
|
2362
|
+
if (!(await tryResolveDefaultRole()) && !model) {
|
|
2363
|
+
const refreshedCandidates = await resolveAllowedModels(
|
|
2364
|
+
modelRegistry,
|
|
2365
|
+
settings,
|
|
2366
|
+
modelMatchPreferences,
|
|
2367
|
+
);
|
|
2368
|
+
pick = pickDefaultAvailableModel(refreshedCandidates.filter(hasModelAuth));
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
if (!model && pick) {
|
|
2373
|
+
model = pick;
|
|
2247
2374
|
}
|
|
2248
2375
|
}
|
|
2249
2376
|
if (model) {
|
|
@@ -2918,6 +3045,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2918
3045
|
agent,
|
|
2919
3046
|
pruneToolDescriptions: inlineToolDescriptors,
|
|
2920
3047
|
thinkingLevel: autoThinking ? AUTO_THINKING : effectiveThinkingLevel,
|
|
3048
|
+
initialRetryFallback,
|
|
2921
3049
|
prewalk: options.prewalk,
|
|
2922
3050
|
planYolo: options.planYolo,
|
|
2923
3051
|
serviceTierByFamily: initialServiceTierByFamily,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract: a turn ending in a provider error surfaces one warn-level log
|
|
3
|
+
* carrying `provider`/`model`/`errorMessage`/`errorStatus`/`errorId`, so
|
|
4
|
+
* recurring provider stream failures are diagnosable from the main log alone
|
|
5
|
+
* (issue #6177). Non-error stops must not emit it.
|
|
6
|
+
*/
|
|
7
|
+
import { afterAll, afterEach, describe, expect, it } from "bun:test";
|
|
8
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
9
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
10
|
+
import { logProviderTurnError } from "./agent-session";
|
|
11
|
+
|
|
12
|
+
function makeMessage(overrides: Partial<AssistantMessage>): AssistantMessage {
|
|
13
|
+
return {
|
|
14
|
+
role: "assistant",
|
|
15
|
+
content: [],
|
|
16
|
+
api: "cursor",
|
|
17
|
+
provider: "cursor",
|
|
18
|
+
model: "composer-2.5",
|
|
19
|
+
usage: {} as AssistantMessage["usage"],
|
|
20
|
+
stopReason: "error",
|
|
21
|
+
timestamp: 1,
|
|
22
|
+
...overrides,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("logProviderTurnError", () => {
|
|
27
|
+
const events: logger.LogEvent[] = [];
|
|
28
|
+
const dispose = logger.registerLogSink(event => events.push(event));
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
events.length = 0;
|
|
32
|
+
});
|
|
33
|
+
afterAll(() => dispose());
|
|
34
|
+
|
|
35
|
+
it("emits a warn log with the provider error fields for stopReason:error", () => {
|
|
36
|
+
logProviderTurnError(
|
|
37
|
+
makeMessage({
|
|
38
|
+
errorMessage: "stream stall: idle watchdog fired",
|
|
39
|
+
errorStatus: 500,
|
|
40
|
+
errorId: 42,
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const warn = events.find(e => e.level === "warn" && e.message === "agent turn ended with provider error");
|
|
45
|
+
expect(warn).toBeDefined();
|
|
46
|
+
expect(warn?.context).toMatchObject({
|
|
47
|
+
provider: "cursor",
|
|
48
|
+
model: "composer-2.5",
|
|
49
|
+
errorMessage: "stream stall: idle watchdog fired",
|
|
50
|
+
errorStatus: 500,
|
|
51
|
+
errorId: 42,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("does not emit for a successful stop", () => {
|
|
56
|
+
logProviderTurnError(makeMessage({ stopReason: "stop", errorMessage: undefined }));
|
|
57
|
+
expect(events.some(e => e.message === "agent turn ended with provider error")).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
});
|