@mono-agent/agent-runtime 0.20.11 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +50 -11
- package/MIGRATION.md +288 -26
- package/README.md +352 -477
- package/package.json +13 -44
- package/src/agent/tool-bloat.js +145 -9
- package/src/agent/tools/agent-tool.js +108 -9
- package/src/agent/tools/bash.js +11 -26
- package/src/agent/tools/codex-subscription-search.js +123 -29
- package/src/agent/tools/exec.js +10 -2
- package/src/agent/tools/index.js +7 -0
- package/src/agent/tools/monitor.js +149 -0
- package/src/agent/tools/pi-bridge.js +123 -19
- package/src/agent/tools/shared/bash-environment.js +31 -0
- package/src/agent/tools/shared/monitors.js +293 -0
- package/src/agent/tools/shared/path-resolver.js +25 -6
- package/src/agent/tools/shared/process-jobs.js +6 -1
- package/src/agent/tools/shared/process-runner.js +26 -6
- package/src/agent/tools/shared/tool-context.js +8 -0
- package/src/agent/tools/web-access-interstitial.js +70 -0
- package/src/agent/tools/web-browser-render.js +83 -58
- package/src/agent/tools/web-controller.js +112 -21
- package/src/agent/tools/web-document-extractor.js +379 -0
- package/src/agent/tools/web-fetch.js +271 -243
- package/src/agent/tools/web-request.js +65 -0
- package/src/agent/tools/web-search-output.js +165 -0
- package/src/agent/tools/web-search-state.js +75 -0
- package/src/agent/tools/web-search.js +532 -71
- package/src/ai/cost.js +13 -68
- package/src/ai/failure.js +3 -3
- package/src/ai/index.js +5 -17
- package/src/ai/observer.js +8 -0
- package/src/ai/pi-interop.js +221 -1
- package/src/ai/pi-oauth-compat.js +1 -1
- package/src/ai/provider-check.js +131 -0
- package/src/ai/providers/codex/app-server-client.js +592 -0
- package/src/ai/providers/pi-models.js +18 -10
- package/src/ai/providers/pi-native/compaction-driver.js +94 -42
- package/src/ai/providers/pi-native/compaction-summary.js +140 -0
- package/src/ai/providers/pi-native/harness-adapter.js +376 -0
- package/src/ai/providers/pi-native/prompt-cache-diagnostics.js +103 -0
- package/src/ai/providers/pi-native/provider-attribution.js +102 -0
- package/src/ai/providers/pi-native/result-builder.js +38 -14
- package/src/ai/providers/pi-native/session-lifecycle.js +253 -55
- package/src/ai/providers/pi-native/stream-subscriber.js +52 -6
- package/src/ai/providers/pi-native/terminal-recovery.js +40 -0
- package/src/ai/providers/pi-native/turn-runner.js +279 -28
- package/src/ai/providers/pi-native.js +206 -61
- package/src/ai/runtime/capabilities.js +11 -56
- package/src/ai/runtime/live-input-events.js +250 -54
- package/src/ai/runtime/model-refs.js +118 -153
- package/src/ai/runtime/registry.js +22 -56
- package/src/ai/runtime/router.js +76 -417
- package/src/ai/runtime/session-liveness.js +3 -4
- package/src/ai/runtime/sessions.js +4 -5
- package/src/ai/runtime/tool-policy.js +0 -2
- package/src/ai/tool-lifecycle.js +32 -18
- package/src/ai/types.js +37 -112
- package/src/index.js +0 -6
- package/src/runtime.js +29 -16
- package/types/agent/tool-bloat.d.ts +1 -1
- package/types/agent/tools/agent-tool.d.ts +4 -2
- package/types/agent/tools/bash.d.ts +5 -3
- package/types/agent/tools/codex-subscription-search.d.ts +7 -3
- package/types/agent/tools/exec.d.ts +5 -3
- package/types/agent/tools/index.d.ts +1 -0
- package/types/agent/tools/monitor.d.ts +47 -0
- package/types/agent/tools/pi-bridge.d.ts +7 -4
- package/types/agent/tools/shared/bash-environment.d.ts +4 -0
- package/types/agent/tools/shared/monitors.d.ts +98 -0
- package/types/agent/tools/shared/process-jobs.d.ts +5 -1
- package/types/agent/tools/shared/process-runner.d.ts +14 -4
- package/types/agent/tools/shared/tool-context.d.ts +2 -0
- package/types/agent/tools/web-access-interstitial.d.ts +23 -0
- package/types/agent/tools/web-browser-render.d.ts +4 -1
- package/types/agent/tools/web-controller.d.ts +4 -2
- package/types/agent/tools/web-document-extractor.d.ts +27 -0
- package/types/agent/tools/web-fetch.d.ts +19 -24
- package/types/agent/tools/web-request.d.ts +20 -0
- package/types/agent/tools/web-search-output.d.ts +31 -0
- package/types/agent/tools/web-search-state.d.ts +21 -0
- package/types/agent/tools/web-search.d.ts +10 -45
- package/types/ai/cost.d.ts +1 -2
- package/types/ai/index.d.ts +2 -4
- package/types/ai/observer.d.ts +6 -0
- package/types/ai/pi-interop.d.ts +81 -0
- package/types/ai/provider-check.d.ts +53 -0
- package/types/ai/providers/codex/app-server-client.d.ts +37 -0
- package/types/ai/providers/pi-native/compaction-driver.d.ts +2 -1
- package/types/ai/providers/pi-native/compaction-summary.d.ts +19 -0
- package/types/ai/providers/pi-native/harness-adapter.d.ts +58 -0
- package/types/ai/providers/pi-native/prompt-cache-diagnostics.d.ts +3 -0
- package/types/ai/providers/pi-native/provider-attribution.d.ts +26 -0
- package/types/ai/providers/pi-native/result-builder.d.ts +14 -4
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +25 -6
- package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
- package/types/ai/providers/pi-native/terminal-recovery.d.ts +2 -0
- package/types/ai/providers/pi-native/turn-runner.d.ts +68 -10
- package/types/ai/providers/pi-native.d.ts +21 -4
- package/types/ai/runtime/capabilities.d.ts +21 -70
- package/types/ai/runtime/live-input-events.d.ts +32 -8
- package/types/ai/runtime/model-refs.d.ts +0 -24
- package/types/ai/runtime/router.d.ts +3 -10
- package/types/ai/runtime/tool-policy.d.ts +0 -2
- package/types/ai/tool-lifecycle.d.ts +4 -3
- package/types/ai/types.d.ts +162 -256
- package/types/index.d.ts +0 -1
- package/src/ai/providers/acp-client.js +0 -1149
- package/src/ai/providers/acp-privacy.js +0 -124
- package/src/ai/providers/acp-public.js +0 -21
- package/src/ai/providers/acp-session-tokens.js +0 -282
- package/src/ai/providers/acp-transport.js +0 -356
- package/src/ai/providers/acp.js +0 -543
- package/src/ai/providers/claude-cli.js +0 -883
- package/src/ai/providers/claude-sandbox.js +0 -71
- package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
- package/src/ai/providers/claude-sdk-discovery.js +0 -352
- package/src/ai/providers/claude-sdk.js +0 -1127
- package/src/ai/providers/claude-subagent-activity.js +0 -719
- package/src/ai/providers/claude-subagents.js +0 -88
- package/src/ai/providers/codex-app.js +0 -2946
- package/src/ai/providers/opencode-app.js +0 -1109
- package/src/ai/providers/opencode-discovery.js +0 -39
- package/src/ai/providers/opencode-server.js +0 -508
- package/src/ai/runtime/context-windows.js +0 -46
- package/src/ai/runtime/fast-mode.js +0 -8
- package/src/ai/streaming/codex-events.js +0 -146
- package/src/ai/streaming/opencode-events.js +0 -59
- package/types/ai/providers/acp-client.d.ts +0 -227
- package/types/ai/providers/acp-privacy.d.ts +0 -25
- package/types/ai/providers/acp-public.d.ts +0 -7
- package/types/ai/providers/acp-session-tokens.d.ts +0 -41
- package/types/ai/providers/acp-transport.d.ts +0 -45
- package/types/ai/providers/acp.d.ts +0 -93
- package/types/ai/providers/claude-cli.d.ts +0 -305
- package/types/ai/providers/claude-sandbox.d.ts +0 -79
- package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
- package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
- package/types/ai/providers/claude-sdk.d.ts +0 -138
- package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
- package/types/ai/providers/claude-subagents.d.ts +0 -18
- package/types/ai/providers/codex-app.d.ts +0 -151
- package/types/ai/providers/opencode-app.d.ts +0 -96
- package/types/ai/providers/opencode-discovery.d.ts +0 -4
- package/types/ai/providers/opencode-server.d.ts +0 -20
- package/types/ai/runtime/context-windows.d.ts +0 -9
- package/types/ai/runtime/fast-mode.d.ts +0 -2
- package/types/ai/streaming/codex-events.d.ts +0 -40
- package/types/ai/streaming/opencode-events.d.ts +0 -42
package/src/ai/runtime/router.js
CHANGED
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
// so the router is a drop-in replacement for createRuntime(host).
|
|
21
21
|
//
|
|
22
22
|
// chain entries:
|
|
23
|
-
// { model: ModelRef,
|
|
24
|
-
// requires?: Capabilities }
|
|
23
|
+
// { model: ModelRef, effort?: string|null, requires?: Capabilities }
|
|
25
24
|
// shorthand: a bare ModelRef is also accepted (no requirements).
|
|
26
25
|
// effort string = fixed for that route, undefined = inherit the legacy run
|
|
27
26
|
// effort, null = omit effort so the provider chooses its default.
|
|
@@ -29,6 +28,8 @@
|
|
|
29
28
|
// Result:
|
|
30
29
|
// The success run's result, with `failoverHistory` appended describing every
|
|
31
30
|
// prior attempt: [{ model, failureKind, requestId, retryableSubkind }].
|
|
31
|
+
// Successful retries/backups withhold providerSessionId: their stateless
|
|
32
|
+
// answer cannot synchronize the primary provider session.
|
|
32
33
|
// If every eligible retryable/auth entry in the chain fails, returns the last
|
|
33
34
|
// result with `failureKind: "provider_unavailable_exhausted"`. Terminal
|
|
34
35
|
// non-retryable failures are returned as-is with their failover history.
|
|
@@ -43,6 +44,7 @@ import { passthroughSandbox } from "../../agent/sandbox-seam.js";
|
|
|
43
44
|
import { resolveRuntimeBrand } from "../../runtime-brand.js";
|
|
44
45
|
import { createObserverHub } from "../observer.js";
|
|
45
46
|
import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
|
|
47
|
+
import { createWebSearchRunState } from "../../agent/tools/web-search-state.js";
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
* @typedef {import('../types.js').RuntimeModelRef} RuntimeModelRef
|
|
@@ -55,10 +57,8 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
|
|
|
55
57
|
/**
|
|
56
58
|
* @typedef {Object} RouterChainEntryInput
|
|
57
59
|
* A chain entry as accepted by createRouterRuntime: either the shorthand bare
|
|
58
|
-
* RuntimeModelRef, or the full `{model,
|
|
59
|
-
* attempts?}` form.
|
|
60
|
+
* RuntimeModelRef, or the full `{model, effort?, requires?, attempts?}` form.
|
|
60
61
|
* @property {RuntimeModelRef} model
|
|
61
|
-
* @property {string} [executionMode]
|
|
62
62
|
* @property {string|null} [effort]
|
|
63
63
|
* @property {Object<string, *>} [requires]
|
|
64
64
|
* @property {number} [attempts]
|
|
@@ -67,7 +67,6 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
|
|
|
67
67
|
/**
|
|
68
68
|
* @typedef {Object} RouterChainEntry
|
|
69
69
|
* @property {RuntimeModelRef} model
|
|
70
|
-
* @property {string|null} executionMode
|
|
71
70
|
* @property {string|null|undefined} effort
|
|
72
71
|
* @property {Object<string, *>|null} requires
|
|
73
72
|
* @property {number} attempts Total attempts on this route including the first.
|
|
@@ -91,18 +90,19 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
|
|
|
91
90
|
* @property {() => (void|Promise<void>)} [cleanup]
|
|
92
91
|
*/
|
|
93
92
|
|
|
94
|
-
const ROUTE_SAFETY_MODES = new Set(["uniform", "per-route-native"]);
|
|
95
93
|
const ATTEMPT_SCOPED_OPTION_KEYS = ["customProvider", "customModel", "modelCapabilities", "isPrivateProvider"];
|
|
96
94
|
const ROUTER_TOOL_CONTEXT_KEYS = [
|
|
97
|
-
"workspace", "repoRoot", "
|
|
95
|
+
"workspace", "repoRoot", "additionalReadRoots", "additionalWriteRoots",
|
|
96
|
+
"ripgrepPath", "qaOutputDir", "sandboxPolicy", "sandboxEngine",
|
|
98
97
|
];
|
|
99
98
|
const RESOLVER_PROTECTED_OPTION_KEYS = new Set([
|
|
100
|
-
"model", "
|
|
101
|
-
"sessionId", "providerSessionId", "sessionKeepAlive", "sessionIdleTimeoutMs",
|
|
99
|
+
"model", "effort", "messages", "abortSignal", "onEvent",
|
|
100
|
+
"sessionRecovery", "sessionId", "providerSessionId", "providerAttributionSessionId", "sessionKeepAlive", "sessionIdleTimeoutMs",
|
|
102
101
|
"diagnosticsSeed", "systemPromptPrefix", "sandboxPolicy", "sandboxEngine", "sandbox",
|
|
103
102
|
"allowedTools", "disallowedTools", "permissionMode", "mcpServers", "mcpApps", "skills",
|
|
104
|
-
"
|
|
105
|
-
"
|
|
103
|
+
"mcpCallNoTotalTimeoutTools",
|
|
104
|
+
"webSearchState",
|
|
105
|
+
"outputSchema", "liveInput", "toolEnvironment", "persistArtifact",
|
|
106
106
|
]);
|
|
107
107
|
|
|
108
108
|
class ResolverProtectedOptionError extends Error {
|
|
@@ -117,16 +117,12 @@ class ResolverProtectedOptionError extends Error {
|
|
|
117
117
|
* @param {Object} [options]
|
|
118
118
|
* @param {AgentRuntimeHostOptions} [options.host]
|
|
119
119
|
* @param {ReadonlyArray<RuntimeModelRef|RouterChainEntryInput>} [options.chain]
|
|
120
|
-
* @param {
|
|
121
|
-
* @param {(input: {model: RuntimeModelRef, executionMode: string|null, attemptIndex: number, retryIndex: number, routeSafety: "uniform"|"per-route-native"}) => (RouterAttemptResolution|Promise<RouterAttemptResolution>)} [options.resolveAttempt]
|
|
120
|
+
* @param {(input: {model: RuntimeModelRef, attemptIndex: number, retryIndex: number}) => (RouterAttemptResolution|Promise<RouterAttemptResolution>)} [options.resolveAttempt]
|
|
122
121
|
* @param {Partial<RouterRetryPolicy>} [options.retry] Backoff shape for same-model
|
|
123
122
|
* retries. Per-route retry counts live on each chain entry's `attempts`.
|
|
124
123
|
* @returns {AgentRuntimeInstance & {chain: () => Array<RouterChainEntry>}}
|
|
125
124
|
*/
|
|
126
|
-
export function createRouterRuntime({ host = {}, chain = [],
|
|
127
|
-
if (!ROUTE_SAFETY_MODES.has(routeSafety)) {
|
|
128
|
-
throw new Error("createRouterRuntime routeSafety must be uniform or per-route-native");
|
|
129
|
-
}
|
|
125
|
+
export function createRouterRuntime({ host = {}, chain = [], resolveAttempt, retry } = {}) {
|
|
130
126
|
const retryPolicy = normalizeRetryPolicy(retry);
|
|
131
127
|
const entries = normaliseChain(chain);
|
|
132
128
|
if (entries.length === 0) {
|
|
@@ -134,8 +130,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
134
130
|
}
|
|
135
131
|
assertUniqueEntries(entries);
|
|
136
132
|
const inner = createRuntime(host);
|
|
137
|
-
/** @type {Map<string, AgentRuntimeInstance>} */
|
|
138
|
-
const routeRuntimes = new Map();
|
|
139
133
|
/** @type {import('../types.js').AgentRuntimeToolOptions|undefined} */
|
|
140
134
|
let configuredTools;
|
|
141
135
|
// The router builds transcript-tail snapshots outside the inner runtime's
|
|
@@ -148,11 +142,15 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
148
142
|
/**
|
|
149
143
|
* @param {string} systemPrompt
|
|
150
144
|
* @param {Partial<RuntimeRunOptions>} [options] Optional so a bare `{}` call
|
|
151
|
-
* is legal; the router always overrides `model
|
|
152
|
-
*
|
|
145
|
+
* is legal; the router always overrides `model` per chain entry (see
|
|
146
|
+
* AgentRuntimeInstance.run for the public, model-required contract).
|
|
153
147
|
* @returns {Promise<RuntimeResult>}
|
|
154
148
|
*/
|
|
155
149
|
async run(systemPrompt, options = {}) {
|
|
150
|
+
options = {
|
|
151
|
+
...options,
|
|
152
|
+
webSearchState: createWebSearchRunState(options.webSearchConfig, options.webSearchState),
|
|
153
|
+
};
|
|
156
154
|
const liveInputHub = options.liveInput === undefined
|
|
157
155
|
? undefined
|
|
158
156
|
: createObserverHub({
|
|
@@ -169,7 +167,7 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
169
167
|
};
|
|
170
168
|
}
|
|
171
169
|
try {
|
|
172
|
-
/** @type {Array<{model: RuntimeModelRef, failureKind: (string|null), requestId?: (string|null|undefined), retryableSubkind?: (string|null|undefined), requirements?: (Object<string,*>|null),
|
|
170
|
+
/** @type {Array<{model: RuntimeModelRef, failureKind: (string|null), requestId?: (string|null|undefined), retryableSubkind?: (string|null|undefined), requirements?: (Object<string,*>|null), retryIndex?: number}>} */
|
|
173
171
|
const failoverHistory = [];
|
|
174
172
|
/** @type {RuntimeResult|null} */
|
|
175
173
|
let lastResult = null;
|
|
@@ -178,46 +176,9 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
178
176
|
const promptBase = systemPrompt;
|
|
179
177
|
/** @type {*} */
|
|
180
178
|
let pendingSnapshot = null;
|
|
181
|
-
/** @type {Array<{attemptIndex: number, model: RuntimeModelRef, routeSafety: import('../types.js').RuntimeRouteSafetyMode, safetyContract: import('../types.js').RuntimeRouteSafetyContract, status: string}>} */
|
|
182
|
-
const routeSafetyHistory = [];
|
|
183
|
-
|
|
184
179
|
for (let i = 0; i < entries.length; i += 1) {
|
|
185
180
|
const entry = entries[i];
|
|
186
181
|
const effectiveToolOptions = effectiveRouterToolOptions(host, configuredTools);
|
|
187
|
-
const entrySafetyContract = routeSafetyContract(
|
|
188
|
-
routeSafety,
|
|
189
|
-
entry,
|
|
190
|
-
entry.model.sdk === "pi"
|
|
191
|
-
? effectivePiSandboxPolicy(effectiveToolOptions, options)
|
|
192
|
-
: undefined,
|
|
193
|
-
);
|
|
194
|
-
// Internal protected roots are enforced by mono-agent's Pi tool layer
|
|
195
|
-
// and SRT projection. Provider-native non-Pi routes deliberately drop
|
|
196
|
-
// that layer, so attempting one would turn the router into a confused
|
|
197
|
-
// deputy for private host state. Reject the route before resolution or
|
|
198
|
-
// provider construction; a later Pi entry may still satisfy the run.
|
|
199
|
-
if (
|
|
200
|
-
entry.model.sdk !== "pi"
|
|
201
|
-
&& attemptCarriesProtectedRoots(effectiveToolOptions, options)
|
|
202
|
-
) {
|
|
203
|
-
const failure = safetyUnavailableResult();
|
|
204
|
-
lastRouteSkip = failure;
|
|
205
|
-
failoverHistory.push({
|
|
206
|
-
model: entry.model,
|
|
207
|
-
failureKind: "safety_unavailable",
|
|
208
|
-
routeSafety,
|
|
209
|
-
safetyContract: entrySafetyContract,
|
|
210
|
-
});
|
|
211
|
-
const unavailableRecord = routeSafetyRecord(
|
|
212
|
-
i,
|
|
213
|
-
entry,
|
|
214
|
-
entrySafetyContract,
|
|
215
|
-
"safety_unavailable",
|
|
216
|
-
);
|
|
217
|
-
routeSafetyHistory.push(unavailableRecord);
|
|
218
|
-
emit(options, { type: "provider_route_safety", ...unavailableRecord });
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
182
|
if (!entrySatisfiesRequirements(entry, options)) {
|
|
222
183
|
lastRouteSkip = {
|
|
223
184
|
text: null,
|
|
@@ -231,12 +192,7 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
231
192
|
model: entry.model,
|
|
232
193
|
failureKind: "skipped_capability_mismatch",
|
|
233
194
|
requirements: entry.requires,
|
|
234
|
-
routeSafety,
|
|
235
|
-
safetyContract: entrySafetyContract,
|
|
236
195
|
});
|
|
237
|
-
const skippedRecord = routeSafetyRecord(i, entry, entrySafetyContract, "skipped_capability_mismatch");
|
|
238
|
-
routeSafetyHistory.push(skippedRecord);
|
|
239
|
-
emit(options, { type: "provider_route_safety", ...skippedRecord });
|
|
240
196
|
continue;
|
|
241
197
|
}
|
|
242
198
|
|
|
@@ -249,7 +205,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
249
205
|
const entryOptionsBase = {
|
|
250
206
|
...options,
|
|
251
207
|
model: entry.model,
|
|
252
|
-
executionMode: entry.executionMode || options.executionMode,
|
|
253
208
|
};
|
|
254
209
|
// The legacy run-level custom-provider bag describes the primary
|
|
255
210
|
// route. Without a route resolver there is no authoritative metadata
|
|
@@ -263,7 +218,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
263
218
|
let terminalResult = null;
|
|
264
219
|
|
|
265
220
|
for (let retryIndex = 0; retryIndex < entry.attempts; retryIndex += 1) {
|
|
266
|
-
let safetyContract = entrySafetyContract;
|
|
267
221
|
/** @type {*} */
|
|
268
222
|
let callOptions = { ...entryCallBase };
|
|
269
223
|
/** @type {AgentRuntimeInstance} */
|
|
@@ -275,10 +229,8 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
275
229
|
? undefined
|
|
276
230
|
: await resolveAttempt({
|
|
277
231
|
model: entry.model,
|
|
278
|
-
executionMode: entry.executionMode,
|
|
279
232
|
attemptIndex: i,
|
|
280
233
|
retryIndex,
|
|
281
|
-
routeSafety,
|
|
282
234
|
});
|
|
283
235
|
const resolution = normalizeAttemptResolution(resolved);
|
|
284
236
|
attemptCleanup = resolution?.cleanup;
|
|
@@ -286,63 +238,31 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
286
238
|
callOptions = mergeAttemptOptions(callOptions, resolution?.options);
|
|
287
239
|
callOptions = mergeAttemptPolicyOptions(callOptions, resolution?.policyOptions);
|
|
288
240
|
}
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (resolvedRuntime !== undefined) {
|
|
294
|
-
assertRuntimeLike(resolvedRuntime);
|
|
295
|
-
const previousRuntime = routeRuntimes.get(key);
|
|
296
|
-
if (previousRuntime !== undefined && previousRuntime !== resolvedRuntime) {
|
|
297
|
-
try { await previousRuntime.disposeAllSessions?.(); } catch { /* best-effort replacement */ }
|
|
298
|
-
}
|
|
299
|
-
routeRuntimes.set(key, resolvedRuntime);
|
|
300
|
-
if (entry.model.sdk !== "pi") {
|
|
301
|
-
resolvedRuntime.configureTools?.(projectPerRouteNativeToolOptions(entry, configuredTools));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
attemptRuntime = routeRuntimes.get(key) ?? createRouteRuntime(key, entry, host, routeRuntimes, configuredTools);
|
|
305
|
-
if (entry.model.sdk === "pi") {
|
|
306
|
-
projectPiRuntimeToolContext(attemptRuntime, effectiveToolOptions);
|
|
307
|
-
// Derive the attestation from the same complete base context and
|
|
308
|
-
// request-scoped inputs that the supplied/runtime-owned Pi
|
|
309
|
-
// bridge will actually receive. Resolver options cannot alter
|
|
310
|
-
// these protected fields.
|
|
311
|
-
safetyContract = routeSafetyContract(
|
|
312
|
-
routeSafety,
|
|
313
|
-
entry,
|
|
314
|
-
effectivePiSandboxPolicy(effectiveToolOptions, callOptions),
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
} else if (resolution?.runtime !== undefined && resolution.runtime !== inner) {
|
|
318
|
-
throw new Error("uniform route safety cannot replace the shared monotonic runtime");
|
|
241
|
+
if (resolution?.runtime !== undefined) {
|
|
242
|
+
assertRuntimeLike(resolution.runtime);
|
|
243
|
+
attemptRuntime = resolution.runtime;
|
|
244
|
+
projectPiRuntimeToolContext(attemptRuntime, effectiveToolOptions);
|
|
319
245
|
}
|
|
320
246
|
} catch (error) {
|
|
321
247
|
try { await attemptCleanup?.(); } catch { /* cleanup is additive */ }
|
|
322
|
-
const failure =
|
|
323
|
-
|
|
248
|
+
const failure = attemptResolutionFailureResult(error);
|
|
249
|
+
lastResult = failure;
|
|
324
250
|
failoverHistory.push({
|
|
325
251
|
model: entry.model,
|
|
326
|
-
failureKind:
|
|
327
|
-
routeSafety,
|
|
328
|
-
safetyContract,
|
|
252
|
+
failureKind: failure.failureKind || null,
|
|
329
253
|
});
|
|
330
|
-
const unavailableRecord = routeSafetyRecord(i, entry, safetyContract, "safety_unavailable");
|
|
331
|
-
routeSafetyHistory.push(unavailableRecord);
|
|
332
|
-
emit(callOptions, { type: "provider_route_safety", ...unavailableRecord });
|
|
333
254
|
// A resolver fault is a config/credential problem, not a transient
|
|
334
255
|
// provider blip: retrying the same route cannot fix it. Advance.
|
|
335
256
|
break;
|
|
336
257
|
}
|
|
337
258
|
|
|
338
259
|
applyEntryEffort(callOptions, entry.effort);
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (entries.length > 1 || i > 0 || retryIndex > 0 || !entrySupportsSessionResume(entry)) {
|
|
260
|
+
// Only the primary's first attempt may own a provider session. Retries
|
|
261
|
+
// replay the logical turn and must not resume a transcript the failed
|
|
262
|
+
// attempt may have appended to; backup routes never inherit that session.
|
|
263
|
+
const sessionEligibleAttempt = i === 0 && retryIndex === 0 && entrySupportsSessionResume(entry);
|
|
264
|
+
if (!sessionEligibleAttempt) {
|
|
265
|
+
delete callOptions.sessionRecovery;
|
|
346
266
|
delete callOptions.sessionId;
|
|
347
267
|
delete callOptions.providerSessionId;
|
|
348
268
|
delete callOptions.sessionKeepAlive;
|
|
@@ -363,17 +283,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
363
283
|
}
|
|
364
284
|
}
|
|
365
285
|
|
|
366
|
-
// The safety contract is a property of the route: resolver options can
|
|
367
|
-
// never reach the sandbox/tool policy (RESOLVER_PROTECTED_OPTION_KEYS)
|
|
368
|
-
// and effectivePiSandboxPolicy reads only protected fields, so every
|
|
369
|
-
// retry of one entry derives an identical contract. Record it once so
|
|
370
|
-
// the bounded safety telemetry stays one record per chain entry.
|
|
371
|
-
if (retryIndex === 0) {
|
|
372
|
-
const safetyRecord = routeSafetyRecord(i, entry, safetyContract, "attempted");
|
|
373
|
-
routeSafetyHistory.push(safetyRecord);
|
|
374
|
-
emit(callOptions, { type: "provider_route_safety", ...safetyRecord });
|
|
375
|
-
}
|
|
376
|
-
|
|
377
286
|
// A same-model retry is not a failover: only the first attempt of a new
|
|
378
287
|
// route announces a transition.
|
|
379
288
|
if (retryIndex === 0 && failoverHistory.length > 0) {
|
|
@@ -411,6 +320,10 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
411
320
|
}
|
|
412
321
|
|
|
413
322
|
result = normalizeProviderAuthFailure(result);
|
|
323
|
+
if (!sessionEligibleAttempt) {
|
|
324
|
+
const { providerSessionRecovery: _receipt, ...unownedResult } = result;
|
|
325
|
+
result = unownedResult;
|
|
326
|
+
}
|
|
414
327
|
|
|
415
328
|
const retryability = retryableProviderFailureInfo({
|
|
416
329
|
errorText: result.error || "",
|
|
@@ -433,7 +346,10 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
433
346
|
model: modelKey(entry.model),
|
|
434
347
|
});
|
|
435
348
|
}
|
|
436
|
-
|
|
349
|
+
// Pi may report the attribution id as its session id even on a
|
|
350
|
+
// stateless call. Withhold that resumable id so host history cannot
|
|
351
|
+
// synchronize an untouched or failed primary transcript.
|
|
352
|
+
return { ...result, ...(sessionEligibleAttempt ? {} : { providerSessionId: undefined }), failoverHistory };
|
|
437
353
|
}
|
|
438
354
|
|
|
439
355
|
failoverHistory.push({
|
|
@@ -442,8 +358,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
442
358
|
requestId: retryability.requestId,
|
|
443
359
|
retryableSubkind: retryability.subkind,
|
|
444
360
|
...(retryIndex > 0 ? { retryIndex } : {}),
|
|
445
|
-
routeSafety,
|
|
446
|
-
safetyContract,
|
|
447
361
|
});
|
|
448
362
|
if (result.failureKind === "skipped_capability_mismatch") {
|
|
449
363
|
lastRouteSkip = result;
|
|
@@ -494,16 +408,16 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
494
408
|
reason: retryability.subkind || result.failureKind || null,
|
|
495
409
|
});
|
|
496
410
|
if (callOptions.abortSignal?.aborted) {
|
|
497
|
-
return { ...result, cancelled: true, failoverHistory
|
|
411
|
+
return { ...result, cancelled: true, failoverHistory };
|
|
498
412
|
}
|
|
499
413
|
await delay(backoffMs, callOptions.abortSignal);
|
|
500
414
|
if (callOptions.abortSignal?.aborted) {
|
|
501
|
-
return { ...result, cancelled: true, failoverHistory
|
|
415
|
+
return { ...result, cancelled: true, failoverHistory };
|
|
502
416
|
}
|
|
503
417
|
}
|
|
504
418
|
|
|
505
419
|
if (terminalResult !== null) {
|
|
506
|
-
return { ...terminalResult, failoverHistory
|
|
420
|
+
return { ...terminalResult, failoverHistory };
|
|
507
421
|
}
|
|
508
422
|
// Every other inner break falls through to the next chain entry.
|
|
509
423
|
}
|
|
@@ -520,7 +434,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
520
434
|
...exhaustedResult,
|
|
521
435
|
failureKind: lastResult ? "provider_unavailable_exhausted" : exhaustedResult.failureKind,
|
|
522
436
|
failoverHistory,
|
|
523
|
-
routeSafetyHistory,
|
|
524
437
|
};
|
|
525
438
|
} finally {
|
|
526
439
|
await liveInputHub?.flush();
|
|
@@ -529,65 +442,42 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
|
|
|
529
442
|
chain: () => entries.slice(),
|
|
530
443
|
configureTools(next = {}) {
|
|
531
444
|
configuredTools = { ...(configuredTools || {}), ...next };
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
entries.forEach((entry, index) => {
|
|
537
|
-
const runtime = routeRuntimes.get(routeRuntimeKey(entry, index));
|
|
538
|
-
runtime?.configureTools?.(projectPerRouteNativeToolOptions(entry, next));
|
|
539
|
-
});
|
|
540
|
-
// `inner` is deliberately not configured in per-route-native mode: all
|
|
541
|
-
// attempts use their isolated route runtime, and applying one route's
|
|
542
|
-
// policy to this shared standby would defeat that isolation.
|
|
445
|
+
inner.configureTools?.(next);
|
|
446
|
+
},
|
|
447
|
+
async recoverSession(receipt, context) {
|
|
448
|
+
return await inner.recoverSession?.(receipt, context) === true;
|
|
543
449
|
},
|
|
544
450
|
async syncSession(providerSessionId) {
|
|
545
|
-
|
|
546
|
-
for (const runtime of allRuntimes(inner, routeRuntimes)) {
|
|
547
|
-
synced = Boolean(await runtime.syncSession?.(providerSessionId)) || synced;
|
|
548
|
-
}
|
|
549
|
-
return synced;
|
|
451
|
+
return Boolean(await inner.syncSession?.(providerSessionId));
|
|
550
452
|
},
|
|
551
453
|
async refreshSession(providerSessionId) {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
throw new Error("A routed runtime cannot guarantee a cold provider-session reopen");
|
|
555
|
-
}
|
|
556
|
-
await runtime.refreshSession(providerSessionId);
|
|
454
|
+
if (typeof inner.refreshSession !== "function") {
|
|
455
|
+
throw new Error("A routed runtime cannot guarantee a cold provider-session reopen");
|
|
557
456
|
}
|
|
457
|
+
await inner.refreshSession(providerSessionId);
|
|
558
458
|
},
|
|
559
459
|
async retireDurableSession(providerSessionId, sessionsRoot) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
throw new Error("A routed runtime cannot retire durable provider-session state");
|
|
563
|
-
}
|
|
564
|
-
await runtime.retireDurableSession(providerSessionId, sessionsRoot);
|
|
460
|
+
if (typeof inner.retireDurableSession !== "function") {
|
|
461
|
+
throw new Error("A routed runtime cannot retire durable provider-session state");
|
|
565
462
|
}
|
|
463
|
+
await inner.retireDurableSession(providerSessionId, sessionsRoot);
|
|
566
464
|
},
|
|
567
465
|
async disposeSession(providerSessionId) {
|
|
568
|
-
|
|
569
|
-
for (const runtime of allRuntimes(inner, routeRuntimes)) {
|
|
570
|
-
disposed = Boolean(await runtime.disposeSession?.(providerSessionId)) || disposed;
|
|
571
|
-
}
|
|
572
|
-
return disposed;
|
|
466
|
+
return Boolean(await inner.disposeSession?.(providerSessionId));
|
|
573
467
|
},
|
|
574
468
|
async invalidateSession(providerSessionId) {
|
|
575
|
-
|
|
576
|
-
for (const runtime of allRuntimes(inner, routeRuntimes)) {
|
|
577
|
-
invalidated = Boolean(await runtime.invalidateSession?.(providerSessionId)) || invalidated;
|
|
578
|
-
}
|
|
579
|
-
return invalidated;
|
|
469
|
+
return Boolean(await inner.invalidateSession?.(providerSessionId));
|
|
580
470
|
},
|
|
581
471
|
async disposeAllSessions() {
|
|
582
|
-
await
|
|
472
|
+
await inner.disposeAllSessions?.();
|
|
583
473
|
},
|
|
584
474
|
};
|
|
585
475
|
}
|
|
586
476
|
|
|
587
477
|
/**
|
|
588
478
|
* @param {ReadonlyArray<*>} chain ReadonlyArray<RuntimeModelRef|RouterChainEntryInput>, loosened
|
|
589
|
-
* here because distinguishing the two shapes is a runtime duck-type check
|
|
590
|
-
*
|
|
479
|
+
* here because distinguishing the two shapes is a runtime duck-type check,
|
|
480
|
+
* not something a union type narrows cleanly.
|
|
591
481
|
* @returns {Array<RouterChainEntry>}
|
|
592
482
|
*/
|
|
593
483
|
function normaliseChain(chain) {
|
|
@@ -595,14 +485,13 @@ function normaliseChain(chain) {
|
|
|
595
485
|
return /** @type {Array<RouterChainEntry>} */ (chain
|
|
596
486
|
.map((entry) => {
|
|
597
487
|
if (!entry) return null;
|
|
598
|
-
if (entry.
|
|
599
|
-
// ModelRef shorthand: {
|
|
600
|
-
return { model: entry,
|
|
488
|
+
if (typeof entry.provider === "string" && typeof entry.model === "string") {
|
|
489
|
+
// ModelRef shorthand: { provider, model, reference }
|
|
490
|
+
return { model: entry, effort: undefined, requires: null, attempts: 1 };
|
|
601
491
|
}
|
|
602
492
|
if (entry.model) {
|
|
603
493
|
return {
|
|
604
494
|
model: entry.model,
|
|
605
|
-
executionMode: typeof entry.executionMode === "string" ? entry.executionMode : null,
|
|
606
495
|
effort: normalizeChainEffort(entry.effort),
|
|
607
496
|
requires: entry.requires && typeof entry.requires === "object" ? entry.requires : null,
|
|
608
497
|
attempts: normalizeChainAttempts(entry.attempts),
|
|
@@ -692,87 +581,7 @@ function assertUniqueEntries(entries) {
|
|
|
692
581
|
|
|
693
582
|
/** @param {RuntimeModelRef} model */
|
|
694
583
|
function modelKey(model) {
|
|
695
|
-
|
|
696
|
-
return `${model.sdk}:${provider}${model.model}`;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* A small fixed vocabulary keeps safety telemetry bounded and prevents route
|
|
701
|
-
* credentials/options from accidentally entering events or persisted results.
|
|
702
|
-
* @param {"uniform"|"per-route-native"} mode
|
|
703
|
-
* @param {RouterChainEntry} entry
|
|
704
|
-
* @param {Object<string, *>|undefined} piSandboxPolicy
|
|
705
|
-
* @returns {import('../types.js').RuntimeRouteSafetyContract}
|
|
706
|
-
*/
|
|
707
|
-
function routeSafetyContract(mode, entry, piSandboxPolicy) {
|
|
708
|
-
if (mode === "uniform") {
|
|
709
|
-
return {
|
|
710
|
-
mode,
|
|
711
|
-
sandbox: "mono-agent-monotonic",
|
|
712
|
-
tools: "mono-agent-monotonic",
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
switch (entry.model.sdk) {
|
|
716
|
-
case "pi":
|
|
717
|
-
return {
|
|
718
|
-
mode,
|
|
719
|
-
sandbox: piSandboxContract(piSandboxPolicy),
|
|
720
|
-
tools: "mono-agent-policy",
|
|
721
|
-
};
|
|
722
|
-
case "claude":
|
|
723
|
-
return { mode, sandbox: "provider-native", tools: "provider-representable" };
|
|
724
|
-
case "codex":
|
|
725
|
-
return { mode, sandbox: "codex-native", tools: "exact-allow-all" };
|
|
726
|
-
case "opencode":
|
|
727
|
-
return { mode, sandbox: "provider-native", tools: "exact-allow-all" };
|
|
728
|
-
case "acp":
|
|
729
|
-
return { mode, sandbox: "provider-native", tools: "exact-allow-all" };
|
|
730
|
-
default:
|
|
731
|
-
return { mode, sandbox: "unsupported", tools: "unsupported" };
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* Describe the Pi sandbox posture without claiming that SRT is enforced when
|
|
737
|
-
* the effective policy explicitly permits an unavailable engine to fall back
|
|
738
|
-
* to an unsandboxed host process. Both fields are required because the runtime
|
|
739
|
-
* adapter treats an unsafe fallback without its explicit opt-in as fail-closed.
|
|
740
|
-
*
|
|
741
|
-
* @param {Object<string, *>|undefined} policy
|
|
742
|
-
* @returns {import('../types.js').RuntimeRouteSandboxContract}
|
|
743
|
-
*/
|
|
744
|
-
function piSandboxContract(policy) {
|
|
745
|
-
if (policy === undefined) return "disabled";
|
|
746
|
-
if (policy.fallback === "unsafe-host-process" && policy.unsafeAllowHostProcess === true) {
|
|
747
|
-
return "mono-agent-srt-unsafe-host-fallback";
|
|
748
|
-
}
|
|
749
|
-
return "mono-agent-srt";
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* Describe the policy Pi tools actually receive after host/configure/run
|
|
754
|
-
* precedence. A request-scoped `off` policy cannot weaken an active host
|
|
755
|
-
* policy, while an active request policy can tighten an absent/off host.
|
|
756
|
-
* `configureTools({ sandboxPolicy: undefined })` explicitly clears the host
|
|
757
|
-
* tool-context policy and must therefore be distinguished from an omitted key.
|
|
758
|
-
*
|
|
759
|
-
* @param {import('../types.js').AgentRuntimeToolOptions} toolOptions
|
|
760
|
-
* @param {Object<string, *>} runOptions
|
|
761
|
-
* @returns {Object<string, *>|undefined}
|
|
762
|
-
*/
|
|
763
|
-
function effectivePiSandboxPolicy(toolOptions, runOptions) {
|
|
764
|
-
// Match the Pi turn runner's implementation precedence: a run-scoped
|
|
765
|
-
// RuntimeSandbox wins, followed by configureTools/host, then the kernel's
|
|
766
|
-
// fail-closed passthrough. Delegating the merge is essential here: the real
|
|
767
|
-
// adapter makes fail-closed dominate unsafe-host-process, so inspecting only
|
|
768
|
-
// the first non-off policy would produce false safety telemetry.
|
|
769
|
-
const sandbox = runOptions.sandbox
|
|
770
|
-
?? toolOptions.sandbox
|
|
771
|
-
?? passthroughSandbox;
|
|
772
|
-
const policy = sandbox.mergePolicies(toolOptions.sandboxPolicy, runOptions.sandboxPolicy);
|
|
773
|
-
return policy && typeof policy === "object" && policy.mode !== "off"
|
|
774
|
-
? policy
|
|
775
|
-
: undefined;
|
|
584
|
+
return model.reference;
|
|
776
585
|
}
|
|
777
586
|
|
|
778
587
|
/**
|
|
@@ -801,70 +610,22 @@ function effectiveRouterToolOptions(host, configuredTools) {
|
|
|
801
610
|
return effective;
|
|
802
611
|
}
|
|
803
612
|
|
|
804
|
-
/**
|
|
805
|
-
* A protected-root policy is host-internal and intentionally has no public
|
|
806
|
-
* provider-native projection. Treat a present malformed value or an accessor
|
|
807
|
-
* failure as protected so untrusted option shapes cannot turn this gate into a
|
|
808
|
-
* fail-open boundary. Empty arrays preserve ordinary provider-native routing.
|
|
809
|
-
*
|
|
810
|
-
* @param {unknown} policy
|
|
811
|
-
* @returns {boolean}
|
|
812
|
-
*/
|
|
813
|
-
function sandboxPolicyHasProtectedRoots(policy) {
|
|
814
|
-
if (policy === null || typeof policy !== "object") return false;
|
|
815
|
-
try {
|
|
816
|
-
if (!("protectedRoots" in policy)) return false;
|
|
817
|
-
const protectedRoots = /** @type {{protectedRoots?: unknown}} */ (policy).protectedRoots;
|
|
818
|
-
if (protectedRoots === undefined) return false;
|
|
819
|
-
return !Array.isArray(protectedRoots) || protectedRoots.length > 0;
|
|
820
|
-
} catch {
|
|
821
|
-
return true;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* @param {import('../types.js').AgentRuntimeToolOptions} toolOptions
|
|
827
|
-
* @param {Object<string, *>} runOptions
|
|
828
|
-
* @returns {boolean}
|
|
829
|
-
*/
|
|
830
|
-
function attemptCarriesProtectedRoots(toolOptions, runOptions) {
|
|
831
|
-
return sandboxPolicyHasProtectedRoots(toolOptions.sandboxPolicy)
|
|
832
|
-
|| sandboxPolicyHasProtectedRoots(runOptions.sandboxPolicy);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
613
|
/**
|
|
836
614
|
* A resolver-supplied Pi runtime is allowed to own credentials/provider
|
|
837
|
-
* lifecycle, never the
|
|
838
|
-
* ToolContext before every execution so a blank
|
|
839
|
-
*
|
|
615
|
+
* lifecycle, never the router-owned tool context. Replace its complete mutable
|
|
616
|
+
* ToolContext before every execution so a blank or stale runtime cannot diverge
|
|
617
|
+
* from the router's host/configured/run policy or telemetry.
|
|
840
618
|
*
|
|
841
619
|
* @param {AgentRuntimeInstance} runtime
|
|
842
620
|
* @param {import('../types.js').AgentRuntimeToolOptions} toolOptions
|
|
843
621
|
*/
|
|
844
622
|
function projectPiRuntimeToolContext(runtime, toolOptions) {
|
|
845
623
|
if (typeof runtime.configureTools !== "function") {
|
|
846
|
-
throw new Error("
|
|
624
|
+
throw new Error("route attempt runtime must expose configureTools() for tool-context projection");
|
|
847
625
|
}
|
|
848
626
|
runtime.configureTools(toolOptions);
|
|
849
627
|
}
|
|
850
628
|
|
|
851
|
-
/**
|
|
852
|
-
* @param {number} attemptIndex
|
|
853
|
-
* @param {RouterChainEntry} entry
|
|
854
|
-
* @param {import('../types.js').RuntimeRouteSafetyContract} contract
|
|
855
|
-
* @param {string} status
|
|
856
|
-
* @returns {{attemptIndex: number, model: RuntimeModelRef, routeSafety: import('../types.js').RuntimeRouteSafetyMode, safetyContract: import('../types.js').RuntimeRouteSafetyContract, status: string}}
|
|
857
|
-
*/
|
|
858
|
-
function routeSafetyRecord(attemptIndex, entry, contract, status) {
|
|
859
|
-
return {
|
|
860
|
-
attemptIndex,
|
|
861
|
-
model: entry.model,
|
|
862
|
-
routeSafety: contract.mode,
|
|
863
|
-
safetyContract: contract,
|
|
864
|
-
status,
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
|
|
868
629
|
/** @param {RouterAttemptResolution|undefined} value */
|
|
869
630
|
function normalizeAttemptResolution(value) {
|
|
870
631
|
if (value === undefined) return undefined;
|
|
@@ -947,89 +708,6 @@ function withoutAttemptScopedOptions(options) {
|
|
|
947
708
|
return projected;
|
|
948
709
|
}
|
|
949
710
|
|
|
950
|
-
/**
|
|
951
|
-
* Explicit mixed-route projection. Capability-bearing inputs (MCP, skills,
|
|
952
|
-
* schema, live input, native subagents) are never removed here; the capability
|
|
953
|
-
* gate either proves support or skips the route before execution.
|
|
954
|
-
* @param {RouterChainEntry} entry
|
|
955
|
-
* @param {Object<string, *>} options
|
|
956
|
-
*/
|
|
957
|
-
function projectPerRouteNativeOptions(entry, options) {
|
|
958
|
-
const projected = { ...options };
|
|
959
|
-
switch (entry.model.sdk) {
|
|
960
|
-
case "pi":
|
|
961
|
-
return projected;
|
|
962
|
-
case "claude":
|
|
963
|
-
delete projected.sandboxPolicy;
|
|
964
|
-
delete projected.sandboxEngine;
|
|
965
|
-
return projected;
|
|
966
|
-
case "codex":
|
|
967
|
-
case "opencode":
|
|
968
|
-
case "acp":
|
|
969
|
-
delete projected.sandboxPolicy;
|
|
970
|
-
delete projected.sandboxEngine;
|
|
971
|
-
projected.allowedTools = ["*"];
|
|
972
|
-
projected.disallowedTools = [];
|
|
973
|
-
return projected;
|
|
974
|
-
default:
|
|
975
|
-
throw new Error(`per-route-native safety has no contract for sdk ${entry.model.sdk}`);
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
/**
|
|
980
|
-
* @param {string} key
|
|
981
|
-
* @param {RouterChainEntry} entry
|
|
982
|
-
* @param {AgentRuntimeHostOptions} host
|
|
983
|
-
* @param {Map<string, AgentRuntimeInstance>} runtimes
|
|
984
|
-
* @param {import('../types.js').AgentRuntimeToolOptions|undefined} configuredTools
|
|
985
|
-
*/
|
|
986
|
-
function createRouteRuntime(key, entry, host, runtimes, configuredTools) {
|
|
987
|
-
const runtime = createRuntime(projectPerRouteNativeHost(entry, host));
|
|
988
|
-
if (configuredTools !== undefined) {
|
|
989
|
-
runtime.configureTools?.(projectPerRouteNativeToolOptions(entry, configuredTools));
|
|
990
|
-
}
|
|
991
|
-
runtimes.set(key, runtime);
|
|
992
|
-
return runtime;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
/**
|
|
996
|
-
* Provider-native routes retain the injected sandbox implementation seam, but
|
|
997
|
-
* must never inherit mono-agent policy data or a concrete srt engine. Assigning
|
|
998
|
-
* explicit `undefined` values (rather than deleting the keys) also clears a
|
|
999
|
-
* previously configured runtime when configureTools is called again.
|
|
1000
|
-
* @param {RouterChainEntry} entry
|
|
1001
|
-
* @param {import('../types.js').AgentRuntimeToolOptions|undefined} configuredTools
|
|
1002
|
-
* @returns {import('../types.js').AgentRuntimeToolOptions}
|
|
1003
|
-
*/
|
|
1004
|
-
function projectPerRouteNativeToolOptions(entry, configuredTools = {}) {
|
|
1005
|
-
const projected = { ...configuredTools };
|
|
1006
|
-
if (entry.model.sdk !== "pi") {
|
|
1007
|
-
projected.sandboxPolicy = undefined;
|
|
1008
|
-
projected.sandboxEngine = undefined;
|
|
1009
|
-
}
|
|
1010
|
-
return projected;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
/**
|
|
1014
|
-
* Host-level policy must be isolated alongside request-level policy. The
|
|
1015
|
-
* sandbox implementation itself remains available; only enforcing policy data
|
|
1016
|
-
* and its route-specific engine are removed for provider-native bridges.
|
|
1017
|
-
* @param {RouterChainEntry} entry
|
|
1018
|
-
* @param {AgentRuntimeHostOptions} host
|
|
1019
|
-
*/
|
|
1020
|
-
function projectPerRouteNativeHost(entry, host) {
|
|
1021
|
-
if (entry.model.sdk === "pi") return host;
|
|
1022
|
-
const projected = { ...host };
|
|
1023
|
-
delete projected.sandboxPolicy;
|
|
1024
|
-
delete projected.sandboxEngine;
|
|
1025
|
-
return projected;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
/** @param {RouterChainEntry} entry @param {number} index */
|
|
1029
|
-
function routeRuntimeKey(entry, index) {
|
|
1030
|
-
return `${index}:${modelKey(entry.model)}:${entry.executionMode ?? "default"}`;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
711
|
/** @param {AgentRuntimeInstance} runtime */
|
|
1034
712
|
function assertRuntimeLike(runtime) {
|
|
1035
713
|
if (runtime === null || typeof runtime !== "object" || typeof runtime.run !== "function") {
|
|
@@ -1047,7 +725,7 @@ function applyEntryEffort(options, effort) {
|
|
|
1047
725
|
}
|
|
1048
726
|
|
|
1049
727
|
/** @param {unknown} error @returns {RuntimeResult} */
|
|
1050
|
-
function
|
|
728
|
+
function attemptResolutionFailureResult(error) {
|
|
1051
729
|
// Host resolvers may handle credentials. Never echo their exception text
|
|
1052
730
|
// into persisted results or route telemetry. ResolverProtectedOptionError is
|
|
1053
731
|
// constructed only from a repository-owned allowlist key, so it is safe and
|
|
@@ -1056,8 +734,8 @@ function safetyUnavailableResult(error) {
|
|
|
1056
734
|
text: null,
|
|
1057
735
|
error: error instanceof ResolverProtectedOptionError
|
|
1058
736
|
? error.message
|
|
1059
|
-
: "The route
|
|
1060
|
-
failureKind: "
|
|
737
|
+
: "The route attempt could not be resolved before execution.",
|
|
738
|
+
failureKind: "provider_unavailable",
|
|
1061
739
|
events: [],
|
|
1062
740
|
cancelled: false,
|
|
1063
741
|
usage: {},
|
|
@@ -1070,14 +748,6 @@ function isMidTurnSafetyFailure(failureKind) {
|
|
|
1070
748
|
&& (failureKind.startsWith("sandbox_") || failureKind.startsWith("safety_"));
|
|
1071
749
|
}
|
|
1072
750
|
|
|
1073
|
-
/**
|
|
1074
|
-
* @param {AgentRuntimeInstance} inner
|
|
1075
|
-
* @param {Map<string, AgentRuntimeInstance>} routeRuntimes
|
|
1076
|
-
*/
|
|
1077
|
-
function allRuntimes(inner, routeRuntimes) {
|
|
1078
|
-
return [...new Set([inner, ...routeRuntimes.values()])];
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
751
|
/**
|
|
1082
752
|
* Merge progress into one bounded snapshot so prompts never accumulate nested
|
|
1083
753
|
* resume blocks across a long provider chain.
|
|
@@ -1166,17 +836,6 @@ function entrySatisfiesRequirements(entry, options) {
|
|
|
1166
836
|
if (options.toolEnvironment !== undefined) {
|
|
1167
837
|
effectiveRequires.supports_request_tool_environment = true;
|
|
1168
838
|
}
|
|
1169
|
-
if (options.fastMode === true) {
|
|
1170
|
-
effectiveRequires.supports_fast_mode = true;
|
|
1171
|
-
}
|
|
1172
|
-
// Native teammates likewise require a capable route; Pi/OpenCode must skip
|
|
1173
|
-
// rather than silently dropping them.
|
|
1174
|
-
if (
|
|
1175
|
-
Array.isArray(options.nativeSubagents?.teammates)
|
|
1176
|
-
&& options.nativeSubagents.teammates.length > 0
|
|
1177
|
-
) {
|
|
1178
|
-
effectiveRequires.supports_native_subagents = true;
|
|
1179
|
-
}
|
|
1180
839
|
if (Object.keys(effectiveRequires).length === 0) return true;
|
|
1181
840
|
let caps;
|
|
1182
841
|
try {
|
|
@@ -1191,10 +850,10 @@ function entrySatisfiesRequirements(entry, options) {
|
|
|
1191
850
|
}
|
|
1192
851
|
|
|
1193
852
|
/**
|
|
1194
|
-
* Session identifiers belong to the
|
|
1195
|
-
*
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
853
|
+
* Session identifiers belong to the route that created them. Never forward one
|
|
854
|
+
* into a route whose capabilities declare no resume support, including when
|
|
855
|
+
* that route is reached through fallback. Unknown model references retain the
|
|
856
|
+
* existing fail-later behavior.
|
|
1198
857
|
* @param {RouterChainEntry} entry
|
|
1199
858
|
* @returns {boolean}
|
|
1200
859
|
*/
|