@mono-agent/agent-runtime 0.18.1 → 0.18.3

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.
@@ -323,6 +323,8 @@ export function buildDiagnostics(params) {
323
323
  lastToolName,
324
324
  structuredRetry,
325
325
  contextCompactionDiagnostics,
326
+ transportErrorCode,
327
+ transportErrorSource,
326
328
  } = params;
327
329
  return {
328
330
  provider_session_id: providerSessionId,
@@ -335,6 +337,15 @@ export function buildDiagnostics(params) {
335
337
  pi_max_retries: maxRetries,
336
338
  pi_transport_requested: piTransport,
337
339
  ...(lastToolName ? { last_tool_name: lastToolName } : {}),
340
+ // Present only when an opaque transport failure was resolved to a real
341
+ // reason. `provider_transport_error_source` says whether that came from the
342
+ // error's own cause chain (exact) or from process-wide correlation.
343
+ ...(transportErrorCode
344
+ ? {
345
+ provider_transport_error_code: transportErrorCode,
346
+ provider_transport_error_source: transportErrorSource,
347
+ }
348
+ : {}),
338
349
  ...structuredOutputRetryDiagnostics(
339
350
  structuredRetry.attempts,
340
351
  structuredRetry.reason,
@@ -44,6 +44,7 @@ import {
44
44
  } from "./pi-messages.js";
45
45
  import { emitCaptured } from "./pi-events.js";
46
46
  import { normalizePiErrorMessage } from "./pi-errors.js";
47
+ import { annotateProviderErrorMessage, installTransportErrorProbe } from "./transport-errors.js";
47
48
  import {
48
49
  runStructuredOutputFinalizationRetry,
49
50
  shouldRetryStructuredOutputFinalization,
@@ -252,6 +253,10 @@ function splitUserContent(content) {
252
253
  }
253
254
 
254
255
  export async function generatePiNativeResponse(systemPrompt, options = {}) {
256
+ // Idempotent; arms the undici diagnostics-channel probe so a transport
257
+ // failure during this run can be resolved back to a real reason even after
258
+ // an intermediate layer flattens the Error to its message.
259
+ installTransportErrorProbe();
255
260
  const resolved = options.model;
256
261
  const start = Date.now();
257
262
  const events = [];
@@ -682,7 +687,15 @@ export async function generatePiNativeResponse(systemPrompt, options = {}) {
682
687
  : (stopReason === "error" || stopReason === "aborted"
683
688
  ? lastAssistant?.errorMessage || runError?.message || "Pi agent aborted before final output"
684
689
  : (runError ? runError.message || String(runError) : null));
685
- const errorMessage = normalizePiErrorMessage(rawErrorMessage);
690
+ // pi-agent-core stores `error.message` on the failure assistant message and
691
+ // discards the cause, so `runError` is usually the only object still
692
+ // carrying one. When neither has a cause, fall back to the correlated
693
+ // transport probe rather than surfacing a bare "terminated".
694
+ const annotatedError = annotateProviderErrorMessage(
695
+ normalizePiErrorMessage(rawErrorMessage),
696
+ runError,
697
+ );
698
+ const errorMessage = annotatedError.message;
686
699
 
687
700
  const structuredRetry = {
688
701
  attempts: structuredOutputFinalizationRetryAttempts,
@@ -702,6 +715,8 @@ export async function generatePiNativeResponse(systemPrompt, options = {}) {
702
715
  lastToolName: runState.lastToolName,
703
716
  structuredRetry,
704
717
  contextCompactionDiagnostics: runState.compaction.diagnostics,
718
+ transportErrorCode: annotatedError.causeCode,
719
+ transportErrorSource: annotatedError.causeSource,
705
720
  });
706
721
  const errorDetails = buildErrorDetails({
707
722
  errorMessage,
@@ -812,7 +827,12 @@ export async function generatePiNativeResponse(systemPrompt, options = {}) {
812
827
  // leaf for host/runtime-side throws that landed after the harness already
813
828
  // mutated the live session (guards preserved in cleanupSessionOnThrow).
814
829
  await cleanupSessionOnThrow(runState, { durableRepo });
815
- const errorMessage = normalizePiErrorMessage(err?.message || String(err));
830
+ // The throw path still holds the original Error, so its cause chain is the
831
+ // authoritative source here — no correlation guesswork needed.
832
+ const errorMessage = annotateProviderErrorMessage(
833
+ normalizePiErrorMessage(err?.message || String(err)),
834
+ err,
835
+ ).message;
816
836
  const isRetryable = retryableProviderFailureInfo({
817
837
  errorText: errorMessage,
818
838
  failureKind: "provider_unavailable",
@@ -0,0 +1,154 @@
1
+ // Recover the real reason behind opaque provider transport failures.
2
+ //
3
+ // Node's fetch (undici) reports a cut response body as `TypeError: terminated`
4
+ // with the actual reason — UND_ERR_BODY_TIMEOUT, ECONNRESET, "other side
5
+ // closed" — only on `error.cause`. Several layers between the socket and us
6
+ // flatten errors to `error.message`, most notably pi-agent-core's
7
+ // `handleRunFailure`, which stores `error.message` and drops the cause before
8
+ // any mono-agent code sees the Error object. The result is a run that fails
9
+ // with the single uninformative word "terminated".
10
+ //
11
+ // Two recovery paths, in order of trustworthiness:
12
+ //
13
+ // 1. `describeErrorCause` walks the cause chain of an Error we still hold.
14
+ // Exact, but only available where the original object survives.
15
+ // 2. `recentTransportErrorCode` reads a bounded ring of `undici:request:error`
16
+ // diagnostics-channel events. This sees the error before any library
17
+ // flattens it, at the cost of attribution: the channel is process-wide, so
18
+ // with concurrent requests in flight we cannot prove which run a given
19
+ // socket error belongs to. Correlation is reported as such, and a window
20
+ // containing conflicting codes reports ambiguity instead of guessing.
21
+
22
+ import diagnosticsChannel from "node:diagnostics_channel";
23
+
24
+ // Messages that carry no diagnostic content on their own. Annotating only these
25
+ // keeps already-descriptive provider errors untouched.
26
+ const OPAQUE_ERROR_RE = /^(?:terminated|fetch failed|connection error\.?|network error|socket hang up|premature close|other side closed)$/i;
27
+
28
+ const MAX_RECORDED = 32;
29
+ // A stream cut is observed on the socket within milliseconds of the rejection
30
+ // surfacing. Anything older is a different request's failure.
31
+ const DEFAULT_CORRELATION_WINDOW_MS = 5_000;
32
+ const MAX_CAUSE_DEPTH = 5;
33
+
34
+ /** @type {{code: string, at: number, origin: string|null}[]} */
35
+ const recorded = [];
36
+ let installed = false;
37
+
38
+ function codeFromError(error) {
39
+ if (!error || typeof error !== "object") return null;
40
+ const code = error.code ?? error.errno;
41
+ if (typeof code === "string" && code.trim()) return code.trim();
42
+ // Undici's timeout errors expose a stable `name` even when `code` is absent.
43
+ const name = typeof error.name === "string" ? error.name.trim() : "";
44
+ if (name && name !== "Error" && name !== "TypeError") return name;
45
+ return null;
46
+ }
47
+
48
+ /**
49
+ * Walk an error's `cause` chain and return the first transport-level code found.
50
+ * Returns null when the chain carries no code (i.e. nothing worth appending).
51
+ * @param {unknown} error
52
+ * @returns {string|null}
53
+ */
54
+ export function describeErrorCause(error) {
55
+ /** @type {any} */
56
+ let current = error;
57
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && current; depth += 1) {
58
+ // Skip the outermost error: its code (if any) is what the caller already
59
+ // has. We want the reason underneath it.
60
+ if (depth > 0) {
61
+ const code = codeFromError(current);
62
+ if (code) return code;
63
+ }
64
+ current = current?.cause;
65
+ }
66
+ return null;
67
+ }
68
+
69
+ /**
70
+ * Subscribe to undici's request-error channel. Idempotent and safe to call on
71
+ * every run; a Node build without the channel simply records nothing.
72
+ */
73
+ export function installTransportErrorProbe() {
74
+ if (installed) return;
75
+ installed = true;
76
+ try {
77
+ diagnosticsChannel.subscribe("undici:request:error", (/** @type {any} */ message) => {
78
+ const error = message?.error;
79
+ const code = codeFromError(error) || describeErrorCause(error);
80
+ if (!code) return;
81
+ /** @type {string|null} */
82
+ let origin = null;
83
+ try {
84
+ const raw = message?.request?.origin;
85
+ origin = typeof raw === "string" ? raw : (raw?.origin ?? null);
86
+ } catch {
87
+ origin = null;
88
+ }
89
+ recorded.push({ code, at: Date.now(), origin });
90
+ if (recorded.length > MAX_RECORDED) recorded.splice(0, recorded.length - MAX_RECORDED);
91
+ });
92
+ } catch {
93
+ // Channel unavailable on this runtime: fall back to cause-chain walking only.
94
+ installed = false;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Most recent transport error code seen within the correlation window.
100
+ * `ambiguous` is true when the window holds more than one distinct code, in
101
+ * which case attribution to a specific run would be a guess.
102
+ * @param {{withinMs?: number, now?: number}} [opts]
103
+ * @returns {{code: string, ambiguous: boolean}|null}
104
+ */
105
+ export function recentTransportErrorCode(opts = {}) {
106
+ const withinMs = Number.isFinite(Number(opts.withinMs))
107
+ ? Number(opts.withinMs)
108
+ : DEFAULT_CORRELATION_WINDOW_MS;
109
+ const now = Number.isFinite(Number(opts.now)) ? Number(opts.now) : Date.now();
110
+ const fresh = recorded.filter((entry) => now - entry.at <= withinMs);
111
+ if (fresh.length === 0) return null;
112
+ const distinct = new Set(fresh.map((entry) => entry.code));
113
+ return { code: fresh[fresh.length - 1].code, ambiguous: distinct.size > 1 };
114
+ }
115
+
116
+ /**
117
+ * Append the underlying transport reason to an otherwise contentless provider
118
+ * error. Descriptive messages, and messages that already name their cause, are
119
+ * returned unchanged.
120
+ *
121
+ * @param {string|null} message normalized provider error text
122
+ * @param {unknown} [error] the original Error, when it survived
123
+ * @returns {{message: string|null, causeCode: string|null, causeSource: "cause_chain"|"transport_probe"|null}}
124
+ */
125
+ export function annotateProviderErrorMessage(message, error) {
126
+ const text = String(message || "").trim();
127
+ if (!text) return { message: message ?? null, causeCode: null, causeSource: null };
128
+ if (!OPAQUE_ERROR_RE.test(text)) return { message: text, causeCode: null, causeSource: null };
129
+
130
+ const fromChain = describeErrorCause(error);
131
+ if (fromChain) {
132
+ return { message: `${text} (${fromChain})`, causeCode: fromChain, causeSource: "cause_chain" };
133
+ }
134
+
135
+ const correlated = recentTransportErrorCode();
136
+ if (correlated && !correlated.ambiguous) {
137
+ return {
138
+ message: `${text} (${correlated.code}, correlated)`,
139
+ causeCode: correlated.code,
140
+ causeSource: "transport_probe",
141
+ };
142
+ }
143
+ return { message: text, causeCode: null, causeSource: null };
144
+ }
145
+
146
+ /** Test seam: drop recorded transport errors. */
147
+ export function resetTransportErrorProbeForTests() {
148
+ recorded.length = 0;
149
+ }
150
+
151
+ /** Test seam: record a transport error without a real socket. */
152
+ export function recordTransportErrorForTests(code, at = Date.now()) {
153
+ recorded.push({ code, at, origin: null });
154
+ }
@@ -85,6 +85,9 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
85
85
  * Returned options are never copied into router telemetry.
86
86
  * @property {AgentRuntimeInstance} [runtime]
87
87
  * @property {Object<string, *>} [options]
88
+ * @property {{allowedTools?: ReadonlyArray<string>, disallowedTools?: ReadonlyArray<string>, permissionMode?: string}} [policyOptions]
89
+ * Provider-specific projection of the logical tool policy. This deliberately
90
+ * cannot replace any other protected request field.
88
91
  * @property {() => (void|Promise<void>)} [cleanup]
89
92
  */
90
93
 
@@ -243,6 +246,7 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
243
246
  attemptCleanup = resolution?.cleanup;
244
247
  if (resolveAttempt !== undefined) {
245
248
  callOptions = mergeAttemptOptions(callOptions, resolution?.options);
249
+ callOptions = mergeAttemptPolicyOptions(callOptions, resolution?.policyOptions);
246
250
  }
247
251
  if (routeSafety === "per-route-native") {
248
252
  callOptions = projectPerRouteNativeOptions(entry, callOptions);
@@ -804,6 +808,33 @@ function normalizeAttemptResolution(value) {
804
808
  if (value.cleanup !== undefined && typeof value.cleanup !== "function") {
805
809
  throw new Error("route attempt resolver cleanup must be a function");
806
810
  }
811
+ return {
812
+ ...value,
813
+ ...(value.policyOptions === undefined
814
+ ? {}
815
+ : { policyOptions: normalizeAttemptPolicyOptions(value.policyOptions) }),
816
+ };
817
+ }
818
+
819
+ const ATTEMPT_POLICY_OPTION_KEYS = new Set(["allowedTools", "disallowedTools", "permissionMode"]);
820
+
821
+ function normalizeAttemptPolicyOptions(value) {
822
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
823
+ throw new Error("route attempt resolver policyOptions must be an object");
824
+ }
825
+ for (const key of Object.keys(value)) {
826
+ if (!ATTEMPT_POLICY_OPTION_KEYS.has(key)) {
827
+ throw new Error(`route attempt resolver policyOptions cannot override ${key}`);
828
+ }
829
+ }
830
+ for (const key of ["allowedTools", "disallowedTools"]) {
831
+ if (value[key] !== undefined && !Array.isArray(value[key])) {
832
+ throw new Error(`route attempt resolver policyOptions.${key} must be an array or undefined`);
833
+ }
834
+ }
835
+ if (value.permissionMode !== undefined && typeof value.permissionMode !== "string") {
836
+ throw new Error("route attempt resolver policyOptions.permissionMode must be a string or undefined");
837
+ }
807
838
  return value;
808
839
  }
809
840
 
@@ -826,6 +857,17 @@ function mergeAttemptOptions(base, resolved) {
826
857
  return merged;
827
858
  }
828
859
 
860
+ function mergeAttemptPolicyOptions(base, policyOptions) {
861
+ if (policyOptions === undefined) return base;
862
+ const merged = { ...base };
863
+ for (const key of ATTEMPT_POLICY_OPTION_KEYS) {
864
+ if (!Object.hasOwn(policyOptions, key)) continue;
865
+ if (policyOptions[key] === undefined) delete merged[key];
866
+ else merged[key] = policyOptions[key];
867
+ }
868
+ return merged;
869
+ }
870
+
829
871
  /**
830
872
  * @param {Object<string, *>} options
831
873
  * @returns {Object<string, *>}
package/src/ai/types.js CHANGED
@@ -40,10 +40,80 @@
40
40
  */
41
41
 
42
42
  /**
43
- * @typedef {{type: string, [key: string]: *}} RuntimeEvent
44
- * Structured runtime/telemetry event. `type` is the only required field;
45
- * every event kind (tool_approval_pending, provider_failover_started,
46
- * context_compaction_applied, ...) adds its own extra fields.
43
+ * @typedef {Object} RuntimeNativeSubagentDefinition
44
+ * One caller-defined Claude native `Task` profile. Codex collaboration-agent
45
+ * definitions are owned by Codex and are not represented by this type.
46
+ * @property {string} name
47
+ * @property {string} [displayName]
48
+ * @property {string} [description]
49
+ * @property {string} [helperSystemPrompt]
50
+ * @property {string} [instructions]
51
+ * @property {ReadonlyArray<string>} [allowedTools]
52
+ * @property {ReadonlyArray<string>} [disallowedTools]
53
+ * @property {string | RuntimeModelRef} [modelRef]
54
+ * @property {RuntimeModelRef} [model]
55
+ * @property {string} [effort]
56
+ * @property {Object<string, Object>} [mcpServers]
57
+ */
58
+
59
+ /**
60
+ * @typedef {Object} RuntimeNativeSubagentsOptions
61
+ * Caller-defined native profiles are supported only by the Claude bridges.
62
+ * Codex owns its collaboration agents; use `codexLoadProjectDocs` when those
63
+ * agents should receive repository instructions.
64
+ * @property {"claude"} provider
65
+ * @property {ReadonlyArray<RuntimeNativeSubagentDefinition>} teammates
66
+ */
67
+
68
+ /**
69
+ * @typedef {Object} RuntimeSubagentIdentity
70
+ * Provider-neutral identity attached to every `subagent_activity` event.
71
+ * @property {string} id The canonical parent attachment key: the initiating
72
+ * parent tool-use id when the provider exposes it, or a stable synthetic key
73
+ * for an orphan lifecycle record. A provider-native task/thread id never replaces it.
74
+ * @property {string} [nativeId] Provider-native task or thread id, retained only
75
+ * as diagnostic/correlation metadata.
76
+ * @property {string} name Provider-neutral profile/agent name.
77
+ * @property {number} callIndex Provider call-order ordinal; consumers must not
78
+ * use it as an identity key.
79
+ * @property {string} [label] Short task label or description.
80
+ * @property {string} [agentPath] Provider-reported ancestry for a
81
+ * nested native agent. Informational only; `id` remains the attachment key.
82
+ * @property {number} [costUsd] Priced delegation cost, when the runtime can
83
+ * attribute it to this subagent.
84
+ */
85
+
86
+ /**
87
+ * @typedef {"agent_started"|"started"|"completed"|"message"|"agent_completed"} RuntimeSubagentActivityPhase
88
+ * `agent_started`/`agent_completed` bracket the delegation; `started`/`completed`
89
+ * bracket one child tool call; `message` carries optional child-only prose or
90
+ * thinking and must never be treated as parent answer text or a completed tool.
91
+ */
92
+
93
+ /**
94
+ * @typedef {Object} RuntimeSubagentActivityEvent
95
+ * One normalized native or in-process subagent activity event.
96
+ * @property {"subagent_activity"} type
97
+ * @property {RuntimeSubagentIdentity} subagent
98
+ * @property {RuntimeSubagentActivityPhase} phase
99
+ * @property {string} id Unique activity-row id, namespaced from the canonical
100
+ * `subagent.id` for lifecycle and tool rows.
101
+ * @property {string} [name]
102
+ * @property {*} [arguments]
103
+ * @property {*} [content]
104
+ * @property {"text"|"thinking"|"status"|"warning"|"error"} [kind] Present on a
105
+ * `message` phase when known.
106
+ * @property {"assistant"|"user"} [role] Present on a `message` phase when known.
107
+ * @property {boolean} [isError]
108
+ * @property {number} [executionMs]
109
+ * @property {number} [totalTokens]
110
+ */
111
+
112
+ /**
113
+ * @typedef {RuntimeSubagentActivityEvent | {type: string, [key: string]: *}} RuntimeEvent
114
+ * Structured runtime/telemetry event. Subagent activity uses the normalized
115
+ * shape above; every other event kind (tool_approval_pending,
116
+ * provider_failover_started, context_compaction, ...) adds its own fields.
47
117
  */
48
118
 
49
119
  /** @typedef {"uniform"|"per-route-native"} RuntimeRouteSafetyMode */
@@ -171,7 +241,20 @@
171
241
  * @property {"sequential"|"safe-parallel"} [piToolExecutionMode] Pi built-in tool scheduling mode. Safe parallelism is the default.
172
242
  * @property {"one-at-a-time"|"all"} [piToolParallelismMode] DEPRECATED. Compatibility alias mapped to piToolExecutionMode.
173
243
  * @property {Object} [settings] DEPRECATED. Legacy flat settings bag; consumed only as a per-group FALLBACK when the corresponding typed object (`toolLimits` / `compaction`) is absent. Consuming any key emits one `deprecated_settings_option` runtime_warning per run. Migrate via resolveRuntimePolicies (@mono-agent/runtime-adapter).
174
- * @property {Object} [nativeSubagents] Same-runtime teammate helpers exposed through native provider subagent surfaces.
244
+ * @property {ReadonlyArray<"user" | "project" | "local">} [settingSources] Claude Agent SDK only. Filesystem
245
+ * settings the SDK may load for this run. Omitted/empty disables user, project, and local sources, including their
246
+ * CLAUDE.md, hooks, plugins, and on-disk agent profiles. Anthropic managed settings remain in force and may still
247
+ * configure hooks or plugins; this option is not a managed-policy bypass. Each opted-in source may execute configured
248
+ * hooks and plugins, so enable only trusted settings and avoid these sources in an untrusted checkout. Include
249
+ * `"project"`/`"user"` to let the native `Task` tool discover `.claude/agents` definitions. Unrecognized entries are
250
+ * dropped. The Claude Code CLI bridge does not take this option: that binary performs its own settings discovery and
251
+ * mono-agent passes no `--setting-sources`, so a CLI run already reads the host config regardless of this value.
252
+ * @property {boolean} [codexLoadProjectDocs] Codex app-server only. Omitted/false starts the managed app-server with
253
+ * `project_doc_max_bytes=0`, preventing automatic repository-instruction discovery. True restores Codex's native
254
+ * project-doc loading defaults. An explicit `codexAppServerArgs` array wins over this convenience option.
255
+ * @property {RuntimeNativeSubagentsOptions} [nativeSubagents] Caller-defined Claude native `Task` profiles. Direct
256
+ * Codex owns its collaboration agents and rejects configured teammate definitions; `codexLoadProjectDocs` controls
257
+ * whether Codex loads repository instructions for its own agents.
175
258
  * @property {RuntimeSubagentsOptions} [subagents] In-process `Agent` built-in: profiles, caps, and the nested-run callback.
176
259
  * @property {Object} [diagnosticsSeed] Set by createRouterRuntime (ai/runtime/router.js) with a `resume_snapshot` when
177
260
  * failing over mid-chain; a host-level coordinator may relay it forward (see agent/transcript.js), not read by any
@@ -222,11 +305,14 @@
222
305
 
223
306
  /**
224
307
  * @typedef {Object} RuntimeInlineSubagentsOptions
225
- * Policy for subagents the model authors at call time rather than picking from
226
- * `definitions`. Absent suppresses authoring entirely.
308
+ * Policy for the runtime-owned general-purpose helper and subagents the model
309
+ * authors at call time rather than picking from `definitions`. Absent
310
+ * suppresses authoring entirely and leaves general-purpose on its safe default.
227
311
  * @property {boolean} [enabled] Only `false` turns authoring off.
228
- * @property {ReadonlyArray<string>} [allowedTools] Ceiling on what an authored subagent may
229
- * request. Absent means the safe read-only default set, never every built-in.
312
+ * @property {ReadonlyArray<string>} [allowedTools] Ceiling on general-purpose's
313
+ * read-only tools and what an authored subagent may request. Configured
314
+ * definitions keep their explicit contracts. Absent means the safe read-only
315
+ * default set, never every built-in.
230
316
  */
231
317
 
232
318
  /**
@@ -282,7 +368,9 @@
282
368
  * @property {boolean} [supports_skills]
283
369
  * @property {boolean} [supports_builtin_tools]
284
370
  * @property {boolean} [supports_live_input]
285
- * @property {boolean} [supports_native_subagents]
371
+ * @property {boolean} [supports_native_subagents] Whether the bridge exposes provider-native subagent surfaces and
372
+ * normalized activity. This does not imply it accepts caller-defined `nativeSubagents`: Codex owns its collaboration
373
+ * agents, while only the Claude bridges project caller-defined profiles.
286
374
  * @property {boolean} [supports_request_tool_environment]
287
375
  * @property {boolean} [supports_fast_mode]
288
376
  * @property {"projected"|"allow_all_only"} [tool_policy] Whether the bridge can
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * One ephemeral web-tool controller for one model run. It owns in-memory
3
- * deduplication, result caches, anonymous browser namespaces, and cleanup.
3
+ * deduplication, the fetch result cache, anonymous browser namespaces, and
4
+ * cleanup. Search results are the exception: they live in the process-wide
5
+ * cache above so sibling subagents and later turns can reuse them.
4
6
  *
5
7
  * @param {{searchConfig?: any, fetchConfig?: any, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, fetchImpl?: typeof fetch, browserRenderer?: any}} [options]
6
8
  */
@@ -18,3 +20,5 @@ export function createWebToolController({ searchConfig, fetchConfig, sandboxPoli
18
20
  fetch(params: any, execution?: {}): Promise<any>;
19
21
  close(): Promise<void>;
20
22
  };
23
+ /** Test hook: the shared cache is module state and would leak between cases. */
24
+ export function __resetSharedSearchCacheForTests(): void;
@@ -68,9 +68,22 @@ export function performWebSearch({ query, limit, alternate_queries, domains, exc
68
68
  truncated: boolean;
69
69
  resultCount: number;
70
70
  providerFailureCount: number;
71
+ rateLimited: boolean;
72
+ cooldownBackends: string[];
71
73
  };
72
74
  error: boolean;
73
75
  }>;
76
+ /**
77
+ * Test hook: restores the shipped throttle values and clears cooldown/spacing
78
+ * state. Module-scoped state would otherwise leak between test cases.
79
+ *
80
+ * @param {{maxConcurrency?: number, minSpacingMs?: number, cooldownMs?: number}} [overrides]
81
+ */
82
+ export function __resetWebSearchThrottleForTests(overrides?: {
83
+ maxConcurrency?: number;
84
+ minSpacingMs?: number;
85
+ cooldownMs?: number;
86
+ }): void;
74
87
  export function parseDuckDuckGoResults(html: any): {
75
88
  title: string;
76
89
  url: string;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Correlate Claude's live native-agent stream into provider-neutral
3
+ * `subagent_activity` events.
4
+ *
5
+ * `observe()` returns `consumed: true` for every event owned by a native
6
+ * subagent. Provider bridges must not additionally forward or interpret those
7
+ * records as parent events. Non-agent background tasks are consumed without
8
+ * creating activity.
9
+ */
10
+ export function createClaudeSubagentActivityNormalizer(): {
11
+ observe: (raw: Record<string, any>) => {
12
+ consumed: boolean;
13
+ events: {
14
+ type: string;
15
+ subagent: {
16
+ label?: any;
17
+ nativeId?: any;
18
+ id: any;
19
+ name: any;
20
+ callIndex: any;
21
+ };
22
+ }[];
23
+ forwarded?: undefined;
24
+ } | {
25
+ consumed: boolean;
26
+ events: {
27
+ type: string;
28
+ subagent: {
29
+ label?: any;
30
+ nativeId?: any;
31
+ id: any;
32
+ name: any;
33
+ callIndex: any;
34
+ };
35
+ }[];
36
+ forwarded: {
37
+ message: any;
38
+ };
39
+ };
40
+ /** Close every still-open child and its tools exactly once. */
41
+ drain(reason?: string): {
42
+ type: string;
43
+ subagent: {
44
+ label?: any;
45
+ nativeId?: any;
46
+ id: any;
47
+ name: any;
48
+ callIndex: any;
49
+ };
50
+ }[];
51
+ subagentInvoked: () => boolean;
52
+ nativeSubagentsUsed: () => any[];
53
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Walk an error's `cause` chain and return the first transport-level code found.
3
+ * Returns null when the chain carries no code (i.e. nothing worth appending).
4
+ * @param {unknown} error
5
+ * @returns {string|null}
6
+ */
7
+ export function describeErrorCause(error: unknown): string | null;
8
+ /**
9
+ * Subscribe to undici's request-error channel. Idempotent and safe to call on
10
+ * every run; a Node build without the channel simply records nothing.
11
+ */
12
+ export function installTransportErrorProbe(): void;
13
+ /**
14
+ * Most recent transport error code seen within the correlation window.
15
+ * `ambiguous` is true when the window holds more than one distinct code, in
16
+ * which case attribution to a specific run would be a guess.
17
+ * @param {{withinMs?: number, now?: number}} [opts]
18
+ * @returns {{code: string, ambiguous: boolean}|null}
19
+ */
20
+ export function recentTransportErrorCode(opts?: {
21
+ withinMs?: number;
22
+ now?: number;
23
+ }): {
24
+ code: string;
25
+ ambiguous: boolean;
26
+ } | null;
27
+ /**
28
+ * Append the underlying transport reason to an otherwise contentless provider
29
+ * error. Descriptive messages, and messages that already name their cause, are
30
+ * returned unchanged.
31
+ *
32
+ * @param {string|null} message normalized provider error text
33
+ * @param {unknown} [error] the original Error, when it survived
34
+ * @returns {{message: string|null, causeCode: string|null, causeSource: "cause_chain"|"transport_probe"|null}}
35
+ */
36
+ export function annotateProviderErrorMessage(message: string | null, error?: unknown): {
37
+ message: string | null;
38
+ causeCode: string | null;
39
+ causeSource: "cause_chain" | "transport_probe" | null;
40
+ };
41
+ /** Test seam: drop recorded transport errors. */
42
+ export function resetTransportErrorProbeForTests(): void;
43
+ /** Test seam: record a transport error without a real socket. */
44
+ export function recordTransportErrorForTests(code: any, at?: number): void;
@@ -73,5 +73,14 @@ export type RouterAttemptResolution = {
73
73
  options?: {
74
74
  [x: string]: any;
75
75
  };
76
+ /**
77
+ * Provider-specific projection of the logical tool policy. This deliberately
78
+ * cannot replace any other protected request field.
79
+ */
80
+ policyOptions?: {
81
+ allowedTools?: ReadonlyArray<string>;
82
+ disallowedTools?: ReadonlyArray<string>;
83
+ permissionMode?: string;
84
+ };
76
85
  cleanup?: () => (void | Promise<void>);
77
86
  };