@oh-my-pi/pi-coding-agent 17.0.4 → 17.0.6
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 +209 -0
- package/dist/cli.js +3616 -3676
- package/dist/types/advisor/advise-tool.d.ts +3 -0
- package/dist/types/advisor/runtime.d.ts +7 -1
- package/dist/types/async/job-manager.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +7 -0
- package/dist/types/config/model-resolver.d.ts +8 -0
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/models-config.d.ts +6 -0
- package/dist/types/dap/client.d.ts +10 -0
- package/dist/types/dap/types.d.ts +6 -5
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/extensions/load-errors.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +4 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +16 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +8 -0
- package/dist/types/launch/spawn-options.d.ts +10 -0
- package/dist/types/launch/spawn-options.test.d.ts +1 -0
- package/dist/types/modes/components/status-line/component.d.ts +10 -3
- package/dist/types/modes/components/status-line/types.d.ts +3 -1
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +4 -3
- package/dist/types/modes/components/tree-selector.d.ts +6 -2
- package/dist/types/modes/components/usage-row.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/print-mode.d.ts +4 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +9 -0
- package/dist/types/modes/rpc/rpc-input.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/types/modes/setup-wizard/index.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/model.d.ts +3 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +42 -6
- package/dist/types/session/messages.d.ts +6 -0
- package/dist/types/session/session-paths.d.ts +21 -4
- package/dist/types/tools/browser/aria/aria-snapshot.d.ts +17 -7
- package/dist/types/tools/browser/cmux/socket-client.d.ts +2 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker.d.ts +2 -0
- package/dist/types/tools/gh.d.ts +5 -3
- package/dist/types/tools/hub/index.d.ts +1 -1
- package/dist/types/tools/hub/jobs.d.ts +1 -0
- package/dist/types/tools/hub/types.d.ts +2 -0
- package/dist/types/tools/tool-timeouts.d.ts +1 -1
- package/dist/types/tools/xdev.d.ts +5 -3
- package/dist/types/utils/git.d.ts +33 -0
- package/dist/types/vibe/__tests__/token-rate.test.d.ts +1 -0
- package/dist/types/vibe/runtime.d.ts +23 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +150 -0
- package/src/advisor/advise-tool.ts +4 -0
- package/src/advisor/runtime.ts +38 -14
- package/src/async/job-manager.ts +3 -0
- package/src/cli/bench-cli.ts +8 -2
- package/src/cli/dry-balance-cli.ts +1 -0
- package/src/config/model-registry.ts +89 -8
- package/src/config/model-resolver.ts +78 -14
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings.ts +3 -1
- package/src/dap/client.ts +168 -1
- package/src/dap/config.ts +51 -1
- package/src/dap/session.ts +575 -234
- package/src/dap/types.ts +6 -5
- package/src/discovery/agents.ts +2 -2
- package/src/exec/bash-executor.ts +68 -8
- package/src/extensibility/extensions/load-errors.ts +13 -0
- package/src/extensibility/extensions/runner.ts +6 -4
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +26 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +41 -6
- package/src/launch/broker.ts +34 -31
- package/src/launch/client.ts +7 -1
- package/src/launch/spawn-options.test.ts +31 -0
- package/src/launch/spawn-options.ts +17 -0
- package/src/lsp/types.ts +5 -1
- package/src/main.ts +25 -4
- package/src/mcp/transports/stdio.test.ts +9 -1
- package/src/modes/components/ask-dialog.ts +137 -73
- package/src/modes/components/chat-transcript-builder.ts +10 -1
- package/src/modes/components/status-line/component.ts +57 -3
- package/src/modes/components/status-line/segments.ts +20 -3
- package/src/modes/components/status-line/types.ts +3 -1
- package/src/modes/components/tool-execution.ts +5 -0
- package/src/modes/components/transcript-container.ts +23 -122
- package/src/modes/components/tree-selector.ts +10 -4
- package/src/modes/components/usage-row.ts +17 -1
- package/src/modes/controllers/command-controller.ts +41 -1
- package/src/modes/controllers/event-controller.ts +7 -3
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +29 -8
- package/src/modes/interactive-mode.ts +102 -60
- package/src/modes/noninteractive-dispose.test.ts +14 -1
- package/src/modes/print-mode.ts +81 -9
- package/src/modes/rpc/rpc-client.ts +76 -35
- package/src/modes/rpc/rpc-frame.ts +156 -0
- package/src/modes/rpc/rpc-input.ts +38 -0
- package/src/modes/rpc/rpc-mode.ts +11 -4
- package/src/modes/setup-wizard/index.ts +2 -0
- package/src/modes/setup-wizard/scenes/model.ts +134 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +3 -1
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +9 -3
- package/src/prompts/system/workflow-notice.md +89 -74
- package/src/prompts/tools/browser.md +3 -2
- package/src/prompts/tools/debug.md +2 -7
- package/src/prompts/tools/github.md +6 -1
- package/src/prompts/tools/hub.md +4 -2
- package/src/prompts/tools/task-async-contract.md +1 -0
- package/src/prompts/tools/task.md +9 -2
- package/src/sdk.ts +38 -6
- package/src/session/agent-session.ts +484 -188
- package/src/session/messages.test.ts +91 -0
- package/src/session/messages.ts +248 -110
- package/src/session/session-manager.ts +34 -3
- package/src/session/session-paths.ts +38 -9
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/task/executor.ts +104 -33
- package/src/task/index.ts +46 -9
- package/src/task/worktree.ts +10 -0
- package/src/tools/browser/aria/aria-snapshot.ts +36 -8
- package/src/tools/browser/cmux/cmux-tab.ts +2 -1
- package/src/tools/browser/cmux/socket-client.ts +139 -3
- package/src/tools/browser/run-cancellation.ts +4 -0
- package/src/tools/browser/tab-protocol.ts +2 -0
- package/src/tools/browser/tab-supervisor.ts +21 -11
- package/src/tools/browser/tab-worker.ts +199 -33
- package/src/tools/debug.ts +3 -0
- package/src/tools/gh.ts +40 -2
- package/src/tools/hub/index.ts +4 -1
- package/src/tools/hub/jobs.ts +42 -1
- package/src/tools/hub/types.ts +2 -0
- package/src/tools/tool-timeouts.ts +1 -1
- package/src/tools/xdev.ts +11 -4
- package/src/tools/yield.ts +4 -1
- package/src/utils/git.ts +237 -0
- package/src/{modes/components/status-line → utils}/token-rate.ts +6 -0
- package/src/vibe/__tests__/token-rate.test.ts +96 -0
- package/src/vibe/runtime.ts +50 -0
- package/src/web/search/index.ts +9 -5
- package/src/web/search/providers/codex.ts +195 -99
- /package/dist/types/{modes/components/status-line → utils}/token-rate.d.ts +0 -0
package/src/advisor/runtime.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
|
2
2
|
import { estimateTokens } from "@oh-my-pi/pi-agent-core/compaction";
|
|
3
3
|
import type { AssistantMessage, ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
5
|
+
import { type CursorExecResolvedCarrier, kCursorExecResolved } from "@oh-my-pi/pi-ai/utils/block-symbols";
|
|
5
6
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
6
7
|
import { obfuscateToolArguments, type SecretObfuscator } from "../secrets/obfuscator";
|
|
7
8
|
import { formatSessionHistoryMarkdown, PRIMARY_CONTEXT_CUSTOM_TYPES } from "../session/session-history-format";
|
|
@@ -129,7 +130,20 @@ export function quarantineAdvisorUnsafeOutput(
|
|
|
129
130
|
const unavailableToolNames = new Set<string>();
|
|
130
131
|
const generatedParts: string[] = [];
|
|
131
132
|
for (const block of message.content) {
|
|
132
|
-
|
|
133
|
+
// Cursor exec-channel native blocks (bash/read/grep/...) are stamped
|
|
134
|
+
// kCursorExecResolved: they already ran server-side through the
|
|
135
|
+
// advisor-scoped CursorExecHandlers bridge, which rejects ungranted
|
|
136
|
+
// tools in-band ("Tool not available") and lets the model self-correct.
|
|
137
|
+
// Quarantining them would discard the legitimate advise emitted in the
|
|
138
|
+
// same turn (issue #5900). The scoped bridge is the grant gate here, not
|
|
139
|
+
// this pre-dispatch check.
|
|
140
|
+
if (
|
|
141
|
+
block.type === "toolCall" &&
|
|
142
|
+
!availableToolNames.has(block.name) &&
|
|
143
|
+
(block as CursorExecResolvedCarrier)[kCursorExecResolved] !== true
|
|
144
|
+
) {
|
|
145
|
+
unavailableToolNames.add(block.name);
|
|
146
|
+
}
|
|
133
147
|
if (block.type === "toolCall" && block.name === "advise" && typeof block.arguments.note === "string") {
|
|
134
148
|
generatedParts.push(block.arguments.note);
|
|
135
149
|
}
|
|
@@ -219,8 +233,7 @@ interface PendingDelta {
|
|
|
219
233
|
|
|
220
234
|
interface CatchupWaiter {
|
|
221
235
|
threshold: number;
|
|
222
|
-
|
|
223
|
-
finish: () => void;
|
|
236
|
+
finish: (caughtUp: boolean) => void;
|
|
224
237
|
timer?: NodeJS.Timeout;
|
|
225
238
|
}
|
|
226
239
|
|
|
@@ -366,7 +379,13 @@ export class AdvisorRuntime {
|
|
|
366
379
|
}
|
|
367
380
|
}
|
|
368
381
|
|
|
369
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Wait until the advisor backlog falls below `threshold`.
|
|
384
|
+
*
|
|
385
|
+
* Returns `false` when the deadline, abort signal, or a runtime failure releases
|
|
386
|
+
* the waiter before the requested backlog was drained.
|
|
387
|
+
*/
|
|
388
|
+
waitForCatchup(maxMs: number, threshold: number, signal?: AbortSignal): Promise<boolean> {
|
|
370
389
|
if (
|
|
371
390
|
this.disposed ||
|
|
372
391
|
signal?.aborted ||
|
|
@@ -378,21 +397,26 @@ export class AdvisorRuntime {
|
|
|
378
397
|
// primary would otherwise park for the full catch-up budget.
|
|
379
398
|
this.#failing
|
|
380
399
|
)
|
|
381
|
-
return Promise.resolve();
|
|
382
|
-
const { promise, resolve } = Promise.withResolvers<
|
|
400
|
+
return Promise.resolve(this.#backlog < threshold);
|
|
401
|
+
const { promise, resolve } = Promise.withResolvers<boolean>();
|
|
383
402
|
let waiter!: CatchupWaiter;
|
|
384
|
-
const finish = (): void => {
|
|
403
|
+
const finish = (caughtUp: boolean): void => {
|
|
385
404
|
const idx = this.#waiters.indexOf(waiter);
|
|
386
405
|
if (idx >= 0) this.#waiters.splice(idx, 1);
|
|
387
406
|
clearTimeout(waiter.timer);
|
|
388
|
-
signal?.removeEventListener("abort",
|
|
389
|
-
resolve();
|
|
407
|
+
signal?.removeEventListener("abort", abort);
|
|
408
|
+
resolve(caughtUp);
|
|
409
|
+
};
|
|
410
|
+
const abort = (): void => finish(false);
|
|
411
|
+
waiter = {
|
|
412
|
+
threshold,
|
|
413
|
+
finish,
|
|
414
|
+
timer: setTimeout(abort, maxMs),
|
|
390
415
|
};
|
|
391
|
-
waiter = { threshold, resolve, finish, timer: setTimeout(finish, maxMs) };
|
|
392
416
|
this.#waiters.push(waiter);
|
|
393
|
-
signal?.addEventListener("abort",
|
|
417
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
394
418
|
if (signal?.aborted) {
|
|
395
|
-
|
|
419
|
+
abort();
|
|
396
420
|
}
|
|
397
421
|
return promise;
|
|
398
422
|
}
|
|
@@ -571,14 +595,14 @@ export class AdvisorRuntime {
|
|
|
571
595
|
for (let i = this.#waiters.length - 1; i >= 0; i--) {
|
|
572
596
|
const w = this.#waiters[i];
|
|
573
597
|
if (this.#backlog < w.threshold) {
|
|
574
|
-
w.finish();
|
|
598
|
+
w.finish(true);
|
|
575
599
|
}
|
|
576
600
|
}
|
|
577
601
|
}
|
|
578
602
|
|
|
579
603
|
#wakeAllWaiters(): void {
|
|
580
604
|
for (const w of [...this.#waiters]) {
|
|
581
|
-
w.finish();
|
|
605
|
+
w.finish(false);
|
|
582
606
|
}
|
|
583
607
|
}
|
|
584
608
|
|
package/src/async/job-manager.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface AsyncJob {
|
|
|
37
37
|
promise: Promise<void>;
|
|
38
38
|
resultText?: string;
|
|
39
39
|
errorText?: string;
|
|
40
|
+
/** Latest tool-render details reported by the running job. */
|
|
41
|
+
latestDetails?: Record<string, unknown>;
|
|
40
42
|
/**
|
|
41
43
|
* Registry id of the agent that registered the job (e.g. "Main",
|
|
42
44
|
* "AuthLoader"). Used by scoped cancel/list APIs so a subagent's teardown
|
|
@@ -205,6 +207,7 @@ export class AsyncJobManager {
|
|
|
205
207
|
};
|
|
206
208
|
|
|
207
209
|
const reportProgress = async (text: string, details?: Record<string, unknown>): Promise<void> => {
|
|
210
|
+
if (details) job.latestDetails = details;
|
|
208
211
|
if (!options?.onProgress) return;
|
|
209
212
|
try {
|
|
210
213
|
await options.onProgress(text, details);
|
package/src/cli/bench-cli.ts
CHANGED
|
@@ -443,7 +443,7 @@ function resolveBenchModels(
|
|
|
443
443
|
const resolved: BenchTarget[] = [];
|
|
444
444
|
const errors: string[] = [];
|
|
445
445
|
for (const selector of selectors) {
|
|
446
|
-
const result = resolveCliModel({ cliModel: selector, modelRegistry, preferences });
|
|
446
|
+
const result = resolveCliModel({ cliModel: selector, modelRegistry, settings, preferences });
|
|
447
447
|
if (result.error) {
|
|
448
448
|
errors.push(`${selector}: ${result.error}`);
|
|
449
449
|
continue;
|
|
@@ -454,7 +454,13 @@ function resolveBenchModels(
|
|
|
454
454
|
}
|
|
455
455
|
if (result.warning) writeStderr(`${chalk.yellow(`Warning: ${result.warning}`)}\n`);
|
|
456
456
|
let model = result.model;
|
|
457
|
-
const
|
|
457
|
+
const authSelector = result.configuredPatterns?.[result.configuredPatternIndex ?? 0] ?? selector;
|
|
458
|
+
const authenticated = resolveAuthenticatedAlternative(
|
|
459
|
+
authSelector,
|
|
460
|
+
model,
|
|
461
|
+
modelRegistry,
|
|
462
|
+
preferences.providerOrder,
|
|
463
|
+
);
|
|
458
464
|
if (authenticated) {
|
|
459
465
|
writeStderr(
|
|
460
466
|
`${chalk.yellow(
|
|
@@ -857,6 +857,18 @@ export class ModelRegistry {
|
|
|
857
857
|
}
|
|
858
858
|
}
|
|
859
859
|
await this.#refreshRuntimeDiscoveries(strategy, new Set([providerId]));
|
|
860
|
+
// #reloadStaticModels above may have rebuilt #models from static sources,
|
|
861
|
+
// dropping models previously discovered by OTHER runtime providers (their
|
|
862
|
+
// fetchDynamicModels results live only in #models + the SQLite cache, not
|
|
863
|
+
// in #loadModels' static inputs). Restore them from cache with the default
|
|
864
|
+
// online-if-uncached strategy: no network while their cached row is
|
|
865
|
+
// fresh, so the scoped refresh above stays the only forced fetch.
|
|
866
|
+
const otherRuntimeProviderIds = new Set(
|
|
867
|
+
[...this.#runtimeModelManagers.keys()].filter(runtimeId => runtimeId !== providerId),
|
|
868
|
+
);
|
|
869
|
+
if (otherRuntimeProviderIds.size > 0) {
|
|
870
|
+
await this.#refreshRuntimeDiscoveries("online-if-uncached", otherRuntimeProviderIds);
|
|
871
|
+
}
|
|
860
872
|
}
|
|
861
873
|
|
|
862
874
|
/**
|
|
@@ -1105,9 +1117,33 @@ export class ModelRegistry {
|
|
|
1105
1117
|
if (cache.fresh && cache.authoritative) {
|
|
1106
1118
|
authoritativeFreshProviders.add(providerId);
|
|
1107
1119
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1120
|
+
// The v10 model cache never persists request headers (#5780): restore
|
|
1121
|
+
// them from the bundled static catalog, and drop cached rows whose
|
|
1122
|
+
// headers cannot be rebuilt so the bundled fallback (which still
|
|
1123
|
+
// carries its headers) wins the startup merge instead of a cached
|
|
1124
|
+
// model with required transport headers missing.
|
|
1125
|
+
const omittedHeaderIds = new Set(cache.headerOmittedModelIds);
|
|
1126
|
+
const unrestorableHeaderIds = new Set(cache.unrestorableHeaderModelIds);
|
|
1127
|
+
const bundledById =
|
|
1128
|
+
omittedHeaderIds.size > 0
|
|
1129
|
+
? new Map(
|
|
1130
|
+
(getBundledModels(providerId as Parameters<typeof getBundledModels>[0]) as Model<Api>[]).map(
|
|
1131
|
+
bundledModel => [bundledModel.id, bundledModel],
|
|
1132
|
+
),
|
|
1133
|
+
)
|
|
1134
|
+
: undefined;
|
|
1135
|
+
const models: ModelSpec<Api>[] = [];
|
|
1136
|
+
for (const cachedModel of cache.models) {
|
|
1137
|
+
const spec = cachedModel.provider === providerId ? cachedModel : { ...cachedModel, provider: providerId };
|
|
1138
|
+
if (!omittedHeaderIds.has(spec.id)) {
|
|
1139
|
+
models.push(spec);
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
if (unrestorableHeaderIds.has(spec.id)) continue;
|
|
1143
|
+
const bundledHeaders = bundledById?.get(spec.id)?.headers;
|
|
1144
|
+
if (!bundledHeaders) continue;
|
|
1145
|
+
models.push({ ...spec, headers: bundledHeaders });
|
|
1146
|
+
}
|
|
1111
1147
|
const providerOverride = this.#providerOverrides.get(providerId);
|
|
1112
1148
|
const withTransport = providerOverride
|
|
1113
1149
|
? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
|
|
@@ -1145,13 +1181,20 @@ export class ModelRegistry {
|
|
|
1145
1181
|
continue;
|
|
1146
1182
|
}
|
|
1147
1183
|
const configStale = this.#isDiscoveryCacheOlderThanModelsConfig(cache.updatedAt);
|
|
1184
|
+
// Cached rows never persist headers (#5780); models that had live
|
|
1185
|
+
// headers cannot be rebuilt here, so exclude them and mark the
|
|
1186
|
+
// discovery stale to force a refetch instead of returning models
|
|
1187
|
+
// missing required transport headers.
|
|
1188
|
+
const omittedHeaderIds = new Set(cache.headerOmittedModelIds);
|
|
1189
|
+
const usableCacheModels =
|
|
1190
|
+
omittedHeaderIds.size > 0 ? cache.models.filter(model => !omittedHeaderIds.has(model.id)) : cache.models;
|
|
1148
1191
|
const models = this.#applyProviderModelOverrides(
|
|
1149
1192
|
providerConfig.provider,
|
|
1150
1193
|
this.#normalizeDiscoverableModels(
|
|
1151
1194
|
providerConfig,
|
|
1152
1195
|
this.#applyProviderCompat(
|
|
1153
1196
|
providerConfig.compat,
|
|
1154
|
-
|
|
1197
|
+
usableCacheModels.map(model => buildModel(model)),
|
|
1155
1198
|
),
|
|
1156
1199
|
),
|
|
1157
1200
|
);
|
|
@@ -1160,7 +1203,12 @@ export class ModelRegistry {
|
|
|
1160
1203
|
provider: providerConfig.provider,
|
|
1161
1204
|
status: "cached",
|
|
1162
1205
|
optional: providerConfig.optional ?? false,
|
|
1163
|
-
stale:
|
|
1206
|
+
stale:
|
|
1207
|
+
providerConfig.discovery.type === "llama.cpp" ||
|
|
1208
|
+
!cache.fresh ||
|
|
1209
|
+
!cache.authoritative ||
|
|
1210
|
+
configStale ||
|
|
1211
|
+
omittedHeaderIds.size > 0,
|
|
1164
1212
|
fetchedAt: cache.updatedAt,
|
|
1165
1213
|
models: models.map(model => model.id),
|
|
1166
1214
|
});
|
|
@@ -1593,6 +1641,7 @@ export class ModelRegistry {
|
|
|
1593
1641
|
providerId: string,
|
|
1594
1642
|
strategy: ModelRefreshStrategy,
|
|
1595
1643
|
cacheProviderId: string,
|
|
1644
|
+
authoritative: boolean,
|
|
1596
1645
|
): Promise<string | undefined> {
|
|
1597
1646
|
const peekedKey = await this.#peekApiKeyForProvider(providerId);
|
|
1598
1647
|
if (isAuthenticated(peekedKey) || strategy === "offline") {
|
|
@@ -1602,7 +1651,13 @@ export class ModelRegistry {
|
|
|
1602
1651
|
if (oauthCredentials.length === 0) {
|
|
1603
1652
|
return peekedKey;
|
|
1604
1653
|
}
|
|
1605
|
-
|
|
1654
|
+
// Authoritative providers prune bundled models only when their manager is
|
|
1655
|
+
// actually constructed, which needs an authenticated key. A fresh cache does
|
|
1656
|
+
// not let us skip the refresh here: with an expired OAuth token peekedKey is
|
|
1657
|
+
// undefined, the manager is never added, and stale bundled models survive the
|
|
1658
|
+
// full cache TTL. So only take the no-refresh shortcut for non-authoritative
|
|
1659
|
+
// providers, whose bundled models stay visible regardless.
|
|
1660
|
+
if (strategy === "online-if-uncached" && !authoritative) {
|
|
1606
1661
|
// Mirror shouldFetchRemoteSources: built-in managers use the catalog's
|
|
1607
1662
|
// default TTL, so only refresh when the manager will actually fetch.
|
|
1608
1663
|
const cache = readModelCache<Api>(
|
|
@@ -1634,11 +1689,13 @@ export class ModelRegistry {
|
|
|
1634
1689
|
): Promise<ModelManagerOptions<Api>[]> {
|
|
1635
1690
|
const specialProviderDescriptors: Array<{
|
|
1636
1691
|
providerId: string;
|
|
1692
|
+
authoritative: boolean;
|
|
1637
1693
|
resolveKey: (value: string | undefined) => string | undefined;
|
|
1638
1694
|
createOptions: (key: string) => ModelManagerOptions<Api>;
|
|
1639
1695
|
}> = [
|
|
1640
1696
|
{
|
|
1641
1697
|
providerId: "google-antigravity",
|
|
1698
|
+
authoritative: false,
|
|
1642
1699
|
resolveKey: extractGoogleOAuthToken,
|
|
1643
1700
|
createOptions: oauthToken =>
|
|
1644
1701
|
googleAntigravityModelManagerOptions({
|
|
@@ -1649,6 +1706,7 @@ export class ModelRegistry {
|
|
|
1649
1706
|
},
|
|
1650
1707
|
{
|
|
1651
1708
|
providerId: "google-gemini-cli",
|
|
1709
|
+
authoritative: false,
|
|
1652
1710
|
resolveKey: extractGoogleOAuthToken,
|
|
1653
1711
|
createOptions: oauthToken =>
|
|
1654
1712
|
googleGeminiCliModelManagerOptions({
|
|
@@ -1659,12 +1717,14 @@ export class ModelRegistry {
|
|
|
1659
1717
|
},
|
|
1660
1718
|
{
|
|
1661
1719
|
providerId: "openai-codex",
|
|
1720
|
+
authoritative: true,
|
|
1662
1721
|
resolveKey: value => value,
|
|
1663
1722
|
createOptions: accessToken => {
|
|
1664
1723
|
const accountId = resolveOAuthAccountIdForAccessToken(this.authStorage, "openai-codex", accessToken);
|
|
1665
1724
|
return openaiCodexModelManagerOptions({
|
|
1666
1725
|
accessToken,
|
|
1667
1726
|
accountId,
|
|
1727
|
+
fetch: this.#fetch,
|
|
1668
1728
|
});
|
|
1669
1729
|
},
|
|
1670
1730
|
},
|
|
@@ -1689,12 +1749,22 @@ export class ModelRegistry {
|
|
|
1689
1749
|
const cacheProviderId =
|
|
1690
1750
|
descriptor.createModelManagerOptions({ baseUrl: discoveryBaseUrl, fetch: this.#fetch })
|
|
1691
1751
|
.cacheProviderId ?? descriptor.providerId;
|
|
1692
|
-
return this.#resolveBuiltInDiscoveryApiKey(
|
|
1752
|
+
return this.#resolveBuiltInDiscoveryApiKey(
|
|
1753
|
+
descriptor.providerId,
|
|
1754
|
+
strategy,
|
|
1755
|
+
cacheProviderId,
|
|
1756
|
+
descriptor.dynamicModelsAuthoritative ?? false,
|
|
1757
|
+
);
|
|
1693
1758
|
}),
|
|
1694
1759
|
);
|
|
1695
1760
|
const specialKeys = await Promise.all(
|
|
1696
1761
|
enabledSpecialProviderDescriptors.map(descriptor =>
|
|
1697
|
-
this.#resolveBuiltInDiscoveryApiKey(
|
|
1762
|
+
this.#resolveBuiltInDiscoveryApiKey(
|
|
1763
|
+
descriptor.providerId,
|
|
1764
|
+
strategy,
|
|
1765
|
+
descriptor.providerId,
|
|
1766
|
+
descriptor.authoritative,
|
|
1767
|
+
),
|
|
1698
1768
|
),
|
|
1699
1769
|
);
|
|
1700
1770
|
const options: ModelManagerOptions<Api>[] = [];
|
|
@@ -1986,6 +2056,17 @@ export class ModelRegistry {
|
|
|
1986
2056
|
);
|
|
1987
2057
|
}
|
|
1988
2058
|
|
|
2059
|
+
/**
|
|
2060
|
+
* Whether the provider's configured API key is resolved from a command.
|
|
2061
|
+
*
|
|
2062
|
+
* Callers use this to distinguish the registry's command-first resolver
|
|
2063
|
+
* path from lower-priority credentials in {@link authStorage}.
|
|
2064
|
+
*/
|
|
2065
|
+
hasCommandBackedApiKey(provider: string): boolean {
|
|
2066
|
+
const keyConfig = this.#customProviderApiKeys.get(provider);
|
|
2067
|
+
return isCommandConfigValue(keyConfig);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
1989
2070
|
getDiscoverableProviders(): string[] {
|
|
1990
2071
|
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
1991
2072
|
return this.#discoverableProviders
|
|
@@ -1138,6 +1138,8 @@ export function resolveAgentPrewalkPattern(options: AgentPrewalkResolutionOption
|
|
|
1138
1138
|
export interface ResolvedModelRoleValue {
|
|
1139
1139
|
model: Model<Api> | undefined;
|
|
1140
1140
|
thinkingLevel?: ConfiguredThinkingLevel;
|
|
1141
|
+
/** matchedPatternIndex identifies the first configured pattern that matched an available model. */
|
|
1142
|
+
matchedPatternIndex?: number;
|
|
1141
1143
|
explicitThinkingLevel: boolean;
|
|
1142
1144
|
warning: string | undefined;
|
|
1143
1145
|
}
|
|
@@ -1167,11 +1169,12 @@ export function resolveModelRoleValue(
|
|
|
1167
1169
|
// models) once and reuse it across every fallback pattern instead of
|
|
1168
1170
|
// rebuilding it per pattern inside parseModelPattern.
|
|
1169
1171
|
const preferenceContext = buildPreferenceContext(availableModels, matchPreferences);
|
|
1170
|
-
for (const effectivePattern of effectivePatterns) {
|
|
1172
|
+
for (const [patternIndex, effectivePattern] of effectivePatterns.entries()) {
|
|
1171
1173
|
const resolved = matchPatternWithContext(effectivePattern, availableModels, preferenceContext);
|
|
1172
1174
|
if (resolved.model) {
|
|
1173
1175
|
return {
|
|
1174
1176
|
model: resolved.model,
|
|
1177
|
+
matchedPatternIndex: patternIndex,
|
|
1175
1178
|
thinkingLevel: resolved.explicitThinkingLevel
|
|
1176
1179
|
? resolved.thinkingLevel === AUTO_THINKING
|
|
1177
1180
|
? AUTO_THINKING
|
|
@@ -1598,6 +1601,10 @@ export function filterAvailableModelsByEnabledPatterns(
|
|
|
1598
1601
|
|
|
1599
1602
|
export interface ResolveCliModelResult {
|
|
1600
1603
|
model: Model<Api> | undefined;
|
|
1604
|
+
/** configuredPatterns is the full configured fallback chain when the selector resolves through a role. */
|
|
1605
|
+
configuredPatterns?: string[];
|
|
1606
|
+
/** configuredPatternIndex identifies the configured role pattern that matched an available model. */
|
|
1607
|
+
configuredPatternIndex?: number;
|
|
1601
1608
|
selector?: string;
|
|
1602
1609
|
thinkingLevel?: ConfiguredThinkingLevel;
|
|
1603
1610
|
warning: string | undefined;
|
|
@@ -1606,6 +1613,8 @@ export interface ResolveCliModelResult {
|
|
|
1606
1613
|
|
|
1607
1614
|
/**
|
|
1608
1615
|
* Resolve a single model from CLI flags.
|
|
1616
|
+
*
|
|
1617
|
+
* Exact model names take precedence over configured role names.
|
|
1609
1618
|
*/
|
|
1610
1619
|
export function resolveCliModel(options: {
|
|
1611
1620
|
cliProvider?: string;
|
|
@@ -1630,19 +1639,6 @@ export function resolveCliModel(options: {
|
|
|
1630
1639
|
};
|
|
1631
1640
|
}
|
|
1632
1641
|
|
|
1633
|
-
if (!cliProvider && modelRoleAliasPrefixLength(cliModel) !== undefined) {
|
|
1634
|
-
const resolved = resolveModelRoleValue(cliModel, availableModels, { settings, matchPreferences: preferences });
|
|
1635
|
-
if (resolved.model) {
|
|
1636
|
-
return {
|
|
1637
|
-
model: resolved.model,
|
|
1638
|
-
selector: formatModelString(resolved.model),
|
|
1639
|
-
thinkingLevel: resolved.thinkingLevel,
|
|
1640
|
-
warning: resolved.warning,
|
|
1641
|
-
error: undefined,
|
|
1642
|
-
};
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
1642
|
const providerMap = new Map<string, string>();
|
|
1647
1643
|
for (const model of availableModels) {
|
|
1648
1644
|
providerMap.set(model.provider.toLowerCase(), model.provider);
|
|
@@ -1682,6 +1678,73 @@ export function resolveCliModel(options: {
|
|
|
1682
1678
|
error: undefined,
|
|
1683
1679
|
};
|
|
1684
1680
|
}
|
|
1681
|
+
const { base: exactBase, level: exactThinkingLevel } = splitThinkingSuffix(
|
|
1682
|
+
trimmedModel,
|
|
1683
|
+
-1,
|
|
1684
|
+
MAX_THINKING_SUFFIX_OPTIONS,
|
|
1685
|
+
);
|
|
1686
|
+
if (exactThinkingLevel) {
|
|
1687
|
+
let exactSuffixed = findExactModelReferenceMatch(exactBase, availableModels);
|
|
1688
|
+
if (!exactSuffixed) {
|
|
1689
|
+
const lowerExactBase = exactBase.toLowerCase();
|
|
1690
|
+
exactSuffixed = availableModels.find(
|
|
1691
|
+
model =>
|
|
1692
|
+
model.id.toLowerCase() === lowerExactBase ||
|
|
1693
|
+
`${model.provider}/${model.id}`.toLowerCase() === lowerExactBase,
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
if (exactSuffixed) {
|
|
1697
|
+
return {
|
|
1698
|
+
model: exactSuffixed,
|
|
1699
|
+
selector: formatModelString(exactSuffixed),
|
|
1700
|
+
warning: undefined,
|
|
1701
|
+
thinkingLevel: exactThinkingLevel,
|
|
1702
|
+
error: undefined,
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
let configuredPatterns: string[] | undefined;
|
|
1708
|
+
if (!cliProvider) {
|
|
1709
|
+
const { base: bareRoleName, level: bareRoleThinkingLevel } = splitThinkingSuffix(
|
|
1710
|
+
trimmedModel,
|
|
1711
|
+
-1,
|
|
1712
|
+
MAX_THINKING_SUFFIX_OPTIONS,
|
|
1713
|
+
);
|
|
1714
|
+
const roleSelector =
|
|
1715
|
+
modelRoleAliasPrefixLength(trimmedModel) !== undefined
|
|
1716
|
+
? trimmedModel
|
|
1717
|
+
: settings?.getModelRole(bareRoleName) !== undefined
|
|
1718
|
+
? `${formatModelRoleAlias(bareRoleName)}${bareRoleThinkingLevel ? `:${bareRoleThinkingLevel}` : ""}`
|
|
1719
|
+
: undefined;
|
|
1720
|
+
if (roleSelector) {
|
|
1721
|
+
configuredPatterns = resolveConfiguredModelPatterns([roleSelector], settings);
|
|
1722
|
+
const resolved = resolveModelRoleValue(roleSelector, availableModels, {
|
|
1723
|
+
settings,
|
|
1724
|
+
matchPreferences: preferences,
|
|
1725
|
+
});
|
|
1726
|
+
if (resolved.model) {
|
|
1727
|
+
return {
|
|
1728
|
+
model: resolved.model,
|
|
1729
|
+
selector: formatModelString(resolved.model),
|
|
1730
|
+
configuredPatterns,
|
|
1731
|
+
configuredPatternIndex: resolved.matchedPatternIndex,
|
|
1732
|
+
thinkingLevel: resolved.thinkingLevel,
|
|
1733
|
+
warning: resolved.warning,
|
|
1734
|
+
error: undefined,
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
if (configuredPatterns && configuredPatterns.length > 0) {
|
|
1738
|
+
return {
|
|
1739
|
+
model: undefined,
|
|
1740
|
+
configuredPatterns,
|
|
1741
|
+
selector: undefined,
|
|
1742
|
+
thinkingLevel: undefined,
|
|
1743
|
+
warning: resolved.warning,
|
|
1744
|
+
error: `Model "${trimmedModel}" not found. Run "omp models" to see available models.`,
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1685
1748
|
}
|
|
1686
1749
|
|
|
1687
1750
|
let pattern = trimmedModel;
|
|
@@ -1725,6 +1788,7 @@ export function resolveCliModel(options: {
|
|
|
1725
1788
|
const display = provider ? `${provider}/${pattern}` : cliModel;
|
|
1726
1789
|
return {
|
|
1727
1790
|
model: undefined,
|
|
1791
|
+
configuredPatterns,
|
|
1728
1792
|
selector: undefined,
|
|
1729
1793
|
thinkingLevel: undefined,
|
|
1730
1794
|
warning,
|
|
@@ -61,6 +61,7 @@ const OpenAICompatFields = {
|
|
|
61
61
|
"supportsImageDetailOriginal?": "boolean",
|
|
62
62
|
// anthropic-messages compat flags (same `compat` slot, per-api interpretation)
|
|
63
63
|
"supportsEagerToolInputStreaming?": "boolean",
|
|
64
|
+
"allowAnthropicHeaderOverrides?": "boolean",
|
|
64
65
|
"requiresToolResultId?": "boolean",
|
|
65
66
|
"replayUnsignedThinking?": "boolean",
|
|
66
67
|
} as const;
|
package/src/config/settings.ts
CHANGED
|
@@ -355,7 +355,9 @@ export class Settings {
|
|
|
355
355
|
this.#cwd = path.normalize(options.cwd ?? getProjectDir());
|
|
356
356
|
this.#agentDir = path.normalize(options.agentDir ?? getAgentDir());
|
|
357
357
|
this.#configPath = options.inMemory ? null : path.join(this.#agentDir, MAIN_CONFIG_FILENAMES[0]);
|
|
358
|
-
|
|
358
|
+
const configFiles = process.env.PI_CONFIG_FILES?.split(path.delimiter).filter(Boolean) ?? [];
|
|
359
|
+
if (options.configFiles) configFiles.push(...options.configFiles);
|
|
360
|
+
this.#configFiles = configFiles.map(file => path.resolve(this.#cwd, expandTilde(file)));
|
|
359
361
|
this.#persist = !options.inMemory && options.readOnly !== true;
|
|
360
362
|
|
|
361
363
|
if (options.overrides) {
|