@oh-my-pi/pi-coding-agent 16.5.0 → 16.5.2
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 +3556 -3504
- package/dist/types/advisor/advise-tool.d.ts +12 -1
- package/dist/types/advisor/runtime.d.ts +41 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +4 -1
- package/dist/types/cli/usage-cli.d.ts +3 -0
- package/dist/types/cli/usage-error.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +2 -2
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +8 -1
- package/dist/types/config/models-config.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/discovery/substitute-plugin-root.d.ts +22 -0
- package/dist/types/eval/__tests__/process-entry-import.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +3 -3
- package/dist/types/eval/bridge-timeout.d.ts +9 -1
- package/dist/types/eval/js/context-manager.d.ts +5 -3
- package/dist/types/eval/js/process-entry.d.ts +6 -0
- package/dist/types/eval/js/worker-core.d.ts +15 -1
- package/dist/types/eval/py/spawn-options.d.ts +10 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -6
- package/dist/types/extensibility/plugins/bun-git-cache.d.ts +3 -0
- package/dist/types/goals/guided-setup.d.ts +12 -0
- package/dist/types/internal-urls/history-protocol.d.ts +3 -2
- package/dist/types/internal-urls/memory-protocol.d.ts +6 -7
- package/dist/types/internal-urls/registry-helpers.d.ts +19 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +2 -0
- package/dist/types/modes/components/__tests__/dynamic-border.test.d.ts +1 -0
- package/dist/types/modes/components/agent-hub.d.ts +10 -0
- package/dist/types/modes/components/dynamic-border.d.ts +5 -3
- package/dist/types/modes/components/login-dialog.d.ts +2 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +0 -2
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/modes/orchestrate.d.ts +1 -1
- package/dist/types/modes/rpc/host-tools.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +26 -6
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/ultrathink.d.ts +1 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +12 -0
- package/dist/types/modes/workflow.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/exit-diagnostics.d.ts +11 -0
- package/dist/types/session/messages.d.ts +15 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +11 -0
- package/dist/types/subprocess/worker-client.d.ts +6 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -0
- package/dist/types/tools/grep.d.ts +0 -2
- package/dist/types/tools/read.d.ts +0 -4
- package/dist/types/web/search/provider.d.ts +10 -3
- package/dist/types/web/search/providers/codex.d.ts +5 -4
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +917 -42
- package/src/advisor/advise-tool.ts +17 -1
- package/src/advisor/runtime.ts +270 -67
- package/src/autolearn/controller.ts +15 -3
- package/src/autoresearch/dashboard.ts +2 -2
- package/src/cli/args.ts +12 -0
- package/src/cli/auth-broker-cli.ts +30 -11
- package/src/cli/auth-gateway-cli.ts +5 -1
- package/src/cli/config-cli.ts +15 -3
- package/src/cli/dry-balance-cli.ts +14 -4
- package/src/cli/flag-tables.ts +21 -7
- package/src/cli/update-cli.ts +62 -11
- package/src/cli/usage-cli.ts +58 -5
- package/src/cli/usage-error.ts +7 -0
- package/src/cli.ts +23 -1
- package/src/commands/acp.ts +11 -2
- package/src/commands/launch.ts +12 -3
- package/src/commands/token.ts +3 -1
- package/src/config/api-key-resolver.ts +12 -3
- package/src/config/config-file.ts +30 -12
- package/src/config/model-registry.ts +7 -7
- package/src/config/model-resolver.ts +21 -7
- package/src/config/models-config.ts +1 -1
- package/src/config/settings-schema.ts +10 -0
- package/src/cursor.ts +2 -0
- package/src/discovery/claude-plugins.ts +9 -3
- package/src/discovery/omp-plugins.ts +6 -2
- package/src/discovery/substitute-plugin-root.ts +32 -0
- package/src/eval/__tests__/agent-bridge.test.ts +19 -14
- package/src/eval/__tests__/bridge-timeout.test.ts +106 -0
- package/src/eval/__tests__/js-context-manager.test.ts +158 -1
- package/src/eval/__tests__/kernel-spawn.test.ts +12 -0
- package/src/eval/__tests__/prelude-agent.test.ts +20 -0
- package/src/eval/__tests__/process-entry-import.test.ts +27 -0
- package/src/eval/agent-bridge.ts +121 -116
- package/src/eval/backend.ts +3 -3
- package/src/eval/bridge-timeout.ts +20 -2
- package/src/eval/executor-base.ts +85 -7
- package/src/eval/jl/kernel.ts +2 -1
- package/src/eval/js/context-manager.ts +109 -32
- package/src/eval/js/process-entry.ts +27 -0
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/js/worker-core.ts +70 -9
- package/src/eval/js/worker-entry.ts +1 -1
- package/src/eval/py/__tests__/prelude.test.ts +72 -0
- package/src/eval/py/kernel.ts +2 -1
- package/src/eval/py/prelude.py +28 -1
- package/src/eval/py/spawn-options.ts +13 -0
- package/src/eval/py/tool-bridge.ts +13 -14
- package/src/eval/rb/kernel.ts +2 -1
- package/src/exec/bash-executor.ts +30 -43
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/extensions/wrapper.ts +18 -18
- package/src/extensibility/plugins/bun-git-cache.ts +91 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +32 -16
- package/src/extensibility/plugins/manager.ts +28 -7
- package/src/goals/guided-setup.ts +29 -1
- package/src/internal-urls/history-protocol.ts +95 -15
- package/src/internal-urls/memory-protocol.ts +13 -9
- package/src/internal-urls/registry-helpers.ts +50 -1
- package/src/launch/broker.ts +38 -25
- package/src/lsp/client.ts +7 -1
- package/src/main.ts +29 -0
- package/src/mcp/oauth-discovery.ts +20 -1
- package/src/mcp/oauth-flow.ts +3 -1
- package/src/mcp/tool-bridge.ts +57 -6
- package/src/modes/components/__tests__/dynamic-border.test.ts +55 -0
- package/src/modes/components/agent-dashboard.ts +2 -2
- package/src/modes/components/agent-hub.ts +15 -2
- package/src/modes/components/agent-transcript-viewer.ts +2 -2
- package/src/modes/components/chat-transcript-builder.ts +26 -4
- package/src/modes/components/dynamic-border.ts +9 -6
- package/src/modes/components/extensions/extension-list.ts +2 -2
- package/src/modes/components/hook-selector.ts +10 -4
- package/src/modes/components/login-dialog.ts +5 -0
- package/src/modes/components/mcp-add-wizard.ts +5 -0
- package/src/modes/components/plan-review-overlay.ts +11 -11
- package/src/modes/components/read-tool-group.ts +1 -8
- package/src/modes/components/status-line/component.ts +10 -1
- package/src/modes/components/transcript-container.ts +110 -7
- package/src/modes/controllers/command-controller.ts +12 -4
- package/src/modes/controllers/event-controller.ts +80 -15
- package/src/modes/controllers/input-controller.ts +4 -2
- package/src/modes/controllers/mcp-command-controller.ts +6 -7
- package/src/modes/controllers/selector-controller.ts +24 -5
- package/src/modes/controllers/todo-command-controller.ts +18 -14
- package/src/modes/interactive-mode.ts +7 -3
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/orchestrate.ts +6 -5
- package/src/modes/print-mode.ts +9 -0
- package/src/modes/prompt-action-autocomplete.ts +6 -1
- package/src/modes/rpc/host-tools.ts +15 -0
- package/src/modes/rpc/rpc-mode.ts +123 -48
- package/src/modes/types.ts +1 -1
- package/src/modes/ultrathink.ts +6 -5
- package/src/modes/utils/transcript-render-helpers.ts +54 -0
- package/src/modes/utils/ui-helpers.ts +27 -1
- package/src/modes/workflow.ts +6 -5
- package/src/prompts/advisor/system.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/grep.md +1 -2
- package/src/prompts/tools/read.md +2 -4
- package/src/sdk.ts +61 -34
- package/src/session/agent-session.ts +283 -39
- package/src/session/exit-diagnostics.ts +108 -0
- package/src/session/messages.test.ts +66 -0
- package/src/session/messages.ts +37 -0
- package/src/session/streaming-output.ts +40 -12
- package/src/slash-commands/helpers/active-oauth-account.ts +22 -2
- package/src/slash-commands/helpers/logout.ts +23 -3
- package/src/slash-commands/helpers/usage-report.ts +14 -2
- package/src/subprocess/worker-client.ts +9 -2
- package/src/system-prompt.test.ts +36 -0
- package/src/system-prompt.ts +1 -1
- package/src/task/executor.ts +8 -0
- package/src/task/render.test.ts +36 -0
- package/src/task/render.ts +55 -43
- package/src/tools/bash-skill-urls.ts +4 -1
- package/src/tools/bash.ts +1 -0
- package/src/tools/browser/registry.ts +17 -3
- package/src/tools/eval.ts +14 -9
- package/src/tools/gh.ts +3 -1
- package/src/tools/grep.ts +5 -45
- package/src/tools/path-utils.ts +7 -1
- package/src/tools/read.ts +23 -74
- package/src/tools/write.ts +82 -9
- package/src/tools/yield.ts +29 -1
- package/src/utils/title-generator.ts +10 -6
- package/src/web/search/index.ts +39 -22
- package/src/web/search/provider.ts +33 -16
- package/src/web/search/providers/codex.ts +68 -21
- package/src/web/search/providers/perplexity-auth.ts +20 -11
- package/src/web/search/providers/perplexity.ts +14 -2
package/src/cli/config-cli.ts
CHANGED
|
@@ -241,7 +241,7 @@ export async function runConfigCommand(cmd: ConfigCommandArgs): Promise<void> {
|
|
|
241
241
|
|
|
242
242
|
switch (cmd.action) {
|
|
243
243
|
case "list":
|
|
244
|
-
handleList(cmd.flags);
|
|
244
|
+
await handleList(cmd.flags);
|
|
245
245
|
break;
|
|
246
246
|
case "get":
|
|
247
247
|
handleGet(cmd.key, cmd.flags);
|
|
@@ -261,7 +261,19 @@ export async function runConfigCommand(cmd: ConfigCommandArgs): Promise<void> {
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
function
|
|
264
|
+
async function writeStdout(text: string): Promise<void> {
|
|
265
|
+
const pending = Promise.withResolvers<void>();
|
|
266
|
+
process.stdout.write(text, error => {
|
|
267
|
+
if (error) {
|
|
268
|
+
pending.reject(error);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
pending.resolve();
|
|
272
|
+
});
|
|
273
|
+
await pending.promise;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function handleList(flags: { json?: boolean }): Promise<void> {
|
|
265
277
|
const defs = ALL_SETTING_PATHS.map(path => findSettingDef(path)).filter((def): def is CliSettingDef => !!def);
|
|
266
278
|
|
|
267
279
|
if (flags.json) {
|
|
@@ -273,7 +285,7 @@ function handleList(flags: { json?: boolean }): void {
|
|
|
273
285
|
description: def.description,
|
|
274
286
|
};
|
|
275
287
|
}
|
|
276
|
-
|
|
288
|
+
await writeStdout(`${JSON.stringify(result, null, 2)}\n`);
|
|
277
289
|
return;
|
|
278
290
|
}
|
|
279
291
|
|
|
@@ -214,8 +214,15 @@ function extractAccount(access: {
|
|
|
214
214
|
accountId?: string;
|
|
215
215
|
projectId?: string;
|
|
216
216
|
enterpriseUrl?: string;
|
|
217
|
+
orgId?: string;
|
|
218
|
+
orgName?: string;
|
|
217
219
|
}): string {
|
|
218
|
-
|
|
220
|
+
const base =
|
|
221
|
+
access.email ?? access.accountId ?? access.projectId ?? access.enterpriseUrl ?? "(unknown oauth account)";
|
|
222
|
+
// Two subscriptions (orgs) can share one email — name the org so per-account
|
|
223
|
+
// bench rows stay tellable apart.
|
|
224
|
+
const org = access.orgName ?? access.orgId;
|
|
225
|
+
return org ? `${base} (${org})` : base;
|
|
219
226
|
}
|
|
220
227
|
|
|
221
228
|
function getBenchTargetKey(access: {
|
|
@@ -225,15 +232,18 @@ function getBenchTargetKey(access: {
|
|
|
225
232
|
projectId?: string;
|
|
226
233
|
enterpriseUrl?: string;
|
|
227
234
|
accessToken?: string;
|
|
235
|
+
orgId?: string;
|
|
228
236
|
}): string {
|
|
229
|
-
|
|
237
|
+
const base =
|
|
230
238
|
access.email ??
|
|
231
239
|
access.accountId ??
|
|
232
240
|
access.projectId ??
|
|
233
241
|
access.enterpriseUrl ??
|
|
234
242
|
(access.credentialId === undefined ? access.accessToken : `credential:${access.credentialId}`) ??
|
|
235
|
-
"(unknown oauth account)"
|
|
236
|
-
|
|
243
|
+
"(unknown oauth account)";
|
|
244
|
+
// Org-qualify: two org-scoped credentials under one email are two distinct
|
|
245
|
+
// benchmark targets, not duplicates.
|
|
246
|
+
return access.orgId ? `${base}|org:${access.orgId}` : base;
|
|
237
247
|
}
|
|
238
248
|
|
|
239
249
|
function sanitizeBenchText(text: string, width: number): string {
|
package/src/cli/flag-tables.ts
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
import type { ConfiguredThinkingLevel } from "../thinking";
|
|
34
34
|
import type { Args } from "./args";
|
|
35
|
+
import { CliUsageError } from "./usage-error";
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* Runtime dependencies injected into setters that need to validate input or
|
|
@@ -85,6 +86,24 @@ const setResume: OptionalSetter = (result, value) => {
|
|
|
85
86
|
result.resume = value !== undefined ? value : true;
|
|
86
87
|
};
|
|
87
88
|
|
|
89
|
+
const MAX_TIME_DURATION_RE = /^(\d+(?:\.\d+)?)([smh])$/;
|
|
90
|
+
|
|
91
|
+
function maxTimeMultiplier(unit: string | undefined): number {
|
|
92
|
+
if (unit === "h") return 3600;
|
|
93
|
+
if (unit === "m") return 60;
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseMaxTimeSeconds(value: string): number {
|
|
98
|
+
const trimmed = value.trim();
|
|
99
|
+
const duration = MAX_TIME_DURATION_RE.exec(trimmed);
|
|
100
|
+
const seconds = duration ? Number(duration[1]) * maxTimeMultiplier(duration[2]) : Number(trimmed);
|
|
101
|
+
if (Number.isFinite(seconds) && seconds > 0) return seconds;
|
|
102
|
+
throw new CliUsageError(
|
|
103
|
+
`Invalid --max-time value: ${JSON.stringify(value)}. Expected a positive number of seconds or duration like "5s", "10m", "1h".`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
88
107
|
/**
|
|
89
108
|
* Setters for flags with string values. Most built-ins consume the next argv
|
|
90
109
|
* token even when it starts with `-`; flags listed in
|
|
@@ -127,13 +146,8 @@ export const STRING_SETTERS: Record<string, StringSetter> = {
|
|
|
127
146
|
"--plan-yolo-into": (result, value) => {
|
|
128
147
|
result.planYoloInto = value;
|
|
129
148
|
},
|
|
130
|
-
"--max-time": (result, value
|
|
131
|
-
|
|
132
|
-
if (Number.isFinite(seconds) && seconds > 0) {
|
|
133
|
-
result.maxTime = seconds;
|
|
134
|
-
} else {
|
|
135
|
-
deps.logger.warn("Invalid seconds passed to --max-time", { value });
|
|
136
|
-
}
|
|
149
|
+
"--max-time": (result, value) => {
|
|
150
|
+
result.maxTime = parseMaxTimeSeconds(value);
|
|
137
151
|
},
|
|
138
152
|
"--api-key": (result, value) => {
|
|
139
153
|
result.apiKey = value;
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -109,6 +109,19 @@ async function getBunGlobalBinDir(): Promise<string | undefined> {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
async function getNpmGlobalBinDir(): Promise<string | undefined> {
|
|
113
|
+
if (!$which("npm")) return undefined;
|
|
114
|
+
try {
|
|
115
|
+
const result = await $`npm prefix -g`.quiet().nothrow();
|
|
116
|
+
if (result.exitCode !== 0) return undefined;
|
|
117
|
+
const prefix = result.text().trim();
|
|
118
|
+
if (prefix.length === 0) return undefined;
|
|
119
|
+
return process.platform === "win32" ? prefix : path.join(prefix, "bin");
|
|
120
|
+
} catch {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
async function getHomebrewFormulaPrefix(): Promise<string | undefined> {
|
|
113
126
|
if (!$which("brew")) return undefined;
|
|
114
127
|
for (const formula of [HOMEBREW_FORMULA, APP_NAME]) {
|
|
@@ -189,26 +202,36 @@ function isPathInDirectory(filePath: string, directoryPath: string): boolean {
|
|
|
189
202
|
return isPathInDirectoryLexical(resolvedFile, dirReal);
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
type UpdateMethod = "brew" | "mise" | "bun" | "binary";
|
|
205
|
+
type UpdateMethod = "brew" | "mise" | "bun" | "npm" | "binary";
|
|
193
206
|
|
|
194
207
|
interface UpdateMethodResolutionOptions {
|
|
195
208
|
homebrewPrefix?: string;
|
|
196
209
|
miseBinDirs?: readonly string[];
|
|
197
210
|
miseDataDir?: string;
|
|
211
|
+
npmBinDir?: string;
|
|
198
212
|
}
|
|
199
213
|
|
|
200
|
-
type UpdateTarget =
|
|
214
|
+
type UpdateTarget =
|
|
215
|
+
| { method: "brew" }
|
|
216
|
+
| { method: "mise" }
|
|
217
|
+
| { method: "bun" }
|
|
218
|
+
| { method: "npm" }
|
|
219
|
+
| { method: "binary"; path: string };
|
|
201
220
|
|
|
202
221
|
function resolveUpdateMethod(
|
|
203
222
|
ompPath: string,
|
|
204
223
|
bunBinDir: string | undefined,
|
|
205
224
|
options: UpdateMethodResolutionOptions = {},
|
|
206
225
|
): UpdateMethod {
|
|
207
|
-
const { homebrewPrefix, miseBinDirs = [], miseDataDir } = options;
|
|
226
|
+
const { homebrewPrefix, miseBinDirs = [], miseDataDir, npmBinDir } = options;
|
|
227
|
+
const launcherExtension = path.extname(ompPath).toLowerCase();
|
|
228
|
+
const isWindowsScriptLauncher =
|
|
229
|
+
launcherExtension === ".cmd" || launcherExtension === ".ps1" || launcherExtension === ".bat";
|
|
208
230
|
if (homebrewPrefix && isPathInDirectory(ompPath, path.join(homebrewPrefix, "bin"))) return "brew";
|
|
209
231
|
if (miseBinDirs.some(dir => isPathInDirectory(ompPath, dir))) return "mise";
|
|
210
232
|
if (miseDataDir && isPathInDirectory(ompPath, path.join(miseDataDir, "shims"))) return "mise";
|
|
211
233
|
if (bunBinDir && isPathInDirectory(ompPath, bunBinDir)) return "bun";
|
|
234
|
+
if ((npmBinDir && isPathInDirectory(ompPath, npmBinDir)) || isWindowsScriptLauncher) return "npm";
|
|
212
235
|
return "binary";
|
|
213
236
|
}
|
|
214
237
|
|
|
@@ -221,6 +244,7 @@ export function resolveUpdateMethodForTest(
|
|
|
221
244
|
}
|
|
222
245
|
async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
|
223
246
|
const bunBinDir = await getBunGlobalBinDir();
|
|
247
|
+
const npmBinDir = await getNpmGlobalBinDir();
|
|
224
248
|
const homebrewPrefix = await getHomebrewFormulaPrefix();
|
|
225
249
|
const miseAvailable = $which("mise") !== undefined;
|
|
226
250
|
const miseBinDirs = miseAvailable ? await getMiseBinDirs() : [];
|
|
@@ -228,7 +252,7 @@ async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
|
|
228
252
|
const ompPath = resolveOmpPath();
|
|
229
253
|
|
|
230
254
|
if (ompPath) {
|
|
231
|
-
const method = resolveUpdateMethod(ompPath, bunBinDir, { homebrewPrefix, miseBinDirs, miseDataDir });
|
|
255
|
+
const method = resolveUpdateMethod(ompPath, bunBinDir, { homebrewPrefix, miseBinDirs, miseDataDir, npmBinDir });
|
|
232
256
|
if (method === "binary") return { method, path: ompPath };
|
|
233
257
|
return { method };
|
|
234
258
|
}
|
|
@@ -715,6 +739,14 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
|
|
|
715
739
|
}
|
|
716
740
|
}
|
|
717
741
|
|
|
742
|
+
function buildVersionedPackageInstallArgs(expectedVersion: string, nativeTag: string): string[] {
|
|
743
|
+
const args = [`${PACKAGE}@${expectedVersion}`, `${NATIVES_PACKAGE}@${expectedVersion}`];
|
|
744
|
+
if (SUPPORTED_NATIVE_TAGS.has(nativeTag)) {
|
|
745
|
+
args.push(`${NATIVES_PACKAGE}-${nativeTag}@${expectedVersion}`);
|
|
746
|
+
}
|
|
747
|
+
return args;
|
|
748
|
+
}
|
|
749
|
+
|
|
718
750
|
/**
|
|
719
751
|
* Build the bun argv used to globally install a specific omp version.
|
|
720
752
|
*
|
|
@@ -746,17 +778,23 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
|
|
|
746
778
|
* See #1824.
|
|
747
779
|
*/
|
|
748
780
|
export function buildBunInstallArgs(expectedVersion: string, nativeTag: string = currentNativeTag()): string[] {
|
|
749
|
-
|
|
781
|
+
return [
|
|
750
782
|
"install",
|
|
751
783
|
"-g",
|
|
752
784
|
"--no-cache",
|
|
753
785
|
`--registry=${NPM_REGISTRY}`,
|
|
754
|
-
|
|
755
|
-
|
|
786
|
+
...buildVersionedPackageInstallArgs(expectedVersion, nativeTag),
|
|
787
|
+
];
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/** Build the npm argv used to update npm-managed global installs. */
|
|
791
|
+
export function buildNpmInstallArgs(expectedVersion: string, nativeTag: string = currentNativeTag()): string[] {
|
|
792
|
+
const args = [
|
|
793
|
+
"install",
|
|
794
|
+
"-g",
|
|
795
|
+
`--registry=${NPM_REGISTRY}`,
|
|
796
|
+
...buildVersionedPackageInstallArgs(expectedVersion, nativeTag),
|
|
756
797
|
];
|
|
757
|
-
if (SUPPORTED_NATIVE_TAGS.has(nativeTag)) {
|
|
758
|
-
args.push(`${NATIVES_PACKAGE}-${nativeTag}@${expectedVersion}`);
|
|
759
|
-
}
|
|
760
798
|
return args;
|
|
761
799
|
}
|
|
762
800
|
|
|
@@ -773,7 +811,7 @@ export function buildMiseForceInstallArgs(expectedVersion: string): string[] {
|
|
|
773
811
|
}
|
|
774
812
|
|
|
775
813
|
/**
|
|
776
|
-
* Update via
|
|
814
|
+
* Update via package manager.
|
|
777
815
|
*/
|
|
778
816
|
async function updateViaBun(expectedVersion: string): Promise<void> {
|
|
779
817
|
console.log(chalk.dim("Updating via bun..."));
|
|
@@ -794,6 +832,17 @@ async function updateViaBun(expectedVersion: string): Promise<void> {
|
|
|
794
832
|
}
|
|
795
833
|
}
|
|
796
834
|
|
|
835
|
+
async function updateViaNpm(expectedVersion: string): Promise<void> {
|
|
836
|
+
console.log(chalk.dim("Updating via npm..."));
|
|
837
|
+
const args = buildNpmInstallArgs(expectedVersion);
|
|
838
|
+
const result = await $`npm ${args}`.nothrow();
|
|
839
|
+
if (result.exitCode !== 0) {
|
|
840
|
+
throw new Error(`npm install failed with exit code ${result.exitCode}`);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
await printVerification(expectedVersion);
|
|
844
|
+
}
|
|
845
|
+
|
|
797
846
|
async function updateViaHomebrew(expectedVersion: string, force: boolean): Promise<void> {
|
|
798
847
|
console.log(chalk.dim("Updating Homebrew formulae..."));
|
|
799
848
|
const update = await $`brew update`.nothrow();
|
|
@@ -920,6 +969,8 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
|
|
920
969
|
await updateViaMise(release.version, opts.force);
|
|
921
970
|
} else if (target.method === "bun") {
|
|
922
971
|
await updateViaBun(release.version);
|
|
972
|
+
} else if (target.method === "npm") {
|
|
973
|
+
await updateViaNpm(release.version);
|
|
923
974
|
} else {
|
|
924
975
|
await updateViaBinaryAt(target.path, release.version);
|
|
925
976
|
}
|
package/src/cli/usage-cli.ts
CHANGED
|
@@ -41,6 +41,9 @@ export interface UsageAccountIdentity {
|
|
|
41
41
|
accountId?: string;
|
|
42
42
|
projectId?: string;
|
|
43
43
|
enterpriseUrl?: string;
|
|
44
|
+
/** Organization/workspace the credential is scoped to (Anthropic multi-subscription). */
|
|
45
|
+
orgId?: string;
|
|
46
|
+
orgName?: string;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
/**
|
|
@@ -134,6 +137,7 @@ function collectIdentityStrings(reports: UsageReport[], accounts: UsageAccountId
|
|
|
134
137
|
add(meta.accountId);
|
|
135
138
|
add(meta.projectId);
|
|
136
139
|
add(meta.orgId);
|
|
140
|
+
add(meta.orgName);
|
|
137
141
|
for (const limit of report.limits) {
|
|
138
142
|
add(limit.scope.accountId);
|
|
139
143
|
add(limit.scope.projectId);
|
|
@@ -144,6 +148,8 @@ function collectIdentityStrings(reports: UsageReport[], accounts: UsageAccountId
|
|
|
144
148
|
add(account.email);
|
|
145
149
|
add(account.accountId);
|
|
146
150
|
add(account.projectId);
|
|
151
|
+
add(account.orgId);
|
|
152
|
+
add(account.orgName);
|
|
147
153
|
add(account.enterpriseUrl);
|
|
148
154
|
}
|
|
149
155
|
return values;
|
|
@@ -293,9 +299,38 @@ export function collectUnreportedAccounts(
|
|
|
293
299
|
const providerReports = byProvider.get(account.provider) ?? [];
|
|
294
300
|
if (providerReports.length === 0) return true;
|
|
295
301
|
if (account.type === "api_key") return false;
|
|
302
|
+
// Org-decisive attribution when EITHER side carries an org (Anthropic
|
|
303
|
+
// multi-subscription): two orgs share every other identifier, so an
|
|
304
|
+
// org-scoped account is covered only by its own org's report, and an
|
|
305
|
+
// org-less legacy account is never covered by an org-attributed sibling
|
|
306
|
+
// report — its own fetch failing must surface as "no usage data". The
|
|
307
|
+
// shared org is a GATE, not a match: two Team members share the org id
|
|
308
|
+
// while drawing on per-user pools, so coverage also requires the
|
|
309
|
+
// account's own base identity inside the same-org subset (an org-only
|
|
310
|
+
// account, with no base identifiers, is covered by any same-org
|
|
311
|
+
// report). The email/account fallback below applies only when both
|
|
312
|
+
// sides are org-less.
|
|
313
|
+
const accountOrg = account.orgId?.toLowerCase();
|
|
296
314
|
const ids = [account.email, account.accountId, account.projectId]
|
|
297
315
|
.filter((value): value is string => typeof value === "string" && value.length > 0)
|
|
298
316
|
.map(value => value.toLowerCase());
|
|
317
|
+
const sameOrgReports: UsageReport[] = [];
|
|
318
|
+
let sawReportOrg = false;
|
|
319
|
+
for (const report of providerReports) {
|
|
320
|
+
const metaOrg = report.metadata?.orgId;
|
|
321
|
+
if (typeof metaOrg === "string" && metaOrg) {
|
|
322
|
+
sawReportOrg = true;
|
|
323
|
+
if (accountOrg !== undefined && metaOrg.toLowerCase() === accountOrg) sameOrgReports.push(report);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (accountOrg || sawReportOrg) {
|
|
327
|
+
if (!accountOrg || sameOrgReports.length === 0) return true;
|
|
328
|
+
if (ids.length === 0) return false;
|
|
329
|
+
return !sameOrgReports.some(report => {
|
|
330
|
+
const identifiers = reportIdentifiers(report);
|
|
331
|
+
return ids.some(id => identifiers.has(id));
|
|
332
|
+
});
|
|
333
|
+
}
|
|
299
334
|
if (ids.length === 0) return false;
|
|
300
335
|
const reported = new Set<string>();
|
|
301
336
|
let anyIdentified = false;
|
|
@@ -309,9 +344,17 @@ export function collectUnreportedAccounts(
|
|
|
309
344
|
});
|
|
310
345
|
}
|
|
311
346
|
|
|
312
|
-
|
|
347
|
+
/** Compose the account label from parts, masking each part individually so `--redact` cannot be bypassed by the composite string. */
|
|
348
|
+
function accountIdentityLabel(account: UsageAccountIdentity, redaction?: Map<string, string>): string {
|
|
313
349
|
if (account.type === "api_key") return "API key";
|
|
314
|
-
|
|
350
|
+
const base = account.email ?? account.accountId ?? account.projectId ?? account.enterpriseUrl ?? "OAuth account";
|
|
351
|
+
const masked = redaction?.get(base) ?? base;
|
|
352
|
+
// orgId fallback: the uuid is the actual scoped identity; a token response
|
|
353
|
+
// can carry it without a display name, and two same-email rows must still
|
|
354
|
+
// be tellable apart.
|
|
355
|
+
const org = account.orgName ?? account.orgId;
|
|
356
|
+
if (!org || org === base) return masked;
|
|
357
|
+
return `${masked} · ${redaction?.get(org) ?? org}`;
|
|
315
358
|
}
|
|
316
359
|
|
|
317
360
|
function formatAccountHeader(
|
|
@@ -324,6 +367,12 @@ function formatAccountHeader(
|
|
|
324
367
|
const icon = STATUS_COLOR[status]("●");
|
|
325
368
|
const label = reportAccountLabel(report, index);
|
|
326
369
|
let header = `${icon} ${chalk.bold(redaction?.get(label) ?? label)}`;
|
|
370
|
+
const metaOrgName = report.metadata?.orgName;
|
|
371
|
+
const metaOrgId = report.metadata?.orgId;
|
|
372
|
+
const org = typeof metaOrgName === "string" && metaOrgName ? metaOrgName : metaOrgId;
|
|
373
|
+
if (typeof org === "string" && org && org !== label) {
|
|
374
|
+
header += chalk.dim(` · ${redaction?.get(org) ?? org}`);
|
|
375
|
+
}
|
|
327
376
|
const planType = report.metadata?.planType;
|
|
328
377
|
if (typeof planType === "string" && planType) header += chalk.dim(` · plan: ${planType}`);
|
|
329
378
|
const savedResets = report.resetCredits?.availableCount ?? 0;
|
|
@@ -520,8 +569,8 @@ export function formatUsageBreakdown(
|
|
|
520
569
|
});
|
|
521
570
|
|
|
522
571
|
for (const account of providerUnreported) {
|
|
523
|
-
const label = accountIdentityLabel(account);
|
|
524
|
-
lines.push(` ${chalk.dim("○")} ${chalk.dim(`${
|
|
572
|
+
const label = accountIdentityLabel(account, redaction);
|
|
573
|
+
lines.push(` ${chalk.dim("○")} ${chalk.dim(`${label} — no usage data`)}`);
|
|
525
574
|
}
|
|
526
575
|
|
|
527
576
|
const stats = computeProviderWindowStats(providerReports);
|
|
@@ -690,6 +739,8 @@ function collectStoredAccounts(authStorage: AuthStorage): UsageAccountIdentity[]
|
|
|
690
739
|
accountId: credential.accountId,
|
|
691
740
|
projectId: credential.projectId,
|
|
692
741
|
enterpriseUrl: credential.enterpriseUrl,
|
|
742
|
+
orgId: credential.orgId,
|
|
743
|
+
orgName: credential.orgName,
|
|
693
744
|
});
|
|
694
745
|
} else {
|
|
695
746
|
accounts.push({ provider, type: "api_key" });
|
|
@@ -726,7 +777,7 @@ function maskIdentity(redaction: Map<string, string>, value: string | undefined)
|
|
|
726
777
|
return value === undefined ? undefined : (redaction.get(value) ?? value);
|
|
727
778
|
}
|
|
728
779
|
|
|
729
|
-
const IDENTITY_METADATA_KEYS = ["email", "accountId", "projectId", "orgId"] as const;
|
|
780
|
+
const IDENTITY_METADATA_KEYS = ["email", "accountId", "projectId", "orgId", "orgName"] as const;
|
|
730
781
|
|
|
731
782
|
/** Mask identity fields in a raw-stripped report for `--redact --json`. */
|
|
732
783
|
function redactReportForJson(
|
|
@@ -830,6 +881,8 @@ export async function runUsageCommand(cmd: UsageCommandArgs): Promise<void> {
|
|
|
830
881
|
accountId: maskIdentity(redaction, account.accountId),
|
|
831
882
|
projectId: maskIdentity(redaction, account.projectId),
|
|
832
883
|
enterpriseUrl: maskIdentity(redaction, account.enterpriseUrl),
|
|
884
|
+
orgId: maskIdentity(redaction, account.orgId),
|
|
885
|
+
orgName: maskIdentity(redaction, account.orgName),
|
|
833
886
|
}));
|
|
834
887
|
}
|
|
835
888
|
const capacity: Record<string, ProviderWindowStat[]> = {};
|
package/src/cli.ts
CHANGED
|
@@ -108,6 +108,7 @@ const TINY_WORKER_ARG = "__omp_worker_tiny_inference";
|
|
|
108
108
|
const STATS_SYNC_WORKER_ARG = "__omp_worker_stats_sync";
|
|
109
109
|
const TAB_WORKER_ARG = "__omp_worker_tab";
|
|
110
110
|
const JS_EVAL_WORKER_ARG = "__omp_worker_js_eval";
|
|
111
|
+
const JS_EVAL_PROCESS_ARG = "__omp_worker_js_eval_process";
|
|
111
112
|
const STT_WORKER_ARG = "__omp_worker_stt";
|
|
112
113
|
const TTS_WORKER_ARG = "__omp_worker_tts";
|
|
113
114
|
const MNEMOPI_EMBED_WORKER_ARG = "__omp_worker_mnemopi_embed";
|
|
@@ -156,6 +157,14 @@ async function runWorkerEntrypoint(arg: string | undefined): Promise<boolean> {
|
|
|
156
157
|
await import("./eval/js/worker-entry");
|
|
157
158
|
return true;
|
|
158
159
|
}
|
|
160
|
+
if (arg === JS_EVAL_PROCESS_ARG) {
|
|
161
|
+
const { startJsEvalProcess } = await import("./eval/js/process-entry");
|
|
162
|
+
// The JS evaluator forwards user-controlled payloads (tool-call args,
|
|
163
|
+
// display outputs); a non-serializable one must fail that cell, not
|
|
164
|
+
// SIGKILL the kernel and erase the eval session's state.
|
|
165
|
+
await runIpcSubprocessWorker(startJsEvalProcess, { rethrowConnectedSendErrors: true });
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
159
168
|
if (arg === STT_WORKER_ARG) {
|
|
160
169
|
const { startSttWorker } = await import("./stt/asr-worker");
|
|
161
170
|
await runIpcSubprocessWorker(startSttWorker);
|
|
@@ -196,6 +205,18 @@ async function runIpcSubprocessWorker<In, Out>(
|
|
|
196
205
|
sendAndFlush(message: Out): Promise<void>;
|
|
197
206
|
onMessage(handler: (message: In) => void): () => void;
|
|
198
207
|
}) => void,
|
|
208
|
+
options?: {
|
|
209
|
+
/**
|
|
210
|
+
* Rethrow send failures while the IPC channel is still connected instead
|
|
211
|
+
* of shutting down. A connected-channel failure means this particular
|
|
212
|
+
* message could not be serialized (e.g. a JS eval cell passed a function
|
|
213
|
+
* into tool args, a DataCloneError under advanced serialization) — the
|
|
214
|
+
* caller must see that error, exactly as Worker `postMessage` would
|
|
215
|
+
* deliver it, rather than losing the whole worker and its state.
|
|
216
|
+
* Channel-gone failures still shut down.
|
|
217
|
+
*/
|
|
218
|
+
rethrowConnectedSendErrors?: boolean;
|
|
219
|
+
},
|
|
199
220
|
): Promise<void> {
|
|
200
221
|
const { promise: shuttingDown, resolve: shutdown } = Promise.withResolvers<void>();
|
|
201
222
|
type IpcSend = (this: NodeJS.Process, message: unknown, callback?: (error: Error | null) => void) => boolean;
|
|
@@ -211,7 +232,8 @@ async function runIpcSubprocessWorker<In, Out>(
|
|
|
211
232
|
}
|
|
212
233
|
try {
|
|
213
234
|
sender.call(process, message);
|
|
214
|
-
} catch {
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (options?.rethrowConnectedSendErrors && process.connected) throw error;
|
|
215
237
|
shutdown();
|
|
216
238
|
}
|
|
217
239
|
};
|
package/src/commands/acp.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* ACP terminal-auth flag asks the same command to open the interactive TUI.
|
|
6
6
|
*/
|
|
7
7
|
import { Command } from "@oh-my-pi/pi-utils/cli";
|
|
8
|
-
import { parseArgs } from "../cli/args";
|
|
8
|
+
import { type Args as ParsedArgs, parseArgs, reportCliUsageError } from "../cli/args";
|
|
9
9
|
import { runRootCommand } from "../main";
|
|
10
10
|
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
11
11
|
|
|
@@ -15,7 +15,16 @@ export default class Acp extends Command {
|
|
|
15
15
|
|
|
16
16
|
async run(): Promise<void> {
|
|
17
17
|
const { args, terminalAuth } = prepareAcpTerminalAuthArgs(this.argv);
|
|
18
|
-
|
|
18
|
+
let parsed: ParsedArgs;
|
|
19
|
+
try {
|
|
20
|
+
parsed = parseArgs(args);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (reportCliUsageError(error)) {
|
|
23
|
+
process.exitCode = 2;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
19
28
|
if (!terminalAuth) {
|
|
20
29
|
parsed.mode = "acp";
|
|
21
30
|
}
|
package/src/commands/launch.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
7
|
-
import { parseArgs } from "../cli/args";
|
|
7
|
+
import { type Args as ParsedArgs, parseArgs, reportCliUsageError } from "../cli/args";
|
|
8
8
|
import { runRootCommand } from "../main";
|
|
9
9
|
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
10
10
|
import { CLI_THINKING_LEVELS } from "../thinking";
|
|
@@ -157,7 +157,7 @@ export default class Index extends Command {
|
|
|
157
157
|
description: "Include thinking blocks in print mode text output",
|
|
158
158
|
}),
|
|
159
159
|
"max-time": Flags.string({
|
|
160
|
-
description: "Stop the session after this
|
|
160
|
+
description: "Stop the session after this duration (e.g., 600, 10m, 1h)",
|
|
161
161
|
}),
|
|
162
162
|
// `--auto-approve` / `--yolo`: declared here so oclif's auto-generated `--help` lists it.
|
|
163
163
|
// Runtime parsing happens in `cli/args.ts parseArgs` (line 176 in that file) — `runRootCommand`
|
|
@@ -193,7 +193,16 @@ export default class Index extends Command {
|
|
|
193
193
|
|
|
194
194
|
async run(): Promise<void> {
|
|
195
195
|
const { args } = prepareAcpTerminalAuthArgs(this.argv);
|
|
196
|
-
|
|
196
|
+
let parsed: ParsedArgs;
|
|
197
|
+
try {
|
|
198
|
+
parsed = parseArgs(args);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (reportCliUsageError(error)) {
|
|
201
|
+
process.exitCode = 2;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
197
206
|
await runRootCommand(parsed, args);
|
|
198
207
|
}
|
|
199
208
|
}
|
package/src/commands/token.ts
CHANGED
|
@@ -64,12 +64,14 @@ export default class Token extends Command {
|
|
|
64
64
|
}
|
|
65
65
|
if (flags.list) {
|
|
66
66
|
for (const acct of accounts) {
|
|
67
|
-
const
|
|
67
|
+
const base =
|
|
68
68
|
acct.email ??
|
|
69
69
|
acct.accountId ??
|
|
70
70
|
acct.projectId ??
|
|
71
71
|
acct.enterpriseUrl ??
|
|
72
72
|
`credential #${acct.credentialId}`;
|
|
73
|
+
const org = acct.orgName ?? acct.orgId;
|
|
74
|
+
const label = org && org !== base ? `${base} (${org})` : base;
|
|
73
75
|
process.stdout.write(`${acct.position + 1}. ${label}\n`);
|
|
74
76
|
}
|
|
75
77
|
return;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Api, type ApiKeyResolver, type AuthStorage, isUsageLimitOutcome, type Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
2
3
|
|
|
3
4
|
/** Model slice accepted by the model-form `resolver(model, sessionId)` overload. */
|
|
4
5
|
export type ApiKeyResolverModel = Pick<Model<Api>, "provider" | "baseUrl" | "id">;
|
|
@@ -27,7 +28,7 @@ export interface ApiKeyResolverRegistry {
|
|
|
27
28
|
/**
|
|
28
29
|
* Build an {@link ApiKeyResolver} implementing the central a/b/c auth-retry
|
|
29
30
|
* policy: initial → resolve; step (b) → force-refresh same account; step (c)
|
|
30
|
-
* → rotate to a sibling
|
|
31
|
+
* → rotate to a sibling and re-resolve, unless quota exhaustion has no sibling.
|
|
31
32
|
*
|
|
32
33
|
* Two call forms: `resolver(provider, options?)` for provider-scoped keys,
|
|
33
34
|
* and `resolver(model, sessionId?)` which derives `baseUrl`/`modelId` from
|
|
@@ -59,12 +60,20 @@ export function createApiKeyResolver(
|
|
|
59
60
|
// sibling exists we switch immediately; the precise no-sibling backoff
|
|
60
61
|
// is owned by `markUsageLimitReached` (default + server usage-report
|
|
61
62
|
// reset) and the outer whole-turn retry layer.
|
|
62
|
-
await registry.authStorage.rotateSessionCredential(provider, sessionId, {
|
|
63
|
+
const switched = await registry.authStorage.rotateSessionCredential(provider, sessionId, {
|
|
63
64
|
error,
|
|
64
65
|
modelId,
|
|
65
66
|
signal,
|
|
66
67
|
apiKey: previousKey,
|
|
67
68
|
});
|
|
69
|
+
if (!switched) {
|
|
70
|
+
const status = AIError.status(error);
|
|
71
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : undefined;
|
|
72
|
+
// No sibling for an account-quota failure: stop so the outer
|
|
73
|
+
// whole-turn retry layer can honor the recorded backoff. A hard
|
|
74
|
+
// auth decline can instead mean a peer refreshed the bearer.
|
|
75
|
+
if (AIError.isUsageLimit(error) || isUsageLimitOutcome(status, message)) return undefined;
|
|
76
|
+
}
|
|
68
77
|
return registry.getApiKeyForProvider(provider, sessionId, { baseUrl, modelId });
|
|
69
78
|
}
|
|
70
79
|
return registry.getApiKeyForProvider(provider, sessionId, { baseUrl, modelId, forceRefresh: true, signal });
|