@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,544 @@
1
+ /**
2
+ * Structured runtime helpers for reading `agent.yaml`.
3
+ *
4
+ * Lookup order is intentionally small and explicit:
5
+ * 1. `configPath` argument: exact `agent.yaml` path, or a directory that
6
+ * contains it
7
+ * 2. `AGENTIC_AGENT_CONFIG_PATH`: exact in-container file path baked into
8
+ * runtime images
9
+ * 3. `AGENTIC_AGENT_WORKDIR`: agent working directory used by generated
10
+ * local dev stacks
11
+ * 4. current working directory: supports tests and ad-hoc SDK usage
12
+ */
13
+ import * as fs from "node:fs";
14
+ import * as nodePath from "node:path";
15
+ import * as yaml from "js-yaml";
16
+ import { z } from "zod";
17
+ import { isPlatformEnvVar } from "./utils.js";
18
+ // =============================================================================
19
+ // Constants
20
+ // =============================================================================
21
+ /** Pydantic's `Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)]`. */
22
+ const NonEmptyString = z.string().trim().min(1);
23
+ // =============================================================================
24
+ // Feature flags
25
+ // =============================================================================
26
+ /**
27
+ * Runtime feature flags from `agent.yaml`.
28
+ *
29
+ * Keep field names in parity with Python `AgentFeatureConfig` and the CLI
30
+ * allowlist. Add a flag by adding a field here; `FeatureName` and runtime
31
+ * feature access derive from it. `null` means "omitted", letting the runtime
32
+ * fall back to legacy environment variables.
33
+ */
34
+ export const AgentFeatureConfigSchema = z
35
+ .object({
36
+ memory: z.boolean().nullable().default(null),
37
+ /**
38
+ * Whether guardrails are enforced for this agent at runtime. `null` (the
39
+ * default) means omitted, letting the runtime fall back to legacy behavior.
40
+ */
41
+ guardrails: z.boolean().nullable().default(null),
42
+ /**
43
+ * Whether the platform provisions playground UI for the agent
44
+ * (null/true = provisioned, today's behavior). When false — e.g. for
45
+ * non-chat agents with no conversation to preview — no playground is
46
+ * built or served; callers use the invoke API directly. Read at
47
+ * deploy/provisioning time only — no runtime effect.
48
+ */
49
+ playground: z.boolean().nullable().default(null),
50
+ /**
51
+ * Whether the agent uses the deep-agent (deepagents) harness. Gates the
52
+ * Tool Pod's built-in filesystem + shell handler registration so tenants
53
+ * that don't run deep agents get no filesystem/shell surface on their
54
+ * Tool Pod. The key stays snake_case `deep_agent` because `agent.yaml` is
55
+ * a cross-language artifact shared with the Python runtime and platform.
56
+ */
57
+ deep_agent: z.boolean().nullable().default(null),
58
+ /**
59
+ * Opt-in for author-defined streaming output shaping. When true the
60
+ * adapter runs the registered output parser and emits `custom_event`
61
+ * frames; off leaves the stream unchanged.
62
+ */
63
+ use_custom_parser: z.boolean().nullable().default(null),
64
+ /**
65
+ * Opt-in for OE-owned durable workflow. Omitted or false means the agent
66
+ * stays on native checkpoints; only an explicit true opts in. When set,
67
+ * OE uses this flag with the advertised language to sticky-assign the
68
+ * session's workflow authority.
69
+ */
70
+ durable_workflow: z.boolean().nullable().default(null),
71
+ })
72
+ // Python: `extra="ignore"` — Zod default behaviour already drops unknown keys.
73
+ .strip();
74
+ /**
75
+ * Return only the flags explicitly present in `agent.yaml` (omit unset /
76
+ * `null` fields). Mirrors Python's `AgentFeatureConfig.explicit()` — used by
77
+ * the AER's capability advertise to send OE only the flags the author
78
+ * actually set, before SDK-injected defaults (e.g. `owner_callback_fallback`)
79
+ * are layered on top.
80
+ */
81
+ export function explicitFeatures(features) {
82
+ const result = {};
83
+ for (const [name, value] of Object.entries(features)) {
84
+ if (typeof value === "boolean")
85
+ result[name] = value;
86
+ }
87
+ return result;
88
+ }
89
+ // =============================================================================
90
+ // SecretsConfig
91
+ // =============================================================================
92
+ // Under `tools`, `invoke_llm` is a reserved key: the platform runs model calls
93
+ // in a tool pod, so the LLM provider key(s) are declared there rather than
94
+ // under a user-defined tool.
95
+ export const SecretsConfigSchema = z
96
+ .object({
97
+ // Parsed for compatibility; ignored at enforcement.
98
+ disable_restriction: z.boolean().default(false),
99
+ aer: z.array(z.string()).default([]),
100
+ tools: z.record(z.string(), z.array(z.string())).default({}),
101
+ })
102
+ .strip();
103
+ // =============================================================================
104
+ // MCP config
105
+ // =============================================================================
106
+ //
107
+ // Port of `RuntimeMCPAuthConfig` / `RuntimeMCPServerConfig` / `RuntimeMCPConfig`
108
+ // in `agent_engine_runner_shared/agent_config.py`. Keep field names, defaults, and
109
+ // validation messages aligned with the Python side.
110
+ const FORBIDDEN_MCP_HEADER_KEYS = new Set([
111
+ "authorization",
112
+ "cookie",
113
+ "proxy-authorization",
114
+ ]);
115
+ const MCP_AUTH_FIELD_NAMES = [
116
+ "token_env",
117
+ "redirect_uri",
118
+ "client_name",
119
+ "scope",
120
+ "token_url",
121
+ "client_id_env",
122
+ "client_secret_env",
123
+ ];
124
+ const MCP_AUTH_REQUIRED_FIELDS = {
125
+ none: [],
126
+ bearer_env: ["token_env"],
127
+ oauth: [],
128
+ client_credentials: ["client_id_env", "client_secret_env"],
129
+ };
130
+ const MCP_AUTH_ALLOWED_FIELDS = {
131
+ none: [],
132
+ bearer_env: ["token_env"],
133
+ oauth: ["redirect_uri", "client_name", "scope"],
134
+ client_credentials: [
135
+ "token_url",
136
+ "client_id_env",
137
+ "client_secret_env",
138
+ "scope",
139
+ ],
140
+ };
141
+ function tryParseUrl(value) {
142
+ try {
143
+ return new URL(value);
144
+ }
145
+ catch {
146
+ return null;
147
+ }
148
+ }
149
+ export const RuntimeMCPAuthConfigSchema = z
150
+ .object({
151
+ type: z
152
+ .enum(["none", "bearer_env", "oauth", "client_credentials"])
153
+ .default("none"),
154
+ token_env: NonEmptyString.nullable().default(null),
155
+ redirect_uri: NonEmptyString.nullable().default(null),
156
+ client_name: NonEmptyString.nullable().default(null),
157
+ scope: NonEmptyString.nullable().default(null),
158
+ token_url: NonEmptyString.nullable().default(null),
159
+ client_id_env: NonEmptyString.nullable().default(null),
160
+ client_secret_env: NonEmptyString.nullable().default(null),
161
+ })
162
+ .strip()
163
+ .superRefine((value, ctx) => {
164
+ const configuredFields = MCP_AUTH_FIELD_NAMES.filter((name) => value[name] !== null);
165
+ const missing = (MCP_AUTH_REQUIRED_FIELDS[value.type] ?? [])
166
+ .filter((name) => !configuredFields.includes(name))
167
+ .sort();
168
+ if (missing.length > 0) {
169
+ const required = missing.map((name) => `auth.${name}`).join(", ");
170
+ ctx.addIssue({
171
+ code: "custom",
172
+ message: `mcp server auth.type ${value.type} requires fields: ${required}`,
173
+ });
174
+ }
175
+ const allowed = MCP_AUTH_ALLOWED_FIELDS[value.type] ?? [];
176
+ const unsupported = configuredFields
177
+ .filter((name) => !allowed.includes(name))
178
+ .sort();
179
+ if (unsupported.length > 0) {
180
+ const unsupportedStr = unsupported
181
+ .map((name) => `auth.${name}`)
182
+ .join(", ");
183
+ ctx.addIssue({
184
+ code: "custom",
185
+ message: `mcp server auth.type ${value.type} does not support fields: ${unsupportedStr}`,
186
+ });
187
+ }
188
+ if (value.type === "client_credentials" && value.token_url !== null) {
189
+ const parsedTokenUrl = tryParseUrl(value.token_url);
190
+ if (parsedTokenUrl === null ||
191
+ parsedTokenUrl.protocol !== "https:" ||
192
+ !parsedTokenUrl.hostname) {
193
+ ctx.addIssue({
194
+ code: "custom",
195
+ message: "mcp server auth.token_url must be an absolute https URL",
196
+ path: ["token_url"],
197
+ });
198
+ }
199
+ }
200
+ for (const field of [
201
+ "token_env",
202
+ "client_id_env",
203
+ "client_secret_env",
204
+ ]) {
205
+ const envVar = value[field];
206
+ // These fields are secret-indirection paths read from raw process.env
207
+ // and sent to the configured MCP server or token endpoint. Reject
208
+ // platform-owned names so tenant YAML cannot redirect platform secrets
209
+ // to outbound services.
210
+ if (envVar !== null && isPlatformEnvVar(envVar)) {
211
+ ctx.addIssue({
212
+ code: "custom",
213
+ message: "Please use a different environment variable name",
214
+ path: [field],
215
+ });
216
+ }
217
+ }
218
+ });
219
+ export const RuntimeMCPServerConfigSchema = z
220
+ .object({
221
+ transport: z.literal("streamable_http").default("streamable_http"),
222
+ url: NonEmptyString,
223
+ headers: z.record(z.string(), z.string()).default({}),
224
+ auth: RuntimeMCPAuthConfigSchema.default(RuntimeMCPAuthConfigSchema.parse({})),
225
+ allowed_tools: z.array(NonEmptyString).nullable().default(null),
226
+ timeout_seconds: z.number().int().gt(0).default(30),
227
+ })
228
+ .strip()
229
+ .superRefine((value, ctx) => {
230
+ const parsedUrl = tryParseUrl(value.url);
231
+ const validScheme = parsedUrl !== null &&
232
+ (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") &&
233
+ !!parsedUrl.hostname;
234
+ if (!validScheme) {
235
+ ctx.addIssue({
236
+ code: "custom",
237
+ message: "mcp server url must be an absolute http(s) URL",
238
+ path: ["url"],
239
+ });
240
+ }
241
+ else if (parsedUrl?.protocol === "http:" && value.auth.type !== "none") {
242
+ ctx.addIssue({
243
+ code: "custom",
244
+ message: "mcp server url must be https when auth.type is set",
245
+ path: ["url"],
246
+ });
247
+ }
248
+ const forbiddenHeaders = Object.keys(value.headers)
249
+ .filter((header) => FORBIDDEN_MCP_HEADER_KEYS.has(header.toLowerCase()))
250
+ .sort();
251
+ if (forbiddenHeaders.length > 0) {
252
+ ctx.addIssue({
253
+ code: "custom",
254
+ message: "mcp server headers must not include credential headers: " +
255
+ `[${forbiddenHeaders.map((h) => `'${h}'`).join(", ")}]; use auth.token_env instead`,
256
+ path: ["headers"],
257
+ });
258
+ }
259
+ });
260
+ // A server name with zero alphanumeric characters (e.g. "---") sanitizes to
261
+ // an empty string in mcp_tools.ts's `makeMcpSdkToolName` (used to build the
262
+ // SDK-visible tool name), which only fails at discovery/call time. Reject it
263
+ // here instead so a malformed `mcp.servers` key fails fast at config load.
264
+ const MCP_SERVER_NAME_HAS_ALNUM_RE = /[a-zA-Z0-9]/;
265
+ export const RuntimeMCPConfigSchema = z
266
+ .object({
267
+ servers: z.record(NonEmptyString, RuntimeMCPServerConfigSchema).default({}),
268
+ })
269
+ .strip()
270
+ .superRefine((value, ctx) => {
271
+ for (const serverName of Object.keys(value.servers)) {
272
+ if (!MCP_SERVER_NAME_HAS_ALNUM_RE.test(serverName)) {
273
+ ctx.addIssue({
274
+ code: "custom",
275
+ message: `mcp server name '${serverName}' must contain alphanumeric characters`,
276
+ path: ["servers", serverName],
277
+ });
278
+ }
279
+ }
280
+ });
281
+ // =============================================================================
282
+ // `${VAR}` interpolation
283
+ // =============================================================================
284
+ //
285
+ // Port of `_interpolate_env_vars` / `_INTERPOLATABLE_PATHS` in
286
+ // `agent_engine_runner_shared/agent_config.py`. Intentionally narrow: only
287
+ // `mcp.servers.*.url` is interpolatable today. Adding a path is one array
288
+ // entry plus a happy-path test.
289
+ const INTERPOLATABLE_PATHS = [
290
+ ["mcp", "servers", "*", "url"],
291
+ ];
292
+ // Pattern for a well-formed `${VAR}` substitution token.
293
+ const VAR_REFERENCE_SOURCE = String.raw `\$\{([A-Za-z_][A-Za-z0-9_]*)\}`;
294
+ // Captures any `${...}` token, well-formed or not, so malformed markers
295
+ // (`${VAR` unclosed, `${}` empty, `${1bad}` invalid identifier, `${VAR with
296
+ // spaces}`) can be rejected with a clear error instead of flowing through to
297
+ // the URL validator above.
298
+ const ANY_VAR_REFERENCE_SOURCE = String.raw `\$\{[^}]*\}?`;
299
+ function pathIsInterpolatable(path) {
300
+ return INTERPOLATABLE_PATHS.some((pattern) => pattern.length === path.length &&
301
+ pattern.every((segment, i) => segment === "*" || segment === path[i]));
302
+ }
303
+ function interpolateEnvVars(data, envVars, path = []) {
304
+ if (Array.isArray(data)) {
305
+ return data.map((item, index) => interpolateEnvVars(item, envVars, [...path, `[${index}]`]));
306
+ }
307
+ if (data !== null && typeof data === "object") {
308
+ const result = {};
309
+ for (const [key, value] of Object.entries(data)) {
310
+ result[key] = interpolateEnvVars(value, envVars, [...path, key]);
311
+ }
312
+ return result;
313
+ }
314
+ if (typeof data !== "string" || !pathIsInterpolatable(path)) {
315
+ return data;
316
+ }
317
+ const yamlPath = path.join(".");
318
+ // Reject malformed markers before the envVars branch so the error is the
319
+ // same with or without a mapping.
320
+ for (const marker of data.matchAll(new RegExp(ANY_VAR_REFERENCE_SOURCE, "g"))) {
321
+ if (!new RegExp(`^${VAR_REFERENCE_SOURCE}$`).test(marker[0])) {
322
+ throw new Error(`agent.yaml at ${yamlPath} has malformed environment variable reference ` +
323
+ `${JSON.stringify(marker[0])}; expected \${VAR} where VAR is a valid identifier`);
324
+ }
325
+ }
326
+ if (envVars === undefined) {
327
+ if (new RegExp(VAR_REFERENCE_SOURCE).test(data)) {
328
+ throw new Error(`agent.yaml at ${yamlPath} contains \${...} but environment variable ` +
329
+ "interpolation is not enabled for this load. The runtime launcher " +
330
+ "must pass envVars= to loadRuntimeAgentConfig().");
331
+ }
332
+ return data;
333
+ }
334
+ const unset = [];
335
+ const substituted = data.replace(new RegExp(VAR_REFERENCE_SOURCE, "g"), (_match, name) => {
336
+ if (name in envVars)
337
+ return envVars[name] ?? "";
338
+ unset.push(name);
339
+ return "";
340
+ });
341
+ if (unset.length > 0) {
342
+ // Report every unset name in one error so a tenant fixing a multi-var URL
343
+ // doesn't have to redeploy once per missing var. Deduplicate while
344
+ // preserving first-seen order for stable messages.
345
+ const seen = new Set();
346
+ const names = [];
347
+ for (const name of unset) {
348
+ if (!seen.has(name)) {
349
+ seen.add(name);
350
+ names.push(`\${${name}}`);
351
+ }
352
+ }
353
+ throw new Error(`agent.yaml at ${yamlPath} references unset environment variables: ${names.join(", ")}`);
354
+ }
355
+ return substituted;
356
+ }
357
+ // =============================================================================
358
+ // _AgentFileConfig — internal raw-file shape
359
+ // =============================================================================
360
+ const AgentFileConfigSchema = z
361
+ .object({
362
+ entrypoint: NonEmptyString.optional(),
363
+ language: z.string().nullable().optional(),
364
+ framework: z.string().nullable().optional(),
365
+ /**
366
+ * `config` is mostly application-owned. The runtime exposes LLM hints
367
+ * while keeping platform-owned keys out of the provider-specific
368
+ * pass-through map. Python uses `dict[str, Any] | None = None`.
369
+ */
370
+ features: AgentFeatureConfigSchema.default({
371
+ memory: null,
372
+ guardrails: null,
373
+ playground: null,
374
+ deep_agent: null,
375
+ use_custom_parser: null,
376
+ durable_workflow: null,
377
+ }),
378
+ required_secrets: SecretsConfigSchema.default({
379
+ disable_restriction: false,
380
+ aer: [],
381
+ tools: {},
382
+ }),
383
+ mcp: RuntimeMCPConfigSchema.default({ servers: {} }),
384
+ })
385
+ .strip();
386
+ // =============================================================================
387
+ // RuntimeAgentConfig
388
+ // =============================================================================
389
+ /**
390
+ * Validated runtime view of `agent.yaml`.
391
+ *
392
+ * Class-based because Python uses computed `@property` accessors and helper
393
+ * methods (`feature_enabled`, `configured_feature`). Properties are exposed as
394
+ * TS getters under camelCase names; Python field names on
395
+ * `AgentFeatureConfig` are preserved verbatim so wire/log parity is
396
+ * maintained.
397
+ */
398
+ export class RuntimeAgentConfig {
399
+ path;
400
+ entrypoint;
401
+ /** Runtime language from agent.yaml; omitted means unset at advertise time. */
402
+ language;
403
+ /** Application framework from agent.yaml; omitted means unset at advertise time. */
404
+ framework;
405
+ features;
406
+ requiredSecrets;
407
+ mcp;
408
+ constructor(init = {}) {
409
+ this.path = init.path ?? null;
410
+ this.entrypoint = init.entrypoint ?? null;
411
+ this.language = init.language ?? null;
412
+ this.framework = init.framework ?? null;
413
+ this.features = init.features ?? AgentFeatureConfigSchema.parse({});
414
+ this.requiredSecrets = SecretsConfigSchema.parse(init.requiredSecrets ?? {});
415
+ this.mcp = init.mcp ?? RuntimeMCPConfigSchema.parse({});
416
+ }
417
+ /** Return a feature flag value, falling back to `defaultValue` when omitted. */
418
+ featureEnabled(name, defaultValue = false) {
419
+ const value = this.configuredFeature(name);
420
+ if (value === null)
421
+ return defaultValue;
422
+ return value;
423
+ }
424
+ /** Return the explicit feature value from `agent.yaml`, if it exists. */
425
+ configuredFeature(name) {
426
+ // Normalize absent keys to null (Python getattr returns None) — a
427
+ // RuntimeAgentConfig constructed with a plain object won't have schema
428
+ // defaults applied.
429
+ return this.features[name] ?? null;
430
+ }
431
+ }
432
+ // =============================================================================
433
+ // Config file discovery + loading
434
+ // =============================================================================
435
+ function candidateLocations(configPath) {
436
+ if (configPath !== undefined) {
437
+ // Explicit override from the caller. Accept the exact file path or the
438
+ // directory that contains `agent.yaml`.
439
+ return [configPath];
440
+ }
441
+ const candidates = [];
442
+ for (const raw of [
443
+ // Built/runtime images set the exact in-container `agent.yaml` path.
444
+ process.env["AGENTIC_AGENT_CONFIG_PATH"],
445
+ // Local dev stacks set the agent workspace directory.
446
+ process.env["AGENTIC_AGENT_WORKDIR"],
447
+ // Tests and ad-hoc usage often run directly from the agent directory.
448
+ process.cwd(),
449
+ ]) {
450
+ if (!raw)
451
+ continue;
452
+ if (!candidates.includes(raw))
453
+ candidates.push(raw);
454
+ }
455
+ return candidates;
456
+ }
457
+ function configFileForLocation(location) {
458
+ if (nodePath.basename(location) === "agent.yaml")
459
+ return location;
460
+ return nodePath.join(location, "agent.yaml");
461
+ }
462
+ function discoverAgentConfigPath(configPath) {
463
+ for (const candidate of candidateLocations(configPath)) {
464
+ const filePath = configFileForLocation(candidate);
465
+ try {
466
+ if (fs.statSync(filePath).isFile())
467
+ return filePath;
468
+ }
469
+ catch {
470
+ // ENOENT or similar — try the next candidate.
471
+ }
472
+ }
473
+ return null;
474
+ }
475
+ /**
476
+ * Load and validate `agent.yaml` for runtime use.
477
+ *
478
+ * Missing files are treated as an empty config so unit tests and ad-hoc
479
+ * SDK usage can still construct `App`/`TenantRuntime` outside generated
480
+ * runtime environments. When a file exists, the runtime validates the
481
+ * fields it owns directly (entrypoint/features) while passing the
482
+ * application-owned `config` block through as raw data.
483
+ *
484
+ * `envVars` is the substitution mapping used to resolve `${VAR}` references
485
+ * at allowlisted YAML paths (currently `mcp.servers.*.url`). Pass the
486
+ * tenant-owned subset of the process environment — via `tenantEnvVars()` —
487
+ * never raw `process.env`, so tenant `agent.yaml` cannot dereference platform
488
+ * secrets. When `envVars` is `undefined` and a `${...}` reference is present
489
+ * at an allowlisted path, the loader throws so the misconfiguration is
490
+ * visible instead of falling through to `new URL()` with the literal string.
491
+ */
492
+ export function loadRuntimeAgentConfig(configPath, envVars) {
493
+ const path = discoverAgentConfigPath(configPath);
494
+ if (path === null)
495
+ return new RuntimeAgentConfig();
496
+ let text;
497
+ try {
498
+ text = fs.readFileSync(path, { encoding: "utf-8" });
499
+ }
500
+ catch (err) {
501
+ if (err.code === "ENOENT") {
502
+ return new RuntimeAgentConfig();
503
+ }
504
+ throw err;
505
+ }
506
+ let data;
507
+ try {
508
+ data = yaml.load(text) ?? {};
509
+ }
510
+ catch (err) {
511
+ throw new Error(`Invalid YAML in agent config at ${path}: ${err.message}`, { cause: err });
512
+ }
513
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
514
+ throw new Error(`agent.yaml at ${path} must contain a top-level mapping`);
515
+ }
516
+ try {
517
+ data = interpolateEnvVars(data, envVars);
518
+ }
519
+ catch (err) {
520
+ throw new Error(`Invalid agent.yaml at ${path}: ${err.message}`, {
521
+ cause: err,
522
+ });
523
+ }
524
+ let parsed;
525
+ try {
526
+ parsed = AgentFileConfigSchema.parse(data);
527
+ }
528
+ catch (err) {
529
+ throw new Error(`Invalid agent.yaml at ${path}: ${err.message}`, {
530
+ cause: err,
531
+ });
532
+ }
533
+ const language = typeof parsed.language === "string" ? parsed.language.trim() : null;
534
+ const framework = typeof parsed.framework === "string" ? parsed.framework.trim() : null;
535
+ return new RuntimeAgentConfig({
536
+ path,
537
+ entrypoint: typeof parsed.entrypoint === "string" ? parsed.entrypoint.trim() : null,
538
+ language: language || null,
539
+ framework: framework || null,
540
+ features: parsed.features,
541
+ requiredSecrets: parsed.required_secrets,
542
+ mcp: parsed.mcp,
543
+ });
544
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Frozen interrupt-artifact wire key.
3
+ *
4
+ * Dependency leaf (no imports): `workflow/memory.ts` and `secure_wrapper.ts`
5
+ * both consume this without forming a module cycle through the workflow
6
+ * barrel. The key is persisted into checkpoints and replay logs, and
7
+ * duplicated in runner-shared/src/agent_engine_runner_shared/secure_wrapper.py. Changing
8
+ * either value breaks interrupt detection on already-checkpointed sessions
9
+ * and/or cross-language parity — keep the two in lockstep.
10
+ */
11
+ export declare const CALL_INTERRUPTED_ARTIFACT_KEY = "__agent_engine_oe_call_interrupted__";
12
+ //# sourceMappingURL=call_interrupted.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call_interrupted.d.ts","sourceRoot":"","sources":["../src/call_interrupted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,yCACF,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Frozen interrupt-artifact wire key.
3
+ *
4
+ * Dependency leaf (no imports): `workflow/memory.ts` and `secure_wrapper.ts`
5
+ * both consume this without forming a module cycle through the workflow
6
+ * barrel. The key is persisted into checkpoints and replay logs, and
7
+ * duplicated in runner-shared/src/agent_engine_runner_shared/secure_wrapper.py. Changing
8
+ * either value breaks interrupt detection on already-checkpointed sessions
9
+ * and/or cross-language parity — keep the two in lockstep.
10
+ */
11
+ export const CALL_INTERRUPTED_ARTIFACT_KEY = "__agent_engine_oe_call_interrupted__";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Checkpoint `thread_id` workspace scope — shared by write and read paths.
3
+ *
4
+ * Mirrors Python's `agent_engine_runner_shared/checkpoint_workspace.py`.
5
+ */
6
+ /** Resolve the workspace scope used for LangGraph checkpoint thread_ids. */
7
+ export declare function resolveCheckpointWorkspaceId(wireWorkspaceId?: string | null): string | null;
8
+ /**
9
+ * Remember the wire `workspace_id` from `/execute` for query reads.
10
+ *
11
+ * Query routes carry no workspace identifier; when `APP_ID` is unset the read
12
+ * path falls back to the most recently observed wire value.
13
+ */
14
+ export declare function noteCheckpointWireWorkspaceId(wireWorkspaceId?: string | null): void;
15
+ /**
16
+ * Resolved workspace scope for checkpoint reads (matches write path).
17
+ *
18
+ * Returns "" only for intentionally unscoped local runtimes. Managed AERs
19
+ * carry REQUIRE_PROJECT_SCOPED_DB and require APP_ID; they deliberately reject
20
+ * the wire fallback if APP_ID is missing.
21
+ */
22
+ export declare function getCheckpointWorkspaceId(): string;
23
+ /** Test helper — reset module state between cases. */
24
+ export declare function resetCheckpointWorkspaceState(): void;
25
+ //# sourceMappingURL=checkpoint_workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint_workspace.d.ts","sourceRoot":"","sources":["../src/checkpoint_workspace.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,4EAA4E;AAC5E,wBAAgB,4BAA4B,CAC1C,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAC9B,MAAM,GAAG,IAAI,CAUf;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAC9B,IAAI,CAIN;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,sDAAsD;AACtD,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Checkpoint `thread_id` workspace scope — shared by write and read paths.
3
+ *
4
+ * Mirrors Python's `agent_engine_runner_shared/checkpoint_workspace.py`.
5
+ */
6
+ import { projectScopingRequired } from "./db_naming.js";
7
+ let checkpointWireWorkspaceId = null;
8
+ /** Resolve the workspace scope used for LangGraph checkpoint thread_ids. */
9
+ export function resolveCheckpointWorkspaceId(wireWorkspaceId) {
10
+ const appId = process.env["APP_ID"];
11
+ if (appId)
12
+ return appId;
13
+ if (projectScopingRequired()) {
14
+ throw new Error("checkpoint workspace scope is required but APP_ID is not set");
15
+ }
16
+ if (wireWorkspaceId)
17
+ return wireWorkspaceId;
18
+ return null;
19
+ }
20
+ /**
21
+ * Remember the wire `workspace_id` from `/execute` for query reads.
22
+ *
23
+ * Query routes carry no workspace identifier; when `APP_ID` is unset the read
24
+ * path falls back to the most recently observed wire value.
25
+ */
26
+ export function noteCheckpointWireWorkspaceId(wireWorkspaceId) {
27
+ if (wireWorkspaceId) {
28
+ checkpointWireWorkspaceId = wireWorkspaceId;
29
+ }
30
+ }
31
+ /**
32
+ * Resolved workspace scope for checkpoint reads (matches write path).
33
+ *
34
+ * Returns "" only for intentionally unscoped local runtimes. Managed AERs
35
+ * carry REQUIRE_PROJECT_SCOPED_DB and require APP_ID; they deliberately reject
36
+ * the wire fallback if APP_ID is missing.
37
+ */
38
+ export function getCheckpointWorkspaceId() {
39
+ return resolveCheckpointWorkspaceId(checkpointWireWorkspaceId) ?? "";
40
+ }
41
+ /** Test helper — reset module state between cases. */
42
+ export function resetCheckpointWorkspaceState() {
43
+ checkpointWireWorkspaceId = null;
44
+ }