@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,351 @@
1
+ /**
2
+ * TenantRuntime — framework-agnostic platform runtime for tenant agent
3
+ * applications.
4
+ *
5
+ * Port of `agent_engine_runner_shared/runtime.py`. Same Python class shape: the runtime
6
+ * discovers its role from `RUNNER_MODE`, wires logging / tracing / tools,
7
+ * and delegates to a framework SDK's `App` (the `GraphBuilderLike`) for
8
+ * graph construction.
9
+ *
10
+ * Scope deltas vs. Python (see `AGENTS.md` for rationale):
11
+ * - No memory-server mode (`RuntimeMode` has `AER`, `TOOL`, and `TOOL_FUNCTION`).
12
+ * - `setupTracing` owns the MongoClient build (async). Python builds it
13
+ * inline in `_setup_tracing` (sync pymongo).
14
+ * - `shutdown()` and SIGTERM/SIGINT wiring are TS additions — Python's
15
+ * uvicorn handles signals itself; here we explicitly close the tracing
16
+ * MongoClient before exit.
17
+ */
18
+ import { loadRuntimeAgentConfig } from "./agent_config.js";
19
+ import { getCurrentSessionId, getCurrentUserId } from "./context.js";
20
+ import { getLogger, setupLogging } from "./logger.js";
21
+ import { AppBoundRuntime } from "./memory_appbound.js";
22
+ import { MemoryWriter } from "./memory_writer.js";
23
+ import { AERServer } from "./server/aer.js";
24
+ import { ToolFunctionRunner } from "./server/function.js";
25
+ import { ToolServer } from "./server/tool.js";
26
+ import { scrubCredentials, setupTracing, shutdownTracing, } from "./tracing/index.js";
27
+ import { getEnvBool, getEnvInt, getRuntimeMode, RuntimeMode, tenantEnvVars, } from "./utils.js";
28
+ const logger = getLogger("agent_engine_runner_shared.runtime");
29
+ /**
30
+ * Resolve the listen host from `APP_HOST`, defaulting to `"0.0.0.0"`.
31
+ *
32
+ * Matches Python's `_resolve_listen_host`: ECP stamps `APP_HOST` per
33
+ * executor type at deploy time (`"::"` for vm-mode, `"0.0.0.0"` for
34
+ * container-mode). Empty string normalises to the default — a shell that
35
+ * exports `APP_HOST=""` must not bind to `""`.
36
+ */
37
+ export function resolveListenHost() {
38
+ return process.env["APP_HOST"] || "0.0.0.0";
39
+ }
40
+ /** Resolve the server drain budget without changing the existing Tool policy. */
41
+ export function shutdownGracePeriodMs(mode) {
42
+ if (mode !== RuntimeMode.AER) {
43
+ return getEnvInt("SHUTDOWN_GRACE_PERIOD_MS", 25000);
44
+ }
45
+ const raw = process.env["SHUTDOWN_GRACE_PERIOD_MS"];
46
+ if (!raw)
47
+ return 25000;
48
+ const normalized = raw.trim();
49
+ if (!/^\+?\d+$/.test(normalized)) {
50
+ throw new Error("SHUTDOWN_GRACE_PERIOD_MS must be a positive integer in milliseconds");
51
+ }
52
+ const milliseconds = Number(normalized);
53
+ if (!Number.isSafeInteger(milliseconds) || milliseconds <= 0) {
54
+ throw new Error("SHUTDOWN_GRACE_PERIOD_MS must be a positive integer in milliseconds");
55
+ }
56
+ return milliseconds;
57
+ }
58
+ const SUPPORTED_RUN_KWARGS = new Set(["logLevel"]);
59
+ // Reserved slot: kwargs listed here are accepted-but-ignored (warned in
60
+ // `normalizeRunKwargs`) rather than rejected as unexpected. Empty for now —
61
+ // no run kwarg is currently deprecated; future ones get added here.
62
+ const DEPRECATED_RUN_KWARGS = new Set([]);
63
+ /**
64
+ * Tenant Runtime SDK.
65
+ *
66
+ * Constructed once per process. `registerAndRun` wires the framework SDK's
67
+ * graph builder and starts the per-mode Fastify server.
68
+ */
69
+ export class TenantRuntime {
70
+ appName;
71
+ appVersion;
72
+ mode;
73
+ orgId;
74
+ projectId;
75
+ memoryWriter;
76
+ graphBuilder = null;
77
+ tools = {};
78
+ toolDefinitions = {};
79
+ agentConfig;
80
+ mongodbUri;
81
+ databaseNameOverride;
82
+ tracesCollectionName;
83
+ // Guards `shutdown()` against concurrent signal-handler invocations.
84
+ shutdownStarted = false;
85
+ // In-flight tracing setup. `initTracing` kicks the async Mongo wiring off in
86
+ // the constructor; `runAsync` awaits this before binding the server so the
87
+ // global tracer provider is attached before the first request can land
88
+ // (matching the deterministic effect of Python's synchronous `_setup_tracing`).
89
+ tracingReady = Promise.resolve();
90
+ constructor(opts = {}) {
91
+ const { appName = "Agent", appVersion = "1.0.0", mongodbUri, databaseName, tracesCollectionName = "traces", orgId, projectId, } = opts;
92
+ this.appName = appName;
93
+ this.appVersion = appVersion;
94
+ this.mode = getRuntimeMode();
95
+ // org/project come exclusively from the platform-injected env vars. The
96
+ // `orgId`/`projectId` options are deprecated and intentionally ignored —
97
+ // honoring an author-supplied override is what caused the silent
98
+ // tenant-mismatch bug. Use `||` not `??` so an empty-string env
99
+ // var falls through to null; otherwise `ORG_ID=""` would slip past the
100
+ // `=== null` scoping guards downstream.
101
+ this.orgId = process.env["ORG_ID"] || null;
102
+ this.projectId = process.env["PROJECT_ID"] || null;
103
+ // Logging first so subsequent setup phases land in the configured sink.
104
+ setupLogging({ appName, mode: this.mode });
105
+ // Deprecation warnings emitted after setupLogging so the log line routes
106
+ // through the configured sink (mirrors the Python ordering).
107
+ if (orgId != null) {
108
+ logger.warn("TenantRuntime({ orgId }) is deprecated and ignored. " +
109
+ "Set the ORG_ID environment variable instead; " +
110
+ "this option will be removed in a future release.");
111
+ }
112
+ if (projectId != null) {
113
+ logger.warn("TenantRuntime({ projectId }) is deprecated and ignored. " +
114
+ "Set the PROJECT_ID environment variable instead; " +
115
+ "this option will be removed in a future release.");
116
+ }
117
+ this.agentConfig = loadRuntimeAgentConfig(undefined, tenantEnvVars());
118
+ const configuredMemory = this.agentConfig.configuredFeature("memory");
119
+ const memoryEnabled = configuredMemory ?? getEnvBool("ENABLE_MEMORY", false);
120
+ this.memoryWriter = memoryEnabled
121
+ ? new MemoryWriter(new AppBoundRuntime())
122
+ : null;
123
+ this.mongodbUri = mongodbUri ?? process.env["MONGODB_URI"] ?? null;
124
+ this.databaseNameOverride = databaseName || undefined;
125
+ this.tracesCollectionName = tracesCollectionName;
126
+ // Tracing is always on so every runtime mode emits consistent spans.
127
+ this.initTracing();
128
+ logger.info(`TenantRuntime initialized: mode=${this.mode}, app=${appName}`);
129
+ }
130
+ // =========================================================================
131
+ // Accessors
132
+ // =========================================================================
133
+ /** Parsed `agent.yaml` configuration. */
134
+ getAgentConfig() {
135
+ return this.agentConfig;
136
+ }
137
+ /** Resolved MongoDB URI: constructor option > `MONGODB_URI` env var > null. */
138
+ getMongodbUri() {
139
+ return this.mongodbUri;
140
+ }
141
+ // =========================================================================
142
+ // Tracing setup
143
+ // =========================================================================
144
+ initTracing() {
145
+ // Python builds the Mongo collection synchronously here, but Node's
146
+ // `mongodb` driver `connect()` is async — so the collection wiring lives
147
+ // inside `setupTracing`. Kick it off from the constructor and retain the
148
+ // promise: `runAsync` awaits `tracingReady` before binding the server, so
149
+ // the global tracer provider is attached before the first request lands
150
+ // (deterministic, unlike a bare fire-and-forget). A failed setup is logged
151
+ // and swallowed so a degraded trace store never blocks serving traffic.
152
+ this.tracingReady = setupTracing({
153
+ serviceName: this.appName,
154
+ mongodbUri: this.mongodbUri,
155
+ mongodbDatabaseName: this.databaseNameOverride,
156
+ mongodbCollectionName: this.tracesCollectionName,
157
+ }).catch((e) => {
158
+ // Scrub defensively: a setup failure whose message echoes the raw store
159
+ // URI must never write its credentials to the log sink. Rejections may
160
+ // carry non-Error values (string throws) — keep their text so it gets
161
+ // scrubbed rather than dropped.
162
+ logger.warn(`Tracing setup failed: ${scrubCredentials(e?.message ?? e)}`);
163
+ });
164
+ }
165
+ // =========================================================================
166
+ // Agent / graph builder
167
+ // =========================================================================
168
+ getAgent(opts) {
169
+ if (this.graphBuilder === null) {
170
+ throw new Error("No App registered. Call registerAndRun() with a BaseApp instance first.");
171
+ }
172
+ if (typeof this.graphBuilder.getAgent !== "function") {
173
+ throw new Error("Graph builder must be a BaseApp instance with getAgent(). " +
174
+ "Plain callables are no longer supported.");
175
+ }
176
+ return this.graphBuilder.getAgent(opts);
177
+ }
178
+ warmUpAgent() {
179
+ this.graphBuilder?.warmUp?.();
180
+ }
181
+ // =========================================================================
182
+ // Context helpers
183
+ // =========================================================================
184
+ /**
185
+ * Get the current user_id from execution context.
186
+ *
187
+ * @returns User ID from the current execution context, or null if not available
188
+ */
189
+ getCurrentUserId() {
190
+ return getCurrentUserId();
191
+ }
192
+ getCurrentSessionId() {
193
+ return getCurrentSessionId();
194
+ }
195
+ // =========================================================================
196
+ // Tool registration
197
+ // =========================================================================
198
+ /**
199
+ * Register a raw tool function and its metadata.
200
+ *
201
+ * This stores the function for Tool Pod execution and the metadata
202
+ * for routing decisions. It does NOT create any framework-specific
203
+ * tool objects — that is the responsibility of the framework SDK.
204
+ *
205
+ * @param name Tool name
206
+ * @param func Raw tool function
207
+ * @param metadata Tool metadata (is_local, network, timeout, etc.)
208
+ */
209
+ registerTool(name, func, metadata) {
210
+ this.tools[name] = func;
211
+ this.toolDefinitions[name] = metadata;
212
+ logger.debug(`Registered tool: ${name}`);
213
+ }
214
+ /**
215
+ * Get metadata for a registered tool.
216
+ *
217
+ * @param name Tool name
218
+ * @returns Tool metadata object (is_local, network, timeout, etc.)
219
+ * or empty object if tool not found.
220
+ */
221
+ getToolMetadata(name) {
222
+ return this.toolDefinitions[name] ?? {};
223
+ }
224
+ // =========================================================================
225
+ // Shutdown
226
+ // =========================================================================
227
+ /**
228
+ * Close the tracing MongoClient. Idempotent — safe to call from multiple
229
+ * signal handlers. TS-only addition: Python's uvicorn handles SIGTERM
230
+ * itself, so the Python `TenantRuntime` has no equivalent method.
231
+ */
232
+ async shutdown() {
233
+ if (this.shutdownStarted)
234
+ return;
235
+ this.shutdownStarted = true;
236
+ logger.info("TenantRuntime shutdown initiated");
237
+ try {
238
+ await shutdownTracing();
239
+ }
240
+ catch (e) {
241
+ logger.warn(`Tracing shutdown failed: ${e.message}`);
242
+ }
243
+ logger.info("TenantRuntime shutdown complete");
244
+ }
245
+ // =========================================================================
246
+ // Run
247
+ // =========================================================================
248
+ /**
249
+ * Register the graph builder and start the mode-specific server.
250
+ *
251
+ * Resolves once Fastify is listening — the open socket keeps the Node
252
+ * event loop alive, matching the practical effect of Python's
253
+ * `asyncio.run(uvicorn.serve())` blocking until shutdown.
254
+ */
255
+ async registerAndRun(graphBuilder, options = {}) {
256
+ if (graphBuilder) {
257
+ this.graphBuilder = graphBuilder;
258
+ logger.info("Graph builder registered");
259
+ }
260
+ const normalized = this.normalizeRunKwargs(options);
261
+ await this.runAsync(normalized);
262
+ }
263
+ normalizeRunKwargs(opts) {
264
+ const raw = opts;
265
+ const provided = Object.keys(raw).filter((k) => raw[k] !== undefined);
266
+ const unexpected = provided
267
+ .filter((k) => !SUPPORTED_RUN_KWARGS.has(k) && !DEPRECATED_RUN_KWARGS.has(k))
268
+ .sort();
269
+ if (unexpected.length > 0) {
270
+ if (unexpected.length === 1) {
271
+ throw new TypeError(`registerAndRun() got an unexpected keyword argument '${unexpected[0] ?? ""}'`);
272
+ }
273
+ const formatted = unexpected.map((k) => `'${k}'`).join(", ");
274
+ throw new TypeError(`registerAndRun() got unexpected keyword arguments: ${formatted}`);
275
+ }
276
+ // No deprecated run kwargs are currently accepted. When one is added to
277
+ // DEPRECATED_RUN_KWARGS, warn-and-ignore it here.
278
+ const result = {};
279
+ if (opts.logLevel !== undefined)
280
+ result.logLevel = opts.logLevel;
281
+ return result;
282
+ }
283
+ async runAsync(_opts) {
284
+ // `logLevel` is accepted for Python parity (uvicorn's log_level) but is
285
+ // not yet wired to Fastify, which has no equivalent knob. When the wiring
286
+ // is added, resolve it as:
287
+ // _opts.logLevel ?? process.env["LOG_LEVEL"] ?? "info"
288
+ // Block startup on tracing init so the global tracer provider is attached
289
+ // before the listener accepts requests — otherwise early spans fall through
290
+ // to the OTel no-op tracer and are silently dropped. Setup failures are
291
+ // already logged-and-swallowed in `initTracing`, so this never rejects.
292
+ await this.tracingReady;
293
+ // Function mode is not a server: one invocation, report result, return.
294
+ // Must branch before the server dispatch so no HTTP listener is bound.
295
+ // graphBuilder.ready() runs inside ToolFunctionRunner after the request
296
+ // is read, so startup failures still carry platform_trace_id.
297
+ if (this.mode === RuntimeMode.TOOL_FUNCTION) {
298
+ try {
299
+ await new ToolFunctionRunner(this).run();
300
+ }
301
+ finally {
302
+ // BatchSpanProcessor's periodic flush timer is unref'd (so it can't
303
+ // pin a long-running server's event loop) — but that same unref
304
+ // means nothing keeps a one-shot function-mode process alive long
305
+ // enough for that timer to ever fire. Without an explicit shutdown
306
+ // here, this invocation's spans (Mongo and OTLP)
307
+ // are silently lost the moment the process exits. Runs even when
308
+ // run() throws, so a failed invocation's spans aren't lost either.
309
+ await this.shutdown();
310
+ }
311
+ return;
312
+ }
313
+ // Block startup on the graph builder's own async setup (e.g. MCP tool
314
+ // discovery) so no request can land before tools are registered.
315
+ await this.graphBuilder?.ready?.();
316
+ const ServerClass = this.mode === RuntimeMode.AER ? AERServer : ToolServer;
317
+ const server = new ServerClass(this);
318
+ const port = getEnvInt("APP_PORT", server.defaultPort);
319
+ const host = resolveListenHost();
320
+ logger.info(`listener bind resolved: host='${host}' port=${port}`);
321
+ // K8s sends SIGTERM on pod termination. Drain the Fastify server first so
322
+ // in-flight /execute and /invoke_llm requests can finish — emitting their
323
+ // terminal stream chunks and /executor/callback reports — before we tear
324
+ // down tracing and exit. Python gets this for free from uvicorn's graceful
325
+ // shutdown; Node needs it wired explicitly. `once` so a repeated signal
326
+ // doesn't re-enter (shutdown() is itself re-entry safe). If draining
327
+ // exceeds the grace budget we exit anyway so termination can't hang.
328
+ const gracePeriodMs = shutdownGracePeriodMs(this.mode);
329
+ const onSignal = (signal) => {
330
+ logger.info(`Received ${signal}, draining server (grace=${gracePeriodMs}ms)...`);
331
+ const drainAndExit = async () => {
332
+ try {
333
+ const graceTimer = new Promise((resolve) => {
334
+ const t = setTimeout(resolve, gracePeriodMs);
335
+ if (typeof t.unref === "function")
336
+ t.unref();
337
+ });
338
+ await Promise.race([server.close(), graceTimer]);
339
+ }
340
+ catch (e) {
341
+ logger.warn(`Server drain error: ${e.message}`);
342
+ }
343
+ await this.shutdown();
344
+ };
345
+ void drainAndExit().finally(() => process.exit(0));
346
+ };
347
+ process.once("SIGTERM", onSignal);
348
+ process.once("SIGINT", onSignal);
349
+ await server.run(host, port);
350
+ }
351
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * SecureLLMProxy — framework-neutral proxy for secure LLM calls through OE.
3
+ *
4
+ * Packages intercepted invoke_llm requests for the Orchestration Engine and
5
+ * unwraps the streamed OE relay back into agent-engine-sdk models. OE owns approval,
6
+ * routing, live SSE relay, and final audit/result recording.
7
+ */
8
+ import type { LLMStreamChunk, LLMInvocationOptions } from "@mongodb-js/agent-engine-sdk";
9
+ import { LLMResponse, type Message } from "@mongodb-js/agent-engine-sdk";
10
+ import { type OperationalStepSource } from "./secure_wrapper.js";
11
+ import { type DurableMemoryState } from "./workflow/index.js";
12
+ export declare class SecureLLMProxy {
13
+ readonly oeUrl: string;
14
+ readonly executionId: string;
15
+ readonly modelName: string;
16
+ readonly llmId: string;
17
+ readonly boundTools: unknown[] | null;
18
+ readonly boundToolChoice: unknown;
19
+ /** Prefer the wrapper's allocator so tool + LLM share one sequence. */
20
+ readonly operationalSteps: OperationalStepSource;
21
+ readonly durableMemory: DurableMemoryState | null;
22
+ lastDurationMs: number;
23
+ lastFromCache: boolean;
24
+ lastLatestStepNumber: number | null;
25
+ lastPodName: string | null;
26
+ constructor(args: {
27
+ oeUrl: string;
28
+ executionId: string;
29
+ modelName?: string;
30
+ llmId?: string;
31
+ boundTools?: unknown[] | null;
32
+ boundToolChoice?: unknown;
33
+ operationalSteps?: OperationalStepSource;
34
+ durableMemory?: DurableMemoryState | null;
35
+ });
36
+ /** Current operational-step watermark for compatibility readers. */
37
+ get stepCounter(): number;
38
+ private allocateStep;
39
+ /**
40
+ * Invoke LLM by collecting the stream-oriented execution path.
41
+ * Equivalent to Python's `invoke()` which calls `list(self.stream(...))`.
42
+ */
43
+ invoke(messages: Message[], step?: number | null, stop?: string[] | null, options?: LLMInvocationOptions | null): Promise<LLMResponse>;
44
+ /**
45
+ * Stream invoke_llm chunks through OE approval and OE-owned SSE relay.
46
+ *
47
+ * Step auto-increments when not supplied. If OE returns a cached/sync
48
+ * result, a synthetic LLMStreamChunk is yielded instead of opening an
49
+ * SSE connection.
50
+ */
51
+ stream(messages: Message[], step?: number | null, stop?: string[] | null, options?: LLMInvocationOptions | null): AsyncGenerator<LLMStreamChunk>;
52
+ private streamDurably;
53
+ private streamActivityEffect;
54
+ private streamNative;
55
+ private replayActivityResult;
56
+ private completeActivityStream;
57
+ private preallocateToolCallsFromResult;
58
+ private static chunksFromResponse;
59
+ private static completeChunkFromResponse;
60
+ private static responsePayloadFromChunks;
61
+ /**
62
+ * Handle a guardrail require_review response by suspending via the framework
63
+ * suspend handler (LangGraph `interrupt`). On first call the handler suspends
64
+ * the node and never returns; on resume it returns the OE-dispatched decision
65
+ * — an object with a top-level `guardrail_review` key. Approve yields the
66
+ * pending LLM content; deny (or an unrecognised decision) denies the call.
67
+ * Mirrors Python's `_handle_require_review`.
68
+ */
69
+ private handleRequireReview;
70
+ /**
71
+ * Collect streamed sdk-core chunks into a final sdk-core LLMResponse.
72
+ * Static — can be called without a proxy instance (e.g. after parallel streaming).
73
+ */
74
+ static responseFromStreamChunks(chunks: LLMStreamChunk[]): LLMResponse;
75
+ /**
76
+ * Serialize bound tools to LLMToolSchema for transmission to OE.
77
+ *
78
+ * TS divergence from Python's `_serialize_bound_tools`: LangChain JS has no
79
+ * `tool_call_schema` attribute on tools (it's a Python-only API). Tools
80
+ * carrying Zod input schemas are normalized to OpenAI-canonical dicts
81
+ * upstream by `_normalizeBoundTool` in agent-engine-sdk-langgraph-ts via
82
+ * `convertToOpenAITool`, so every entry that reaches us here is already
83
+ * either a plain dict or an `LLMToolSchema` instance — both go through
84
+ * `LLMToolSchemaValidator`.
85
+ */
86
+ private serializeBoundTools;
87
+ private safeParseToolSchema;
88
+ /**
89
+ * Validate `boundToolChoice` (typed `unknown` at the proxy boundary) as a
90
+ * `JsonValue` for the wire. Rejects non-JSON values rather than letting them
91
+ * break `JSON.stringify` or be silently dropped. `null`/`undefined` → omit
92
+ * the field; `false` is preserved to disable forced tool use.
93
+ */
94
+ private resolveToolChoice;
95
+ private buildInvokeRequest;
96
+ private requestOeExecution;
97
+ private static convertResultToResponse;
98
+ private static convertToolCallsToStreamChunks;
99
+ private static normalizeToolCallArgs;
100
+ private static parseToolCallArgs;
101
+ private static convertStreamChunksToToolCalls;
102
+ private static streamChunkFromEvent;
103
+ /**
104
+ * Stream real-time LLM chunks from OE's audited SSE relay.
105
+ *
106
+ * Timeout: a single idle AbortController whose timer resets on every raw
107
+ * recv (via parseSseStream's onChunk callback). This matches httpx's
108
+ * `read=LLM_READ_TIMEOUT` behavior — the timeout only fires when the
109
+ * connection goes *silent* for LLM_READ_TIMEOUT seconds, not after that
110
+ * many seconds of wall-clock time. Active streams are never cut short.
111
+ * The timer also covers the connect phase (server not responding at all).
112
+ */
113
+ private streamFromOe;
114
+ }
115
+ //# sourceMappingURL=secure_llm_proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure_llm_proxy.d.ts","sourceRoot":"","sources":["../src/secure_llm_proxy.ts"],"names":[],"mappings":"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"}