@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/launcher.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launcher module for pipeline-built agent containers (TS port of
|
|
3
|
+
* `agent_engine_runner_shared/launcher.py`).
|
|
4
|
+
*
|
|
5
|
+
* Reads `AGENT_ENTRYPOINT` and starts the user's agent code. The generated
|
|
6
|
+
* Dockerfile CMD invokes this file directly:
|
|
7
|
+
*
|
|
8
|
+
* CMD ["node", "/app/node_modules/@mongodb-js/agent-engine-runner-shared/dist/launcher.js"]
|
|
9
|
+
*
|
|
10
|
+
* Environment variables:
|
|
11
|
+
* AGENT_ENTRYPOINT Startup target in one of these forms:
|
|
12
|
+
* 1) `<module-specifier>:<export-name>` — module is dynamically
|
|
13
|
+
* imported and the named export is invoked.
|
|
14
|
+
* 2) `<module-specifier>:<export-name>` where the export is not a
|
|
15
|
+
* function but has a callable `run()` method — `.run()` is invoked.
|
|
16
|
+
* 3) `<module-specifier>` (no colon) — defaults to `:main`.
|
|
17
|
+
*
|
|
18
|
+
* Structured logging is installed at the start of `runLauncher()` (before
|
|
19
|
+
* import) so import-time and entrypoint failures emit `ERROR` records
|
|
20
|
+
* instead of raw stderr stacks. `TenantRuntime` re-installs later; that
|
|
21
|
+
* call is idempotent. After install, `console.error` is captured as
|
|
22
|
+
* WARNING, so failure paths must use the logger.
|
|
23
|
+
*
|
|
24
|
+
* Security note: `AGENT_ENTRYPOINT` is baked into the Dockerfile ENV at
|
|
25
|
+
* image-build time by the platform pipeline, not supplied by the user at
|
|
26
|
+
* container runtime. The dynamic `import()` here serves the same role as
|
|
27
|
+
* `node -e "import('module')"` — bootstrapping a known, pre-installed
|
|
28
|
+
* module — and is an intentional exception to the "no dynamic code
|
|
29
|
+
* execution in platform code" guideline.
|
|
30
|
+
*/
|
|
31
|
+
import { writeFileSync } from "node:fs";
|
|
32
|
+
import { resolve } from "node:path";
|
|
33
|
+
import { pathToFileURL } from "node:url";
|
|
34
|
+
import { getLogger, setupLogging } from "./logger.js";
|
|
35
|
+
import { runWithCustomerOrigin } from "./context.js";
|
|
36
|
+
import { redactText } from "./error_reporting.js";
|
|
37
|
+
import { materializeMcpOauthSecretCache } from "./mcp_oauth_secret.js";
|
|
38
|
+
import { getRuntimeMode } from "./utils.js";
|
|
39
|
+
const logger = getLogger("agent_engine_runner_shared.launcher");
|
|
40
|
+
/**
|
|
41
|
+
* Kubernetes' default termination-message path (the "File"
|
|
42
|
+
* TerminationMessagePolicy Kubelet always checks first, before falling back
|
|
43
|
+
* to log-tail capture). Nothing in the deployed pod spec sets a custom
|
|
44
|
+
* terminationMessagePath, so the default applies. A mutable binding so tests
|
|
45
|
+
* can redirect it to a temp file.
|
|
46
|
+
*/
|
|
47
|
+
export let terminationLogPath = "/dev/termination-log";
|
|
48
|
+
/** Test-only override, mirroring `setHomeDirForTest` in error_reporting.ts. */
|
|
49
|
+
export function setTerminationLogPathForTest(path) {
|
|
50
|
+
terminationLogPath = path;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 4 KiB / 40 lines, whichever is reached first — the same bound the deploy
|
|
54
|
+
* event pipeline applies (see pkg/logredaction.DefaultMaxBytes/DefaultMaxLines
|
|
55
|
+
* in the Go services, and `_bound_text` in the Python launcher; kept in sync
|
|
56
|
+
* by convention across all three, since there's no shared package for this
|
|
57
|
+
* across languages).
|
|
58
|
+
*/
|
|
59
|
+
const TERMINATION_MESSAGE_MAX_BYTES = 4 * 1024;
|
|
60
|
+
const TERMINATION_MESSAGE_MAX_LINES = 40;
|
|
61
|
+
/**
|
|
62
|
+
* Truncate `text` to at most `maxLines` lines or `maxBytes` UTF-8 bytes,
|
|
63
|
+
* whichever limit is hit first, appending a truncation marker when either
|
|
64
|
+
* limit was hit.
|
|
65
|
+
*/
|
|
66
|
+
export function boundText(text, maxBytes = TERMINATION_MESSAGE_MAX_BYTES, maxLines = TERMINATION_MESSAGE_MAX_LINES) {
|
|
67
|
+
const marker = "\n[truncated]";
|
|
68
|
+
let truncated = false;
|
|
69
|
+
const lines = text.split("\n");
|
|
70
|
+
if (lines.length > maxLines) {
|
|
71
|
+
text = lines.slice(0, maxLines).join("\n");
|
|
72
|
+
truncated = true;
|
|
73
|
+
}
|
|
74
|
+
const encoded = Buffer.from(text, "utf-8");
|
|
75
|
+
if (encoded.length > maxBytes) {
|
|
76
|
+
// toString("utf-8") on a boundary-split multi-byte sequence replaces the
|
|
77
|
+
// partial tail with U+FFFD rather than throwing, which is an acceptable
|
|
78
|
+
// (rare, cosmetic) outcome for a diagnostic message under a hard cap.
|
|
79
|
+
text = encoded.subarray(0, maxBytes).toString("utf-8");
|
|
80
|
+
truncated = true;
|
|
81
|
+
}
|
|
82
|
+
if (!truncated)
|
|
83
|
+
return text;
|
|
84
|
+
// Reserve headroom for `marker` so appending it below can't push the
|
|
85
|
+
// final write past maxBytes — kubelet's own read of /dev/termination-log
|
|
86
|
+
// is a blind byte-level cut that would otherwise mangle the tail,
|
|
87
|
+
// potentially the marker itself.
|
|
88
|
+
const budget = Math.max(0, maxBytes - marker.length);
|
|
89
|
+
const finalEncoded = Buffer.from(text, "utf-8");
|
|
90
|
+
if (finalEncoded.length > budget) {
|
|
91
|
+
text = finalEncoded.subarray(0, budget).toString("utf-8");
|
|
92
|
+
}
|
|
93
|
+
return `${text}${marker}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Record a bounded, redacted summary of a fatal startup error to the
|
|
97
|
+
* container's termination message before the process exits, so the real
|
|
98
|
+
* cause of the crash survives past this process's own stdout into
|
|
99
|
+
* `ContainerStatus.LastTerminationState.Terminated.Message` — the field the
|
|
100
|
+
* platform's crash diagnostics read, and from there into the
|
|
101
|
+
* customer-facing deploy timeline.
|
|
102
|
+
*
|
|
103
|
+
* This is customer code (container mode), so unlike the platform's own
|
|
104
|
+
* components the design intentionally keeps the full exception name,
|
|
105
|
+
* message, and stack — redacted, not summarized away — since that detail is
|
|
106
|
+
* what the customer needs to fix their own agent. Best-effort: if the write
|
|
107
|
+
* fails, nothing is lost beyond what
|
|
108
|
+
* `TerminationMessagePolicy: FallbackToLogsOnError` already provides.
|
|
109
|
+
*/
|
|
110
|
+
export function writeTerminationMessage(summary, err) {
|
|
111
|
+
let text = summary;
|
|
112
|
+
if (err !== undefined) {
|
|
113
|
+
text = `${summary}\n${err.stack ?? `${err.name}: ${err.message}`}`;
|
|
114
|
+
}
|
|
115
|
+
text = redactText(text);
|
|
116
|
+
text = boundText(text);
|
|
117
|
+
try {
|
|
118
|
+
writeFileSync(terminationLogPath, text, { encoding: "utf-8" });
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Best-effort — see doc comment above.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Log an ERROR, write a termination message via `writeTerminationMessage`,
|
|
126
|
+
* and exit. Routes every deployment-breaking launcher exit through one path
|
|
127
|
+
* — rather than instrumenting a hand-picked few — so the container's
|
|
128
|
+
* termination message always carries the real cause forward to
|
|
129
|
+
* `ContainerStatus.LastTerminationState.Terminated.Message`.
|
|
130
|
+
*/
|
|
131
|
+
function fatalWithTermination(summary, err, code = 1) {
|
|
132
|
+
if (err !== undefined) {
|
|
133
|
+
logger.error(err, `${summary}: ${err.message}`);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
logger.error(summary);
|
|
137
|
+
}
|
|
138
|
+
writeTerminationMessage(summary, err);
|
|
139
|
+
process.exit(code);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Startup-failure exit codes. OE's readiness wait can observe a
|
|
143
|
+
* workload's real exit code once it exits (via fctr's Wait RPC) but not the
|
|
144
|
+
* exception that caused it, so the exit code itself is the only signal that
|
|
145
|
+
* reliably survives a startup crash to reach OE. Chosen to avoid every range
|
|
146
|
+
* fctr's own const.go already claims: 0/1 (generic), 64-78 (sysexits.h), and
|
|
147
|
+
* 128+signal (signal deaths, e.g. 137=SIGKILL, 143=SIGTERM).
|
|
148
|
+
*
|
|
149
|
+
* Frozen wire contract: OE's classification of a startup failure depends on
|
|
150
|
+
* these exact values, and they are duplicated in
|
|
151
|
+
* runner-shared/src/agent_engine_runner_shared/launcher.py. Changing either file breaks
|
|
152
|
+
* OE's ability to distinguish failure causes and/or cross-language parity —
|
|
153
|
+
* keep the two in lockstep.
|
|
154
|
+
*/
|
|
155
|
+
export const EXIT_IMPORT_ERROR = 82;
|
|
156
|
+
export const EXIT_NO_ENTRYPOINT = 83;
|
|
157
|
+
export const EXIT_STARTUP_CRASH = 84;
|
|
158
|
+
/**
|
|
159
|
+
* Resolve RUNNER_MODE for the early structured-logging install, falling back
|
|
160
|
+
* to "aer" when unset or unrecognized. An unrecognized value would reach the
|
|
161
|
+
* record's `service` field verbatim, and `/agent-logs` drops anything outside
|
|
162
|
+
* `{agent-execution-runtime, tool-executor}` — so the crash log would vanish.
|
|
163
|
+
* AGENT_ENTRYPOINT pods only ever run aer/tool.
|
|
164
|
+
*/
|
|
165
|
+
function resolveRunnerMode() {
|
|
166
|
+
try {
|
|
167
|
+
return getRuntimeMode();
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return "aer";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Normalize a thrown value to an `Error`: `layout.ts` only populates
|
|
175
|
+
* `exc_type`/`exc_message`/`exc_traceback` for a genuine `Error`, and casting
|
|
176
|
+
* a non-Error would make `.name`/`.message` read `undefined`.
|
|
177
|
+
*/
|
|
178
|
+
function toError(value) {
|
|
179
|
+
return value instanceof Error
|
|
180
|
+
? value
|
|
181
|
+
: new Error(String(value), { cause: value });
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Resolve an AGENT_ENTRYPOINT module path to a value Node's dynamic `import()`
|
|
185
|
+
* can load.
|
|
186
|
+
*
|
|
187
|
+
* The platform bakes the `agent.yaml` `entrypoint` verbatim into
|
|
188
|
+
* AGENT_ENTRYPOINT (same contract as the Python launcher), so the common form
|
|
189
|
+
* is a dotted, Python-style module path — e.g. `agent_pkg.main` — that maps to
|
|
190
|
+
* the agent's *compiled* output `<agentRoot>/dist/agent_pkg/main.js`. Node ESM
|
|
191
|
+
* `import()` treats a dotted string as a bare package specifier and cannot
|
|
192
|
+
* resolve it, so we translate it here: dots → path separators, under the
|
|
193
|
+
* compiled `dist/` directory, with a `.js` suffix, returned as a `file://` URL
|
|
194
|
+
* (the portable form for importing an absolute path across platforms).
|
|
195
|
+
*
|
|
196
|
+
* `agentRoot` defaults to `process.cwd()`, which at runtime is the agent
|
|
197
|
+
* package root: the generated Dockerfile sets `WORKDIR` to the install target
|
|
198
|
+
* and the `CMD` runs the launcher from there. It is injectable for tests.
|
|
199
|
+
*
|
|
200
|
+
* A value that is already directly importable — a relative path, an absolute
|
|
201
|
+
* path, or any path-bearing specifier (one that contains a `/`) — is returned
|
|
202
|
+
* unchanged, so a pre-resolved entrypoint (or a test passing an absolute file
|
|
203
|
+
* path) still works and is never double-translated. A path-shape signal (not a
|
|
204
|
+
* file extension) is used deliberately: a dotted module path whose final
|
|
205
|
+
* segment happens to be `js`/`mjs`/`cjs` (e.g. `agent_pkg.cjs`) must still be
|
|
206
|
+
* translated, not mistaken for an already-importable file.
|
|
207
|
+
*/
|
|
208
|
+
export function resolveImportTarget(modulePath, agentRoot = process.cwd()) {
|
|
209
|
+
const alreadyImportable = modulePath.startsWith(".") ||
|
|
210
|
+
modulePath.startsWith("/") ||
|
|
211
|
+
modulePath.includes("/");
|
|
212
|
+
if (alreadyImportable) {
|
|
213
|
+
return modulePath;
|
|
214
|
+
}
|
|
215
|
+
const relPath = modulePath.split(".").join("/");
|
|
216
|
+
const absPath = resolve(agentRoot, "dist", `${relPath}.js`);
|
|
217
|
+
return pathToFileURL(absPath).href;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Parse `AGENT_ENTRYPOINT` into `{ modulePath, exportName }`.
|
|
221
|
+
*
|
|
222
|
+
* Exits the process with `EXIT_NO_ENTRYPOINT` if the env var is unset or
|
|
223
|
+
* malformed — matches Python `_resolve_entrypoint`'s equivalent exit code.
|
|
224
|
+
* Exported so tests can spawn this in a subprocess and assert on exit
|
|
225
|
+
* code / stderr.
|
|
226
|
+
*/
|
|
227
|
+
export function resolveEntrypoint() {
|
|
228
|
+
// Surrounding whitespace is stripped because the build pipeline extracts this
|
|
229
|
+
// value from agent.yaml with a shell pipeline: a file authored on Windows
|
|
230
|
+
// leaves a trailing CR that would otherwise become part of the export name
|
|
231
|
+
// looked up below.
|
|
232
|
+
const raw = (process.env["AGENT_ENTRYPOINT"] ?? "").trim();
|
|
233
|
+
if (!raw) {
|
|
234
|
+
fatalWithTermination("AGENT_ENTRYPOINT is not set", undefined, EXIT_NO_ENTRYPOINT);
|
|
235
|
+
}
|
|
236
|
+
let modulePath;
|
|
237
|
+
let exportName;
|
|
238
|
+
if (raw.includes(":")) {
|
|
239
|
+
const idx = raw.lastIndexOf(":");
|
|
240
|
+
modulePath = raw.slice(0, idx).trim();
|
|
241
|
+
exportName = raw.slice(idx + 1).trim();
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
modulePath = raw;
|
|
245
|
+
exportName = "main";
|
|
246
|
+
}
|
|
247
|
+
if (!modulePath || !exportName) {
|
|
248
|
+
fatalWithTermination(`Invalid AGENT_ENTRYPOINT '${raw}': both module path and export name must be non-empty`, undefined, EXIT_NO_ENTRYPOINT);
|
|
249
|
+
}
|
|
250
|
+
return { modulePath, exportName };
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Entry point invoked when this file is run directly:
|
|
254
|
+
*
|
|
255
|
+
* `node /app/node_modules/@mongodb-js/agent-engine-runner-shared/dist/launcher.js`
|
|
256
|
+
*
|
|
257
|
+
* Exits the process with the exit code matching the failure category
|
|
258
|
+
* (see `EXIT_IMPORT_ERROR`/`EXIT_NO_ENTRYPOINT`/`EXIT_STARTUP_CRASH` above)
|
|
259
|
+
* on any error path; resolves normally after the user's target function or
|
|
260
|
+
* `.run()` returns.
|
|
261
|
+
*/
|
|
262
|
+
export async function runLauncher() {
|
|
263
|
+
// Install before any import/entrypoint work. After this, console.error is
|
|
264
|
+
// captured as WARNING (not ERROR), so failure paths must use the logger.
|
|
265
|
+
setupLogging({ appName: "launcher", mode: resolveRunnerMode() });
|
|
266
|
+
// Decode platform-injected MCP OAuth secrets into the file cache before the
|
|
267
|
+
// agent module is imported, so any OAuth MCP client it constructs finds its
|
|
268
|
+
// token. Sets AGENTIC_MCP_OAUTH_DIR before mcp_oauth.ts resolves its cache
|
|
269
|
+
// dir on first (lazy) import.
|
|
270
|
+
try {
|
|
271
|
+
await materializeMcpOauthSecretCache();
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
fatalWithTermination("Cannot materialize MCP OAuth credentials", toError(e));
|
|
275
|
+
}
|
|
276
|
+
const { modulePath, exportName } = resolveEntrypoint();
|
|
277
|
+
logger.info(`Launcher: importing ${modulePath}:${exportName}`);
|
|
278
|
+
const importTarget = resolveImportTarget(modulePath);
|
|
279
|
+
let mod;
|
|
280
|
+
try {
|
|
281
|
+
mod = (await runWithCustomerOrigin(() => import(importTarget)));
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
// Include the resolved target so a cwd/WORKDIR mismatch or missing
|
|
285
|
+
// compiled file is visible during on-call debugging, not just the dotted
|
|
286
|
+
// module name the user wrote.
|
|
287
|
+
fatalWithTermination(`Cannot import module '${modulePath}' (resolved to '${importTarget}')`, toError(e), EXIT_IMPORT_ERROR);
|
|
288
|
+
}
|
|
289
|
+
const target = mod[exportName];
|
|
290
|
+
if (target === undefined) {
|
|
291
|
+
fatalWithTermination(`Module '${modulePath}' has no export '${exportName}'`, undefined, EXIT_NO_ENTRYPOINT);
|
|
292
|
+
}
|
|
293
|
+
// Preferred form module:function; compatibility form module:appObject with a
|
|
294
|
+
// callable .run(). Function first, so a callable export that also carries a
|
|
295
|
+
// .run property still invokes the export itself.
|
|
296
|
+
const run = target !== null && typeof target === "object"
|
|
297
|
+
? target["run"]
|
|
298
|
+
: undefined;
|
|
299
|
+
const invoke = typeof target === "function"
|
|
300
|
+
? target
|
|
301
|
+
: typeof run === "function"
|
|
302
|
+
? run.bind(target)
|
|
303
|
+
: null;
|
|
304
|
+
if (invoke !== null) {
|
|
305
|
+
try {
|
|
306
|
+
// Scope covers the entrypoint's execution, not just its import —
|
|
307
|
+
// AsyncLocalStorage propagates across awaits inside the callback.
|
|
308
|
+
await runWithCustomerOrigin(() => invoke());
|
|
309
|
+
}
|
|
310
|
+
catch (e) {
|
|
311
|
+
fatalWithTermination(`Unhandled exception from agent entrypoint '${modulePath}:${exportName}'`, toError(e), EXIT_STARTUP_CRASH);
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
fatalWithTermination(`'${modulePath}:${exportName}' is not callable and has no callable 'run()' method`, undefined, EXIT_NO_ENTRYPOINT);
|
|
316
|
+
}
|
|
317
|
+
// CLI entrypoint guard: only auto-run when invoked as `node launcher.js`,
|
|
318
|
+
// not when imported as a library (e.g., from tests or barrel re-exports).
|
|
319
|
+
const isCliEntry = process.argv[1] !== undefined &&
|
|
320
|
+
import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
321
|
+
if (isCliEntry) {
|
|
322
|
+
runLauncher().catch((e) => {
|
|
323
|
+
fatalWithTermination("Launcher failed", toError(e));
|
|
324
|
+
});
|
|
325
|
+
}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module logger factory for the Runner SDK.
|
|
3
|
+
*
|
|
4
|
+
* Wraps log4js to provide a Python-equivalent logging surface. log4js was
|
|
5
|
+
* chosen over pino because its named-category registry mirrors Python's
|
|
6
|
+
* `logging.getLogger(name)` semantics one-for-one: a global, hierarchical
|
|
7
|
+
* registry of named loggers with per-name level overrides — none of which
|
|
8
|
+
* pino offers natively. Reconfiguring the root via `setupLogging` is
|
|
9
|
+
* automatically visible to every previously-issued `getLogger()` reference,
|
|
10
|
+
* matching Python's behaviour without the Proxy-rebind dance pino required.
|
|
11
|
+
*
|
|
12
|
+
* Surface:
|
|
13
|
+
*
|
|
14
|
+
* - `getLogger("agent_engine_runner_shared.utils")` — named logger from the global
|
|
15
|
+
* registry, equivalent to Python `logging.getLogger(__name__)`. The same
|
|
16
|
+
* instance is returned on every call for a given name.
|
|
17
|
+
* - `setupLogging({mode, level, ...})` — configure the root. When
|
|
18
|
+
* `STRUCTURED_LOGGING=true` delegates to `installStructuredLogging` so all
|
|
19
|
+
* output emerges as single-line JSON matching the agent-log
|
|
20
|
+
* contract; otherwise installs a human-readable pattern layout to stdout.
|
|
21
|
+
* On-disk logging is not part of the production model — container stdout
|
|
22
|
+
* is shipped to S3 by Fluent Bit, so a duplicate copy adds no value. When
|
|
23
|
+
* `AGENTIC_DEV_MODES` is set (local dev-up compose stacks only), a
|
|
24
|
+
* best-effort `dateFile` appender is added so the Local Dev UI log viewer
|
|
25
|
+
* can read agent output from disk; it rotates daily and keeps 5 files,
|
|
26
|
+
* mirroring Python's `TimedRotatingFileHandler`.
|
|
27
|
+
* - Noisy HTTP libraries (`undici`, `httpx`, `httpcore`, `urllib3`,
|
|
28
|
+
* `fastify.access`, `uvicorn.access`) are silenced to `warn` — equivalent
|
|
29
|
+
* to Python's `logging.getLogger("httpx").setLevel(WARNING)` block.
|
|
30
|
+
*/
|
|
31
|
+
import { type Logger } from "log4js";
|
|
32
|
+
/**
|
|
33
|
+
* Mark the log4js root as already configured.
|
|
34
|
+
*
|
|
35
|
+
* Called by `installStructuredLogging` so that a direct `installStructuredLogging()`
|
|
36
|
+
* (the documented startup entrypoint) followed by `getLogger()` does NOT let
|
|
37
|
+
* `ensureDefaultConfig()` clobber the structured config with the
|
|
38
|
+
* human stdout appender. Without this the structured pipeline could be torn
|
|
39
|
+
* down — and re-running `log4js.configure` over patched stdio risks recursion.
|
|
40
|
+
*/
|
|
41
|
+
export declare function markConfigured(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Return a logger named after the caller's module.
|
|
44
|
+
*
|
|
45
|
+
* Mirrors Python `logging.getLogger(__name__)`. log4js maintains a global
|
|
46
|
+
* registry, so a later `setupLogging` reconfiguration is automatically
|
|
47
|
+
* visible to every previously-issued reference. First call performs a
|
|
48
|
+
* default configuration so module-scope `const logger = getLogger(__name__)`
|
|
49
|
+
* patterns work without an explicit `setupLogging` at startup.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getLogger(name?: string): Logger;
|
|
52
|
+
export interface SetupLoggingArgs {
|
|
53
|
+
/**
|
|
54
|
+
* Application name — surfaced in the install log line and used to name
|
|
55
|
+
* the dev-mode log file (`<appName>-<mode>.log`) when AGENTIC_DEV_MODES
|
|
56
|
+
* is set. Matches Python `app_name`.
|
|
57
|
+
*/
|
|
58
|
+
appName?: string;
|
|
59
|
+
/** Runner mode (`aer` / `tool` / `orchestrator` / `memory-server`). */
|
|
60
|
+
mode?: string;
|
|
61
|
+
/** Log level — string ("debug", "info", ...) or `LOG_LEVEL` env when omitted. */
|
|
62
|
+
logLevel?: string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Log directory — used only when `AGENTIC_DEV_MODES` is set to write
|
|
65
|
+
* a dev-mode file log alongside console output.
|
|
66
|
+
*/
|
|
67
|
+
logDir?: string | null;
|
|
68
|
+
/** Unused — parity placeholder. */
|
|
69
|
+
backupCount?: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Configure the root logger. Mirrors `agent_engine_runner_shared.utils.setup_logging`.
|
|
73
|
+
*
|
|
74
|
+
* When `STRUCTURED_LOGGING=true` is set, delegates to
|
|
75
|
+
* `installStructuredLogging` so all output emerges as single-line JSON
|
|
76
|
+
* matching the agent-log contract. On-disk logging is not part of the
|
|
77
|
+
* production model — Fluent Bit ships container stdout to S3, so a
|
|
78
|
+
* duplicate copy adds no value. The exception is local dev: when
|
|
79
|
+
* `AGENTIC_DEV_MODES` is set (dev-up compose stacks only), a rotating file
|
|
80
|
+
* sink is attached alongside the console/structured output.
|
|
81
|
+
*
|
|
82
|
+
* Unlike Python's `setup_logging` we don't need to manually drop existing
|
|
83
|
+
* handlers before re-adding the console handler: `log4js.configure` fully
|
|
84
|
+
* replaces the prior `appenders`/`categories` config on each call, so a
|
|
85
|
+
* repeat call never accumulates duplicate writers. There's also no
|
|
86
|
+
* stdout/stderr handler-close hazard to guard against — log4js's `stdout`
|
|
87
|
+
* appender owns its own write path.
|
|
88
|
+
*
|
|
89
|
+
* @param args.appName Application name — install line + dev log file name.
|
|
90
|
+
* @param args.mode Runtime mode (aer, tool, orchestrator, memory-server).
|
|
91
|
+
* @param args.logLevel Log level (default: from `LOG_LEVEL` env or INFO).
|
|
92
|
+
* @param args.logDir Log directory for dev-mode file logging (only when AGENTIC_DEV_MODES is set).
|
|
93
|
+
* @param args.backupCount Unused — parity placeholder.
|
|
94
|
+
*/
|
|
95
|
+
export declare function setupLogging(args?: SetupLoggingArgs): Logger;
|
|
96
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAe,EAAsB,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AA4EjE;;;;;;;;GAQG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAuCD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAAC,IAAI,GAAE,gBAAqB,GAAG,MAAM,CAyChE"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module logger factory for the Runner SDK.
|
|
3
|
+
*
|
|
4
|
+
* Wraps log4js to provide a Python-equivalent logging surface. log4js was
|
|
5
|
+
* chosen over pino because its named-category registry mirrors Python's
|
|
6
|
+
* `logging.getLogger(name)` semantics one-for-one: a global, hierarchical
|
|
7
|
+
* registry of named loggers with per-name level overrides — none of which
|
|
8
|
+
* pino offers natively. Reconfiguring the root via `setupLogging` is
|
|
9
|
+
* automatically visible to every previously-issued `getLogger()` reference,
|
|
10
|
+
* matching Python's behaviour without the Proxy-rebind dance pino required.
|
|
11
|
+
*
|
|
12
|
+
* Surface:
|
|
13
|
+
*
|
|
14
|
+
* - `getLogger("agent_engine_runner_shared.utils")` — named logger from the global
|
|
15
|
+
* registry, equivalent to Python `logging.getLogger(__name__)`. The same
|
|
16
|
+
* instance is returned on every call for a given name.
|
|
17
|
+
* - `setupLogging({mode, level, ...})` — configure the root. When
|
|
18
|
+
* `STRUCTURED_LOGGING=true` delegates to `installStructuredLogging` so all
|
|
19
|
+
* output emerges as single-line JSON matching the agent-log
|
|
20
|
+
* contract; otherwise installs a human-readable pattern layout to stdout.
|
|
21
|
+
* On-disk logging is not part of the production model — container stdout
|
|
22
|
+
* is shipped to S3 by Fluent Bit, so a duplicate copy adds no value. When
|
|
23
|
+
* `AGENTIC_DEV_MODES` is set (local dev-up compose stacks only), a
|
|
24
|
+
* best-effort `dateFile` appender is added so the Local Dev UI log viewer
|
|
25
|
+
* can read agent output from disk; it rotates daily and keeps 5 files,
|
|
26
|
+
* mirroring Python's `TimedRotatingFileHandler`.
|
|
27
|
+
* - Noisy HTTP libraries (`undici`, `httpx`, `httpcore`, `urllib3`,
|
|
28
|
+
* `fastify.access`, `uvicorn.access`) are silenced to `warn` — equivalent
|
|
29
|
+
* to Python's `logging.getLogger("httpx").setLevel(WARNING)` block.
|
|
30
|
+
*/
|
|
31
|
+
import log4js from "log4js";
|
|
32
|
+
import { accessSync, constants, mkdirSync } from "node:fs";
|
|
33
|
+
import { join } from "node:path";
|
|
34
|
+
import { installStructuredLogging } from "./structured_logging.js";
|
|
35
|
+
const NOISY_LOGGERS = ["undici", "fastify.access"];
|
|
36
|
+
// Pattern matches Python's `setup_logging` console formatter shape:
|
|
37
|
+
// `%(asctime)s | %(levelname)-8s | %(message)s`. The file variant adds the
|
|
38
|
+
// category (mirroring Python's detailed file formatter).
|
|
39
|
+
const HUMAN_PATTERN = "%d{yyyy-MM-dd hh:mm:ss} | %p | %c | %m";
|
|
40
|
+
const FILE_PATTERN = "%d{yyyy-MM-dd hh:mm:ss} | %p | %c{1} | %m";
|
|
41
|
+
// Daily rotation with 5 kept files, matching Python's TimedRotatingFileHandler
|
|
42
|
+
// (which="midnight", backupCount=5). Keeps the dev log file bounded so a noisy
|
|
43
|
+
// local agent cannot fill the host disk.
|
|
44
|
+
const DEV_FILE_ROTATION = {
|
|
45
|
+
pattern: "yyyy-MM-dd",
|
|
46
|
+
numToKeep: 5,
|
|
47
|
+
keepFileExt: true,
|
|
48
|
+
};
|
|
49
|
+
function envLevel() {
|
|
50
|
+
return (process.env["LOG_LEVEL"] ?? "info").toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
function noisyCategories() {
|
|
53
|
+
return Object.fromEntries(NOISY_LOGGERS.map((name) => [
|
|
54
|
+
name,
|
|
55
|
+
{ appenders: ["console"], level: "warn" },
|
|
56
|
+
]));
|
|
57
|
+
}
|
|
58
|
+
function buildHumanConfig(level, fileOpts) {
|
|
59
|
+
const appenders = {
|
|
60
|
+
console: {
|
|
61
|
+
type: "stdout",
|
|
62
|
+
layout: { type: "pattern", pattern: HUMAN_PATTERN },
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
const defaultAppenders = ["console"];
|
|
66
|
+
if (fileOpts) {
|
|
67
|
+
appenders.devFile = {
|
|
68
|
+
type: "dateFile",
|
|
69
|
+
filename: fileOpts.logPath,
|
|
70
|
+
layout: { type: "pattern", pattern: FILE_PATTERN },
|
|
71
|
+
...DEV_FILE_ROTATION,
|
|
72
|
+
};
|
|
73
|
+
defaultAppenders.push("devFile");
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
appenders,
|
|
77
|
+
categories: {
|
|
78
|
+
default: { appenders: defaultAppenders, level },
|
|
79
|
+
...noisyCategories(),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
let configured = false;
|
|
84
|
+
function ensureDefaultConfig() {
|
|
85
|
+
if (configured)
|
|
86
|
+
return;
|
|
87
|
+
log4js.configure(buildHumanConfig(envLevel()));
|
|
88
|
+
configured = true;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Mark the log4js root as already configured.
|
|
92
|
+
*
|
|
93
|
+
* Called by `installStructuredLogging` so that a direct `installStructuredLogging()`
|
|
94
|
+
* (the documented startup entrypoint) followed by `getLogger()` does NOT let
|
|
95
|
+
* `ensureDefaultConfig()` clobber the structured config with the
|
|
96
|
+
* human stdout appender. Without this the structured pipeline could be torn
|
|
97
|
+
* down — and re-running `log4js.configure` over patched stdio risks recursion.
|
|
98
|
+
*/
|
|
99
|
+
export function markConfigured() {
|
|
100
|
+
configured = true;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Return a logger named after the caller's module.
|
|
104
|
+
*
|
|
105
|
+
* Mirrors Python `logging.getLogger(__name__)`. log4js maintains a global
|
|
106
|
+
* registry, so a later `setupLogging` reconfiguration is automatically
|
|
107
|
+
* visible to every previously-issued reference. First call performs a
|
|
108
|
+
* default configuration so module-scope `const logger = getLogger(__name__)`
|
|
109
|
+
* patterns work without an explicit `setupLogging` at startup.
|
|
110
|
+
*/
|
|
111
|
+
export function getLogger(name) {
|
|
112
|
+
ensureDefaultConfig();
|
|
113
|
+
return log4js.getLogger(name);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Keep the dev log file name inside the log directory. `appName`/`mode` are
|
|
117
|
+
* caller-supplied (`RuntimeOpts.appName`) and this is the one place the
|
|
118
|
+
* package writes to disk, so a value containing a path separator could
|
|
119
|
+
* otherwise redirect the write outside `logDir`. Separators and NUL are
|
|
120
|
+
* replaced; an empty result falls back to a default so the name stays
|
|
121
|
+
* deterministic.
|
|
122
|
+
*/
|
|
123
|
+
function sanitizeFileComponent(part, fallback) {
|
|
124
|
+
const cleaned = part.replace(/[/\\\0]/g, "_").trim();
|
|
125
|
+
return cleaned.length > 0 ? cleaned : fallback;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Best-effort mkdir + writability probe for the dev-mode log directory.
|
|
129
|
+
* Returns false (and warns) when the directory cannot be created or written
|
|
130
|
+
* so both logging paths skip the file appender instead of installing one
|
|
131
|
+
* that errors on every write. The write probe matters because
|
|
132
|
+
* `mkdirSync(recursive)` is a no-op success on an existing directory that
|
|
133
|
+
* happens to be read-only. Mirrors Python's warn-and-continue in
|
|
134
|
+
* `_install_file_handler`.
|
|
135
|
+
*/
|
|
136
|
+
function ensureLogDir(logDir) {
|
|
137
|
+
try {
|
|
138
|
+
mkdirSync(logDir, { recursive: true });
|
|
139
|
+
accessSync(logDir, constants.W_OK);
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
// Pre-configuration diagnostic — the logger itself isn't set up yet,
|
|
144
|
+
// so warn on stderr rather than pulling in a default log4js config.
|
|
145
|
+
console.warn(`File logging disabled (logDir=${logDir} not writable: ${err instanceof Error ? err.message : String(err)}). Continuing with console output only.`);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Configure the root logger. Mirrors `agent_engine_runner_shared.utils.setup_logging`.
|
|
151
|
+
*
|
|
152
|
+
* When `STRUCTURED_LOGGING=true` is set, delegates to
|
|
153
|
+
* `installStructuredLogging` so all output emerges as single-line JSON
|
|
154
|
+
* matching the agent-log contract. On-disk logging is not part of the
|
|
155
|
+
* production model — Fluent Bit ships container stdout to S3, so a
|
|
156
|
+
* duplicate copy adds no value. The exception is local dev: when
|
|
157
|
+
* `AGENTIC_DEV_MODES` is set (dev-up compose stacks only), a rotating file
|
|
158
|
+
* sink is attached alongside the console/structured output.
|
|
159
|
+
*
|
|
160
|
+
* Unlike Python's `setup_logging` we don't need to manually drop existing
|
|
161
|
+
* handlers before re-adding the console handler: `log4js.configure` fully
|
|
162
|
+
* replaces the prior `appenders`/`categories` config on each call, so a
|
|
163
|
+
* repeat call never accumulates duplicate writers. There's also no
|
|
164
|
+
* stdout/stderr handler-close hazard to guard against — log4js's `stdout`
|
|
165
|
+
* appender owns its own write path.
|
|
166
|
+
*
|
|
167
|
+
* @param args.appName Application name — install line + dev log file name.
|
|
168
|
+
* @param args.mode Runtime mode (aer, tool, orchestrator, memory-server).
|
|
169
|
+
* @param args.logLevel Log level (default: from `LOG_LEVEL` env or INFO).
|
|
170
|
+
* @param args.logDir Log directory for dev-mode file logging (only when AGENTIC_DEV_MODES is set).
|
|
171
|
+
* @param args.backupCount Unused — parity placeholder.
|
|
172
|
+
*/
|
|
173
|
+
export function setupLogging(args = {}) {
|
|
174
|
+
const mode = args.mode ?? "aer";
|
|
175
|
+
const logLevel = args.logLevel ?? null;
|
|
176
|
+
const appName = args.appName ?? "runner";
|
|
177
|
+
const logDir = args.logDir ?? process.env["LOG_DIR"] ?? "./logs";
|
|
178
|
+
// Dev-only file logging: gated on AGENTIC_DEV_MODES (set exclusively by
|
|
179
|
+
// the CLI's dev-up compose templates) AND a usable log directory.
|
|
180
|
+
const devFileEnabled = !!process.env["AGENTIC_DEV_MODES"] && ensureLogDir(logDir);
|
|
181
|
+
const devLogPath = devFileEnabled
|
|
182
|
+
? join(logDir, `${sanitizeFileComponent(appName, "runner")}-${sanitizeFileComponent(mode, "aer")}.log`)
|
|
183
|
+
: null;
|
|
184
|
+
if ((process.env["STRUCTURED_LOGGING"] ?? "").toLowerCase() === "true") {
|
|
185
|
+
// Pass `mode` through explicitly so the layout's `service` field
|
|
186
|
+
// reflects the caller's intent even if `RUNNER_MODE` env happens to
|
|
187
|
+
// be unset — without this, a `setupLogging({mode: "aer"})` call with
|
|
188
|
+
// no env would silently produce `service="agent-execution-runtime"`
|
|
189
|
+
// (the default), lying about which component emitted the line.
|
|
190
|
+
installStructuredLogging({
|
|
191
|
+
level: logLevel,
|
|
192
|
+
mode,
|
|
193
|
+
fileLogPath: devLogPath,
|
|
194
|
+
});
|
|
195
|
+
configured = true;
|
|
196
|
+
return log4js.getLogger();
|
|
197
|
+
}
|
|
198
|
+
const level = (logLevel ?? envLevel()).toLowerCase();
|
|
199
|
+
log4js.configure(buildHumanConfig(level, devLogPath ? { logPath: devLogPath } : undefined));
|
|
200
|
+
configured = true;
|
|
201
|
+
const root = log4js.getLogger();
|
|
202
|
+
root.info(`Logging initialized: level=${level.toUpperCase()}, file=${devLogPath ?? "<disabled>"}, mode=${mode}, app=${appName}`);
|
|
203
|
+
return root;
|
|
204
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth provider and file-backed token storage for remote MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* Port of `agent_engine_runner_shared/mcp_oauth.py`, adapted to `@modelcontextprotocol/sdk`'s
|
|
5
|
+
* `OAuthClientProvider` interface — a lower-level, method-based contract
|
|
6
|
+
* (`tokens()`/`saveTokens()`/`clientInformation()`/...) rather than Python's
|
|
7
|
+
* httpx-`Auth`-flow-based `OAuthClientProvider`. The cache file format/path
|
|
8
|
+
* is kept byte-identical to the Python side so `agentengine dev mcp auth
|
|
9
|
+
* login/status/upload` (Go CLI) and this runtime read/write the same file
|
|
10
|
+
* regardless of which SDK the agent uses.
|
|
11
|
+
*/
|
|
12
|
+
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
13
|
+
import type { RuntimeMCPServerConfig } from "./agent_config.js";
|
|
14
|
+
export declare const DEFAULT_MCP_OAUTH_CLIENT_NAME = "Atlas Agent Engine Dev MCP Client";
|
|
15
|
+
export declare const DEFAULT_MCP_OAUTH_REDIRECT_URI = "http://127.0.0.1:8765/callback";
|
|
16
|
+
/**
|
|
17
|
+
* Cache directory, resolved at call time — not a module-level constant.
|
|
18
|
+
*
|
|
19
|
+
* `materializeMcpOauthSecretCache()` (in `mcp_oauth_secret.ts`) sets
|
|
20
|
+
* `AGENTIC_MCP_OAUTH_DIR` at startup. A frozen import-time const would capture
|
|
21
|
+
* the value from before that runs whenever this module is imported first (e.g.
|
|
22
|
+
* via the package barrel), so the reader and writer could resolve to different
|
|
23
|
+
* directories. Reading the env on each call keeps them in lockstep regardless
|
|
24
|
+
* of import order.
|
|
25
|
+
*/
|
|
26
|
+
export declare function mcpOauthCacheDir(): string;
|
|
27
|
+
/**
|
|
28
|
+
* Return a collision-free cache basename scoped to alias and endpoint.
|
|
29
|
+
* agent.yaml imposes no charset on aliases, so uniqueness comes from hashing
|
|
30
|
+
* the raw alias whenever the readable form would lose information, and from
|
|
31
|
+
* the endpoint hash that stops the same alias sharing credentials across
|
|
32
|
+
* different MCP servers.
|
|
33
|
+
*/
|
|
34
|
+
export declare function mcpOauthCacheName(serverName: string, serverUrl: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Return a non-interactive OAuth provider for a configured MCP server.
|
|
37
|
+
*
|
|
38
|
+
* `cacheDir` overrides `mcpOauthCacheDir()` — used by tests and any caller
|
|
39
|
+
* that needs an isolated cache location instead of the shared `agentengine dev`
|
|
40
|
+
* cache directory.
|
|
41
|
+
*/
|
|
42
|
+
export declare function makeMcpOauthAuth(serverName: string, config: RuntimeMCPServerConfig, cacheDir?: string): OAuthClientProvider;
|
|
43
|
+
/**
|
|
44
|
+
* Return a client-credentials OAuth provider for a configured MCP server.
|
|
45
|
+
*
|
|
46
|
+
* `cacheDir` overrides `mcpOauthCacheDir()` — used by tests and any caller
|
|
47
|
+
* that needs an isolated cache location instead of the shared `agentengine dev`
|
|
48
|
+
* cache directory.
|
|
49
|
+
*/
|
|
50
|
+
export declare function makeMcpClientCredentialsAuth(serverName: string, config: RuntimeMCPServerConfig, cacheDir?: string): OAuthClientProvider;
|
|
51
|
+
//# sourceMappingURL=mcp_oauth.d.ts.map
|