@oh-my-pi/pi-coding-agent 17.2.12 → 17.2.13
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 +57 -0
- package/dist/{CHANGELOG-k9ghy5sn.md → CHANGELOG-d8xh7keh.md} +57 -0
- package/dist/cli.js +3069 -3047
- package/dist/types/advisor/delta-split.d.ts +24 -0
- package/dist/types/advisor/runtime.d.ts +2 -2
- package/dist/types/async/job-manager.d.ts +8 -1
- package/dist/types/cli/update-cli.d.ts +53 -1
- package/dist/types/config/keybindings.d.ts +10 -0
- package/dist/types/config/model-resolver.d.ts +15 -2
- package/dist/types/config/settings-schema.d.ts +4 -0
- package/dist/types/discovery/agents-md.d.ts +10 -1
- package/dist/types/eval/runner-cache.d.ts +12 -0
- package/dist/types/extensibility/extensions/runner.d.ts +12 -3
- package/dist/types/extensibility/extensions/types.d.ts +15 -4
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +4 -1
- package/dist/types/lib/xai-http.d.ts +0 -1
- package/dist/types/mcp/tool-bridge.d.ts +8 -5
- package/dist/types/modes/components/agent-hub-renderer.d.ts +6 -1
- package/dist/types/modes/components/status-line/types.d.ts +4 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -4
- package/dist/types/modes/interactive-mode.d.ts +17 -8
- package/dist/types/modes/types.d.ts +7 -10
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +22 -3
- package/dist/types/session/messages.d.ts +20 -0
- package/dist/types/session/retry-fallback-chains.d.ts +13 -0
- package/dist/types/session/session-advisors.d.ts +1 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +24 -5
- package/dist/types/session/turn-recovery.d.ts +34 -5
- package/dist/types/slash-commands/types.d.ts +5 -1
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/tools/approval.d.ts +7 -0
- package/dist/types/tools/todo.d.ts +14 -15
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/local-date.d.ts +2 -0
- package/dist/types/vibe/runtime.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/brave.d.ts +8 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +3 -2
- package/dist/types/web/search/providers/jina.d.ts +3 -3
- package/dist/types/web/search/providers/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +4 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +2 -0
- package/package.json +13 -13
- package/src/advisor/delta-split.ts +98 -0
- package/src/advisor/runtime.ts +321 -69
- package/src/async/job-manager.ts +14 -3
- package/src/cli/plugin-cli.ts +30 -2
- package/src/cli/update-cli.ts +259 -24
- package/src/config/keybindings.ts +52 -9
- package/src/config/model-resolver.ts +19 -3
- package/src/config/settings-schema.ts +5 -0
- package/src/cursor.ts +10 -5
- package/src/discovery/agents-md.ts +61 -23
- package/src/eval/jl/kernel.ts +2 -20
- package/src/eval/py/kernel.ts +2 -20
- package/src/eval/rb/kernel.ts +2 -20
- package/src/eval/runner-cache.ts +41 -0
- package/src/exec/non-interactive-env.ts +14 -3
- package/src/extensibility/extensions/loader.ts +5 -2
- package/src/extensibility/extensions/runner.ts +184 -66
- package/src/extensibility/extensions/types.ts +26 -2
- package/src/extensibility/extensions/wrapper.ts +13 -7
- package/src/extensibility/plugins/marketplace/manager.ts +6 -2
- package/src/hindsight/client.ts +1 -1
- package/src/lib/xai-http.ts +0 -4
- package/src/lsp/client.ts +2 -0
- package/src/lsp/servers.ts +1 -1
- package/src/mcp/tool-bridge.ts +15 -6
- package/src/modes/components/agent-hub-renderer.ts +9 -3
- package/src/modes/components/agent-hub.ts +2 -1
- package/src/modes/components/status-line/component.ts +58 -6
- package/src/modes/components/status-line/segments.ts +12 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/user-message.ts +20 -5
- package/src/modes/controllers/event-controller.ts +48 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -7
- package/src/modes/controllers/input-controller.ts +25 -6
- package/src/modes/interactive-mode.ts +315 -129
- package/src/modes/rpc/rpc-frame.ts +13 -5
- package/src/modes/theme/tui-adapters.ts +4 -5
- package/src/modes/types.ts +13 -7
- package/src/modes/utils/hotkeys-markdown.ts +10 -6
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/registry/persisted-agents.ts +43 -8
- package/src/sdk.ts +139 -8
- package/src/session/agent-session-types.ts +2 -0
- package/src/session/agent-session.ts +66 -10
- package/src/session/messages.ts +98 -28
- package/src/session/retry-fallback-chains.ts +14 -0
- package/src/session/session-advisors.ts +32 -15
- package/src/session/session-history-format.ts +15 -1
- package/src/session/session-maintenance.ts +8 -8
- package/src/session/session-manager.ts +6 -2
- package/src/session/session-tools.ts +321 -184
- package/src/session/turn-recovery.ts +225 -47
- package/src/slash-commands/builtin-modes.ts +41 -12
- package/src/slash-commands/types.ts +5 -1
- package/src/task/executor.ts +92 -45
- package/src/task/structured-subagent.ts +5 -5
- package/src/tools/approval.ts +44 -10
- package/src/tools/fetch.ts +21 -2
- package/src/tools/image-gen.ts +6 -8
- package/src/tools/todo.ts +70 -26
- package/src/tools/tts.ts +3 -2
- package/src/tools/write.ts +7 -3
- package/src/utils/local-date.ts +13 -0
- package/src/utils/tools-manager.ts +2 -2
- package/src/vibe/runtime.ts +22 -14
- package/src/web/kagi.ts +91 -34
- package/src/web/parallel.ts +11 -2
- package/src/web/scrapers/crates-io.ts +2 -2
- package/src/web/scrapers/discogs.ts +2 -2
- package/src/web/scrapers/docs-rs.ts +2 -2
- package/src/web/scrapers/github.ts +2 -2
- package/src/web/scrapers/musicbrainz.ts +1 -2
- package/src/web/scrapers/pubmed.ts +2 -2
- package/src/web/scrapers/sec-edgar.ts +2 -2
- package/src/web/search/providers/brave.ts +121 -46
- package/src/web/search/providers/codex.ts +88 -12
- package/src/web/search/providers/exa.ts +45 -10
- package/src/web/search/providers/firecrawl.ts +53 -11
- package/src/web/search/providers/gemini.ts +139 -27
- package/src/web/search/providers/jina.ts +48 -25
- package/src/web/search/providers/parallel.ts +23 -9
- package/src/web/search/providers/perplexity.ts +24 -7
- package/src/web/search/providers/searxng.ts +77 -1
- package/src/web/search/providers/tavily.ts +23 -22
- package/src/web/search/providers/tinyfish.ts +44 -10
- package/src/web/search/providers/xai.ts +85 -14
package/src/task/executor.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { AgentEvent, AgentIdentity, AgentMessage, AgentTelemetryConfig } fr
|
|
|
9
9
|
import { recordHandoff, resolveTelemetry } from "@oh-my-pi/pi-agent-core";
|
|
10
10
|
import type { Api, Model, ServiceTierByFamily, Usage } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import { logger, popLoopPhase, prompt, pushLoopPhase, untilAborted } from "@oh-my-pi/pi-utils";
|
|
12
|
-
import { AsyncJobManager } from "../async";
|
|
12
|
+
import { ASYNC_JOB_MANAGER_SHUTDOWN_REASON, AsyncJobManager } from "../async";
|
|
13
13
|
import type { Rule } from "../capability/rule";
|
|
14
14
|
import { ModelRegistry } from "../config/model-registry";
|
|
15
15
|
import {
|
|
@@ -189,19 +189,24 @@ function resolveSubagentRetryFallbackCandidates(
|
|
|
189
189
|
* Chain a single-model subagent inherits when its own model patterns supply no
|
|
190
190
|
* fallbacks of their own. The child is pinned to a `subagent:<id>` role whose
|
|
191
191
|
* chain shadows every configured role chain (see
|
|
192
|
-
* {@link installSubagentRetryFallbackChain}), so a role-alias request (`@smol
|
|
193
|
-
* MUST inherit that role's chain —
|
|
194
|
-
* child onto the `default` role's
|
|
195
|
-
* inheriting `default`: they carry no role
|
|
196
|
-
* be assigned the same model must not
|
|
192
|
+
* {@link installSubagentRetryFallbackChain}), so a role-alias request (`@smol`,
|
|
193
|
+
* the bundled `task` agent's `@task`) MUST inherit that role's chain —
|
|
194
|
+
* otherwise the pin silently re-routes the child onto the `default` role's
|
|
195
|
+
* chain. Explicit model selectors keep inheriting `default`: they carry no role
|
|
196
|
+
* identity, and a role that happens to be assigned the same model must not
|
|
197
|
+
* capture the child's fallback routing.
|
|
198
|
+
*
|
|
199
|
+
* Spawn paths preserve the pre-expansion alias as `modelRole` because their
|
|
200
|
+
* model patterns are already expanded. Direct callers may still supply an
|
|
201
|
+
* unexpanded alias through `modelOverride` or `agent.model`; retain that
|
|
202
|
+
* existing path by deriving the role only when no preserved role was supplied.
|
|
197
203
|
*/
|
|
198
204
|
function resolveSubagentInheritedRetryFallbackChain(
|
|
199
205
|
settings: Settings,
|
|
200
206
|
modelRegistry: ModelRegistry,
|
|
201
|
-
|
|
207
|
+
role: string | undefined,
|
|
202
208
|
): string[] | undefined {
|
|
203
209
|
const configuredChains = settings.get("retry.fallbackChains");
|
|
204
|
-
const role = resolveExplicitModelRole(modelPatterns, settings);
|
|
205
210
|
// An explicitly emptied role chain means "no fallbacks", not "inherit
|
|
206
211
|
// default" — mirrors expandDefaultRetryFallbackChains.
|
|
207
212
|
const fallbackChain = (role !== undefined ? configuredChains?.[role] : undefined) ?? configuredChains?.default;
|
|
@@ -895,7 +900,7 @@ export function createSubagentSettings(
|
|
|
895
900
|
);
|
|
896
901
|
}
|
|
897
902
|
|
|
898
|
-
export type AbortReason = "signal" | "terminate" | "timeout" | "budget";
|
|
903
|
+
export type AbortReason = "signal" | "shutdown" | "terminate" | "timeout" | "budget";
|
|
899
904
|
|
|
900
905
|
const MAX_YIELD_TOOL_ERRORS = 6;
|
|
901
906
|
|
|
@@ -1095,7 +1100,21 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1095
1100
|
budgetLimitExceeded = true;
|
|
1096
1101
|
}
|
|
1097
1102
|
if (abortSent) {
|
|
1098
|
-
|
|
1103
|
+
// Shutdown is a superseding external abort: a process teardown that
|
|
1104
|
+
// races a self-inflicted budget hard-abort must still follow the
|
|
1105
|
+
// shutdown release path (dispose + unregister) instead of the
|
|
1106
|
+
// budget-resumable path, which would leave the subagent adopted and
|
|
1107
|
+
// alive past AgentLifecycleManager.dispose(). Genuine kills
|
|
1108
|
+
// (signal/timeout/terminate) already dispose terminally, and shutdown
|
|
1109
|
+
// is never downgraded back to signal.
|
|
1110
|
+
if (reason === "shutdown" && abortReason === "budget") {
|
|
1111
|
+
abortReason = "shutdown";
|
|
1112
|
+
} else if (
|
|
1113
|
+
reason === "signal" &&
|
|
1114
|
+
abortReason !== "signal" &&
|
|
1115
|
+
abortReason !== "timeout" &&
|
|
1116
|
+
abortReason !== "shutdown"
|
|
1117
|
+
) {
|
|
1099
1118
|
abortReason = "signal";
|
|
1100
1119
|
}
|
|
1101
1120
|
return;
|
|
@@ -1158,7 +1177,7 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1158
1177
|
signal.addEventListener(
|
|
1159
1178
|
"abort",
|
|
1160
1179
|
() => {
|
|
1161
|
-
if (!resolved) requestAbort("signal");
|
|
1180
|
+
if (!resolved) requestAbort(signal.reason === ASYNC_JOB_MANAGER_SHUTDOWN_REASON ? "shutdown" : "signal");
|
|
1162
1181
|
},
|
|
1163
1182
|
{ once: true, signal: listenerSignal },
|
|
1164
1183
|
);
|
|
@@ -1183,6 +1202,7 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1183
1202
|
}
|
|
1184
1203
|
|
|
1185
1204
|
const resolveSignalAbortReason = (): string => {
|
|
1205
|
+
if (signal?.reason === ASYNC_JOB_MANAGER_SHUTDOWN_REASON) return "Async job manager shutdown";
|
|
1186
1206
|
const reason = signal?.reason;
|
|
1187
1207
|
if (reason instanceof Error) {
|
|
1188
1208
|
const message = reason.message.trim();
|
|
@@ -1659,15 +1679,28 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1659
1679
|
};
|
|
1660
1680
|
|
|
1661
1681
|
const attach = (session: AgentSession): (() => void) => {
|
|
1662
|
-
|
|
1682
|
+
// The session owns attribution: it knows which model produced its output
|
|
1683
|
+
// and withholds an armed-but-unproven fallback. Re-deriving that here from
|
|
1684
|
+
// the event stream got it wrong twice over — the stream also carries
|
|
1685
|
+
// advisor turns running on a different model, and a routing switch was
|
|
1686
|
+
// read as evidence the target had served.
|
|
1687
|
+
const publishServingModel = (): void => {
|
|
1688
|
+
const serving = session.servingModel;
|
|
1689
|
+
if (!serving) return;
|
|
1690
|
+
const isFallback = serving.isFallback;
|
|
1691
|
+
if (
|
|
1692
|
+
serving.selector === progress.resolvedModel &&
|
|
1693
|
+
(progress.resolvedModelIsFallback ?? false) === isFallback
|
|
1694
|
+
) {
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
progress.resolvedModel = serving.selector;
|
|
1698
|
+
progress.resolvedModelIsFallback = isFallback;
|
|
1699
|
+
scheduleProgress(true);
|
|
1700
|
+
};
|
|
1663
1701
|
return session.subscribe(event => {
|
|
1664
1702
|
emitSubagentEvent(event);
|
|
1665
|
-
|
|
1666
|
-
if (nextModel && nextModel !== activeModel) {
|
|
1667
|
-
activeModel = nextModel;
|
|
1668
|
-
progress.resolvedModel = nextModel;
|
|
1669
|
-
scheduleProgress(true);
|
|
1670
|
-
}
|
|
1703
|
+
publishServingModel();
|
|
1671
1704
|
if (event.type === "auto_retry_start") {
|
|
1672
1705
|
progress.retryState = {
|
|
1673
1706
|
attempt: event.attempt,
|
|
@@ -1707,18 +1740,6 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1707
1740
|
popLoopPhase();
|
|
1708
1741
|
}
|
|
1709
1742
|
}
|
|
1710
|
-
if (event.type === "retry_fallback_applied") {
|
|
1711
|
-
progress.resolvedModel = event.to;
|
|
1712
|
-
progress.resolvedModelIsFallback = true;
|
|
1713
|
-
scheduleProgress(true);
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
if (event.type === "retry_fallback_succeeded") {
|
|
1717
|
-
progress.resolvedModel = event.model;
|
|
1718
|
-
progress.resolvedModelIsFallback = true;
|
|
1719
|
-
scheduleProgress(true);
|
|
1720
|
-
return;
|
|
1721
|
-
}
|
|
1722
1743
|
});
|
|
1723
1744
|
};
|
|
1724
1745
|
|
|
@@ -1751,7 +1772,11 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1751
1772
|
runtimeLimitExceeded: () => runtimeLimitExceeded,
|
|
1752
1773
|
terminalError: () => terminalError,
|
|
1753
1774
|
hasExplicitAbortReason: () =>
|
|
1754
|
-
abortReason === "signal" ||
|
|
1775
|
+
abortReason === "signal" ||
|
|
1776
|
+
abortReason === "shutdown" ||
|
|
1777
|
+
runtimeLimitExceeded ||
|
|
1778
|
+
budgetLimitExceeded ||
|
|
1779
|
+
budgetStopRequested,
|
|
1755
1780
|
budgetStopRequested: () => budgetStopRequested,
|
|
1756
1781
|
waitForBudgetStop: () => budgetStopAbortPromise ?? Promise.resolve(),
|
|
1757
1782
|
yieldInvalidatedByAsync: () => yieldInvalidatedByAsync,
|
|
@@ -1777,7 +1802,11 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1777
1802
|
// the lifecycle can park the agent as resumable instead of killing it.
|
|
1778
1803
|
abortKind: () => abortReason ?? (budgetStopRequested ? "budget" : undefined),
|
|
1779
1804
|
isAbortedRun: () =>
|
|
1780
|
-
abortReason === "signal" ||
|
|
1805
|
+
abortReason === "signal" ||
|
|
1806
|
+
abortReason === "shutdown" ||
|
|
1807
|
+
runtimeLimitExceeded ||
|
|
1808
|
+
budgetLimitExceeded ||
|
|
1809
|
+
abortReason === undefined,
|
|
1781
1810
|
requestAbort,
|
|
1782
1811
|
failWithError,
|
|
1783
1812
|
abortActiveSession,
|
|
@@ -2416,23 +2445,37 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2416
2445
|
}
|
|
2417
2446
|
};
|
|
2418
2447
|
|
|
2419
|
-
// A budget abort leaves a consistent session with its transcript on disk
|
|
2420
|
-
//
|
|
2448
|
+
// A budget abort leaves a consistent session with its transcript on disk.
|
|
2449
|
+
// Manager shutdown also preserves the transcript, but disposes and unregisters
|
|
2450
|
+
// the process-local session. Caller signals, wall-clock timeouts, and internal
|
|
2421
2451
|
// terminations are genuine kills and stay terminal.
|
|
2422
2452
|
const resumableAbort =
|
|
2423
2453
|
args.abortKind === "budget" && args.keepAlive && !args.isolated && args.reviveSession !== null;
|
|
2424
2454
|
if (args.aborted && !resumableAbort) {
|
|
2425
2455
|
if (ref && ownsRef) {
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2456
|
+
if (args.abortKind === "shutdown") {
|
|
2457
|
+
try {
|
|
2458
|
+
await AgentLifecycleManager.global().release(args.id, ref);
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
logger.warn("runSubagent: failed to release session during manager shutdown", {
|
|
2461
|
+
id: args.id,
|
|
2462
|
+
error: String(error),
|
|
2463
|
+
});
|
|
2464
|
+
await disposeSession();
|
|
2465
|
+
registry.unregister(args.id, ref);
|
|
2466
|
+
}
|
|
2467
|
+
} else {
|
|
2468
|
+
// Route hard kills through the lifecycle owner so the terminal
|
|
2469
|
+
// decision is durable and a restart cannot rediscover the transcript
|
|
2470
|
+
// as a revivable parked agent.
|
|
2471
|
+
try {
|
|
2472
|
+
await AgentLifecycleManager.global().release(args.id, ref, { tombstone: true });
|
|
2473
|
+
} catch (error) {
|
|
2474
|
+
logger.warn("runSubagent: failed to persist kill tombstone", { id: args.id, error: String(error) });
|
|
2475
|
+
registry.setStatus(args.id, "aborted", ref);
|
|
2476
|
+
registry.detachSession(args.id, ref);
|
|
2477
|
+
await disposeSession();
|
|
2478
|
+
}
|
|
2436
2479
|
}
|
|
2437
2480
|
} else {
|
|
2438
2481
|
await disposeSession();
|
|
@@ -2838,7 +2881,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2838
2881
|
const configuredModelPatterns = resolveConfiguredModelPatterns(modelPatterns, settings);
|
|
2839
2882
|
const inheritedRetryFallbackChain =
|
|
2840
2883
|
configuredModelPatterns.length === 1
|
|
2841
|
-
? resolveSubagentInheritedRetryFallbackChain(
|
|
2884
|
+
? resolveSubagentInheritedRetryFallbackChain(
|
|
2885
|
+
subagentSettings,
|
|
2886
|
+
modelRegistry,
|
|
2887
|
+
modelRole ?? resolveExplicitModelRole(modelPatterns, subagentSettings),
|
|
2888
|
+
)
|
|
2842
2889
|
: undefined;
|
|
2843
2890
|
const {
|
|
2844
2891
|
model,
|
|
@@ -8,7 +8,7 @@ import * as fs from "node:fs/promises";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { $env, prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
11
|
-
import {
|
|
11
|
+
import { resolveAgentModelSelection } from "../config/model-resolver";
|
|
12
12
|
import type { LocalProtocolOptions } from "../internal-urls";
|
|
13
13
|
import { registerArtifactsDir } from "../internal-urls/registry-helpers";
|
|
14
14
|
import { MCPManager } from "../mcp/manager";
|
|
@@ -288,10 +288,10 @@ export async function resolveEffectiveSubagentPolicy(
|
|
|
288
288
|
activeModelPattern: parentActiveModelPattern,
|
|
289
289
|
fallbackModelPattern: request.session.getModelString?.(),
|
|
290
290
|
};
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
const modelOverride =
|
|
291
|
+
// Role identity and patterns come from one call so they cannot be derived
|
|
292
|
+
// from different sources: the expansion below discards the alias, and the
|
|
293
|
+
// child's inherited retry-fallback chain is keyed off the role.
|
|
294
|
+
const { patterns: modelOverride, role: modelRole } = resolveAgentModelSelection(modelResolution);
|
|
295
295
|
const isolationMode = request.session.settings.get("task.isolation.mode");
|
|
296
296
|
const isIsolated = request.isolation?.requested === true;
|
|
297
297
|
if (isIsolated && isolationMode === "none") {
|
package/src/tools/approval.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface ResolvedApproval {
|
|
|
21
21
|
reason?: string;
|
|
22
22
|
override: boolean;
|
|
23
23
|
source?: "tool" | "user" | "mode";
|
|
24
|
+
/** User-policy key that produced `source: "user"` (defaults to the tool name). */
|
|
25
|
+
policyKey?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const POLICY_VALUES: ReadonlySet<ApprovalPolicy> = new Set(["allow", "deny", "prompt"]);
|
|
@@ -61,11 +63,14 @@ function normalizeDecision(value: unknown): Omit<ResolvedApproval, "policy"> & {
|
|
|
61
63
|
const tier = isToolTier(record.tier) ? record.tier : "exec";
|
|
62
64
|
const reason = typeof record.reason === "string" && record.reason.length > 0 ? record.reason : undefined;
|
|
63
65
|
const policy = normalizePolicy(record.policy);
|
|
66
|
+
const policyKey =
|
|
67
|
+
typeof record.policyKey === "string" && record.policyKey.length > 0 ? record.policyKey : undefined;
|
|
64
68
|
return {
|
|
65
69
|
tier,
|
|
66
70
|
override: record.override === true,
|
|
67
71
|
...(policy ? { policy } : {}),
|
|
68
72
|
...(reason ? { reason } : {}),
|
|
73
|
+
...(policyKey ? { policyKey } : {}),
|
|
69
74
|
};
|
|
70
75
|
}
|
|
71
76
|
|
|
@@ -101,6 +106,11 @@ function modeApprovesTier(mode: ApprovalMode, tier: ToolTier): boolean {
|
|
|
101
106
|
*
|
|
102
107
|
* Resolution order:
|
|
103
108
|
* 1. Tool `approval(args)` decision, defaulting to tier "exec" when omitted.
|
|
109
|
+
* A decision may carry a `policyKey` — `tools.approval.<policyKey>` is then
|
|
110
|
+
* the user override consulted instead of `tools.approval.<tool.name>`, with
|
|
111
|
+
* the invoking tool's own policy as the fallback when the user set none for
|
|
112
|
+
* the keyed sub-tool (e.g. an `xd://` device dispatch without a device
|
|
113
|
+
* policy still honors `tools.approval.write`).
|
|
104
114
|
* 2. User per-tool override, if set and valid.
|
|
105
115
|
* 3. Active mode tier comparison.
|
|
106
116
|
*
|
|
@@ -114,7 +124,14 @@ export function resolveApproval(
|
|
|
114
124
|
userConfig: Record<string, unknown> = {},
|
|
115
125
|
): ResolvedApproval {
|
|
116
126
|
const decision = getToolDecision(tool, args);
|
|
117
|
-
const
|
|
127
|
+
const policyKey = decision.policyKey ?? tool.name;
|
|
128
|
+
const userPolicy = Object.hasOwn(userConfig, policyKey) ? normalizePolicy(userConfig[policyKey]) : undefined;
|
|
129
|
+
const fallbackPolicy =
|
|
130
|
+
policyKey !== tool.name && userPolicy === undefined && Object.hasOwn(userConfig, tool.name)
|
|
131
|
+
? normalizePolicy(userConfig[tool.name])
|
|
132
|
+
: undefined;
|
|
133
|
+
const effectiveUserPolicy = userPolicy ?? fallbackPolicy;
|
|
134
|
+
const userPolicyKey = userPolicy !== undefined ? policyKey : tool.name;
|
|
118
135
|
|
|
119
136
|
if (decision.policy === "deny") {
|
|
120
137
|
return {
|
|
@@ -122,11 +139,18 @@ export function resolveApproval(
|
|
|
122
139
|
tier: decision.tier,
|
|
123
140
|
override: decision.override,
|
|
124
141
|
source: "tool",
|
|
142
|
+
...(decision.policyKey ? { policyKey: decision.policyKey } : {}),
|
|
125
143
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
126
144
|
};
|
|
127
145
|
}
|
|
128
|
-
if (
|
|
129
|
-
return {
|
|
146
|
+
if (effectiveUserPolicy === "deny") {
|
|
147
|
+
return {
|
|
148
|
+
policy: "deny",
|
|
149
|
+
tier: decision.tier,
|
|
150
|
+
override: decision.override,
|
|
151
|
+
source: "user",
|
|
152
|
+
policyKey: userPolicyKey,
|
|
153
|
+
};
|
|
130
154
|
}
|
|
131
155
|
|
|
132
156
|
if (mode === "yolo") {
|
|
@@ -136,14 +160,16 @@ export function resolveApproval(
|
|
|
136
160
|
tier: decision.tier,
|
|
137
161
|
override: false,
|
|
138
162
|
source: "tool",
|
|
163
|
+
...(decision.policyKey ? { policyKey: decision.policyKey } : {}),
|
|
139
164
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
140
165
|
};
|
|
141
166
|
}
|
|
142
167
|
return {
|
|
143
|
-
policy:
|
|
168
|
+
policy: effectiveUserPolicy ?? "allow",
|
|
144
169
|
tier: decision.tier,
|
|
145
170
|
override: false,
|
|
146
|
-
source:
|
|
171
|
+
source: effectiveUserPolicy ? "user" : "mode",
|
|
172
|
+
...(effectiveUserPolicy ? { policyKey: userPolicyKey } : {}),
|
|
147
173
|
};
|
|
148
174
|
}
|
|
149
175
|
|
|
@@ -153,6 +179,7 @@ export function resolveApproval(
|
|
|
153
179
|
tier: decision.tier,
|
|
154
180
|
override: true,
|
|
155
181
|
source: "tool",
|
|
182
|
+
...(decision.policyKey ? { policyKey: decision.policyKey } : {}),
|
|
156
183
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
157
184
|
};
|
|
158
185
|
}
|
|
@@ -163,12 +190,19 @@ export function resolveApproval(
|
|
|
163
190
|
tier: decision.tier,
|
|
164
191
|
override: false,
|
|
165
192
|
source: "tool",
|
|
193
|
+
...(decision.policyKey ? { policyKey: decision.policyKey } : {}),
|
|
166
194
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
167
195
|
};
|
|
168
196
|
}
|
|
169
197
|
|
|
170
|
-
if (
|
|
171
|
-
return {
|
|
198
|
+
if (effectiveUserPolicy) {
|
|
199
|
+
return {
|
|
200
|
+
policy: effectiveUserPolicy,
|
|
201
|
+
tier: decision.tier,
|
|
202
|
+
override: false,
|
|
203
|
+
source: "user",
|
|
204
|
+
policyKey: userPolicyKey,
|
|
205
|
+
};
|
|
172
206
|
}
|
|
173
207
|
|
|
174
208
|
if (modeApprovesTier(mode, decision.tier)) {
|
|
@@ -196,15 +230,15 @@ export function requiresApproval(
|
|
|
196
230
|
mode: ApprovalMode,
|
|
197
231
|
userConfig: Record<string, unknown> = {},
|
|
198
232
|
): { required: boolean; reason?: string } {
|
|
199
|
-
const { policy, reason, source } = resolveApproval(tool, args, mode, userConfig);
|
|
233
|
+
const { policy, reason, source, policyKey } = resolveApproval(tool, args, mode, userConfig);
|
|
200
234
|
|
|
201
235
|
if (policy === "deny") {
|
|
202
236
|
if (source === "tool") {
|
|
203
237
|
throw new Error(`Tool "${tool.name}" is blocked by tool policy.${reason ? `\nReason: ${reason}` : ""}`);
|
|
204
238
|
}
|
|
205
239
|
throw new Error(
|
|
206
|
-
`Tool "${tool.name}" is blocked by user policy.\n` +
|
|
207
|
-
`To allow: remove "tools.approval.${tool.name}: deny" from config.`,
|
|
240
|
+
`Tool "${policyKey ?? tool.name}" is blocked by user policy.\n` +
|
|
241
|
+
`To allow: remove "tools.approval.${policyKey ?? tool.name}: deny" from config.`,
|
|
208
242
|
);
|
|
209
243
|
}
|
|
210
244
|
|
package/src/tools/fetch.ts
CHANGED
|
@@ -576,6 +576,19 @@ async function parseFeedToMarkdown(content: string, maxItems = 10): Promise<stri
|
|
|
576
576
|
* local fallback renderers (trafilatura, lynx, native). See #1449.
|
|
577
577
|
*/
|
|
578
578
|
const REMOTE_READER_MAX_MS = 10_000;
|
|
579
|
+
const JINA_MARKDOWN_MARKER = "Markdown Content:";
|
|
580
|
+
const JINA_READER_MAX_BYTES = 2 * 1024 * 1024;
|
|
581
|
+
|
|
582
|
+
function parseJinaReaderContent(responseBody: string): string | null {
|
|
583
|
+
const markerStart = responseBody.indexOf(JINA_MARKDOWN_MARKER);
|
|
584
|
+
if (markerStart < 0) return null;
|
|
585
|
+
|
|
586
|
+
const content = responseBody.slice(markerStart + JINA_MARKDOWN_MARKER.length).trim();
|
|
587
|
+
if (content.length < 100 || content.startsWith("Loading...") || content.startsWith("Please enable JavaScript")) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
return content;
|
|
591
|
+
}
|
|
579
592
|
|
|
580
593
|
/** Reader backends for {@link renderHtmlToText}, in default priority order. */
|
|
581
594
|
export type FetchProvider = "native" | "trafilatura" | "lynx" | "parallel" | "jina";
|
|
@@ -653,10 +666,16 @@ export async function renderHtmlToText(
|
|
|
653
666
|
},
|
|
654
667
|
jina: async () => {
|
|
655
668
|
const response = await fetchImpl(`https://r.jina.ai/${url}`, {
|
|
656
|
-
headers: {
|
|
669
|
+
headers: {
|
|
670
|
+
Accept: "text/markdown",
|
|
671
|
+
"X-No-Cache": "true",
|
|
672
|
+
},
|
|
657
673
|
signal: remoteSignal(),
|
|
658
674
|
});
|
|
659
|
-
|
|
675
|
+
if (!response.ok) return null;
|
|
676
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
677
|
+
if (Number.isFinite(contentLength) && contentLength > JINA_READER_MAX_BYTES) return null;
|
|
678
|
+
return parseJinaReaderContent(await response.text());
|
|
660
679
|
},
|
|
661
680
|
};
|
|
662
681
|
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { type } from "@oh-my-pi/omptype";
|
|
4
|
-
import { type ApiKey, type FetchImpl, getEnvApiKey, type Model, withAuth } from "@oh-my-pi/pi-ai";
|
|
4
|
+
import { type ApiKey, type FetchImpl, getEnvApiKey, getOpenRouterHeaders, type Model, withAuth } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { ProviderHttpError } from "@oh-my-pi/pi-ai/error";
|
|
6
6
|
import {
|
|
7
7
|
CODEX_BASE_URL,
|
|
@@ -19,13 +19,13 @@ import {
|
|
|
19
19
|
ptree,
|
|
20
20
|
readSseJson,
|
|
21
21
|
Snowflake,
|
|
22
|
+
USER_AGENT,
|
|
22
23
|
untilAborted,
|
|
23
24
|
} from "@oh-my-pi/pi-utils";
|
|
24
|
-
import packageJson from "../../package.json" with { type: "json" };
|
|
25
25
|
import { isAuthenticated, type ModelRegistry } from "../config/model-registry";
|
|
26
26
|
import { settings } from "../config/settings";
|
|
27
27
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
28
|
-
import {
|
|
28
|
+
import { resolveXAIHttpCredentials } from "../lib/xai-http";
|
|
29
29
|
import imageGenDescription from "../prompts/tools/image-gen.md" with { type: "text" };
|
|
30
30
|
import { AUTO_IMAGE_PROVIDER_ORDER, type ImageProvider, isImageProviderId } from "./image-providers";
|
|
31
31
|
import { resolveReadPath } from "./path-utils";
|
|
@@ -897,7 +897,7 @@ function buildOpenAIImageHeaders(model: Model, apiKey: string, sessionId: string
|
|
|
897
897
|
}
|
|
898
898
|
headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES);
|
|
899
899
|
headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
|
|
900
|
-
headers.set("User-Agent",
|
|
900
|
+
headers.set("User-Agent", USER_AGENT);
|
|
901
901
|
if (sessionId) {
|
|
902
902
|
headers.set(OPENAI_HEADERS.CONVERSATION_ID, sessionId);
|
|
903
903
|
headers.set(OPENAI_HEADERS.SESSION_ID, sessionId);
|
|
@@ -1389,7 +1389,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1389
1389
|
headers: {
|
|
1390
1390
|
Authorization: `Bearer ${key}`,
|
|
1391
1391
|
"Content-Type": "application/json",
|
|
1392
|
-
"User-Agent":
|
|
1392
|
+
"User-Agent": USER_AGENT,
|
|
1393
1393
|
},
|
|
1394
1394
|
body: JSON.stringify(xaiBody),
|
|
1395
1395
|
signal: requestSignal,
|
|
@@ -1479,9 +1479,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1479
1479
|
headers: {
|
|
1480
1480
|
"Content-Type": "application/json",
|
|
1481
1481
|
Authorization: `Bearer ${key}`,
|
|
1482
|
-
|
|
1483
|
-
"X-OpenRouter-Title": "Oh-My-Pi",
|
|
1484
|
-
"X-OpenRouter-Categories": "cli-agent",
|
|
1482
|
+
...getOpenRouterHeaders(),
|
|
1485
1483
|
},
|
|
1486
1484
|
body: JSON.stringify(requestBody),
|
|
1487
1485
|
signal: requestSignal,
|
package/src/tools/todo.ts
CHANGED
|
@@ -236,6 +236,13 @@ export function todoMatchesAnyDescription(content: string, descriptions: readonl
|
|
|
236
236
|
return false;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
/** Whether a todo is settled: completed or deliberately abandoned. Shared so
|
|
240
|
+
* the collapsed viewport, the HUD progress counters, and the HUD's closed-todo
|
|
241
|
+
* auto-clear can never disagree about what "done" hides. */
|
|
242
|
+
export function isClosedTodo<T extends { status: TodoStatus }>(task: T): boolean {
|
|
243
|
+
return task.status === "completed" || task.status === "abandoned";
|
|
244
|
+
}
|
|
245
|
+
|
|
239
246
|
/**
|
|
240
247
|
* A todo the collapsed viewport treats as current work: the literal
|
|
241
248
|
* `in_progress` task or a pending task a live subagent is executing. Both
|
|
@@ -254,36 +261,33 @@ export interface CollapsedTodoSelection<T> {
|
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
/**
|
|
257
|
-
*
|
|
264
|
+
* Closed rows kept directly above the open window so finishing a task is
|
|
265
|
+
* visible as it happens. Without this the collapsed viewport only ever renders
|
|
266
|
+
* unchecked boxes while a phase has open work: every completion silently
|
|
267
|
+
* removes a row, so a plan mid-flight looks untouched, and the card's
|
|
268
|
+
* completion strike animation (`completedTasks` → {@link TODO_STRIKE_TOTAL_FRAMES})
|
|
269
|
+
* animated a row that was never rendered.
|
|
270
|
+
*/
|
|
271
|
+
const COLLAPSED_CLOSED_CONTEXT = 1;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Rows to show for a display base already reduced to the relevant tasks.
|
|
258
275
|
*
|
|
259
|
-
*
|
|
260
|
-
* 1. While the phase has open work, completed/abandoned tasks are omitted. A
|
|
261
|
-
* phase with no open tasks left falls back to its closed tasks so the sticky
|
|
262
|
-
* HUD's closed-todo persistence still has something to render.
|
|
263
|
-
* 2. Every active task (in-progress, or pending matched to a live subagent) is
|
|
276
|
+
* 1. Every active task (in-progress, or pending matched to a live subagent) is
|
|
264
277
|
* placed at the head in stable todo order — never dropped for lying outside
|
|
265
278
|
* an ordinary window.
|
|
266
|
-
*
|
|
279
|
+
* 2. Remaining rows up to `cap` are filled with the pending tasks that follow
|
|
267
280
|
* the first active one, in todo order (falling back to leading pending tasks
|
|
268
281
|
* when no active task exists), so a freshly-promoted task leads the preview.
|
|
269
|
-
*
|
|
282
|
+
* 3. When active tasks alone exceed `cap`, only the first `cap` active tasks are
|
|
270
283
|
* shown and the summary counts the hidden *active* todos, never replacing
|
|
271
284
|
* them with unrelated pending rows.
|
|
272
|
-
*
|
|
273
|
-
* The summary otherwise counts the remaining tasks in the display base. Returns
|
|
274
|
-
* the whole base with an empty summary when it already fits.
|
|
275
285
|
*/
|
|
276
|
-
|
|
277
|
-
|
|
286
|
+
function selectWithinCap<T extends { status: TodoStatus }>(
|
|
287
|
+
base: T[],
|
|
278
288
|
isMatched: (task: T) => boolean,
|
|
279
289
|
cap: number,
|
|
280
290
|
): CollapsedTodoSelection<T> {
|
|
281
|
-
const open = tasks.filter(
|
|
282
|
-
task => task.status === "pending" || task.status === "in_progress" || task.status === "blocked",
|
|
283
|
-
);
|
|
284
|
-
// No open work: fall back to the closed tasks so a settled phase still
|
|
285
|
-
// renders (HUD closed-todo persistence). Closed tasks are never active.
|
|
286
|
-
const base = open.length > 0 ? open : tasks;
|
|
287
291
|
if (base.length <= cap) return { items: base, summary: "" };
|
|
288
292
|
|
|
289
293
|
const active = base.filter(task => isActiveTodo(task, isMatched));
|
|
@@ -312,6 +316,33 @@ export function selectCollapsedTodos<T extends { status: TodoStatus }>(
|
|
|
312
316
|
return { items, summary: hidden > 0 ? formatMoreItems(hidden, "todo") : "" };
|
|
313
317
|
}
|
|
314
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Walking-viewport selection for a phase's collapsed todo preview (#5873).
|
|
321
|
+
*
|
|
322
|
+
* Applied to `tasks` in todo order: the open tasks run through
|
|
323
|
+
* {@link selectWithinCap}, led by the last {@link COLLAPSED_CLOSED_CONTEXT}
|
|
324
|
+
* closed tasks in todo order so a checked row remains visible even when callers
|
|
325
|
+
* complete work out of sequence. The lead is additive — it never costs an open
|
|
326
|
+
* row — and a phase with no open work left falls back to its closed tasks so the
|
|
327
|
+
* sticky HUD's closed-todo persistence still has something to render.
|
|
328
|
+
*
|
|
329
|
+
* `summary` counts the open tasks that did not fit; the closed lead is context,
|
|
330
|
+
* not part of the budget.
|
|
331
|
+
*/
|
|
332
|
+
export function selectCollapsedTodos<T extends { status: TodoStatus }>(
|
|
333
|
+
tasks: T[],
|
|
334
|
+
isMatched: (task: T) => boolean,
|
|
335
|
+
cap: number,
|
|
336
|
+
): CollapsedTodoSelection<T> {
|
|
337
|
+
const open = tasks.filter(task => !isClosedTodo(task));
|
|
338
|
+
// Closed tasks are never active, so a settled phase selects over itself.
|
|
339
|
+
if (open.length === 0) return selectWithinCap(tasks, isMatched, cap);
|
|
340
|
+
// `done` accepts any named task, so closed tasks are not necessarily a prefix.
|
|
341
|
+
const lead = tasks.filter(isClosedTodo).slice(-COLLAPSED_CLOSED_CONTEXT);
|
|
342
|
+
const selected = selectWithinCap(open, isMatched, cap);
|
|
343
|
+
return { items: [...lead, ...selected.items], summary: selected.summary };
|
|
344
|
+
}
|
|
345
|
+
|
|
315
346
|
function resolveTaskOrError(
|
|
316
347
|
phases: TodoPhase[],
|
|
317
348
|
content: string | undefined,
|
|
@@ -1055,12 +1086,20 @@ function computeTouchedPhases(
|
|
|
1055
1086
|
return touched.size > 0 ? touched : null;
|
|
1056
1087
|
}
|
|
1057
1088
|
|
|
1089
|
+
/**
|
|
1090
|
+
* Dim `closed/total` suffix for a phase header. Counts closed tasks, not just
|
|
1091
|
+
* completed ones: the collapsed viewport hides both, so an abandoned task has to
|
|
1092
|
+
* move the counter or its phase reads as permanently stuck.
|
|
1093
|
+
*/
|
|
1094
|
+
function formatPhaseProgress(phase: TodoPhase, uiTheme: Theme): string {
|
|
1095
|
+
const done = phase.tasks.filter(isClosedTodo).length;
|
|
1096
|
+
return uiTheme.fg("dim", ` ${done}/${phase.tasks.length}`);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1058
1099
|
/** One-line summary for a collapsed (untouched) phase: dim header + progress. */
|
|
1059
1100
|
function formatPhaseSummary(phase: TodoPhase, oneBasedIndex: number, uiTheme: Theme): string {
|
|
1060
|
-
const total = phase.tasks.length;
|
|
1061
|
-
const done = phase.tasks.filter(task => task.status === "completed").length;
|
|
1062
1101
|
const name = uiTheme.fg("dim", chalk.bold(formatPhaseDisplayName(phase.name, oneBasedIndex)));
|
|
1063
|
-
return `${name}${
|
|
1102
|
+
return `${name}${formatPhaseProgress(phase, uiTheme)}`;
|
|
1064
1103
|
}
|
|
1065
1104
|
|
|
1066
1105
|
/**
|
|
@@ -1178,12 +1217,17 @@ export const todoToolRenderer = {
|
|
|
1178
1217
|
continue;
|
|
1179
1218
|
}
|
|
1180
1219
|
if (multiPhase) {
|
|
1181
|
-
|
|
1220
|
+
// Progress belongs on the expanded header too: the collapsed
|
|
1221
|
+
// viewport below hides closed rows, so without it the phase the
|
|
1222
|
+
// agent is actually working in is the one phase with no visible
|
|
1223
|
+
// completion signal at all.
|
|
1224
|
+
const name = uiTheme.fg("accent", chalk.bold(formatPhaseDisplayName(phase.name, p + 1)));
|
|
1225
|
+
bodyLines.push(`${name}${formatPhaseProgress(phase, uiTheme)}`);
|
|
1182
1226
|
}
|
|
1183
1227
|
const completionKeys = completionKeysByPhase.get(phase.name) ?? EMPTY_COMPLETION_KEYS;
|
|
1184
|
-
// Collapsed: walking viewport —
|
|
1185
|
-
// work (in-progress / subagent-matched)
|
|
1186
|
-
//
|
|
1228
|
+
// Collapsed: walking viewport — the last closed task leads, then
|
|
1229
|
+
// active work (in-progress / subagent-matched), then following
|
|
1230
|
+
// pending tasks (#5873). Expanded: every task in order.
|
|
1187
1231
|
const treeLines = expanded
|
|
1188
1232
|
? renderTreeList(
|
|
1189
1233
|
{
|
package/src/tools/tts.ts
CHANGED
|
@@ -7,9 +7,10 @@ import { type } from "@oh-my-pi/omptype";
|
|
|
7
7
|
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import { type ApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import { ProviderHttpError } from "@oh-my-pi/pi-ai/error";
|
|
10
|
+
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
|
10
11
|
import { settings } from "../config/settings";
|
|
11
12
|
import type { CustomTool, CustomToolContext } from "../extensibility/custom-tools/types";
|
|
12
|
-
import {
|
|
13
|
+
import { resolveXAIHttpCredentials } from "../lib/xai-http";
|
|
13
14
|
import { DEFAULT_TTS_LOCAL_MODEL_KEY, DEFAULT_TTS_VOICE, isTtsLocalModelKey, KOKORO_VOICES } from "../tts/models";
|
|
14
15
|
import { ttsClient } from "../tts/tts-client";
|
|
15
16
|
import { encodeWav } from "../tts/wav";
|
|
@@ -150,7 +151,7 @@ async function synthesizeXai(
|
|
|
150
151
|
headers: {
|
|
151
152
|
Authorization: `Bearer ${key}`,
|
|
152
153
|
"Content-Type": "application/json",
|
|
153
|
-
"User-Agent":
|
|
154
|
+
"User-Agent": USER_AGENT,
|
|
154
155
|
},
|
|
155
156
|
body: JSON.stringify(payload),
|
|
156
157
|
signal: combinedSignal,
|