@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 @@
1
+ {"version":3,"file":"mcp_oauth.d.ts","sourceRoot":"","sources":["../src/mcp_oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAOpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,eAAO,MAAM,6BAA6B,sCACL,CAAC;AACtC,eAAO,MAAM,8BAA8B,mCAAmC,CAAC;AAC/E;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAKzC;AAgND;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,MAAM,CAER;AAqGD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,CAAC,EAAE,MAAM,GAChB,mBAAmB,CAIrB;AAkHD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,CAAC,EAAE,MAAM,GAChB,mBAAmB,CAmBrB"}
@@ -0,0 +1,389 @@
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 { createHash } from "node:crypto";
13
+ import * as fs from "node:fs";
14
+ import * as os from "node:os";
15
+ import * as path from "node:path";
16
+ import { lock } from "proper-lockfile";
17
+ import { ClientCredentialsProvider } from "@modelcontextprotocol/sdk/client/auth-extensions.js";
18
+ export const DEFAULT_MCP_OAUTH_CLIENT_NAME = "Atlas Agent Engine Dev MCP Client";
19
+ export const DEFAULT_MCP_OAUTH_REDIRECT_URI = "http://127.0.0.1:8765/callback";
20
+ /**
21
+ * Cache directory, resolved at call time — not a module-level constant.
22
+ *
23
+ * `materializeMcpOauthSecretCache()` (in `mcp_oauth_secret.ts`) sets
24
+ * `AGENTIC_MCP_OAUTH_DIR` at startup. A frozen import-time const would capture
25
+ * the value from before that runs whenever this module is imported first (e.g.
26
+ * via the package barrel), so the reader and writer could resolve to different
27
+ * directories. Reading the env on each call keeps them in lockstep regardless
28
+ * of import order.
29
+ */
30
+ export function mcpOauthCacheDir() {
31
+ return (process.env["AGENTIC_MCP_OAUTH_DIR"] ??
32
+ path.join(os.homedir(), ".agentic", "mcp-oauth"));
33
+ }
34
+ const CACHE_SCHEMA_VERSION = 1;
35
+ function secondsUntilExpiry(rawExpiry) {
36
+ if (typeof rawExpiry !== "string" || rawExpiry === "")
37
+ return undefined;
38
+ const expiry = new Date(rawExpiry);
39
+ if (Number.isNaN(expiry.getTime()))
40
+ return undefined;
41
+ return Math.max(0, Math.floor((expiry.getTime() - Date.now()) / 1000));
42
+ }
43
+ /** Token storage backed by the shared `agentengine dev` cache file. */
44
+ class FileOAuthTokenStorage {
45
+ serverName;
46
+ serverUrl;
47
+ cacheDir;
48
+ clientId;
49
+ configuredScope;
50
+ constructor(serverName, serverUrl, cacheDir = mcpOauthCacheDir(), clientId, configuredScope) {
51
+ this.serverName = serverName;
52
+ this.serverUrl = serverUrl;
53
+ this.cacheDir = cacheDir;
54
+ this.clientId = clientId;
55
+ this.configuredScope = configuredScope;
56
+ }
57
+ get path() {
58
+ return path.join(this.cacheDir, `${mcpOauthCacheName(this.serverName, this.serverUrl)}.json`);
59
+ }
60
+ readPayload() {
61
+ try {
62
+ const raw = fs.readFileSync(this.path, "utf-8");
63
+ const parsed = JSON.parse(raw);
64
+ return parsed !== null && typeof parsed === "object"
65
+ ? parsed
66
+ : {};
67
+ }
68
+ catch {
69
+ return {};
70
+ }
71
+ }
72
+ payloadMatches(payload) {
73
+ if (payload.server_url !== this.serverUrl)
74
+ return false;
75
+ if (this.clientId !== undefined) {
76
+ return (payload.client_id === this.clientId &&
77
+ payload.configured_scope === (this.configuredScope ?? null));
78
+ }
79
+ return true;
80
+ }
81
+ /**
82
+ * Read-modify-write under an advisory file lock.
83
+ *
84
+ * The final write is atomic (tmp file + rename), but without a lock around
85
+ * the read-modify-write as a whole, two processes racing a token refresh
86
+ * for the same server (e.g. two concurrent Tool Pod invocations) can
87
+ * clobber each other's update: the second writer's `readPayload()` can run
88
+ * before the first writer's rename lands, silently discarding it.
89
+ */
90
+ async writePayload(values) {
91
+ fs.mkdirSync(this.cacheDir, { recursive: true, mode: 0o700 });
92
+ let release;
93
+ try {
94
+ // `realpath: false` — the cache file may not exist yet on first write,
95
+ // and resolving symlinks isn't needed for a same-process-family lock.
96
+ // Retries require the async API (`lockSync` rejects a `retries` option).
97
+ release = await lock(this.path, {
98
+ realpath: false,
99
+ stale: 5000,
100
+ retries: { retries: 10, minTimeout: 20, maxTimeout: 200 },
101
+ });
102
+ }
103
+ catch (err) {
104
+ throw new Error(`failed to acquire MCP OAuth cache lock for ${JSON.stringify(this.path)}: ` +
105
+ `${err instanceof Error ? err.message : String(err)}`, { cause: err });
106
+ }
107
+ try {
108
+ const payload = this.readPayload();
109
+ Object.assign(payload, {
110
+ schema_version: CACHE_SCHEMA_VERSION,
111
+ server_name: this.serverName,
112
+ server_url: this.serverUrl,
113
+ updated_at: new Date().toISOString(),
114
+ });
115
+ if (this.clientId !== undefined) {
116
+ payload.client_id = this.clientId;
117
+ payload.configured_scope = this.configuredScope ?? null;
118
+ }
119
+ Object.assign(payload, values);
120
+ const tmpName = path.join(this.cacheDir, `.tmp-${mcpOauthCacheName(this.serverName, this.serverUrl)}-${process.pid}-${Date.now()}`);
121
+ fs.writeFileSync(tmpName, `${JSON.stringify(payload, null, 2)}\n`, {
122
+ encoding: "utf-8",
123
+ mode: 0o600,
124
+ });
125
+ fs.renameSync(tmpName, this.path);
126
+ }
127
+ finally {
128
+ await release();
129
+ }
130
+ }
131
+ getTokens() {
132
+ const payload = this.readPayload();
133
+ if (!this.payloadMatches(payload))
134
+ return undefined;
135
+ const tokenData = payload.tokens;
136
+ if (!tokenData ||
137
+ typeof tokenData !== "object" ||
138
+ !tokenData["access_token"]) {
139
+ return undefined;
140
+ }
141
+ let expiresIn = secondsUntilExpiry(tokenData["expiry"]);
142
+ if (expiresIn === undefined &&
143
+ typeof tokenData["expires_in"] === "number") {
144
+ expiresIn = tokenData["expires_in"];
145
+ }
146
+ return {
147
+ access_token: String(tokenData["access_token"]),
148
+ token_type: tokenData["token_type"] || "Bearer",
149
+ refresh_token: tokenData["refresh_token"],
150
+ expires_in: expiresIn,
151
+ scope: tokenData["scope"],
152
+ };
153
+ }
154
+ async saveTokens(tokens) {
155
+ const tokenData = { ...tokens };
156
+ if (typeof tokenData["expires_in"] === "number") {
157
+ const expiry = new Date(Date.now() + tokenData["expires_in"] * 1000);
158
+ tokenData["expiry"] = expiry.toISOString();
159
+ delete tokenData["expires_in"];
160
+ }
161
+ await this.writePayload({ tokens: tokenData });
162
+ }
163
+ getClientInfo() {
164
+ const payload = this.readPayload();
165
+ if (!this.payloadMatches(payload))
166
+ return undefined;
167
+ const clientData = payload.client;
168
+ if (!clientData ||
169
+ typeof clientData !== "object" ||
170
+ !clientData["client_id"]) {
171
+ return undefined;
172
+ }
173
+ return {
174
+ redirect_uris: clientData["redirect_uris"] ?? [
175
+ DEFAULT_MCP_OAUTH_REDIRECT_URI,
176
+ ],
177
+ token_endpoint_auth_method: clientData["token_endpoint_auth_method"],
178
+ grant_types: clientData["grant_types"] ?? [
179
+ "authorization_code",
180
+ "refresh_token",
181
+ ],
182
+ response_types: clientData["response_types"] ?? ["code"],
183
+ scope: clientData["scope"],
184
+ client_name: clientData["client_name"],
185
+ client_id: clientData["client_id"],
186
+ client_secret: clientData["client_secret"],
187
+ client_id_issued_at: clientData["client_id_issued_at"],
188
+ client_secret_expires_at: clientData["client_secret_expires_at"],
189
+ };
190
+ }
191
+ async saveClientInfo(clientInfo) {
192
+ await this.writePayload({
193
+ client: clientInfo,
194
+ });
195
+ }
196
+ }
197
+ const READABLE_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
198
+ const UNSAFE_NAME_CHAR_RE = /[^A-Za-z0-9_-]+/g;
199
+ const LEADING_TRAILING_DASH_UNDERSCORE_RE = /^[-_]+|[-_]+$/g;
200
+ /**
201
+ * Return a collision-free cache basename scoped to alias and endpoint.
202
+ * agent.yaml imposes no charset on aliases, so uniqueness comes from hashing
203
+ * the raw alias whenever the readable form would lose information, and from
204
+ * the endpoint hash that stops the same alias sharing credentials across
205
+ * different MCP servers.
206
+ */
207
+ export function mcpOauthCacheName(serverName, serverUrl) {
208
+ return `${cacheBase(serverName)}-${sha256Hex(serverUrl)}`;
209
+ }
210
+ function cacheBase(name) {
211
+ if (READABLE_NAME_RE.test(name))
212
+ return name;
213
+ let safe = name
214
+ .trim()
215
+ .replace(UNSAFE_NAME_CHAR_RE, "-")
216
+ .replace(LEADING_TRAILING_DASH_UNDERSCORE_RE, "");
217
+ if (safe.length > 40) {
218
+ safe = safe.slice(0, 40).replace(LEADING_TRAILING_DASH_UNDERSCORE_RE, "");
219
+ }
220
+ const rawHash = sha256Hex(name).slice(0, 16);
221
+ return safe === "" ? rawHash : `${safe}-${rawHash}`;
222
+ }
223
+ function sha256Hex(value) {
224
+ return createHash("sha256").update(value, "utf8").digest("hex");
225
+ }
226
+ const INTERACTIVE_LOGIN_REQUIRED_MESSAGE = (serverName) => `mcp server '${serverName}' requires cached OAuth credentials; run 'agentengine dev mcp auth login ${serverName}'`;
227
+ /**
228
+ * Non-interactive OAuth provider for the interactive `oauth` auth mode.
229
+ *
230
+ * Reads/refreshes tokens the CLI's `agentengine dev mcp auth login` already
231
+ * cached to `mcpOauthCacheDir()`. Never initiates a fresh authorization
232
+ * flow itself — the runtime process has no browser/loopback listener, so any
233
+ * attempt to do so raises telling the user to run the CLI login command.
234
+ */
235
+ class FileBackedOAuthProvider {
236
+ serverName;
237
+ storage;
238
+ _clientMetadata;
239
+ constructor(serverName, config, redirectUri, cacheDir) {
240
+ this.serverName = serverName;
241
+ this.storage = new FileOAuthTokenStorage(serverName, config.url, cacheDir, undefined, config.auth.scope ?? undefined);
242
+ this._clientMetadata = {
243
+ redirect_uris: [redirectUri],
244
+ client_name: config.auth.client_name ?? DEFAULT_MCP_OAUTH_CLIENT_NAME,
245
+ scope: config.auth.scope,
246
+ grant_types: ["authorization_code", "refresh_token"],
247
+ response_types: ["code"],
248
+ // A loopback client cannot keep a client secret. Omitting this lets
249
+ // servers apply RFC 7591's client_secret_basic default and mint a
250
+ // secret the token exchange then has no way to send.
251
+ token_endpoint_auth_method: "none",
252
+ };
253
+ }
254
+ get redirectUrl() {
255
+ const [redirectUri] = this._clientMetadata.redirect_uris;
256
+ if (redirectUri === undefined) {
257
+ throw new Error("MCP OAuth client metadata has no redirect_uris");
258
+ }
259
+ return redirectUri;
260
+ }
261
+ get clientMetadata() {
262
+ return this._clientMetadata;
263
+ }
264
+ clientInformation() {
265
+ return this.storage.getClientInfo();
266
+ }
267
+ async saveClientInformation(info) {
268
+ await this.storage.saveClientInfo(info);
269
+ }
270
+ tokens() {
271
+ return this.storage.getTokens();
272
+ }
273
+ async saveTokens(tokens) {
274
+ await this.storage.saveTokens(tokens);
275
+ }
276
+ redirectToAuthorization() {
277
+ throw new Error(INTERACTIVE_LOGIN_REQUIRED_MESSAGE(this.serverName));
278
+ }
279
+ saveCodeVerifier() {
280
+ throw new Error(INTERACTIVE_LOGIN_REQUIRED_MESSAGE(this.serverName));
281
+ }
282
+ codeVerifier() {
283
+ throw new Error(INTERACTIVE_LOGIN_REQUIRED_MESSAGE(this.serverName));
284
+ }
285
+ }
286
+ /**
287
+ * Return a non-interactive OAuth provider for a configured MCP server.
288
+ *
289
+ * `cacheDir` overrides `mcpOauthCacheDir()` — used by tests and any caller
290
+ * that needs an isolated cache location instead of the shared `agentengine dev`
291
+ * cache directory.
292
+ */
293
+ export function makeMcpOauthAuth(serverName, config, cacheDir) {
294
+ const redirectUri = config.auth.redirect_uri ?? DEFAULT_MCP_OAUTH_REDIRECT_URI;
295
+ return new FileBackedOAuthProvider(serverName, config, redirectUri, cacheDir);
296
+ }
297
+ /**
298
+ * `client_credentials` OAuth provider with file-backed token caching.
299
+ *
300
+ * Wraps the SDK's `ClientCredentialsProvider` (which only caches tokens
301
+ * in-memory) so refreshed tokens persist across process restarts, matching
302
+ * Python's `CachedClientCredentialsOAuthProvider`. When `auth.token_url` is
303
+ * configured (Atlas-style out-of-band token endpoint), pre-seeds discovery
304
+ * state so the SDK's `auth()` orchestrator skips RFC 9728/8414 discovery and
305
+ * posts directly to that endpoint — mirroring Python's
306
+ * `DirectClientCredentialsOAuthProvider`.
307
+ */
308
+ class FileBackedClientCredentialsProvider {
309
+ inner;
310
+ storage;
311
+ tokenUrl;
312
+ constructor(serverName, config, clientId, clientSecret, cacheDir) {
313
+ this.inner = new ClientCredentialsProvider({
314
+ clientId,
315
+ clientSecret,
316
+ scope: config.auth.scope ?? undefined,
317
+ });
318
+ this.storage = new FileOAuthTokenStorage(serverName, config.url, cacheDir, clientId, config.auth.scope ?? undefined);
319
+ this.tokenUrl = config.auth.token_url;
320
+ }
321
+ get redirectUrl() {
322
+ return undefined;
323
+ }
324
+ get clientMetadata() {
325
+ return this.inner.clientMetadata;
326
+ }
327
+ clientInformation() {
328
+ return this.inner.clientInformation();
329
+ }
330
+ saveClientInformation(info) {
331
+ this.inner.saveClientInformation(info);
332
+ }
333
+ tokens() {
334
+ return this.storage.getTokens();
335
+ }
336
+ async saveTokens(tokens) {
337
+ await this.storage.saveTokens(tokens);
338
+ this.inner.saveTokens(tokens);
339
+ }
340
+ redirectToAuthorization() {
341
+ this.inner.redirectToAuthorization();
342
+ }
343
+ saveCodeVerifier() {
344
+ this.inner.saveCodeVerifier();
345
+ }
346
+ codeVerifier() {
347
+ return this.inner.codeVerifier();
348
+ }
349
+ prepareTokenRequest(scope) {
350
+ return this.inner.prepareTokenRequest(scope);
351
+ }
352
+ discoveryState() {
353
+ if (this.tokenUrl === null)
354
+ return undefined;
355
+ return {
356
+ authorizationServerUrl: this.tokenUrl,
357
+ authorizationServerMetadata: {
358
+ issuer: this.tokenUrl,
359
+ authorization_endpoint: this.tokenUrl,
360
+ token_endpoint: this.tokenUrl,
361
+ response_types_supported: ["token"],
362
+ },
363
+ };
364
+ }
365
+ saveDiscoveryState() {
366
+ // No-op: the token URL is static config, not something discovery
367
+ // produces — nothing to persist beyond what discoveryState() returns.
368
+ }
369
+ }
370
+ function requiredEnvValue(serverName, fieldName, envName) {
371
+ const value = envName === null ? undefined : process.env[envName];
372
+ if (value === undefined || value.trim() === "") {
373
+ throw new Error(`mcp server '${serverName}' auth.${fieldName} references missing env var ${envName}`);
374
+ }
375
+ return value.trim();
376
+ }
377
+ /**
378
+ * Return a client-credentials OAuth provider for a configured MCP server.
379
+ *
380
+ * `cacheDir` overrides `mcpOauthCacheDir()` — used by tests and any caller
381
+ * that needs an isolated cache location instead of the shared `agentengine dev`
382
+ * cache directory.
383
+ */
384
+ export function makeMcpClientCredentialsAuth(serverName, config, cacheDir) {
385
+ const auth = config.auth;
386
+ const clientId = requiredEnvValue(serverName, "client_id_env", auth.client_id_env);
387
+ const clientSecret = requiredEnvValue(serverName, "client_secret_env", auth.client_secret_env);
388
+ return new FileBackedClientCredentialsProvider(serverName, config, clientId, clientSecret, cacheDir);
389
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Materialize deployed MCP OAuth secrets into the file-backed token cache.
3
+ *
4
+ * Port of `agent_engine_runner_shared/mcp_oauth_secret.py`. On deploy the platform injects
5
+ * each server's OAuth cache as a base64-encoded `AGENTIC_MCP_OAUTH_B64_*` env
6
+ * var; this decodes them into the cache directory that `mcp_oauth.ts` reads,
7
+ * keeping the on-disk layout byte-identical to the Python side so either SDK
8
+ * (and the Go CLI) can read the same files.
9
+ */
10
+ interface MaterializeOptions {
11
+ defaultCacheDir?: string;
12
+ }
13
+ /**
14
+ * Establish the writable runtime cache and decode MCP OAuth secrets into it.
15
+ *
16
+ * Sets `AGENTIC_MCP_OAUTH_DIR` (if unset) so the reader in `mcp_oauth.ts`
17
+ * resolves the same directory. Returns the number of secrets materialized.
18
+ */
19
+ export declare function materializeMcpOauthSecretCache({ defaultCacheDir, }?: MaterializeOptions): Promise<number>;
20
+ export {};
21
+ //# sourceMappingURL=mcp_oauth_secret.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp_oauth_secret.d.ts","sourceRoot":"","sources":["../src/mcp_oauth_secret.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAeH,UAAU,kBAAkB;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAOD;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAAC,EACnD,eAA8C,GAC/C,GAAE,kBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB3C"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Materialize deployed MCP OAuth secrets into the file-backed token cache.
3
+ *
4
+ * Port of `agent_engine_runner_shared/mcp_oauth_secret.py`. On deploy the platform injects
5
+ * each server's OAuth cache as a base64-encoded `AGENTIC_MCP_OAUTH_B64_*` env
6
+ * var; this decodes them into the cache directory that `mcp_oauth.ts` reads,
7
+ * keeping the on-disk layout byte-identical to the Python side so either SDK
8
+ * (and the Go CLI) can read the same files.
9
+ */
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ import { lock } from "proper-lockfile";
13
+ import { mcpOauthCacheName } from "./mcp_oauth.js";
14
+ const MCP_OAUTH_DIR_ENV = "AGENTIC_MCP_OAUTH_DIR";
15
+ const MCP_OAUTH_SECRET_ENV_PREFIX = "AGENTIC_MCP_OAUTH_B64_";
16
+ const PLATFORM_MCP_OAUTH_CACHE_DIR = "/tmp/agentic/mcp-oauth";
17
+ const SAFE_ENV_CACHE_NAME_RE = /^[A-Z0-9_]+$/;
18
+ const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
19
+ /**
20
+ * Establish the writable runtime cache and decode MCP OAuth secrets into it.
21
+ *
22
+ * Sets `AGENTIC_MCP_OAUTH_DIR` (if unset) so the reader in `mcp_oauth.ts`
23
+ * resolves the same directory. Returns the number of secrets materialized.
24
+ */
25
+ export async function materializeMcpOauthSecretCache({ defaultCacheDir = PLATFORM_MCP_OAUTH_CACHE_DIR, } = {}) {
26
+ const cacheDir = process.env[MCP_OAUTH_DIR_ENV] ?? defaultCacheDir;
27
+ if (process.env[MCP_OAUTH_DIR_ENV] === undefined) {
28
+ process.env[MCP_OAUTH_DIR_ENV] = cacheDir;
29
+ }
30
+ fs.mkdirSync(cacheDir, { recursive: true, mode: 0o700 });
31
+ let materialized = 0;
32
+ for (const [envName, encodedCache] of mcpOauthSecretEnv()) {
33
+ const { rawCache, payload } = decodeCacheSecret(envName, encodedCache);
34
+ const cacheName = cacheNameFor(envName, payload);
35
+ await writeCacheFile(cacheDir, cacheName, rawCache);
36
+ delete process.env[envName];
37
+ materialized += 1;
38
+ }
39
+ return materialized;
40
+ }
41
+ function mcpOauthSecretEnv() {
42
+ return Object.entries(process.env)
43
+ .filter((entry) => entry[0].startsWith(MCP_OAUTH_SECRET_ENV_PREFIX) &&
44
+ entry[1] !== undefined)
45
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
46
+ }
47
+ function decodeCacheSecret(envName, encodedCache) {
48
+ // Strict base64: mirror Python's `validate=True`. Node's decoder silently
49
+ // drops out-of-alphabet characters, so reject them up front rather than
50
+ // materializing a truncated cache.
51
+ if (encodedCache.length % 4 !== 0 || !BASE64_RE.test(encodedCache)) {
52
+ throw new Error(`invalid MCP OAuth secret ${envName}: value is not valid base64`);
53
+ }
54
+ const rawCache = Buffer.from(encodedCache, "base64");
55
+ let payload;
56
+ try {
57
+ payload = JSON.parse(rawCache.toString("utf-8"));
58
+ }
59
+ catch (err) {
60
+ throw new Error(`invalid MCP OAuth secret ${envName}: decoded value is not valid JSON`, { cause: err });
61
+ }
62
+ if (payload === null ||
63
+ typeof payload !== "object" ||
64
+ Array.isArray(payload)) {
65
+ throw new Error(`invalid MCP OAuth secret ${envName}: decoded value must be a JSON object`);
66
+ }
67
+ return { rawCache, payload: payload };
68
+ }
69
+ function cacheNameFor(envName, payload) {
70
+ // The runtime looks the cache up by the configured server URL's hash, so a
71
+ // payload without server_url could never be found — fail loudly instead of
72
+ // materializing an unreadable file.
73
+ const serverUrl = payload["server_url"];
74
+ if (typeof serverUrl !== "string" || serverUrl.trim() === "") {
75
+ throw new Error(`invalid MCP OAuth secret ${envName}: missing server_url`);
76
+ }
77
+ const payloadServerName = payload["server_name"];
78
+ if (typeof payloadServerName === "string" &&
79
+ payloadServerName.trim() !== "") {
80
+ return mcpOauthCacheName(payloadServerName, serverUrl);
81
+ }
82
+ const envCacheName = envName.slice(MCP_OAUTH_SECRET_ENV_PREFIX.length);
83
+ if (envCacheName === "" || !SAFE_ENV_CACHE_NAME_RE.test(envCacheName)) {
84
+ throw new Error(`invalid MCP OAuth secret ${envName}: invalid cache name`);
85
+ }
86
+ return mcpOauthCacheName(envCacheName.toLowerCase(), serverUrl);
87
+ }
88
+ async function writeCacheFile(cacheDir, cacheName, rawCache) {
89
+ const cacheFile = path.join(cacheDir, `${cacheName}.json`);
90
+ let release;
91
+ try {
92
+ // Same lock target/params as `mcp_oauth.ts`'s writePayload, so a
93
+ // materialize write and a runtime token refresh can't clobber each other.
94
+ release = await lock(cacheFile, {
95
+ realpath: false,
96
+ stale: 5000,
97
+ retries: { retries: 10, minTimeout: 20, maxTimeout: 200 },
98
+ });
99
+ }
100
+ catch (err) {
101
+ throw new Error(`failed to acquire MCP OAuth cache lock for ${JSON.stringify(cacheFile)}: ` +
102
+ `${err instanceof Error ? err.message : String(err)}`, { cause: err });
103
+ }
104
+ const tmpName = path.join(cacheDir, `.tmp-${cacheName}-${process.pid}-${Date.now()}`);
105
+ try {
106
+ try {
107
+ fs.writeFileSync(tmpName, rawCache, { mode: 0o600 });
108
+ fs.renameSync(tmpName, cacheFile);
109
+ }
110
+ finally {
111
+ try {
112
+ fs.unlinkSync(tmpName);
113
+ }
114
+ catch {
115
+ // Already renamed into place, or never created — nothing to clean up.
116
+ }
117
+ }
118
+ }
119
+ finally {
120
+ await release();
121
+ }
122
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Framework-neutral helpers for remote MCP tool discovery and execution.
3
+ *
4
+ * Port of `agent_engine_runner_shared/mcp_tools.py`. Built directly on
5
+ * `@modelcontextprotocol/sdk` (mirroring Python's `mcp` PyPI package) rather
6
+ * than a framework-specific adapter, since the platform needs custom result
7
+ * normalization, egress host derivation, and the Tool-Pod/AER dispatch split
8
+ * that a framework wrapper doesn't support.
9
+ */
10
+ import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
11
+ import type { RuntimeMCPConfig, RuntimeMCPServerConfig } from "./agent_config.js";
12
+ /** Raised when remote MCP configuration cannot be resolved. */
13
+ export declare class MCPConfigError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ /** Raised when a remote MCP tool returns an MCP error result or a transport failure occurs. */
17
+ export declare class MCPToolError extends Error {
18
+ constructor(message: string);
19
+ }
20
+ /** Discovered MCP tool bound to a configured server. */
21
+ export interface MCPToolBinding {
22
+ readonly serverName: string;
23
+ readonly toolName: string;
24
+ readonly sdkToolName: string;
25
+ readonly description: string;
26
+ readonly inputSchema: Record<string, unknown>;
27
+ readonly serverConfig: RuntimeMCPServerConfig;
28
+ }
29
+ /** JSON/BSON-safe result returned by an MCP `tools/call` invocation. */
30
+ export interface MCPToolResult {
31
+ readonly content: unknown[];
32
+ readonly structuredContent: Record<string, unknown> | null;
33
+ readonly isError: boolean;
34
+ }
35
+ /** Return the server-prefixed SDK name for a remote MCP tool. */
36
+ export declare function makeMcpSdkToolName(serverName: string, toolName: string): string;
37
+ /** Resolve headers for a remote MCP server without exposing secret values in config. */
38
+ export declare function resolveMcpHeaders(serverName: string, config: RuntimeMCPServerConfig): Record<string, string>;
39
+ /** Resolve an MCP SDK OAuth provider for the configured auth mode, if any. */
40
+ export declare function resolveMcpAuth(serverName: string, config: RuntimeMCPServerConfig): Promise<OAuthClientProvider | undefined>;
41
+ /** Return the outbound hosts needed to reach a configured MCP server. */
42
+ export declare function mcpServerNetworkHosts(config: RuntimeMCPServerConfig): string[];
43
+ /**
44
+ * Discover remote MCP tools for all configured servers.
45
+ *
46
+ * Mirrors common MCP client adapters: initialize each server, call
47
+ * `tools/list`, then expose the returned schemas as framework-native tools.
48
+ */
49
+ export declare function discoverMcpTools(config: RuntimeMCPConfig): Promise<MCPToolBinding[]>;
50
+ /**
51
+ * Map an SDK-visible tool name back to configured MCP call metadata.
52
+ *
53
+ * AER discovers remote MCP schemas at startup and exposes server-prefixed
54
+ * names such as `github__search_issues` to the LLM. Tool Pods skip
55
+ * `tools/list` startup discovery, so call-time execution resolves that SDK
56
+ * name against `agent.yaml` and calls the original MCP tool name.
57
+ */
58
+ export declare function resolveConfiguredMcpToolBinding(config: RuntimeMCPConfig, sdkToolName: string, mcpServerName: string, mcpToolName: string): MCPToolBinding | null;
59
+ /** Return whether `sdkToolName` belongs to a configured MCP server. */
60
+ export declare function isConfiguredMcpSdkToolName(config: RuntimeMCPConfig, sdkToolName: string): boolean;
61
+ /** Execute a remote MCP tool and normalize its result. */
62
+ export declare function callMcpTool(binding: MCPToolBinding, args: Record<string, unknown>): Promise<MCPToolResult>;
63
+ /** Create the async callable used to invoke a discovered MCP tool. */
64
+ export declare function makeMcpToolCallable(binding: MCPToolBinding): (args: Record<string, unknown>) => Promise<MCPToolResult>;
65
+ /** Convert an MCP `CallToolResult` into a JSON-safe result, raising on an error result. */
66
+ export declare function normalizeMcpToolResult(result: {
67
+ content?: unknown[];
68
+ structuredContent?: unknown;
69
+ isError?: boolean;
70
+ }): MCPToolResult;
71
+ //# sourceMappingURL=mcp_tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp_tools.d.ts","sourceRoot":"","sources":["../src/mcp_tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAEpF,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAO3B,+DAA+D;AAC/D,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED,+FAA+F;AAC/F,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,QAAQ,CAAC,YAAY,EAAE,sBAAsB,CAAC;CAC/C;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,MAAM,CAIR;AAqBD,wFAAwF;AACxF,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,sBAAsB,GAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwBxB;AAED,8EAA8E;AAC9E,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAsB1C;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,sBAAsB,GAC7B,MAAM,EAAE,CAOV;AAQD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,cAAc,EAAE,CAAC,CA2C3B;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,cAAc,GAAG,IAAI,CA4BvB;AAED,uEAAuE;AACvE,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,GAClB,OAAO,CAWT;AAED,0DAA0D;AAC1D,wBAAsB,WAAW,CAC/B,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,aAAa,CAAC,CAuBxB;AAED,sEAAsE;AACtE,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,cAAc,GACtB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,aAAa,CAAC,CAE3D;AAED,2FAA2F;AAC3F,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,aAAa,CA8ChB"}