@mongodb-js/agent-engine-runner-shared 0.11.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.
- package/CHANGELOG.md +55 -0
- package/LICENSE.md +201 -0
- package/README.md +29 -0
- package/dist/agent_config.d.ts +167 -0
- package/dist/agent_config.d.ts.map +1 -0
- package/dist/agent_config.js +544 -0
- package/dist/call_interrupted.d.ts +12 -0
- package/dist/call_interrupted.d.ts.map +1 -0
- package/dist/call_interrupted.js +11 -0
- package/dist/checkpoint_workspace.d.ts +25 -0
- package/dist/checkpoint_workspace.d.ts.map +1 -0
- package/dist/checkpoint_workspace.js +44 -0
- package/dist/context.d.ts +235 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +322 -0
- package/dist/db_config.d.ts +28 -0
- package/dist/db_config.d.ts.map +1 -0
- package/dist/db_config.js +66 -0
- package/dist/db_naming.d.ts +54 -0
- package/dist/db_naming.d.ts.map +1 -0
- package/dist/db_naming.js +94 -0
- package/dist/error_reporting.d.ts +67 -0
- package/dist/error_reporting.d.ts.map +1 -0
- package/dist/error_reporting.js +311 -0
- package/dist/generated/workflow/v1/activity_pb.d.ts +342 -0
- package/dist/generated/workflow/v1/activity_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/activity_pb.js +115 -0
- package/dist/generated/workflow/v1/common_pb.d.ts +184 -0
- package/dist/generated/workflow/v1/common_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/common_pb.js +86 -0
- package/dist/generated/workflow/v1/runtime_pb.d.ts +200 -0
- package/dist/generated/workflow/v1/runtime_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/runtime_pb.js +40 -0
- package/dist/generated/workflow/v1/state_pb.d.ts +254 -0
- package/dist/generated/workflow/v1/state_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/state_pb.js +68 -0
- package/dist/guardrails_evaluator/core.d.ts +23 -0
- package/dist/guardrails_evaluator/core.d.ts.map +1 -0
- package/dist/guardrails_evaluator/core.js +122 -0
- package/dist/guardrails_evaluator/index.d.ts +10 -0
- package/dist/guardrails_evaluator/index.d.ts.map +1 -0
- package/dist/guardrails_evaluator/index.js +11 -0
- package/dist/guardrails_evaluator/regex.d.ts +20 -0
- package/dist/guardrails_evaluator/regex.d.ts.map +1 -0
- package/dist/guardrails_evaluator/regex.js +233 -0
- package/dist/hooks.d.ts +109 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +216 -0
- package/dist/http_path.d.ts +18 -0
- package/dist/http_path.d.ts.map +1 -0
- package/dist/http_path.js +53 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/launcher.d.ts +130 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +325 -0
- package/dist/logger.d.ts +96 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +204 -0
- package/dist/mcp_oauth.d.ts +51 -0
- package/dist/mcp_oauth.d.ts.map +1 -0
- package/dist/mcp_oauth.js +389 -0
- package/dist/mcp_oauth_secret.d.ts +21 -0
- package/dist/mcp_oauth_secret.d.ts.map +1 -0
- package/dist/mcp_oauth_secret.js +122 -0
- package/dist/mcp_tools.d.ts +71 -0
- package/dist/mcp_tools.d.ts.map +1 -0
- package/dist/mcp_tools.js +301 -0
- package/dist/memory_appbound.d.ts +42 -0
- package/dist/memory_appbound.d.ts.map +1 -0
- package/dist/memory_appbound.js +159 -0
- package/dist/memory_writer.d.ts +49 -0
- package/dist/memory_writer.d.ts.map +1 -0
- package/dist/memory_writer.js +171 -0
- package/dist/metrics.d.ts +84 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +205 -0
- package/dist/models.d.ts +1458 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +1726 -0
- package/dist/node_logger.d.ts +43 -0
- package/dist/node_logger.d.ts.map +1 -0
- package/dist/node_logger.js +158 -0
- package/dist/owner_callback.d.ts +16 -0
- package/dist/owner_callback.d.ts.map +1 -0
- package/dist/owner_callback.js +40 -0
- package/dist/progress.d.ts +57 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +140 -0
- package/dist/runtime.d.ts +131 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +351 -0
- package/dist/secure_llm_proxy.d.ts +115 -0
- package/dist/secure_llm_proxy.d.ts.map +1 -0
- package/dist/secure_llm_proxy.js +922 -0
- package/dist/secure_wrapper.d.ts +332 -0
- package/dist/secure_wrapper.d.ts.map +1 -0
- package/dist/secure_wrapper.js +1249 -0
- package/dist/server/aer.d.ts +61 -0
- package/dist/server/aer.d.ts.map +1 -0
- package/dist/server/aer.js +1124 -0
- package/dist/server/auth.d.ts +56 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +132 -0
- package/dist/server/base.d.ts +104 -0
- package/dist/server/base.d.ts.map +1 -0
- package/dist/server/base.js +150 -0
- package/dist/server/callInterrupt.d.ts +49 -0
- package/dist/server/callInterrupt.d.ts.map +1 -0
- package/dist/server/callInterrupt.js +68 -0
- package/dist/server/callback_delivery.d.ts +14 -0
- package/dist/server/callback_delivery.d.ts.map +1 -0
- package/dist/server/callback_delivery.js +141 -0
- package/dist/server/chunk_types.d.ts +50 -0
- package/dist/server/chunk_types.d.ts.map +1 -0
- package/dist/server/chunk_types.js +62 -0
- package/dist/server/cors.d.ts +52 -0
- package/dist/server/cors.d.ts.map +1 -0
- package/dist/server/cors.js +107 -0
- package/dist/server/drain.d.ts +169 -0
- package/dist/server/drain.d.ts.map +1 -0
- package/dist/server/drain.js +455 -0
- package/dist/server/function.d.ts +77 -0
- package/dist/server/function.d.ts.map +1 -0
- package/dist/server/function.js +337 -0
- package/dist/server/http_retry.d.ts +37 -0
- package/dist/server/http_retry.d.ts.map +1 -0
- package/dist/server/http_retry.js +157 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +5 -0
- package/dist/server/metadata.d.ts +50 -0
- package/dist/server/metadata.d.ts.map +1 -0
- package/dist/server/metadata.js +193 -0
- package/dist/server/oe_url.d.ts +36 -0
- package/dist/server/oe_url.d.ts.map +1 -0
- package/dist/server/oe_url.js +50 -0
- package/dist/server/owner_url.d.ts +35 -0
- package/dist/server/owner_url.d.ts.map +1 -0
- package/dist/server/owner_url.js +146 -0
- package/dist/server/query.d.ts +42 -0
- package/dist/server/query.d.ts.map +1 -0
- package/dist/server/query.js +28 -0
- package/dist/server/tool.d.ts +138 -0
- package/dist/server/tool.d.ts.map +1 -0
- package/dist/server/tool.js +1017 -0
- package/dist/span_names.d.ts +21 -0
- package/dist/span_names.d.ts.map +1 -0
- package/dist/span_names.js +31 -0
- package/dist/structured_logging/constants.d.ts +17 -0
- package/dist/structured_logging/constants.d.ts.map +1 -0
- package/dist/structured_logging/constants.js +71 -0
- package/dist/structured_logging/env.d.ts +18 -0
- package/dist/structured_logging/env.d.ts.map +1 -0
- package/dist/structured_logging/env.js +39 -0
- package/dist/structured_logging/install.d.ts +56 -0
- package/dist/structured_logging/install.d.ts.map +1 -0
- package/dist/structured_logging/install.js +107 -0
- package/dist/structured_logging/layout.d.ts +9 -0
- package/dist/structured_logging/layout.d.ts.map +1 -0
- package/dist/structured_logging/layout.js +144 -0
- package/dist/structured_logging/serialize.d.ts +27 -0
- package/dist/structured_logging/serialize.d.ts.map +1 -0
- package/dist/structured_logging/serialize.js +61 -0
- package/dist/structured_logging/stdio_capture.d.ts +59 -0
- package/dist/structured_logging/stdio_capture.d.ts.map +1 -0
- package/dist/structured_logging/stdio_capture.js +164 -0
- package/dist/structured_logging/uncaught.d.ts +14 -0
- package/dist/structured_logging/uncaught.d.ts.map +1 -0
- package/dist/structured_logging/uncaught.js +58 -0
- package/dist/structured_logging.d.ts +48 -0
- package/dist/structured_logging.d.ts.map +1 -0
- package/dist/structured_logging.js +47 -0
- package/dist/tls_client.d.ts +61 -0
- package/dist/tls_client.d.ts.map +1 -0
- package/dist/tls_client.js +298 -0
- package/dist/tool_api_error.d.ts +62 -0
- package/dist/tool_api_error.d.ts.map +1 -0
- package/dist/tool_api_error.js +399 -0
- package/dist/tool_memory_ownership.d.ts +10 -0
- package/dist/tool_memory_ownership.d.ts.map +1 -0
- package/dist/tool_memory_ownership.js +36 -0
- package/dist/toolpod_handlers.d.ts +126 -0
- package/dist/toolpod_handlers.d.ts.map +1 -0
- package/dist/toolpod_handlers.js +1016 -0
- package/dist/tracing/exporters.d.ts +51 -0
- package/dist/tracing/exporters.d.ts.map +1 -0
- package/dist/tracing/exporters.js +327 -0
- package/dist/tracing/index.d.ts +3 -0
- package/dist/tracing/index.d.ts.map +1 -0
- package/dist/tracing/index.js +2 -0
- package/dist/tracing/setup.d.ts +76 -0
- package/dist/tracing/setup.d.ts.map +1 -0
- package/dist/tracing/setup.js +436 -0
- package/dist/utils.d.ts +204 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +867 -0
- package/dist/workflow/activity.d.ts +71 -0
- package/dist/workflow/activity.d.ts.map +1 -0
- package/dist/workflow/activity.js +357 -0
- package/dist/workflow/attempt.d.ts +12 -0
- package/dist/workflow/attempt.d.ts.map +1 -0
- package/dist/workflow/attempt.js +96 -0
- package/dist/workflow/client.d.ts +46 -0
- package/dist/workflow/client.d.ts.map +1 -0
- package/dist/workflow/client.js +299 -0
- package/dist/workflow/context.d.ts +37 -0
- package/dist/workflow/context.d.ts.map +1 -0
- package/dist/workflow/context.js +350 -0
- package/dist/workflow/heartbeat.d.ts +15 -0
- package/dist/workflow/heartbeat.d.ts.map +1 -0
- package/dist/workflow/heartbeat.js +78 -0
- package/dist/workflow/index.d.ts +14 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +10 -0
- package/dist/workflow/memory.d.ts +17 -0
- package/dist/workflow/memory.d.ts.map +1 -0
- package/dist/workflow/memory.js +184 -0
- package/package.json +73 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify external API call failures into structured, safe metadata.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Python's `agent_engine_runner_shared/tool_api_error.py`.
|
|
5
|
+
*
|
|
6
|
+
* Native `fetch` (Node ≥ 24, backed by Undici) does NOT throw on non-2xx
|
|
7
|
+
* responses — it only rejects for transport/abort failures. So HTTP-status
|
|
8
|
+
* classification requires a thrown error that retains a numeric `status`,
|
|
9
|
+
* `statusCode`, or `response.status` property (e.g. an Axios-shaped error
|
|
10
|
+
* or a custom wrapper around a fetch Response). Transport failures
|
|
11
|
+
* (timeout, connection refused) are classified from error names and
|
|
12
|
+
* `cause.code` values that Undici/Node produces.
|
|
13
|
+
*/
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { getCurrentAuthorization } from "./context.js";
|
|
16
|
+
import { redactText } from "./error_reporting.js";
|
|
17
|
+
import { tenantEnvVars } from "./utils.js";
|
|
18
|
+
const MAX_ERROR_CODE_LEN = 128;
|
|
19
|
+
const MAX_REASON_LEN = 256;
|
|
20
|
+
const STATUS_CLASSIFICATIONS = {
|
|
21
|
+
401: ["AUTH_FAILED", false],
|
|
22
|
+
403: ["AUTH_FAILED", false],
|
|
23
|
+
429: ["RATE_LIMITED", true],
|
|
24
|
+
503: ["PROVIDER_UNAVAILABLE", true],
|
|
25
|
+
};
|
|
26
|
+
const TIMEOUT_CODES = new Set([
|
|
27
|
+
"ETIMEDOUT",
|
|
28
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
29
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
30
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
31
|
+
]);
|
|
32
|
+
const CONNECTION_CODES = new Set([
|
|
33
|
+
"ECONNREFUSED",
|
|
34
|
+
"ECONNRESET",
|
|
35
|
+
"ENOTFOUND",
|
|
36
|
+
"EAI_AGAIN",
|
|
37
|
+
"UND_ERR_SOCKET",
|
|
38
|
+
"EHOSTUNREACH",
|
|
39
|
+
"ENETUNREACH",
|
|
40
|
+
]);
|
|
41
|
+
const MAX_ENVELOPE_BODY = 4096;
|
|
42
|
+
// Rudimentary URL drop: a scheme-bearing URL or a protocol-relative reference
|
|
43
|
+
// with a non-whitespace authority. Encoded or nested spellings are accepted
|
|
44
|
+
// risk, not chased.
|
|
45
|
+
const PROTOCOL_RELATIVE_URL = /(?<![A-Za-z0-9])\/\/(?=\S)/;
|
|
46
|
+
/**
|
|
47
|
+
* Request-local credential values that must never be surfaced. Combines the
|
|
48
|
+
* tenant environment secrets with the delegated authorization token installed
|
|
49
|
+
* for this execution; both are values a provider can echo.
|
|
50
|
+
*
|
|
51
|
+
* Reads pod-level tenant env; the ToolServer runs with secret restriction
|
|
52
|
+
* disabled (merge, no restore), so these values persist for the request. If
|
|
53
|
+
* per-request apply/restore is ever re-enabled, capture the values inside the
|
|
54
|
+
* credential window instead of reading them at classification time.
|
|
55
|
+
*/
|
|
56
|
+
export function requestCredentialValues() {
|
|
57
|
+
const values = Object.values(tenantEnvVars()).filter((value) => value);
|
|
58
|
+
const token = getCurrentAuthorization()?.token;
|
|
59
|
+
if (token && !values.includes(token))
|
|
60
|
+
values.push(token);
|
|
61
|
+
return values;
|
|
62
|
+
}
|
|
63
|
+
// Name segments marking a tenant env var's value as credential-bearing. Env
|
|
64
|
+
// names are conventionally SCREAMING_SNAKE with the credential kind last
|
|
65
|
+
// (OPENAI_API_KEY, GITHUB_TOKEN, DB_PASSWORD), so the name is what selects
|
|
66
|
+
// credential values. Selecting on the value's shape instead -- its length, or
|
|
67
|
+
// whether it looks alphanumeric -- cannot separate a 7-character token from the
|
|
68
|
+
// working directory or the string "1", and redacting those shreds the message
|
|
69
|
+
// the redaction exists to make readable.
|
|
70
|
+
const CREDENTIAL_NAME_MARKERS = [
|
|
71
|
+
"KEY",
|
|
72
|
+
"TOKEN",
|
|
73
|
+
"SECRET",
|
|
74
|
+
"PASSWORD",
|
|
75
|
+
"CREDENTIAL",
|
|
76
|
+
"CREDS",
|
|
77
|
+
"AUTH",
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* Credential values a name marks as secrets, at any length.
|
|
81
|
+
*
|
|
82
|
+
* A narrower view of `requestCredentialValues`: ordinary tenant env (`PATH`,
|
|
83
|
+
* `HOSTNAME`, `SHLVL`) is excluded, while a value under a credential-named
|
|
84
|
+
* variable is treated as a credential however short it is. Use this where the
|
|
85
|
+
* value is inserted into text a human reads, so that redaction cannot corrupt
|
|
86
|
+
* the message; use `requestCredentialValues` where the sink tolerates a blunt
|
|
87
|
+
* replacement. The delegated authorization token counts as credential-named.
|
|
88
|
+
*/
|
|
89
|
+
export function requestNamedCredentialValues() {
|
|
90
|
+
const values = Object.entries(tenantEnvVars())
|
|
91
|
+
.filter(([name, value]) => Boolean(value) &&
|
|
92
|
+
CREDENTIAL_NAME_MARKERS.some((marker) => name.toUpperCase().includes(marker)))
|
|
93
|
+
.map(([, value]) => value);
|
|
94
|
+
const token = getCurrentAuthorization()?.token;
|
|
95
|
+
if (token && !values.includes(token))
|
|
96
|
+
values.push(token);
|
|
97
|
+
return values;
|
|
98
|
+
}
|
|
99
|
+
export const ToolAPIErrorSchema = z.object({
|
|
100
|
+
provider_type: z.string().nullish().describe("Provider identity when known"),
|
|
101
|
+
classification: z
|
|
102
|
+
.string()
|
|
103
|
+
.describe("AUTH_FAILED, RATE_LIMITED, PROVIDER_UNAVAILABLE, TIMEOUT, CONNECTION_ERROR, or UNKNOWN"),
|
|
104
|
+
http_status: z
|
|
105
|
+
.number()
|
|
106
|
+
.int()
|
|
107
|
+
.nullish()
|
|
108
|
+
.describe("HTTP status when the failure was an HTTP response"),
|
|
109
|
+
retryable: z
|
|
110
|
+
.boolean()
|
|
111
|
+
.describe("Provider-condition guidance only. Must not trigger replay of an already-dispatched tool call."),
|
|
112
|
+
error_code: z
|
|
113
|
+
.string()
|
|
114
|
+
.nullish()
|
|
115
|
+
.describe("Bounded redacted provider error code"),
|
|
116
|
+
reason: z
|
|
117
|
+
.string()
|
|
118
|
+
.nullish()
|
|
119
|
+
.describe("Bounded redacted provider reason; URLs dropped"),
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* Walk the `.cause` chain of an error, yielding each node up to a
|
|
123
|
+
* cycle-safe maximum depth of five. Non-Error objects with a `cause`
|
|
124
|
+
* property are included — Undici wraps transport failures as
|
|
125
|
+
* `TypeError("fetch failed")` with a plain-object `cause` carrying the
|
|
126
|
+
* relevant `code`.
|
|
127
|
+
*/
|
|
128
|
+
function* walkCauseChain(error, maxDepth = 5) {
|
|
129
|
+
let current = error;
|
|
130
|
+
let depth = 0;
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
while (current != null && depth < maxDepth && !seen.has(current)) {
|
|
133
|
+
seen.add(current);
|
|
134
|
+
yield current;
|
|
135
|
+
current = current?.cause;
|
|
136
|
+
depth++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function isNum(v) {
|
|
140
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
141
|
+
}
|
|
142
|
+
function findHttpStatus(error) {
|
|
143
|
+
for (const node of walkCauseChain(error)) {
|
|
144
|
+
if (isNum(node["status"]))
|
|
145
|
+
return node["status"];
|
|
146
|
+
if (isNum(node["statusCode"]))
|
|
147
|
+
return node["statusCode"];
|
|
148
|
+
const resp = node["response"];
|
|
149
|
+
if (resp && typeof resp === "object") {
|
|
150
|
+
const r = resp;
|
|
151
|
+
if (isNum(r["status"]))
|
|
152
|
+
return r["status"];
|
|
153
|
+
if (isNum(r["statusCode"]))
|
|
154
|
+
return r["statusCode"];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function findResponse(error) {
|
|
160
|
+
for (const node of walkCauseChain(error)) {
|
|
161
|
+
const resp = node["response"];
|
|
162
|
+
if (resp != null)
|
|
163
|
+
return resp;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function extractFromBody(body, credentials = []) {
|
|
168
|
+
return [
|
|
169
|
+
envelopeErrorCode(body, credentials),
|
|
170
|
+
envelopeReason(body, credentials),
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
function envelopeErrorCode(body, credentials = []) {
|
|
174
|
+
const rawCode = body["errorCode"];
|
|
175
|
+
if (typeof rawCode === "string") {
|
|
176
|
+
return safeEnvelopeText(rawCode, MAX_ERROR_CODE_LEN, credentials);
|
|
177
|
+
}
|
|
178
|
+
const nested = asRecord(body["error"]);
|
|
179
|
+
if (nested && typeof nested["code"] === "string") {
|
|
180
|
+
return safeEnvelopeText(nested["code"], MAX_ERROR_CODE_LEN, credentials);
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The provider's own explanation from a recognized error envelope. Shapes
|
|
186
|
+
* follow the common conventions: Atlas `reason`, Jira `errorMessages`/`errors`,
|
|
187
|
+
* a top-level `message`, and a nested `error.message` (OpenAI/Stripe/Anthropic).
|
|
188
|
+
*/
|
|
189
|
+
function envelopeReason(body, credentials = []) {
|
|
190
|
+
const candidates = [];
|
|
191
|
+
const rawReason = body["reason"];
|
|
192
|
+
if (typeof rawReason === "string")
|
|
193
|
+
candidates.push(rawReason);
|
|
194
|
+
const messages = body["errorMessages"];
|
|
195
|
+
if (Array.isArray(messages)) {
|
|
196
|
+
for (const message of messages) {
|
|
197
|
+
if (typeof message === "string")
|
|
198
|
+
candidates.push(message);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const errors = asRecord(body["errors"]);
|
|
202
|
+
if (errors) {
|
|
203
|
+
for (const value of Object.values(errors)) {
|
|
204
|
+
if (typeof value === "string")
|
|
205
|
+
candidates.push(value);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const rawMessage = body["message"];
|
|
209
|
+
if (typeof rawMessage === "string")
|
|
210
|
+
candidates.push(rawMessage);
|
|
211
|
+
const nested = asRecord(body["error"]);
|
|
212
|
+
if (nested && typeof nested["message"] === "string") {
|
|
213
|
+
candidates.push(nested["message"]);
|
|
214
|
+
}
|
|
215
|
+
for (const candidate of candidates) {
|
|
216
|
+
const safe = safeEnvelopeText(candidate, MAX_REASON_LEN, credentials);
|
|
217
|
+
if (safe)
|
|
218
|
+
return safe;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
function asRecord(value) {
|
|
223
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
224
|
+
? value
|
|
225
|
+
: null;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Rudimentary hygiene for one provider-authored string. Best-effort, not a
|
|
229
|
+
* guarantee: control characters are dropped, URL-shaped text is rejected,
|
|
230
|
+
* exact credential values are redacted, and the result is bounded. Other
|
|
231
|
+
* encodings and provider-specific content are accepted risk.
|
|
232
|
+
*/
|
|
233
|
+
function safeEnvelopeText(text, maxLen, credentials = []) {
|
|
234
|
+
let printable = text.replace(/\p{C}/gu, "");
|
|
235
|
+
if (printable.includes("://") || PROTOCOL_RELATIVE_URL.test(printable)) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
const ordered = credentials
|
|
239
|
+
.filter((value) => value)
|
|
240
|
+
.sort((a, b) => b.length - a.length);
|
|
241
|
+
for (const value of ordered) {
|
|
242
|
+
printable = printable.split(value).join("<redacted>");
|
|
243
|
+
}
|
|
244
|
+
const out = redactText(printable).slice(0, maxLen);
|
|
245
|
+
return out || null;
|
|
246
|
+
}
|
|
247
|
+
async function extractEnvelope(response, credentials = []) {
|
|
248
|
+
if (!response || typeof response !== "object")
|
|
249
|
+
return [null, null];
|
|
250
|
+
const r = response;
|
|
251
|
+
// Axios-shaped: response.data is the already-parsed body. It gets the same
|
|
252
|
+
// encoded-size limit as a native streamed body, so an oversized document
|
|
253
|
+
// cannot supply a visible reason.
|
|
254
|
+
const data = r["data"];
|
|
255
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
256
|
+
if (!isWithinEnvelopeLimit(data))
|
|
257
|
+
return [null, null];
|
|
258
|
+
return extractFromBody(data, credentials);
|
|
259
|
+
}
|
|
260
|
+
const body = await boundedJsonBody(r);
|
|
261
|
+
if (body && typeof body === "object" && !Array.isArray(body)) {
|
|
262
|
+
return extractFromBody(body, credentials);
|
|
263
|
+
}
|
|
264
|
+
return [null, null];
|
|
265
|
+
}
|
|
266
|
+
// The 4 KB limit is a per-binding heuristic: Axios exposes only parsed data,
|
|
267
|
+
// so this measures the re-serialized document rather than wire bytes, while
|
|
268
|
+
// streamed and Python bodies are measured on raw bytes.
|
|
269
|
+
function isWithinEnvelopeLimit(value) {
|
|
270
|
+
try {
|
|
271
|
+
const encoded = JSON.stringify(value);
|
|
272
|
+
if (typeof encoded !== "string")
|
|
273
|
+
return false;
|
|
274
|
+
return new TextEncoder().encode(encoded).byteLength <= MAX_ENVELOPE_BODY;
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async function boundedJsonBody(r) {
|
|
281
|
+
const stream = r["body"];
|
|
282
|
+
if (stream && typeof stream.getReader === "function") {
|
|
283
|
+
const reader = stream.getReader();
|
|
284
|
+
const chunks = [];
|
|
285
|
+
let n = 0;
|
|
286
|
+
try {
|
|
287
|
+
for (;;) {
|
|
288
|
+
const { done, value } = await reader.read();
|
|
289
|
+
if (done)
|
|
290
|
+
break;
|
|
291
|
+
n += value.byteLength;
|
|
292
|
+
if (n > MAX_ENVELOPE_BODY) {
|
|
293
|
+
await reader.cancel();
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
chunks.push(value);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
function classifyTransport(error) {
|
|
312
|
+
for (const node of walkCauseChain(error)) {
|
|
313
|
+
const name = node["name"];
|
|
314
|
+
if (name === "TimeoutError") {
|
|
315
|
+
return { classification: "TIMEOUT", retryable: true };
|
|
316
|
+
}
|
|
317
|
+
const code = node["code"];
|
|
318
|
+
if (typeof code === "string") {
|
|
319
|
+
if (TIMEOUT_CODES.has(code)) {
|
|
320
|
+
return { classification: "TIMEOUT", retryable: true };
|
|
321
|
+
}
|
|
322
|
+
if (CONNECTION_CODES.has(code)) {
|
|
323
|
+
return { classification: "CONNECTION_ERROR", retryable: true };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
function msg(providerType, detail) {
|
|
330
|
+
return `${providerType || "External"} API call failed: ${detail}`;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Return structured metadata and a safe message, or `undefined` when the
|
|
334
|
+
* error is not a recognized HTTP or transport failure.
|
|
335
|
+
*
|
|
336
|
+
* `credentials` are the request-local secret values applied for this call;
|
|
337
|
+
* they are matched exactly against recognized provider text so a provider
|
|
338
|
+
* echoing an unlabeled credential cannot surface it.
|
|
339
|
+
*/
|
|
340
|
+
export async function classifyToolAPIError(error, providerType, credentials = []) {
|
|
341
|
+
const transport = classifyTransport(error);
|
|
342
|
+
if (transport) {
|
|
343
|
+
return {
|
|
344
|
+
toolApiError: {
|
|
345
|
+
provider_type: providerType ?? null,
|
|
346
|
+
classification: transport.classification,
|
|
347
|
+
http_status: null,
|
|
348
|
+
retryable: transport.retryable,
|
|
349
|
+
},
|
|
350
|
+
message: msg(providerType, transport.classification),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const httpStatus = findHttpStatus(error);
|
|
354
|
+
if (httpStatus !== null) {
|
|
355
|
+
const response = findResponse(error);
|
|
356
|
+
const [errorCode, reason] = await extractEnvelope(response, credentials);
|
|
357
|
+
const [classification, retryable] = STATUS_CLASSIFICATIONS[httpStatus] ?? [
|
|
358
|
+
"UNKNOWN",
|
|
359
|
+
false,
|
|
360
|
+
];
|
|
361
|
+
const base = msg(providerType, `HTTP ${httpStatus} ${classification}`);
|
|
362
|
+
return {
|
|
363
|
+
toolApiError: {
|
|
364
|
+
provider_type: providerType ?? null,
|
|
365
|
+
classification,
|
|
366
|
+
http_status: httpStatus,
|
|
367
|
+
retryable,
|
|
368
|
+
error_code: errorCode,
|
|
369
|
+
reason,
|
|
370
|
+
},
|
|
371
|
+
// The provider's own explanation is the fastest path to the cause; it
|
|
372
|
+
// reaches the user and the agent, and is bounded/redacted above.
|
|
373
|
+
message: reason ? `${base} — ${reason}` : base,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
function providerTypeFromToolDef(toolDef) {
|
|
379
|
+
const providerType = toolDef && typeof toolDef === "object"
|
|
380
|
+
? toolDef["provider_type"]
|
|
381
|
+
: undefined;
|
|
382
|
+
return typeof providerType === "string" ? providerType : undefined;
|
|
383
|
+
}
|
|
384
|
+
/** Error `ToolPodExecuteResponse` for a thrown tool, with classification when recognized. */
|
|
385
|
+
export async function executeErrorResponse(error, toolDef, podName, metadata, credentials = []) {
|
|
386
|
+
const classified = await classifyToolAPIError(error, providerTypeFromToolDef(toolDef), credentials);
|
|
387
|
+
return {
|
|
388
|
+
status: "error",
|
|
389
|
+
error: classified
|
|
390
|
+
? classified.message
|
|
391
|
+
: error instanceof Error
|
|
392
|
+
? error.message
|
|
393
|
+
: String(error),
|
|
394
|
+
pod_name: podName,
|
|
395
|
+
kind: metadata["memory"] ? "memory" : undefined,
|
|
396
|
+
metadata,
|
|
397
|
+
...(classified ? { tool_api_error: classified.toolApiError } : {}),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Request-local ownership for Memory reads performed by registered Tools. */
|
|
2
|
+
/** Whether a live registered Tool callback owns the current Memory read. */
|
|
3
|
+
export declare function isMemoryReadOwnedByTool(): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Run a registered Tool callback as the owner of Memory reads it performs.
|
|
6
|
+
* The mutable holder invalidates ownership in async descendants after the
|
|
7
|
+
* callback returns or its promise settles.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runWithToolMemoryReadOwnership<T>(fn: () => T): T;
|
|
10
|
+
//# sourceMappingURL=tool_memory_ownership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool_memory_ownership.d.ts","sourceRoot":"","sources":["../src/tool_memory_ownership.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAoB9E,4EAA4E;AAC5E,wBAAgB,uBAAuB,IAAI,OAAO,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAehE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Request-local ownership for Memory reads performed by registered Tools. */
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
const storage = new AsyncLocalStorage();
|
|
4
|
+
function isPromiseLike(value) {
|
|
5
|
+
if (value === null ||
|
|
6
|
+
(typeof value !== "object" && typeof value !== "function")) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return typeof value.then === "function";
|
|
10
|
+
}
|
|
11
|
+
/** Whether a live registered Tool callback owns the current Memory read. */
|
|
12
|
+
export function isMemoryReadOwnedByTool() {
|
|
13
|
+
return storage.getStore()?.active === true;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Run a registered Tool callback as the owner of Memory reads it performs.
|
|
17
|
+
* The mutable holder invalidates ownership in async descendants after the
|
|
18
|
+
* callback returns or its promise settles.
|
|
19
|
+
*/
|
|
20
|
+
export function runWithToolMemoryReadOwnership(fn) {
|
|
21
|
+
const ownership = { active: true };
|
|
22
|
+
try {
|
|
23
|
+
const result = storage.run(ownership, fn);
|
|
24
|
+
if (!isPromiseLike(result)) {
|
|
25
|
+
ownership.active = false;
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
return Promise.resolve(result).finally(() => {
|
|
29
|
+
ownership.active = false;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
ownership.active = false;
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Tool Pod handler functions (TypeScript port of Python's
|
|
3
|
+
* `toolpod_handlers.py`).
|
|
4
|
+
*
|
|
5
|
+
* These handlers execute filesystem and shell operations inside the Tool Pod.
|
|
6
|
+
* They are registered at startup via `registerBuiltinTools` (gated on
|
|
7
|
+
* `features.deep_agent`) and dispatched by `ToolServer` when requests arrive
|
|
8
|
+
* from `AgentEngineToolPodBackend` through the OE secure path.
|
|
9
|
+
*
|
|
10
|
+
* All handlers accept a single named-argument object (the `ServerToolFn`
|
|
11
|
+
* contract), return a JSON-serializable object, and catch their own
|
|
12
|
+
* exceptions (returning `{error: ...}` on failure). `shell_execute` and
|
|
13
|
+
* `filesystem_glob` are async (the latter streams matches via
|
|
14
|
+
* `fs.promises.glob` so a broad pattern never materializes a huge list); the
|
|
15
|
+
* rest are synchronous.
|
|
16
|
+
*
|
|
17
|
+
* Return formats match what `AgentEngineToolPodBackend` expects to parse:
|
|
18
|
+
* - filesystem_ls -> {entries: [{path, is_dir}], truncated}
|
|
19
|
+
* - filesystem_read -> {content, encoding: "utf-8"}
|
|
20
|
+
* - filesystem_write -> {path}
|
|
21
|
+
* - filesystem_edit -> {occurrences}
|
|
22
|
+
* - filesystem_glob -> {matches: [{path, is_dir}], truncated}
|
|
23
|
+
* - filesystem_grep -> {matches: [{path, line, text}], truncated}
|
|
24
|
+
* - filesystem_download -> {path, content_base64, encoding: "base64"}
|
|
25
|
+
* - shell_execute -> {output, exit_code, truncated}
|
|
26
|
+
*
|
|
27
|
+
* Read-only skill roots resolve lazily at first use / startup, not at
|
|
28
|
+
* import, so a static SDK import before the agent root is set still works.
|
|
29
|
+
*/
|
|
30
|
+
import type { ITenantRuntime } from "./server/base.js";
|
|
31
|
+
export declare const WORKSPACE_DIR: string;
|
|
32
|
+
/**
|
|
33
|
+
* Canonical set of built-in tool names registered by `registerBuiltinTools`.
|
|
34
|
+
* Consumed by `ToolServer.onStartup`'s completeness assertion and by tests so
|
|
35
|
+
* the callsites can't drift silently.
|
|
36
|
+
*/
|
|
37
|
+
export declare const BUILTIN_TOOL_NAMES: ReadonlySet<string>;
|
|
38
|
+
/**
|
|
39
|
+
* List directory contents, sorted by name. When the directory has more than
|
|
40
|
+
* MAX_LS_ENTRIES entries, `truncated=true` and the returned subset is the
|
|
41
|
+
* readdir-order first MAX_LS_ENTRIES entries (filesystem-defined), then sorted
|
|
42
|
+
* by path. The early break is intentional — sorting all entries first would
|
|
43
|
+
* defeat the memory cap on directories with hundreds of thousands of entries.
|
|
44
|
+
*/
|
|
45
|
+
export declare function filesystemLs(args: Record<string, unknown>): Record<string, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* Read file content as text with line-based slicing (lines
|
|
48
|
+
* `[offset, offset+limit)`). Rejects files larger than
|
|
49
|
+
* FILESYSTEM_READ_MAX_BYTES upfront so pathologically large files cannot
|
|
50
|
+
* exhaust pod memory.
|
|
51
|
+
*/
|
|
52
|
+
export declare function filesystemRead(args: Record<string, unknown>): Record<string, unknown>;
|
|
53
|
+
/**
|
|
54
|
+
* Write *content* to a new file, creating parent directories. Per
|
|
55
|
+
* `BackendProtocol.write` this is create-only: if the file exists the call
|
|
56
|
+
* fails. Agents modify existing files via filesystem_edit (which has a
|
|
57
|
+
* unique-match guard).
|
|
58
|
+
*/
|
|
59
|
+
export declare function filesystemWrite(args: Record<string, unknown>): Record<string, unknown>;
|
|
60
|
+
/**
|
|
61
|
+
* Find and replace text in a file, returning the number of occurrences
|
|
62
|
+
* replaced. When *replace_all* is false the match must be unique — a non-unique
|
|
63
|
+
* `old_string` is rejected so a caller cannot silently corrupt the wrong
|
|
64
|
+
* region. Files larger than FILESYSTEM_READ_MAX_BYTES (before or after the
|
|
65
|
+
* edit) are rejected to mirror filesystem_read's memory guard.
|
|
66
|
+
*/
|
|
67
|
+
export declare function filesystemEdit(args: Record<string, unknown>): Record<string, unknown>;
|
|
68
|
+
/**
|
|
69
|
+
* Match files using a glob pattern under *path* (workspace-relative, default
|
|
70
|
+
* `"."`). Supports recursive `**` patterns. Bounded by MAX_GLOB_MATCHES.
|
|
71
|
+
* Absolute patterns and `..` traversal segments are rejected so they cannot
|
|
72
|
+
* bypass the sandbox; returned matches are also filtered through
|
|
73
|
+
* `isWithinReadableRoot` for defense-in-depth against symlinks.
|
|
74
|
+
*/
|
|
75
|
+
export declare function filesystemGlob(args: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
76
|
+
/**
|
|
77
|
+
* Search file contents for a literal substring (per `BackendProtocol.grep`,
|
|
78
|
+
* not a regex). Walks the tree under *path* (default `"."`); when *glob* is
|
|
79
|
+
* given only files whose names match are searched. Binary files are skipped.
|
|
80
|
+
* Bounded by MAX_GREP_MATCHES and MAX_GREP_SECONDS so a deep-tree walk cannot
|
|
81
|
+
* peg a worker indefinitely.
|
|
82
|
+
*/
|
|
83
|
+
export declare function filesystemGrep(args: Record<string, unknown>): Record<string, unknown>;
|
|
84
|
+
/**
|
|
85
|
+
* Download a file's raw bytes base64-encoded so the JSON transport can carry
|
|
86
|
+
* arbitrary binary content. `AgentEngineToolPodBackend.downloadFiles` base64-decodes
|
|
87
|
+
* this to produce the response bytes.
|
|
88
|
+
*/
|
|
89
|
+
export declare function filesystemDownload(args: Record<string, unknown>): Record<string, unknown>;
|
|
90
|
+
/**
|
|
91
|
+
* Run a shell command and capture its output. Per
|
|
92
|
+
* `SandboxBackendProtocol.execute`, *timeout* is `null` = "use the backend
|
|
93
|
+
* default" (SHELL_DEFAULT_TIMEOUT_SECONDS), not "no timeout".
|
|
94
|
+
*
|
|
95
|
+
* Threat model: `shell: true` is intentional — agents need pipes, redirects,
|
|
96
|
+
* globs. The sandbox is the Tool Pod itself.
|
|
97
|
+
*
|
|
98
|
+
* Per-session isolation gap: unlike the filesystem handlers (which enforce the
|
|
99
|
+
* workspace boundary via `resolvePath()` + `isWithinWorkspace()` realpath
|
|
100
|
+
* checks), `cwd` here is only the spawned shell's default working directory —
|
|
101
|
+
* the process is not OS-confined, so `..`/absolute paths can reach sibling
|
|
102
|
+
* sessions or arbitrary pod paths. Treat as session-shared until the
|
|
103
|
+
* bubblewrap sandbox lands. There is no upstream approval gate on the command
|
|
104
|
+
* string; treat the content as agent-authored. This means shell_execute does
|
|
105
|
+
* NOT match the per-session filesystem isolation the fs handlers enforce —
|
|
106
|
+
* an accepted, tracked risk, not local path validation.
|
|
107
|
+
*
|
|
108
|
+
* Output handling: stdout/stderr are drained as they stream. Each stream stops
|
|
109
|
+
* *appending* once SHELL_OUTPUT_MAX_BYTES is captured but the stream is never
|
|
110
|
+
* paused, so the child never blocks on a full pipe — a runaway `yes` runs to
|
|
111
|
+
* its timeout without OOMing the pod. Partial output is preserved on timeout.
|
|
112
|
+
* Framework errors (e.g. missing `/bin/sh`) return
|
|
113
|
+
* `exit_code = EXIT_CODE_FRAMEWORK_ERROR`, distinct from the timeout sentinel.
|
|
114
|
+
*/
|
|
115
|
+
export declare function shellExecute(args: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
116
|
+
/**
|
|
117
|
+
* Register all built-in tool handlers on *runtime* (callable lookup +
|
|
118
|
+
* metadata for all 8 handlers). Called by `ToolServer.onStartup` when
|
|
119
|
+
* `features.deep_agent` is on.
|
|
120
|
+
*
|
|
121
|
+
* If a user `@app.tool()` registered a tool with a reserved built-in name,
|
|
122
|
+
* this logs a WARNING and overrides it with the built-in — without the warning
|
|
123
|
+
* the collision was silent and surfaced only when the built-in was invoked.
|
|
124
|
+
*/
|
|
125
|
+
export declare function registerBuiltinTools(runtime: ITenantRuntime): void;
|
|
126
|
+
//# sourceMappingURL=toolpod_handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolpod_handlers.d.ts","sourceRoot":"","sources":["../src/toolpod_handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAUH,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,kBAAkB,CAAC;AAyDrE,eAAO,MAAM,aAAa,QAEzB,CAAC;AA6HF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CASjD,CAAC;AAwKH;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6BzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBzB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8BzB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuDzB;AAcD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAwClC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgDzB;AA4DD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBzB;AAkCD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAyJlC;AAgED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAyClE"}
|