@mongodb-js/agent-engine-runner-shared 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/LICENSE.md +201 -0
- package/README.md +29 -0
- package/dist/agent_config.d.ts +167 -0
- package/dist/agent_config.d.ts.map +1 -0
- package/dist/agent_config.js +544 -0
- package/dist/call_interrupted.d.ts +12 -0
- package/dist/call_interrupted.d.ts.map +1 -0
- package/dist/call_interrupted.js +11 -0
- package/dist/checkpoint_workspace.d.ts +25 -0
- package/dist/checkpoint_workspace.d.ts.map +1 -0
- package/dist/checkpoint_workspace.js +44 -0
- package/dist/context.d.ts +235 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +322 -0
- package/dist/db_config.d.ts +28 -0
- package/dist/db_config.d.ts.map +1 -0
- package/dist/db_config.js +66 -0
- package/dist/db_naming.d.ts +54 -0
- package/dist/db_naming.d.ts.map +1 -0
- package/dist/db_naming.js +94 -0
- package/dist/error_reporting.d.ts +67 -0
- package/dist/error_reporting.d.ts.map +1 -0
- package/dist/error_reporting.js +311 -0
- package/dist/generated/workflow/v1/activity_pb.d.ts +342 -0
- package/dist/generated/workflow/v1/activity_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/activity_pb.js +115 -0
- package/dist/generated/workflow/v1/common_pb.d.ts +184 -0
- package/dist/generated/workflow/v1/common_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/common_pb.js +86 -0
- package/dist/generated/workflow/v1/runtime_pb.d.ts +200 -0
- package/dist/generated/workflow/v1/runtime_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/runtime_pb.js +40 -0
- package/dist/generated/workflow/v1/state_pb.d.ts +254 -0
- package/dist/generated/workflow/v1/state_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/state_pb.js +68 -0
- package/dist/guardrails_evaluator/core.d.ts +23 -0
- package/dist/guardrails_evaluator/core.d.ts.map +1 -0
- package/dist/guardrails_evaluator/core.js +122 -0
- package/dist/guardrails_evaluator/index.d.ts +10 -0
- package/dist/guardrails_evaluator/index.d.ts.map +1 -0
- package/dist/guardrails_evaluator/index.js +11 -0
- package/dist/guardrails_evaluator/regex.d.ts +20 -0
- package/dist/guardrails_evaluator/regex.d.ts.map +1 -0
- package/dist/guardrails_evaluator/regex.js +233 -0
- package/dist/hooks.d.ts +109 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +216 -0
- package/dist/http_path.d.ts +18 -0
- package/dist/http_path.d.ts.map +1 -0
- package/dist/http_path.js +53 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/launcher.d.ts +130 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +325 -0
- package/dist/logger.d.ts +96 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +204 -0
- package/dist/mcp_oauth.d.ts +51 -0
- package/dist/mcp_oauth.d.ts.map +1 -0
- package/dist/mcp_oauth.js +389 -0
- package/dist/mcp_oauth_secret.d.ts +21 -0
- package/dist/mcp_oauth_secret.d.ts.map +1 -0
- package/dist/mcp_oauth_secret.js +122 -0
- package/dist/mcp_tools.d.ts +71 -0
- package/dist/mcp_tools.d.ts.map +1 -0
- package/dist/mcp_tools.js +301 -0
- package/dist/memory_appbound.d.ts +42 -0
- package/dist/memory_appbound.d.ts.map +1 -0
- package/dist/memory_appbound.js +159 -0
- package/dist/memory_writer.d.ts +49 -0
- package/dist/memory_writer.d.ts.map +1 -0
- package/dist/memory_writer.js +171 -0
- package/dist/metrics.d.ts +84 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +205 -0
- package/dist/models.d.ts +1458 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +1726 -0
- package/dist/node_logger.d.ts +43 -0
- package/dist/node_logger.d.ts.map +1 -0
- package/dist/node_logger.js +158 -0
- package/dist/owner_callback.d.ts +16 -0
- package/dist/owner_callback.d.ts.map +1 -0
- package/dist/owner_callback.js +40 -0
- package/dist/progress.d.ts +57 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +140 -0
- package/dist/runtime.d.ts +131 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +351 -0
- package/dist/secure_llm_proxy.d.ts +115 -0
- package/dist/secure_llm_proxy.d.ts.map +1 -0
- package/dist/secure_llm_proxy.js +922 -0
- package/dist/secure_wrapper.d.ts +332 -0
- package/dist/secure_wrapper.d.ts.map +1 -0
- package/dist/secure_wrapper.js +1249 -0
- package/dist/server/aer.d.ts +61 -0
- package/dist/server/aer.d.ts.map +1 -0
- package/dist/server/aer.js +1124 -0
- package/dist/server/auth.d.ts +56 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +132 -0
- package/dist/server/base.d.ts +104 -0
- package/dist/server/base.d.ts.map +1 -0
- package/dist/server/base.js +150 -0
- package/dist/server/callInterrupt.d.ts +49 -0
- package/dist/server/callInterrupt.d.ts.map +1 -0
- package/dist/server/callInterrupt.js +68 -0
- package/dist/server/callback_delivery.d.ts +14 -0
- package/dist/server/callback_delivery.d.ts.map +1 -0
- package/dist/server/callback_delivery.js +141 -0
- package/dist/server/chunk_types.d.ts +50 -0
- package/dist/server/chunk_types.d.ts.map +1 -0
- package/dist/server/chunk_types.js +62 -0
- package/dist/server/cors.d.ts +52 -0
- package/dist/server/cors.d.ts.map +1 -0
- package/dist/server/cors.js +107 -0
- package/dist/server/drain.d.ts +169 -0
- package/dist/server/drain.d.ts.map +1 -0
- package/dist/server/drain.js +455 -0
- package/dist/server/function.d.ts +77 -0
- package/dist/server/function.d.ts.map +1 -0
- package/dist/server/function.js +337 -0
- package/dist/server/http_retry.d.ts +37 -0
- package/dist/server/http_retry.d.ts.map +1 -0
- package/dist/server/http_retry.js +157 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +5 -0
- package/dist/server/metadata.d.ts +50 -0
- package/dist/server/metadata.d.ts.map +1 -0
- package/dist/server/metadata.js +193 -0
- package/dist/server/oe_url.d.ts +36 -0
- package/dist/server/oe_url.d.ts.map +1 -0
- package/dist/server/oe_url.js +50 -0
- package/dist/server/owner_url.d.ts +35 -0
- package/dist/server/owner_url.d.ts.map +1 -0
- package/dist/server/owner_url.js +146 -0
- package/dist/server/query.d.ts +42 -0
- package/dist/server/query.d.ts.map +1 -0
- package/dist/server/query.js +28 -0
- package/dist/server/tool.d.ts +138 -0
- package/dist/server/tool.d.ts.map +1 -0
- package/dist/server/tool.js +1017 -0
- package/dist/span_names.d.ts +21 -0
- package/dist/span_names.d.ts.map +1 -0
- package/dist/span_names.js +31 -0
- package/dist/structured_logging/constants.d.ts +17 -0
- package/dist/structured_logging/constants.d.ts.map +1 -0
- package/dist/structured_logging/constants.js +71 -0
- package/dist/structured_logging/env.d.ts +18 -0
- package/dist/structured_logging/env.d.ts.map +1 -0
- package/dist/structured_logging/env.js +39 -0
- package/dist/structured_logging/install.d.ts +56 -0
- package/dist/structured_logging/install.d.ts.map +1 -0
- package/dist/structured_logging/install.js +107 -0
- package/dist/structured_logging/layout.d.ts +9 -0
- package/dist/structured_logging/layout.d.ts.map +1 -0
- package/dist/structured_logging/layout.js +144 -0
- package/dist/structured_logging/serialize.d.ts +27 -0
- package/dist/structured_logging/serialize.d.ts.map +1 -0
- package/dist/structured_logging/serialize.js +61 -0
- package/dist/structured_logging/stdio_capture.d.ts +59 -0
- package/dist/structured_logging/stdio_capture.d.ts.map +1 -0
- package/dist/structured_logging/stdio_capture.js +164 -0
- package/dist/structured_logging/uncaught.d.ts +14 -0
- package/dist/structured_logging/uncaught.d.ts.map +1 -0
- package/dist/structured_logging/uncaught.js +58 -0
- package/dist/structured_logging.d.ts +48 -0
- package/dist/structured_logging.d.ts.map +1 -0
- package/dist/structured_logging.js +47 -0
- package/dist/tls_client.d.ts +61 -0
- package/dist/tls_client.d.ts.map +1 -0
- package/dist/tls_client.js +298 -0
- package/dist/tool_api_error.d.ts +62 -0
- package/dist/tool_api_error.d.ts.map +1 -0
- package/dist/tool_api_error.js +399 -0
- package/dist/tool_memory_ownership.d.ts +10 -0
- package/dist/tool_memory_ownership.d.ts.map +1 -0
- package/dist/tool_memory_ownership.js +36 -0
- package/dist/toolpod_handlers.d.ts +126 -0
- package/dist/toolpod_handlers.d.ts.map +1 -0
- package/dist/toolpod_handlers.js +1016 -0
- package/dist/tracing/exporters.d.ts +51 -0
- package/dist/tracing/exporters.d.ts.map +1 -0
- package/dist/tracing/exporters.js +327 -0
- package/dist/tracing/index.d.ts +3 -0
- package/dist/tracing/index.d.ts.map +1 -0
- package/dist/tracing/index.js +2 -0
- package/dist/tracing/setup.d.ts +76 -0
- package/dist/tracing/setup.d.ts.map +1 -0
- package/dist/tracing/setup.js +436 -0
- package/dist/utils.d.ts +204 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +867 -0
- package/dist/workflow/activity.d.ts +71 -0
- package/dist/workflow/activity.d.ts.map +1 -0
- package/dist/workflow/activity.js +357 -0
- package/dist/workflow/attempt.d.ts +12 -0
- package/dist/workflow/attempt.d.ts.map +1 -0
- package/dist/workflow/attempt.js +96 -0
- package/dist/workflow/client.d.ts +46 -0
- package/dist/workflow/client.d.ts.map +1 -0
- package/dist/workflow/client.js +299 -0
- package/dist/workflow/context.d.ts +37 -0
- package/dist/workflow/context.d.ts.map +1 -0
- package/dist/workflow/context.js +350 -0
- package/dist/workflow/heartbeat.d.ts +15 -0
- package/dist/workflow/heartbeat.d.ts.map +1 -0
- package/dist/workflow/heartbeat.js +78 -0
- package/dist/workflow/index.d.ts +14 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +10 -0
- package/dist/workflow/memory.d.ts +17 -0
- package/dist/workflow/memory.d.ts.map +1 -0
- package/dist/workflow/memory.js +184 -0
- package/package.json +73 -0
package/dist/runtime.js
ADDED
|
@@ -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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"}
|