@mono-agent/agent-runtime 0.20.14 → 0.21.1
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 +30 -7
- package/README.md +219 -35
- package/package.json +9 -4
- package/src/agent/tool-bloat.js +145 -9
- package/src/agent/tools/agent-tool.js +104 -5
- package/src/agent/tools/bash.js +10 -2
- package/src/agent/tools/codex-subscription-search.js +122 -28
- package/src/agent/tools/exec.js +10 -2
- package/src/agent/tools/monitor.js +11 -2
- package/src/agent/tools/pi-bridge.js +70 -26
- package/src/agent/tools/read.js +3 -78
- package/src/agent/tools/shared/image.js +89 -0
- package/src/agent/tools/shared/monitors.js +22 -3
- 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 +3 -1
- 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/failure.js +3 -3
- package/src/ai/index.js +1 -0
- package/src/ai/observer.js +8 -0
- package/src/ai/pi-interop.js +156 -0
- package/src/ai/provider-check.js +131 -0
- package/src/ai/providers/pi-native/compaction-driver.js +45 -21
- package/src/ai/providers/pi-native/compaction-summary.js +140 -0
- package/src/ai/providers/pi-native/harness-adapter.js +40 -2
- 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 +28 -4
- package/src/ai/providers/pi-native/session-lifecycle.js +167 -24
- package/src/ai/providers/pi-native/stream-subscriber.js +30 -2
- package/src/ai/providers/pi-native/terminal-recovery.js +40 -0
- package/src/ai/providers/pi-native/turn-runner.js +245 -13
- package/src/ai/providers/pi-native.js +159 -40
- package/src/ai/runtime/live-input-events.js +250 -54
- package/src/ai/runtime/router.js +30 -11
- package/src/ai/tool-lifecycle.js +32 -18
- package/src/ai/types.js +26 -5
- package/src/runtime.js +24 -5
- package/types/agent/tool-bloat.d.ts +1 -1
- package/types/agent/tools/agent-tool.d.ts +4 -1
- package/types/agent/tools/bash.d.ts +5 -3
- package/types/agent/tools/codex-subscription-search.d.ts +6 -2
- package/types/agent/tools/exec.d.ts +5 -3
- package/types/agent/tools/monitor.d.ts +5 -2
- package/types/agent/tools/pi-bridge.d.ts +7 -5
- package/types/agent/tools/shared/image.d.ts +23 -0
- package/types/agent/tools/shared/monitors.d.ts +17 -2
- package/types/agent/tools/shared/process-jobs.d.ts +5 -1
- package/types/agent/tools/shared/process-runner.d.ts +3 -2
- 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/index.d.ts +1 -0
- package/types/ai/observer.d.ts +6 -0
- package/types/ai/pi-interop.d.ts +61 -0
- package/types/ai/provider-check.d.ts +53 -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 +3 -1
- 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 +11 -1
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +23 -5
- package/types/ai/providers/pi-native/terminal-recovery.d.ts +2 -0
- package/types/ai/providers/pi-native/turn-runner.d.ts +36 -5
- package/types/ai/runtime/live-input-events.d.ts +32 -8
- package/types/ai/tool-lifecycle.d.ts +4 -3
- package/types/ai/types.d.ts +140 -12
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** One deadline covers admission, backend startup, I/O, and retries. */
|
|
4
|
+
export async function withWebDeadline(signal, milliseconds, execute) {
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
const abort = () => controller.abort(signal.reason);
|
|
7
|
+
if (signal?.aborted) abort();
|
|
8
|
+
else signal?.addEventListener("abort", abort, { once: true });
|
|
9
|
+
const timer = setTimeout(() => controller.abort(Object.assign(new Error("Web request deadline exceeded."), { code: "deadline_exceeded" })), milliseconds);
|
|
10
|
+
try { return await execute(controller.signal); }
|
|
11
|
+
finally { clearTimeout(timer); signal?.removeEventListener("abort", abort); }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function coordinatedWebRequest(coordinator, kind, key, signal, execute, classify = classifySearch) {
|
|
15
|
+
signal?.throwIfAborted();
|
|
16
|
+
const permit = coordinator ? await coordinator.acquire({ kind, key, signal, deadlineMs: Date.now() + (kind === "fetch" ? 45_000 : 60_000) }) : undefined;
|
|
17
|
+
const started = Date.now();
|
|
18
|
+
try {
|
|
19
|
+
signal?.throwIfAborted();
|
|
20
|
+
const value = await execute();
|
|
21
|
+
const completion = await permit?.complete(signal?.aborted ? { status: "cancelled" } : classify(value));
|
|
22
|
+
const persistedCooldown = value?.rateLimited ? completion : undefined;
|
|
23
|
+
return {
|
|
24
|
+
...value,
|
|
25
|
+
...(Number.isFinite(persistedCooldown?.retryAfterMs) ? { retryAfterMs: persistedCooldown.retryAfterMs } : {}),
|
|
26
|
+
...(Number.isFinite(persistedCooldown?.retryAtMs) ? { retryAtMs: persistedCooldown.retryAtMs } : {}),
|
|
27
|
+
coordinationWaitMs: permit?.waitMs ?? 0,
|
|
28
|
+
backendDurationMs: Date.now() - started,
|
|
29
|
+
};
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// A run-local budget refusal happens after admission but before a provider
|
|
32
|
+
// request. Release the lease without teaching the shared host coordinator
|
|
33
|
+
// that the provider failed.
|
|
34
|
+
await permit?.complete({
|
|
35
|
+
status: signal?.aborted || error?.code === "search_budget_exhausted"
|
|
36
|
+
? "cancelled"
|
|
37
|
+
: "unavailable",
|
|
38
|
+
});
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function classifySearch(result) {
|
|
44
|
+
return {
|
|
45
|
+
status: result.rateLimited ? "rate_limited" : result.ok ? "ok" : result.retryable ? "unavailable" : "cancelled",
|
|
46
|
+
...(result.retryAfterMs === undefined ? {} : { retryAfterMs: result.retryAfterMs }),
|
|
47
|
+
...(result.retryAtMs === undefined ? {} : { retryAtMs: result.retryAtMs }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function webRequestFailure(error, backend, signal) {
|
|
52
|
+
const code = signal?.aborted ? (signal.reason?.code === "deadline_exceeded" ? "deadline_exceeded" : "aborted") : error?.code;
|
|
53
|
+
return {
|
|
54
|
+
ok: false, backend, code: code || "backend_unavailable",
|
|
55
|
+
message: code === "coordination_unavailable" ? "Web request coordination is unavailable."
|
|
56
|
+
: code === "rate_limited" ? `${backend} is cooling down.`
|
|
57
|
+
: code === "search_budget_exhausted" ? "WebSearch request budget exhausted for this run."
|
|
58
|
+
: code === "deadline_exceeded" ? "Web request deadline exceeded."
|
|
59
|
+
: code === "aborted" ? "Web request was aborted." : `${backend} request unavailable.`,
|
|
60
|
+
retryable: !["aborted", "search_budget_exhausted"].includes(code),
|
|
61
|
+
cooldown: code === "rate_limited", rateLimited: code === "rate_limited",
|
|
62
|
+
retryAfterMs: error?.retryAfterMs,
|
|
63
|
+
retryAtMs: error?.retryAtMs,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
export const WEB_SEARCH_TITLE_MAX_CHARS = 500;
|
|
4
|
+
export const WEB_SEARCH_SNIPPET_MAX_CHARS = 4_000;
|
|
5
|
+
export const WEB_SEARCH_BODY_MAX_BYTES = 64 * 1024;
|
|
6
|
+
export const WEB_SEARCH_SNIPPET_TRUNCATION_MARKER = "[snippet truncated; use WebFetch for full source]";
|
|
7
|
+
|
|
8
|
+
const RESULT_OMISSION_MARKER = "[additional search results omitted by WebSearch output bound]";
|
|
9
|
+
|
|
10
|
+
/** @param {unknown} value */
|
|
11
|
+
function collapseWhitespace(value) {
|
|
12
|
+
return toWellFormedText(value).replace(/\s+/gu, " ").trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Replace isolated UTF-16 surrogates while preserving valid astral pairs. @param {unknown} value */
|
|
16
|
+
export function toWellFormedText(value) {
|
|
17
|
+
let out = "";
|
|
18
|
+
for (const point of String(value || "")) {
|
|
19
|
+
const unit = point.charCodeAt(0);
|
|
20
|
+
out += point.length === 1 && unit >= 0xd800 && unit <= 0xdfff ? "\uFFFD" : point;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @param {unknown} value @param {number} maxChars */
|
|
26
|
+
export function sliceWellFormedCodePoints(value, maxChars) {
|
|
27
|
+
return [...toWellFormedText(value)].slice(0, Math.max(0, maxChars)).join("");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @param {string} value @param {number} maxBytes */
|
|
31
|
+
export function sliceUtf8(value, maxBytes) {
|
|
32
|
+
const budget = Math.max(0, Math.floor(maxBytes));
|
|
33
|
+
const text = toWellFormedText(value);
|
|
34
|
+
if (Buffer.byteLength(text, "utf8") <= budget) return text;
|
|
35
|
+
let used = 0;
|
|
36
|
+
let out = "";
|
|
37
|
+
for (const point of text) {
|
|
38
|
+
const bytes = Buffer.byteLength(point, "utf8");
|
|
39
|
+
if (used + bytes > budget) break;
|
|
40
|
+
out += point;
|
|
41
|
+
used += bytes;
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @param {unknown} value @param {number} [maxChars] */
|
|
47
|
+
export function boundWebSearchSnippet(value, maxChars = WEB_SEARCH_SNIPPET_MAX_CHARS) {
|
|
48
|
+
const text = collapseWhitespace(value);
|
|
49
|
+
if ([...text].length <= maxChars) return { text, truncated: false };
|
|
50
|
+
const markerChars = [...WEB_SEARCH_SNIPPET_TRUNCATION_MARKER].length;
|
|
51
|
+
const prefixChars = Math.max(0, maxChars - markerChars - 1);
|
|
52
|
+
const prefix = sliceWellFormedCodePoints(text, prefixChars).trimEnd();
|
|
53
|
+
return {
|
|
54
|
+
text: `${prefix}${prefix ? " " : ""}${WEB_SEARCH_SNIPPET_TRUNCATION_MARKER}`,
|
|
55
|
+
truncated: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @param {unknown} value */
|
|
60
|
+
function boundTitle(value) {
|
|
61
|
+
const text = collapseWhitespace(value);
|
|
62
|
+
if ([...text].length <= WEB_SEARCH_TITLE_MAX_CHARS) return { text, truncated: false };
|
|
63
|
+
return {
|
|
64
|
+
text: `${sliceWellFormedCodePoints(text, WEB_SEARCH_TITLE_MAX_CHARS - 1).trimEnd()}…`,
|
|
65
|
+
truncated: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @param {unknown} value */
|
|
70
|
+
function escapeMarkdownLabel(value) {
|
|
71
|
+
return collapseWhitespace(value).replace(/[[\]\\]/gu, "\\$&");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {string} value
|
|
76
|
+
* @param {number} maxBytes
|
|
77
|
+
*/
|
|
78
|
+
function truncateSnippetToBytes(value, maxBytes) {
|
|
79
|
+
if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
|
|
80
|
+
const markerBytes = Buffer.byteLength(WEB_SEARCH_SNIPPET_TRUNCATION_MARKER, "utf8");
|
|
81
|
+
if (maxBytes <= markerBytes) return sliceUtf8(WEB_SEARCH_SNIPPET_TRUNCATION_MARKER, maxBytes);
|
|
82
|
+
const source = value.endsWith(WEB_SEARCH_SNIPPET_TRUNCATION_MARKER)
|
|
83
|
+
? value.slice(0, -WEB_SEARCH_SNIPPET_TRUNCATION_MARKER.length).trimEnd()
|
|
84
|
+
: value;
|
|
85
|
+
const prefix = sliceUtf8(source, Math.max(0, maxBytes - markerBytes - 1)).trimEnd();
|
|
86
|
+
return `${prefix}${prefix ? " " : ""}${WEB_SEARCH_SNIPPET_TRUNCATION_MARKER}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {Array<{title?: unknown, url?: unknown, snippet?: unknown, snippetTruncated?: boolean}>} results
|
|
91
|
+
* @param {{maxBytes?: number}} [options]
|
|
92
|
+
*/
|
|
93
|
+
export function renderBoundedWebSearchBody(results, { maxBytes = WEB_SEARCH_BODY_MAX_BYTES } = {}) {
|
|
94
|
+
const normalized = results.map((result, index) => {
|
|
95
|
+
const url = String(result?.url || "");
|
|
96
|
+
const title = boundTitle(result?.title || url);
|
|
97
|
+
const snippet = boundWebSearchSnippet(result?.snippet);
|
|
98
|
+
const snippetTruncated = result?.snippetTruncated === true || snippet.truncated;
|
|
99
|
+
const text = snippetTruncated && !snippet.text.endsWith(WEB_SEARCH_SNIPPET_TRUNCATION_MARKER)
|
|
100
|
+
? boundWebSearchSnippet(`${snippet.text} ${WEB_SEARCH_SNIPPET_TRUNCATION_MARKER}`).text
|
|
101
|
+
: snippet.text;
|
|
102
|
+
return {
|
|
103
|
+
core: `${index + 1}. [${escapeMarkdownLabel(title.text || url)}](${url})`,
|
|
104
|
+
snippet: text,
|
|
105
|
+
truncated: title.truncated || snippetTruncated,
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (normalized.length === 0) {
|
|
110
|
+
return { body: "No results.", renderedResultCount: 0, truncated: false };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let selected = normalized.slice();
|
|
114
|
+
let omittedResults = 0;
|
|
115
|
+
const omissionBody = () => omittedResults > 0
|
|
116
|
+
? `${RESULT_OMISSION_MARKER} (${omittedResults})`
|
|
117
|
+
: "";
|
|
118
|
+
const minimumSnippet = (entry) => entry.snippet
|
|
119
|
+
? `\n ${WEB_SEARCH_SNIPPET_TRUNCATION_MARKER}`
|
|
120
|
+
: "";
|
|
121
|
+
const minimumBody = () => [
|
|
122
|
+
...selected.map((entry) => `${entry.core}${minimumSnippet(entry)}`),
|
|
123
|
+
...(omittedResults > 0 ? [omissionBody()] : []),
|
|
124
|
+
].join("\n\n");
|
|
125
|
+
|
|
126
|
+
while (selected.length > 0 && Buffer.byteLength(minimumBody(), "utf8") > maxBytes) {
|
|
127
|
+
selected.pop();
|
|
128
|
+
omittedResults += 1;
|
|
129
|
+
}
|
|
130
|
+
if (selected.length === 0) {
|
|
131
|
+
return {
|
|
132
|
+
body: sliceUtf8(omissionBody() || RESULT_OMISSION_MARKER, maxBytes),
|
|
133
|
+
renderedResultCount: 0,
|
|
134
|
+
truncated: true,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const minimum = minimumBody();
|
|
139
|
+
let remaining = Math.max(0, maxBytes - Buffer.byteLength(minimum, "utf8"));
|
|
140
|
+
let truncated = omittedResults > 0 || selected.some((entry) => entry.truncated);
|
|
141
|
+
const rendered = [];
|
|
142
|
+
for (const entry of selected) {
|
|
143
|
+
if (!entry.snippet) {
|
|
144
|
+
rendered.push(entry.core);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const prefix = "\n ";
|
|
148
|
+
const minimumTextBytes = Buffer.byteLength(WEB_SEARCH_SNIPPET_TRUNCATION_MARKER, "utf8");
|
|
149
|
+
const desiredBytes = Buffer.byteLength(entry.snippet, "utf8");
|
|
150
|
+
const extraNeeded = Math.max(0, desiredBytes - minimumTextBytes);
|
|
151
|
+
const granted = Math.min(remaining, extraNeeded);
|
|
152
|
+
remaining -= granted;
|
|
153
|
+
const snippetBudget = minimumTextBytes + granted;
|
|
154
|
+
const snippet = truncateSnippetToBytes(entry.snippet, snippetBudget);
|
|
155
|
+
if (snippet !== entry.snippet) truncated = true;
|
|
156
|
+
rendered.push(`${entry.core}${prefix}${snippet}`);
|
|
157
|
+
}
|
|
158
|
+
if (omittedResults > 0) rendered.push(omissionBody());
|
|
159
|
+
const body = rendered.join("\n\n");
|
|
160
|
+
return {
|
|
161
|
+
body,
|
|
162
|
+
renderedResultCount: selected.length,
|
|
163
|
+
truncated,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_WEB_SEARCH_MAX_REQUESTS_PER_RUN = 4;
|
|
4
|
+
export const MAX_WEB_SEARCH_REQUESTS_PER_RUN = 20;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Private mutable state shared by every WebSearch controller created for one
|
|
8
|
+
* logical runtime run. Router retries receive the same object, while child and
|
|
9
|
+
* later runs receive a fresh one.
|
|
10
|
+
*
|
|
11
|
+
* @param {any} searchConfig
|
|
12
|
+
* @param {any} [existing]
|
|
13
|
+
*/
|
|
14
|
+
export function createWebSearchRunState(searchConfig, existing) {
|
|
15
|
+
if (isWebSearchRunState(existing)) return existing;
|
|
16
|
+
const configured = searchConfig?.maxRequestsPerRun;
|
|
17
|
+
const maxRequests = Number.isSafeInteger(configured)
|
|
18
|
+
&& configured >= 1
|
|
19
|
+
&& configured <= MAX_WEB_SEARCH_REQUESTS_PER_RUN
|
|
20
|
+
? configured
|
|
21
|
+
: DEFAULT_WEB_SEARCH_MAX_REQUESTS_PER_RUN;
|
|
22
|
+
return {
|
|
23
|
+
schema: "mono-agent.web-search-run.v1",
|
|
24
|
+
maxRequests,
|
|
25
|
+
requestsUsed: 0,
|
|
26
|
+
deferredProviders: new Map(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Claim one actual provider dispatch synchronously. */
|
|
31
|
+
export function claimWebSearchRequest(state, backend, callClaims) {
|
|
32
|
+
if (!isWebSearchRunState(state)) throw new Error("Invalid WebSearch run state.");
|
|
33
|
+
if (state.requestsUsed >= state.maxRequests) {
|
|
34
|
+
throw Object.assign(new Error("WebSearch request budget exhausted for this run."), {
|
|
35
|
+
code: "search_budget_exhausted",
|
|
36
|
+
backend,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
state.requestsUsed += 1;
|
|
40
|
+
if (callClaims && Number.isSafeInteger(callClaims.requests)) callClaims.requests += 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function webSearchBudgetSnapshot(state, requestsThisCall = 0) {
|
|
44
|
+
const resolved = createWebSearchRunState(undefined, state);
|
|
45
|
+
return {
|
|
46
|
+
requestsThisCall,
|
|
47
|
+
maxRequestsPerRun: resolved.maxRequests,
|
|
48
|
+
requestsUsed: resolved.requestsUsed,
|
|
49
|
+
requestsRemaining: Math.max(0, resolved.maxRequests - resolved.requestsUsed),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function deferWebSearchProvider(state, backend, retryAtMs) {
|
|
54
|
+
if (!isWebSearchRunState(state)) return;
|
|
55
|
+
const current = state.deferredProviders.get(backend);
|
|
56
|
+
const next = Number.isFinite(retryAtMs) ? retryAtMs : undefined;
|
|
57
|
+
if (current === undefined || (next !== undefined && (current.retryAtMs === undefined || next > current.retryAtMs))) {
|
|
58
|
+
state.deferredProviders.set(backend, { retryAtMs: next });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function deferredWebSearchProvider(state, backend) {
|
|
63
|
+
return isWebSearchRunState(state) ? state.deferredProviders.get(backend) : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isWebSearchRunState(value) {
|
|
67
|
+
return value?.schema === "mono-agent.web-search-run.v1"
|
|
68
|
+
&& Number.isSafeInteger(value.maxRequests)
|
|
69
|
+
&& value.maxRequests >= 1
|
|
70
|
+
&& value.maxRequests <= MAX_WEB_SEARCH_REQUESTS_PER_RUN
|
|
71
|
+
&& Number.isSafeInteger(value.requestsUsed)
|
|
72
|
+
&& value.requestsUsed >= 0
|
|
73
|
+
&& value.requestsUsed <= value.maxRequests
|
|
74
|
+
&& value.deferredProviders instanceof Map;
|
|
75
|
+
}
|