@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.
Files changed (220) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/LICENSE.md +201 -0
  3. package/README.md +29 -0
  4. package/dist/agent_config.d.ts +167 -0
  5. package/dist/agent_config.d.ts.map +1 -0
  6. package/dist/agent_config.js +544 -0
  7. package/dist/call_interrupted.d.ts +12 -0
  8. package/dist/call_interrupted.d.ts.map +1 -0
  9. package/dist/call_interrupted.js +11 -0
  10. package/dist/checkpoint_workspace.d.ts +25 -0
  11. package/dist/checkpoint_workspace.d.ts.map +1 -0
  12. package/dist/checkpoint_workspace.js +44 -0
  13. package/dist/context.d.ts +235 -0
  14. package/dist/context.d.ts.map +1 -0
  15. package/dist/context.js +322 -0
  16. package/dist/db_config.d.ts +28 -0
  17. package/dist/db_config.d.ts.map +1 -0
  18. package/dist/db_config.js +66 -0
  19. package/dist/db_naming.d.ts +54 -0
  20. package/dist/db_naming.d.ts.map +1 -0
  21. package/dist/db_naming.js +94 -0
  22. package/dist/error_reporting.d.ts +67 -0
  23. package/dist/error_reporting.d.ts.map +1 -0
  24. package/dist/error_reporting.js +311 -0
  25. package/dist/generated/workflow/v1/activity_pb.d.ts +342 -0
  26. package/dist/generated/workflow/v1/activity_pb.d.ts.map +1 -0
  27. package/dist/generated/workflow/v1/activity_pb.js +115 -0
  28. package/dist/generated/workflow/v1/common_pb.d.ts +184 -0
  29. package/dist/generated/workflow/v1/common_pb.d.ts.map +1 -0
  30. package/dist/generated/workflow/v1/common_pb.js +86 -0
  31. package/dist/generated/workflow/v1/runtime_pb.d.ts +200 -0
  32. package/dist/generated/workflow/v1/runtime_pb.d.ts.map +1 -0
  33. package/dist/generated/workflow/v1/runtime_pb.js +40 -0
  34. package/dist/generated/workflow/v1/state_pb.d.ts +254 -0
  35. package/dist/generated/workflow/v1/state_pb.d.ts.map +1 -0
  36. package/dist/generated/workflow/v1/state_pb.js +68 -0
  37. package/dist/guardrails_evaluator/core.d.ts +23 -0
  38. package/dist/guardrails_evaluator/core.d.ts.map +1 -0
  39. package/dist/guardrails_evaluator/core.js +122 -0
  40. package/dist/guardrails_evaluator/index.d.ts +10 -0
  41. package/dist/guardrails_evaluator/index.d.ts.map +1 -0
  42. package/dist/guardrails_evaluator/index.js +11 -0
  43. package/dist/guardrails_evaluator/regex.d.ts +20 -0
  44. package/dist/guardrails_evaluator/regex.d.ts.map +1 -0
  45. package/dist/guardrails_evaluator/regex.js +233 -0
  46. package/dist/hooks.d.ts +109 -0
  47. package/dist/hooks.d.ts.map +1 -0
  48. package/dist/hooks.js +216 -0
  49. package/dist/http_path.d.ts +18 -0
  50. package/dist/http_path.d.ts.map +1 -0
  51. package/dist/http_path.js +53 -0
  52. package/dist/index.d.ts +35 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +41 -0
  55. package/dist/launcher.d.ts +130 -0
  56. package/dist/launcher.d.ts.map +1 -0
  57. package/dist/launcher.js +325 -0
  58. package/dist/logger.d.ts +96 -0
  59. package/dist/logger.d.ts.map +1 -0
  60. package/dist/logger.js +204 -0
  61. package/dist/mcp_oauth.d.ts +51 -0
  62. package/dist/mcp_oauth.d.ts.map +1 -0
  63. package/dist/mcp_oauth.js +389 -0
  64. package/dist/mcp_oauth_secret.d.ts +21 -0
  65. package/dist/mcp_oauth_secret.d.ts.map +1 -0
  66. package/dist/mcp_oauth_secret.js +122 -0
  67. package/dist/mcp_tools.d.ts +71 -0
  68. package/dist/mcp_tools.d.ts.map +1 -0
  69. package/dist/mcp_tools.js +301 -0
  70. package/dist/memory_appbound.d.ts +42 -0
  71. package/dist/memory_appbound.d.ts.map +1 -0
  72. package/dist/memory_appbound.js +159 -0
  73. package/dist/memory_writer.d.ts +49 -0
  74. package/dist/memory_writer.d.ts.map +1 -0
  75. package/dist/memory_writer.js +171 -0
  76. package/dist/metrics.d.ts +84 -0
  77. package/dist/metrics.d.ts.map +1 -0
  78. package/dist/metrics.js +205 -0
  79. package/dist/models.d.ts +1458 -0
  80. package/dist/models.d.ts.map +1 -0
  81. package/dist/models.js +1726 -0
  82. package/dist/node_logger.d.ts +43 -0
  83. package/dist/node_logger.d.ts.map +1 -0
  84. package/dist/node_logger.js +158 -0
  85. package/dist/owner_callback.d.ts +16 -0
  86. package/dist/owner_callback.d.ts.map +1 -0
  87. package/dist/owner_callback.js +40 -0
  88. package/dist/progress.d.ts +57 -0
  89. package/dist/progress.d.ts.map +1 -0
  90. package/dist/progress.js +140 -0
  91. package/dist/runtime.d.ts +131 -0
  92. package/dist/runtime.d.ts.map +1 -0
  93. package/dist/runtime.js +351 -0
  94. package/dist/secure_llm_proxy.d.ts +115 -0
  95. package/dist/secure_llm_proxy.d.ts.map +1 -0
  96. package/dist/secure_llm_proxy.js +922 -0
  97. package/dist/secure_wrapper.d.ts +332 -0
  98. package/dist/secure_wrapper.d.ts.map +1 -0
  99. package/dist/secure_wrapper.js +1249 -0
  100. package/dist/server/aer.d.ts +61 -0
  101. package/dist/server/aer.d.ts.map +1 -0
  102. package/dist/server/aer.js +1124 -0
  103. package/dist/server/auth.d.ts +56 -0
  104. package/dist/server/auth.d.ts.map +1 -0
  105. package/dist/server/auth.js +132 -0
  106. package/dist/server/base.d.ts +104 -0
  107. package/dist/server/base.d.ts.map +1 -0
  108. package/dist/server/base.js +150 -0
  109. package/dist/server/callInterrupt.d.ts +49 -0
  110. package/dist/server/callInterrupt.d.ts.map +1 -0
  111. package/dist/server/callInterrupt.js +68 -0
  112. package/dist/server/callback_delivery.d.ts +14 -0
  113. package/dist/server/callback_delivery.d.ts.map +1 -0
  114. package/dist/server/callback_delivery.js +141 -0
  115. package/dist/server/chunk_types.d.ts +50 -0
  116. package/dist/server/chunk_types.d.ts.map +1 -0
  117. package/dist/server/chunk_types.js +62 -0
  118. package/dist/server/cors.d.ts +52 -0
  119. package/dist/server/cors.d.ts.map +1 -0
  120. package/dist/server/cors.js +107 -0
  121. package/dist/server/drain.d.ts +169 -0
  122. package/dist/server/drain.d.ts.map +1 -0
  123. package/dist/server/drain.js +455 -0
  124. package/dist/server/function.d.ts +77 -0
  125. package/dist/server/function.d.ts.map +1 -0
  126. package/dist/server/function.js +337 -0
  127. package/dist/server/http_retry.d.ts +37 -0
  128. package/dist/server/http_retry.d.ts.map +1 -0
  129. package/dist/server/http_retry.js +157 -0
  130. package/dist/server/index.d.ts +7 -0
  131. package/dist/server/index.d.ts.map +1 -0
  132. package/dist/server/index.js +5 -0
  133. package/dist/server/metadata.d.ts +50 -0
  134. package/dist/server/metadata.d.ts.map +1 -0
  135. package/dist/server/metadata.js +193 -0
  136. package/dist/server/oe_url.d.ts +36 -0
  137. package/dist/server/oe_url.d.ts.map +1 -0
  138. package/dist/server/oe_url.js +50 -0
  139. package/dist/server/owner_url.d.ts +35 -0
  140. package/dist/server/owner_url.d.ts.map +1 -0
  141. package/dist/server/owner_url.js +146 -0
  142. package/dist/server/query.d.ts +42 -0
  143. package/dist/server/query.d.ts.map +1 -0
  144. package/dist/server/query.js +28 -0
  145. package/dist/server/tool.d.ts +138 -0
  146. package/dist/server/tool.d.ts.map +1 -0
  147. package/dist/server/tool.js +1017 -0
  148. package/dist/span_names.d.ts +21 -0
  149. package/dist/span_names.d.ts.map +1 -0
  150. package/dist/span_names.js +31 -0
  151. package/dist/structured_logging/constants.d.ts +17 -0
  152. package/dist/structured_logging/constants.d.ts.map +1 -0
  153. package/dist/structured_logging/constants.js +71 -0
  154. package/dist/structured_logging/env.d.ts +18 -0
  155. package/dist/structured_logging/env.d.ts.map +1 -0
  156. package/dist/structured_logging/env.js +39 -0
  157. package/dist/structured_logging/install.d.ts +56 -0
  158. package/dist/structured_logging/install.d.ts.map +1 -0
  159. package/dist/structured_logging/install.js +107 -0
  160. package/dist/structured_logging/layout.d.ts +9 -0
  161. package/dist/structured_logging/layout.d.ts.map +1 -0
  162. package/dist/structured_logging/layout.js +144 -0
  163. package/dist/structured_logging/serialize.d.ts +27 -0
  164. package/dist/structured_logging/serialize.d.ts.map +1 -0
  165. package/dist/structured_logging/serialize.js +61 -0
  166. package/dist/structured_logging/stdio_capture.d.ts +59 -0
  167. package/dist/structured_logging/stdio_capture.d.ts.map +1 -0
  168. package/dist/structured_logging/stdio_capture.js +164 -0
  169. package/dist/structured_logging/uncaught.d.ts +14 -0
  170. package/dist/structured_logging/uncaught.d.ts.map +1 -0
  171. package/dist/structured_logging/uncaught.js +58 -0
  172. package/dist/structured_logging.d.ts +48 -0
  173. package/dist/structured_logging.d.ts.map +1 -0
  174. package/dist/structured_logging.js +47 -0
  175. package/dist/tls_client.d.ts +61 -0
  176. package/dist/tls_client.d.ts.map +1 -0
  177. package/dist/tls_client.js +298 -0
  178. package/dist/tool_api_error.d.ts +62 -0
  179. package/dist/tool_api_error.d.ts.map +1 -0
  180. package/dist/tool_api_error.js +399 -0
  181. package/dist/tool_memory_ownership.d.ts +10 -0
  182. package/dist/tool_memory_ownership.d.ts.map +1 -0
  183. package/dist/tool_memory_ownership.js +36 -0
  184. package/dist/toolpod_handlers.d.ts +126 -0
  185. package/dist/toolpod_handlers.d.ts.map +1 -0
  186. package/dist/toolpod_handlers.js +1016 -0
  187. package/dist/tracing/exporters.d.ts +51 -0
  188. package/dist/tracing/exporters.d.ts.map +1 -0
  189. package/dist/tracing/exporters.js +327 -0
  190. package/dist/tracing/index.d.ts +3 -0
  191. package/dist/tracing/index.d.ts.map +1 -0
  192. package/dist/tracing/index.js +2 -0
  193. package/dist/tracing/setup.d.ts +76 -0
  194. package/dist/tracing/setup.d.ts.map +1 -0
  195. package/dist/tracing/setup.js +436 -0
  196. package/dist/utils.d.ts +204 -0
  197. package/dist/utils.d.ts.map +1 -0
  198. package/dist/utils.js +867 -0
  199. package/dist/workflow/activity.d.ts +71 -0
  200. package/dist/workflow/activity.d.ts.map +1 -0
  201. package/dist/workflow/activity.js +357 -0
  202. package/dist/workflow/attempt.d.ts +12 -0
  203. package/dist/workflow/attempt.d.ts.map +1 -0
  204. package/dist/workflow/attempt.js +96 -0
  205. package/dist/workflow/client.d.ts +46 -0
  206. package/dist/workflow/client.d.ts.map +1 -0
  207. package/dist/workflow/client.js +299 -0
  208. package/dist/workflow/context.d.ts +37 -0
  209. package/dist/workflow/context.d.ts.map +1 -0
  210. package/dist/workflow/context.js +350 -0
  211. package/dist/workflow/heartbeat.d.ts +15 -0
  212. package/dist/workflow/heartbeat.d.ts.map +1 -0
  213. package/dist/workflow/heartbeat.js +78 -0
  214. package/dist/workflow/index.d.ts +14 -0
  215. package/dist/workflow/index.d.ts.map +1 -0
  216. package/dist/workflow/index.js +10 -0
  217. package/dist/workflow/memory.d.ts +17 -0
  218. package/dist/workflow/memory.d.ts.map +1 -0
  219. package/dist/workflow/memory.js +184 -0
  220. 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"}