stitchkit 0.88.0 → 0.90.0

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 (184) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/dist/agent-runtime/child-blocking.d.ts +17 -0
  3. package/dist/agent-runtime/child-blocking.d.ts.map +1 -0
  4. package/dist/agent-runtime/child-budget-policy.d.ts +23 -0
  5. package/dist/agent-runtime/child-budget-policy.d.ts.map +1 -0
  6. package/dist/agent-runtime/children-contract.d.ts +155 -0
  7. package/dist/agent-runtime/children-contract.d.ts.map +1 -0
  8. package/dist/agent-runtime/children.d.ts +4 -99
  9. package/dist/agent-runtime/children.d.ts.map +1 -1
  10. package/dist/agent-runtime/coding-shell-process.d.ts +33 -0
  11. package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -0
  12. package/dist/agent-runtime/coding-tool-contract.d.ts +18 -0
  13. package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
  14. package/dist/agent-runtime/coding-tool-files.d.ts.map +1 -1
  15. package/dist/agent-runtime/coding-tool-listing.d.ts.map +1 -1
  16. package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
  17. package/dist/agent-runtime/coding-tool-shell.d.ts.map +1 -1
  18. package/dist/agent-runtime/coding-tool-utf8.d.ts +28 -0
  19. package/dist/agent-runtime/coding-tool-utf8.d.ts.map +1 -0
  20. package/dist/agent-runtime/contained-files.d.ts +20 -0
  21. package/dist/agent-runtime/contained-files.d.ts.map +1 -1
  22. package/dist/agent-runtime/durability-contract.d.ts +176 -0
  23. package/dist/agent-runtime/durability-contract.d.ts.map +1 -0
  24. package/dist/agent-runtime/durability-json.d.ts +3 -0
  25. package/dist/agent-runtime/durability-json.d.ts.map +1 -0
  26. package/dist/agent-runtime/durability-ledger.d.ts +16 -0
  27. package/dist/agent-runtime/durability-ledger.d.ts.map +1 -0
  28. package/dist/agent-runtime/durability-scheduler.d.ts +29 -0
  29. package/dist/agent-runtime/durability-scheduler.d.ts.map +1 -0
  30. package/dist/agent-runtime/durability.d.ts +14 -0
  31. package/dist/agent-runtime/durability.d.ts.map +1 -0
  32. package/dist/agent-runtime/event-search.d.ts.map +1 -1
  33. package/dist/agent-runtime/harness-contract.d.ts +37 -0
  34. package/dist/agent-runtime/harness-contract.d.ts.map +1 -1
  35. package/dist/agent-runtime/harness.d.ts +8 -0
  36. package/dist/agent-runtime/harness.d.ts.map +1 -1
  37. package/dist/agent-runtime/prompt.d.ts +23 -0
  38. package/dist/agent-runtime/prompt.d.ts.map +1 -1
  39. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  40. package/dist/agent-runtime/runtime-internals.d.ts +4 -0
  41. package/dist/agent-runtime/runtime-internals.d.ts.map +1 -1
  42. package/dist/agent-runtime/runtime.d.ts +13 -0
  43. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  44. package/dist/agent-runtime/sandbox-broker.d.ts +7 -0
  45. package/dist/agent-runtime/sandbox-broker.d.ts.map +1 -0
  46. package/dist/agent-runtime/sandbox-bubblewrap-args.d.ts +9 -0
  47. package/dist/agent-runtime/sandbox-bubblewrap-args.d.ts.map +1 -0
  48. package/dist/agent-runtime/sandbox-bubblewrap-storage.d.ts +22 -0
  49. package/dist/agent-runtime/sandbox-bubblewrap-storage.d.ts.map +1 -0
  50. package/dist/agent-runtime/sandbox-bubblewrap.d.ts +14 -0
  51. package/dist/agent-runtime/sandbox-bubblewrap.d.ts.map +1 -0
  52. package/dist/agent-runtime/sandbox-coding-adapter.d.ts +15 -0
  53. package/dist/agent-runtime/sandbox-coding-adapter.d.ts.map +1 -0
  54. package/dist/agent-runtime/sandbox-coding.d.ts +15 -0
  55. package/dist/agent-runtime/sandbox-coding.d.ts.map +1 -0
  56. package/dist/agent-runtime/sandbox-contract.d.ts +93 -0
  57. package/dist/agent-runtime/sandbox-contract.d.ts.map +1 -0
  58. package/dist/agent-runtime/sandbox-process-owner.d.ts +11 -0
  59. package/dist/agent-runtime/sandbox-process-owner.d.ts.map +1 -0
  60. package/dist/agent-runtime/sandbox-process.d.ts +8 -0
  61. package/dist/agent-runtime/sandbox-process.d.ts.map +1 -0
  62. package/dist/agent-runtime/sandbox-session.d.ts +5 -0
  63. package/dist/agent-runtime/sandbox-session.d.ts.map +1 -0
  64. package/dist/agent-runtime/sandbox.d.ts +19 -0
  65. package/dist/agent-runtime/sandbox.d.ts.map +1 -1
  66. package/dist/agent-runtime/sqlite-purge.d.ts.map +1 -1
  67. package/dist/agent-runtime/sqlite.d.ts.map +1 -1
  68. package/dist/agent-runtime/store-driver.d.ts +127 -0
  69. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  70. package/dist/agent-runtime/store-events.d.ts +104 -0
  71. package/dist/agent-runtime/store-events.d.ts.map +1 -1
  72. package/dist/agent-runtime/store-migrations/v2-to-v3.d.ts +14 -0
  73. package/dist/agent-runtime/store-migrations/v2-to-v3.d.ts.map +1 -0
  74. package/dist/agent-runtime/store.d.ts +115 -0
  75. package/dist/agent-runtime/store.d.ts.map +1 -1
  76. package/dist/agent-runtime-coding-tools.js +11 -1247
  77. package/dist/agent-runtime-harness.d.ts +1 -1
  78. package/dist/agent-runtime-harness.d.ts.map +1 -1
  79. package/dist/agent-runtime-harness.js +65 -15
  80. package/dist/agent-runtime-openrouter.d.ts +3 -12
  81. package/dist/agent-runtime-openrouter.d.ts.map +1 -1
  82. package/dist/agent-runtime-openrouter.js +10 -7
  83. package/dist/agent-runtime-sandbox.d.ts +5 -0
  84. package/dist/agent-runtime-sandbox.d.ts.map +1 -0
  85. package/dist/agent-runtime-sandbox.js +797 -0
  86. package/dist/agent-runtime-sqlite-bun.js +3 -3
  87. package/dist/agent-runtime-sqlite-node.js +3 -3
  88. package/dist/agent-runtime.d.ts +5 -4
  89. package/dist/agent-runtime.d.ts.map +1 -1
  90. package/dist/agent-runtime.js +259 -65
  91. package/dist/application/watch-hub.d.ts +24 -2
  92. package/dist/application/watch-hub.d.ts.map +1 -1
  93. package/dist/application.js +127 -37
  94. package/dist/cli.js +8 -8
  95. package/dist/index-2na0qrfn.js +214 -0
  96. package/dist/{index-rcgsnrr6.js → index-31jw8fjq.js} +3 -3
  97. package/dist/{index-9hw9eq48.js → index-7601qfg1.js} +14 -14
  98. package/dist/{index-3xnq72rz.js → index-7rkhw9ec.js} +14 -1
  99. package/dist/{index-0vfdh685.js → index-8c3a7m4g.js} +1 -1
  100. package/dist/{index-t7gh5w3v.js → index-bn2cjajt.js} +27 -6
  101. package/dist/index-cp20z0n6.js +1351 -0
  102. package/dist/{index-qp3vshvc.js → index-fadnxwna.js} +87 -5
  103. package/dist/{index-v1qk37y9.js → index-hrbajay7.js} +106 -4
  104. package/dist/{index-p86mxmpp.js → index-hw2chdvy.js} +646 -43
  105. package/dist/{index-0ydchz76.js → index-mnm4qp0p.js} +5 -5
  106. package/dist/{index-1szetq6f.js → index-q3ve2d1k.js} +30 -8
  107. package/dist/{index-6krwcdcf.js → index-zwkgfq1s.js} +95 -85
  108. package/dist/{index-wn9cb82y.js → index-zyvby92n.js} +12 -9
  109. package/dist/index.js +13 -13
  110. package/dist/live/watch-client.d.ts +2 -1
  111. package/dist/live/watch-client.d.ts.map +1 -1
  112. package/dist/live/watch-contract.d.ts +70 -4
  113. package/dist/live/watch-contract.d.ts.map +1 -1
  114. package/dist/live/watch-delta.d.ts +110 -0
  115. package/dist/live/watch-delta.d.ts.map +1 -0
  116. package/dist/live.d.ts +2 -1
  117. package/dist/live.d.ts.map +1 -1
  118. package/dist/live.js +56 -4
  119. package/dist/observability/changes.d.ts +55 -0
  120. package/dist/observability/changes.d.ts.map +1 -0
  121. package/dist/observability/index.d.ts +2 -0
  122. package/dist/observability/index.d.ts.map +1 -1
  123. package/dist/observability/index.js +127 -0
  124. package/dist/observability/spool.d.ts +53 -0
  125. package/dist/observability/spool.d.ts.map +1 -0
  126. package/dist/server/index.js +9 -9
  127. package/dist/testing/agent-store-conformance.d.ts.map +1 -1
  128. package/dist/testing.js +57 -23
  129. package/dist/tool-invoker.js +6 -6
  130. package/dist/tools/agent.d.ts +6 -0
  131. package/dist/tools/agent.d.ts.map +1 -1
  132. package/dist/tools/connections/define.d.ts +6 -0
  133. package/dist/tools/connections/define.d.ts.map +1 -0
  134. package/dist/tools/connections/errors.d.ts +50 -0
  135. package/dist/tools/connections/errors.d.ts.map +1 -0
  136. package/dist/tools/connections/http.d.ts +3 -0
  137. package/dist/tools/connections/http.d.ts.map +1 -0
  138. package/dist/tools/connections/index.d.ts +19 -0
  139. package/dist/tools/connections/index.d.ts.map +1 -0
  140. package/dist/tools/connections/index.js +1047 -0
  141. package/dist/tools/connections/instance.d.ts +3 -0
  142. package/dist/tools/connections/instance.d.ts.map +1 -0
  143. package/dist/tools/connections/json-rpc.d.ts +8 -0
  144. package/dist/tools/connections/json-rpc.d.ts.map +1 -0
  145. package/dist/tools/connections/limits.d.ts +36 -0
  146. package/dist/tools/connections/limits.d.ts.map +1 -0
  147. package/dist/tools/connections/mcp-client.d.ts +44 -0
  148. package/dist/tools/connections/mcp-client.d.ts.map +1 -0
  149. package/dist/tools/connections/mcp.d.ts +5 -0
  150. package/dist/tools/connections/mcp.d.ts.map +1 -0
  151. package/dist/tools/connections/mount.d.ts +12 -0
  152. package/dist/tools/connections/mount.d.ts.map +1 -0
  153. package/dist/tools/connections/openapi-document.d.ts +4 -0
  154. package/dist/tools/connections/openapi-document.d.ts.map +1 -0
  155. package/dist/tools/connections/openapi.d.ts +5 -0
  156. package/dist/tools/connections/openapi.d.ts.map +1 -0
  157. package/dist/tools/connections/runtime.d.ts +29 -0
  158. package/dist/tools/connections/runtime.d.ts.map +1 -0
  159. package/dist/tools/connections/schema-budget.d.ts +8 -0
  160. package/dist/tools/connections/schema-budget.d.ts.map +1 -0
  161. package/dist/tools/connections/sse-frames.d.ts +7 -0
  162. package/dist/tools/connections/sse-frames.d.ts.map +1 -0
  163. package/dist/tools/connections/sse-session.d.ts +26 -0
  164. package/dist/tools/connections/sse-session.d.ts.map +1 -0
  165. package/dist/tools/connections/ssrf.d.ts +15 -0
  166. package/dist/tools/connections/ssrf.d.ts.map +1 -0
  167. package/dist/tools/connections/types.d.ts +60 -0
  168. package/dist/tools/connections/types.d.ts.map +1 -0
  169. package/dist/tools/durability-context.d.ts +7 -0
  170. package/dist/tools/durability-context.d.ts.map +1 -0
  171. package/dist/tools/execute.d.ts +2 -0
  172. package/dist/tools/execute.d.ts.map +1 -1
  173. package/dist/tools/registry.d.ts +38 -0
  174. package/dist/tools/registry.d.ts.map +1 -0
  175. package/dist/tools/runtime-tool.d.ts +4 -0
  176. package/dist/tools/runtime-tool.d.ts.map +1 -1
  177. package/dist/tools.d.ts +1 -0
  178. package/dist/tools.d.ts.map +1 -1
  179. package/dist/tools.js +83 -26
  180. package/llms-full.txt +386 -16
  181. package/llms.txt +1 -0
  182. package/package.json +9 -1
  183. package/dist/index-dre2ywck.js +0 -48
  184. package/dist/{index-evcnmsc3.js → index-t7fkyxcr.js} +3 -3
@@ -0,0 +1,1047 @@
1
+ import {
2
+ defineRuntimeTool
3
+ } from "../../index-kc6h6hg0.js";
4
+ import"../../index-r159gjwy.js";
5
+ import"../../index-cby4ar3v.js";
6
+ import {
7
+ isRecord
8
+ } from "../../index-77fekveh.js";
9
+
10
+ // src/tools/connections/errors.ts
11
+ class ConnectionAuthorizationRequiredError extends Error {
12
+ connectionName;
13
+ instanceId;
14
+ constructor(connectionName, instanceId) {
15
+ super(`Connection "${connectionName}" requires authorization`);
16
+ this.name = "ConnectionAuthorizationRequiredError";
17
+ this.connectionName = connectionName;
18
+ this.instanceId = instanceId;
19
+ }
20
+ }
21
+
22
+ class ConnectionRequestError extends Error {
23
+ connectionName;
24
+ status;
25
+ body;
26
+ constructor(connectionName, status, body) {
27
+ super(`Connection "${connectionName}" request failed with status ${status}`);
28
+ this.name = "ConnectionRequestError";
29
+ this.connectionName = connectionName;
30
+ this.status = status;
31
+ this.body = body;
32
+ }
33
+ }
34
+
35
+ class ConnectionUrlError extends Error {
36
+ url;
37
+ constructor(message, url) {
38
+ super(message);
39
+ this.name = "ConnectionUrlError";
40
+ this.url = url;
41
+ }
42
+ }
43
+
44
+ class ConnectionBudgetExceededError extends Error {
45
+ limit;
46
+ actual;
47
+ constructor(limit, actual, unit = "tools") {
48
+ super(`Connection surface exposes ${actual} ${unit}, above the budget of ${limit}`);
49
+ this.name = "ConnectionBudgetExceededError";
50
+ this.limit = limit;
51
+ this.actual = actual;
52
+ }
53
+ }
54
+
55
+ class ConnectionTimeoutError extends Error {
56
+ connectionName;
57
+ timeoutMs;
58
+ constructor(connectionName, timeoutMs) {
59
+ super(`Connection "${connectionName}" timed out after ${timeoutMs} ms`);
60
+ this.name = "ConnectionTimeoutError";
61
+ this.connectionName = connectionName;
62
+ this.timeoutMs = timeoutMs;
63
+ }
64
+ }
65
+
66
+ class ConnectionResponseTooLargeError extends Error {
67
+ connectionName;
68
+ maxBytes;
69
+ constructor(connectionName, maxBytes) {
70
+ super(`Connection "${connectionName}" response exceeded the ${maxBytes} byte limit`);
71
+ this.name = "ConnectionResponseTooLargeError";
72
+ this.connectionName = connectionName;
73
+ this.maxBytes = maxBytes;
74
+ }
75
+ }
76
+
77
+ // src/tools/connections/ssrf.ts
78
+ function assertConnectionUrl(rawUrl, connectionName) {
79
+ let url;
80
+ try {
81
+ url = new URL(rawUrl);
82
+ } catch {
83
+ throw new ConnectionUrlError(`Connection "${connectionName}" has an unparseable URL: ${rawUrl}`, rawUrl);
84
+ }
85
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
86
+ throw new ConnectionUrlError(`Connection "${connectionName}" must use http(s), got "${url.protocol}"`, rawUrl);
87
+ }
88
+ return url;
89
+ }
90
+ function connectionAllowedHosts(connectionUrl, extra) {
91
+ const hosts = new Set([connectionUrl.host.toLowerCase()]);
92
+ for (const host of extra ?? [])
93
+ hosts.add(host.toLowerCase());
94
+ return hosts;
95
+ }
96
+ function assertAllowedHost(url, allowedHosts, connectionName) {
97
+ if (!allowedHosts.has(url.host.toLowerCase())) {
98
+ throw new ConnectionUrlError(`Connection "${connectionName}" refused request to host "${url.host}": not the connection host or an allowed host`, url.toString());
99
+ }
100
+ }
101
+
102
+ // src/tools/connections/define.ts
103
+ function defineMcpClientConnection(config) {
104
+ if (!config.name.trim())
105
+ throw new Error("MCP connection requires a name");
106
+ assertConnectionUrl(config.transport.url, config.name);
107
+ return { ...config, kind: "mcp" };
108
+ }
109
+ function defineOpenApiConnection(config) {
110
+ if (!config.name.trim())
111
+ throw new Error("OpenAPI connection requires a name");
112
+ if (config.baseUrl)
113
+ assertConnectionUrl(config.baseUrl, config.name);
114
+ return { ...config, kind: "openapi" };
115
+ }
116
+ // src/tools/connections/instance.ts
117
+ import { createHash } from "node:crypto";
118
+ function connectionInstanceId(name, url, instanceKey) {
119
+ return createHash("sha256").update(`${name}\x00${url}\x00${instanceKey ?? ""}`).digest("hex").slice(0, 32);
120
+ }
121
+
122
+ // src/tools/connections/mcp.ts
123
+ import { z as z2 } from "zod";
124
+
125
+ // src/tools/connections/limits.ts
126
+ var DEFAULT_CONNECTION_TIMEOUT_MS = 30000;
127
+ var DEFAULT_MAX_RESPONSE_BYTES = 1024 * 1024;
128
+ function connectionTimeoutMs(value) {
129
+ return value !== undefined && value > 0 ? value : DEFAULT_CONNECTION_TIMEOUT_MS;
130
+ }
131
+ function connectionMaxResponseBytes(value) {
132
+ return value !== undefined && value > 0 ? value : DEFAULT_MAX_RESPONSE_BYTES;
133
+ }
134
+ async function withConnectionDeadline(connectionName, timeoutMs, callerSignal, body) {
135
+ const controller = new AbortController;
136
+ const timer = setTimeout(() => controller.abort(new ConnectionTimeoutError(connectionName, timeoutMs)), timeoutMs);
137
+ const onAbort = () => controller.abort(callerSignal?.reason);
138
+ if (callerSignal) {
139
+ if (callerSignal.aborted)
140
+ controller.abort(callerSignal.reason);
141
+ else
142
+ callerSignal.addEventListener("abort", onAbort, { once: true });
143
+ }
144
+ try {
145
+ return await body(controller.signal);
146
+ } finally {
147
+ clearTimeout(timer);
148
+ callerSignal?.removeEventListener("abort", onAbort);
149
+ }
150
+ }
151
+ async function readBoundedText(response, maxBytes, connectionName) {
152
+ const stream = response.body;
153
+ if (!stream)
154
+ return "";
155
+ const reader = stream.getReader();
156
+ const decoder = new TextDecoder;
157
+ let total = 0;
158
+ let text = "";
159
+ try {
160
+ for (;; ) {
161
+ const { done, value } = await reader.read();
162
+ if (done)
163
+ break;
164
+ total += value.byteLength;
165
+ if (total > maxBytes) {
166
+ await reader.cancel().catch(() => {
167
+ return;
168
+ });
169
+ throw new ConnectionResponseTooLargeError(connectionName, maxBytes);
170
+ }
171
+ text += decoder.decode(value, { stream: true });
172
+ }
173
+ text += decoder.decode();
174
+ return text;
175
+ } finally {
176
+ reader.releaseLock();
177
+ }
178
+ }
179
+ async function readBoundedJson(response, maxBytes, connectionName) {
180
+ const text = await readBoundedText(response, maxBytes, connectionName);
181
+ return JSON.parse(text);
182
+ }
183
+
184
+ // src/tools/connections/http.ts
185
+ async function fetchConnection(target, init, allowedHosts, connectionName) {
186
+ const url = assertConnectionUrl(target.toString(), connectionName);
187
+ assertAllowedHost(url, allowedHosts, connectionName);
188
+ const response = await fetch(url, { ...init, redirect: "manual" });
189
+ if (response.status >= 300 && response.status < 400) {
190
+ await response.body?.cancel();
191
+ throw new ConnectionUrlError(`Connection "${connectionName}" refused a redirect`, url.toString());
192
+ }
193
+ return response;
194
+ }
195
+
196
+ // src/tools/connections/json-rpc.ts
197
+ function unwrap(payload, id, connectionName) {
198
+ if (!isRecord(payload) || payload.id !== id)
199
+ throw new Error(`MCP response id mismatch from "${connectionName}"`);
200
+ if (isRecord(payload.error))
201
+ throw new Error(`MCP error from "${connectionName}": ${typeof payload.error.message === "string" ? payload.error.message : "unknown"}`);
202
+ return payload.result;
203
+ }
204
+
205
+ // src/tools/connections/sse-frames.ts
206
+ async function* readSseFrames(response, maxBytes, connectionName, totalLimit = false) {
207
+ if (!response.body)
208
+ throw new Error("MCP stream ended with no body");
209
+ const reader = response.body.getReader();
210
+ const decoder = new TextDecoder;
211
+ let buffer = "";
212
+ let bytes = 0;
213
+ try {
214
+ for (;; ) {
215
+ const { done, value } = await reader.read();
216
+ if (done)
217
+ break;
218
+ if (totalLimit) {
219
+ bytes += value.byteLength;
220
+ if (bytes > maxBytes)
221
+ throw new ConnectionResponseTooLargeError(connectionName, maxBytes);
222
+ }
223
+ for (let offset = 0;offset < value.byteLength; offset += 1024) {
224
+ buffer += decoder.decode(value.subarray(offset, offset + 1024), { stream: true });
225
+ let match = /\r?\n\r?\n/.exec(buffer);
226
+ while (match) {
227
+ const frame = buffer.slice(0, match.index);
228
+ if (new TextEncoder().encode(frame).byteLength > maxBytes)
229
+ throw new ConnectionResponseTooLargeError(connectionName, maxBytes);
230
+ buffer = buffer.slice(match.index + match[0].length);
231
+ let event = "message";
232
+ const data = [];
233
+ for (const line of frame.split(/\r?\n/)) {
234
+ if (line.startsWith("event:"))
235
+ event = line.slice(6).trim();
236
+ if (line.startsWith("data:"))
237
+ data.push(line.slice(5).replace(/^ /, ""));
238
+ }
239
+ yield { event, data: data.join(`
240
+ `) };
241
+ match = /\r?\n\r?\n/.exec(buffer);
242
+ }
243
+ if (new TextEncoder().encode(buffer).byteLength > maxBytes)
244
+ throw new ConnectionResponseTooLargeError(connectionName, maxBytes);
245
+ }
246
+ }
247
+ } finally {
248
+ await reader.cancel().catch(() => {
249
+ return;
250
+ });
251
+ reader.releaseLock();
252
+ }
253
+ }
254
+
255
+ // src/tools/connections/sse-session.ts
256
+ class SseSession {
257
+ connectionName;
258
+ instanceId;
259
+ options;
260
+ controller = new AbortController;
261
+ pending = new Map;
262
+ endpoint = Promise.withResolvers();
263
+ failure;
264
+ constructor(connectionName, instanceId, options) {
265
+ this.connectionName = connectionName;
266
+ this.instanceId = instanceId;
267
+ this.options = options;
268
+ this.endpoint.promise.catch(() => {
269
+ return;
270
+ });
271
+ this.open();
272
+ }
273
+ async ready(signal) {
274
+ return withConnectionDeadline(this.connectionName, this.options.timeoutMs, signal, async (deadline) => {
275
+ const onAbort = () => this.close(deadline.reason);
276
+ deadline.addEventListener("abort", onAbort, { once: true });
277
+ try {
278
+ deadline.throwIfAborted();
279
+ return await this.endpoint.promise;
280
+ } finally {
281
+ deadline.removeEventListener("abort", onAbort);
282
+ }
283
+ });
284
+ }
285
+ close(reason = new Error("MCP SSE session closed")) {
286
+ this.failure = reason;
287
+ this.controller.abort(reason);
288
+ this.endpoint.reject(reason);
289
+ for (const pending of this.pending.values())
290
+ pending.reject(reason);
291
+ this.pending.clear();
292
+ }
293
+ async request(message, token, signal) {
294
+ return withConnectionDeadline(this.connectionName, this.options.timeoutMs, signal, async (deadline) => {
295
+ const abort = () => this.close(deadline.reason);
296
+ deadline.addEventListener("abort", abort, { once: true });
297
+ try {
298
+ deadline.throwIfAborted();
299
+ if (this.controller.signal.aborted)
300
+ throw this.failure;
301
+ const endpoint = await this.endpoint.promise;
302
+ const response = Promise.withResolvers();
303
+ response.promise.catch(() => {
304
+ return;
305
+ });
306
+ if (message.id !== undefined)
307
+ this.pending.set(message.id, response);
308
+ const post = await fetchConnection(endpoint, {
309
+ method: "POST",
310
+ headers: {
311
+ ...this.options.headers,
312
+ "content-type": "application/json",
313
+ ...token ? { authorization: `Bearer ${token}` } : {}
314
+ },
315
+ body: JSON.stringify(message),
316
+ signal: deadline
317
+ }, this.options.allowedHosts, this.connectionName);
318
+ this.assertStatus(post);
319
+ await post.body?.cancel();
320
+ return message.id === undefined ? undefined : await response.promise;
321
+ } finally {
322
+ if (message.id !== undefined)
323
+ this.pending.delete(message.id);
324
+ deadline.removeEventListener("abort", abort);
325
+ }
326
+ });
327
+ }
328
+ assertStatus(response) {
329
+ if (response.status === 401)
330
+ throw new ConnectionAuthorizationRequiredError(this.connectionName, this.instanceId);
331
+ if (!response.ok)
332
+ throw new ConnectionRequestError(this.connectionName, response.status, "");
333
+ }
334
+ async open() {
335
+ try {
336
+ const response = await fetchConnection(this.options.baseUrl, {
337
+ method: "GET",
338
+ headers: { ...this.options.headers, accept: "text/event-stream" },
339
+ signal: this.controller.signal
340
+ }, this.options.allowedHosts, this.connectionName);
341
+ this.assertStatus(response);
342
+ for await (const frame of readSseFrames(response, this.options.maxResponseBytes, this.connectionName)) {
343
+ if (frame.event === "endpoint") {
344
+ const url = assertConnectionUrl(new URL(frame.data, this.options.baseUrl).toString(), this.connectionName);
345
+ assertAllowedHost(url, this.options.allowedHosts, this.connectionName);
346
+ this.endpoint.resolve(url.toString());
347
+ } else if (frame.event === "message" && frame.data) {
348
+ const message = JSON.parse(frame.data);
349
+ if (!isRecord(message) || typeof message.id !== "number")
350
+ continue;
351
+ const pending = this.pending.get(message.id);
352
+ if (!pending)
353
+ continue;
354
+ try {
355
+ pending.resolve(unwrap(message, message.id, this.connectionName));
356
+ } catch (error) {
357
+ pending.reject(error);
358
+ }
359
+ this.pending.delete(message.id);
360
+ }
361
+ }
362
+ this.close(new Error("MCP SSE stream ended"));
363
+ } catch (error) {
364
+ this.close(error);
365
+ }
366
+ }
367
+ }
368
+
369
+ // src/tools/connections/mcp-client.ts
370
+ var FALLBACK_STATUSES = new Set([400, 404, 405]);
371
+ var PROTOCOL_VERSION = "2024-11-05";
372
+
373
+ class McpHttpClient {
374
+ connectionName;
375
+ instanceId;
376
+ options;
377
+ mode = "streamable";
378
+ sessionId;
379
+ protocolVersion;
380
+ nextId = 1;
381
+ initialized = false;
382
+ sse;
383
+ constructor(connectionName, instanceId, options) {
384
+ this.connectionName = connectionName;
385
+ this.instanceId = instanceId;
386
+ this.options = options;
387
+ }
388
+ teardown() {
389
+ this.initialized = false;
390
+ this.sessionId = undefined;
391
+ this.sse?.close();
392
+ this.sse = undefined;
393
+ this.mode = "streamable";
394
+ }
395
+ async initialize(token, signal) {
396
+ if (this.initialized)
397
+ return;
398
+ const result = await this.request("initialize", {
399
+ protocolVersion: PROTOCOL_VERSION,
400
+ capabilities: {},
401
+ clientInfo: { name: "stitchkit", version: "0.0.0" }
402
+ }, token, signal);
403
+ if (isRecord(result) && typeof result.protocolVersion === "string") {
404
+ this.protocolVersion = result.protocolVersion;
405
+ }
406
+ this.initialized = true;
407
+ try {
408
+ await this.dispatch({ jsonrpc: "2.0", method: "notifications/initialized" }, token, signal);
409
+ } catch {}
410
+ }
411
+ request(method, params, token, signal) {
412
+ return this.dispatch({ jsonrpc: "2.0", id: this.nextId++, method, params }, token, signal);
413
+ }
414
+ async dispatch(message, token, signal) {
415
+ if (this.mode === "sse")
416
+ return this.sseRequest(message, token, signal);
417
+ try {
418
+ return await this.streamableRequest(message, token, signal);
419
+ } catch (error) {
420
+ if (error instanceof ConnectionRequestError && FALLBACK_STATUSES.has(error.status)) {
421
+ await this.openSse(token, signal);
422
+ this.mode = "sse";
423
+ return this.sseRequest(message, token, signal);
424
+ }
425
+ throw error;
426
+ }
427
+ }
428
+ streamableRequest(message, token, signal) {
429
+ const target = new URL(this.options.transport.url);
430
+ assertAllowedHost(target, this.options.allowedHosts, this.connectionName);
431
+ return withConnectionDeadline(this.connectionName, this.options.timeoutMs, signal, async (deadline) => {
432
+ const response = await fetchConnection(target, {
433
+ method: "POST",
434
+ headers: this.headers(token),
435
+ body: JSON.stringify(message),
436
+ signal: deadline
437
+ }, this.options.allowedHosts, this.connectionName);
438
+ if (response.status === 401) {
439
+ throw new ConnectionAuthorizationRequiredError(this.connectionName, this.instanceId);
440
+ }
441
+ if (!response.ok) {
442
+ throw new ConnectionRequestError(this.connectionName, response.status, await this.safeText(response));
443
+ }
444
+ const session = response.headers.get("mcp-session-id");
445
+ if (session)
446
+ this.sessionId = session;
447
+ if (message.id === undefined) {
448
+ await response.body?.cancel();
449
+ return;
450
+ }
451
+ const contentType = response.headers.get("content-type") ?? "";
452
+ if (contentType.includes("text/event-stream")) {
453
+ for await (const frame of readSseFrames(response, this.options.maxResponseBytes, this.connectionName, true)) {
454
+ if (!frame.data)
455
+ continue;
456
+ const payload2 = JSON.parse(frame.data);
457
+ if (isRecord(payload2) && payload2.id === message.id)
458
+ return unwrap(payload2, message.id, this.connectionName);
459
+ }
460
+ throw new Error("MCP stream ended without a response");
461
+ }
462
+ const text = await readBoundedText(response, this.options.maxResponseBytes, this.connectionName);
463
+ let payload;
464
+ try {
465
+ payload = JSON.parse(text);
466
+ } catch {
467
+ throw new Error(`MCP response from "${this.connectionName}" was not JSON`);
468
+ }
469
+ return unwrap(payload, message.id, this.connectionName);
470
+ });
471
+ }
472
+ headers(token) {
473
+ const headers = {
474
+ "content-type": "application/json",
475
+ accept: "application/json, text/event-stream",
476
+ ...this.options.transport.headers
477
+ };
478
+ if (token)
479
+ headers.authorization = `Bearer ${token}`;
480
+ if (this.sessionId)
481
+ headers["mcp-session-id"] = this.sessionId;
482
+ if (this.protocolVersion)
483
+ headers["mcp-protocol-version"] = this.protocolVersion;
484
+ return headers;
485
+ }
486
+ async openSse(token, signal) {
487
+ this.sse?.close();
488
+ this.sse = new SseSession(this.connectionName, this.instanceId, {
489
+ baseUrl: this.options.transport.url,
490
+ allowedHosts: this.options.allowedHosts,
491
+ headers: this.headers(token),
492
+ timeoutMs: this.options.timeoutMs,
493
+ maxResponseBytes: this.options.maxResponseBytes
494
+ });
495
+ await this.sse.ready(signal);
496
+ }
497
+ async sseRequest(message, token, signal) {
498
+ if (!this.sse)
499
+ await this.openSse(token, signal);
500
+ const session = this.sse;
501
+ if (!session)
502
+ throw new Error("MCP SSE session failed to open");
503
+ return session.request(message, token, signal);
504
+ }
505
+ async safeText(response) {
506
+ try {
507
+ return (await readBoundedText(response, this.options.maxResponseBytes, this.connectionName)).slice(0, 512);
508
+ } catch {
509
+ return "";
510
+ }
511
+ }
512
+ }
513
+
514
+ // src/tools/connections/runtime.ts
515
+ import { z } from "zod";
516
+ async function withConnectionToken(params, body) {
517
+ const token = await params.provider?.(params.context);
518
+ try {
519
+ return await body(token);
520
+ } catch (error) {
521
+ if (error instanceof ConnectionAuthorizationRequiredError) {
522
+ params.client?.teardown();
523
+ }
524
+ throw error;
525
+ }
526
+ }
527
+ function zodObjectFromJsonSchema(schema) {
528
+ if (!schema || Object.keys(schema).length === 0)
529
+ return z.looseObject({});
530
+ const parsed = z.fromJSONSchema(schema);
531
+ if (parsed instanceof z.ZodObject)
532
+ return parsed;
533
+ return z.looseObject({});
534
+ }
535
+ function sanitizeToolName(value) {
536
+ return value.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "operation";
537
+ }
538
+
539
+ // src/tools/connections/schema-budget.ts
540
+ var schemaBytes = new WeakMap;
541
+ function recordForeignSchemaBytes(tool, size) {
542
+ schemaBytes.set(tool, size);
543
+ }
544
+ function foreignSchemaBytes(tool) {
545
+ return schemaBytes.get(tool) ?? 0;
546
+ }
547
+ function jsonSchemaBytes(schema) {
548
+ if (!schema || Object.keys(schema).length === 0)
549
+ return 0;
550
+ return new TextEncoder().encode(JSON.stringify(schema)).byteLength;
551
+ }
552
+
553
+ // src/tools/connections/mcp.ts
554
+ async function mountMcpConnection(connection, instanceId) {
555
+ const url = assertConnectionUrl(connection.transport.url, connection.name);
556
+ const allowedHosts = connectionAllowedHosts(url, connection.allowHosts);
557
+ assertAllowedHost(url, allowedHosts, connection.name);
558
+ const createClient = () => new McpHttpClient(connection.name, instanceId, {
559
+ transport: connection.transport,
560
+ allowedHosts,
561
+ timeoutMs: connectionTimeoutMs(connection.timeoutMs),
562
+ maxResponseBytes: connectionMaxResponseBytes(connection.maxResponseBytes)
563
+ });
564
+ const discovery = createClient();
565
+ const listed = await withConnectionToken({ instanceId, provider: connection.token, client: discovery }, async (token) => {
566
+ await discovery.initialize(token);
567
+ return discovery.request("tools/list", {}, token);
568
+ }).finally(() => discovery.teardown());
569
+ const discovered = readDiscoveredTools(listed, connection.name);
570
+ const filtered = filterMcpTools(discovered, connection.tools);
571
+ return filtered.map((tool) => {
572
+ const name = tool.name;
573
+ const description = tool.description ?? `External MCP tool "${name}"`;
574
+ const input = zodObjectFromJsonSchema(tool.inputSchema);
575
+ const definition = defineRuntimeTool({
576
+ name,
577
+ description,
578
+ identity: { serviceName: connection.name, action: name, method: "POST" },
579
+ input,
580
+ output: z2.unknown(),
581
+ handler: (context) => {
582
+ const client = createClient();
583
+ return withConnectionToken({ instanceId, provider: connection.token, client, context }, async (token) => {
584
+ await client.initialize(token, context.signal);
585
+ const result = await client.request("tools/call", { name, arguments: context.input }, token, context.signal);
586
+ if (isRecord(result) && result.isError === true) {
587
+ throw new Error(`External MCP tool "${name}" returned an error`);
588
+ }
589
+ return result;
590
+ }).finally(() => client.teardown());
591
+ }
592
+ });
593
+ recordForeignSchemaBytes(definition, jsonSchemaBytes(tool.inputSchema));
594
+ return definition;
595
+ });
596
+ }
597
+ function readDiscoveredTools(listed, connectionName) {
598
+ if (!isRecord(listed) || !Array.isArray(listed.tools)) {
599
+ throw new Error(`MCP connection "${connectionName}" returned no tools list`);
600
+ }
601
+ const tools = [];
602
+ for (const raw of listed.tools) {
603
+ if (!isRecord(raw) || typeof raw.name !== "string")
604
+ continue;
605
+ tools.push({
606
+ name: raw.name,
607
+ ...typeof raw.description === "string" && { description: raw.description },
608
+ ...isRecord(raw.inputSchema) && { inputSchema: raw.inputSchema }
609
+ });
610
+ }
611
+ return tools;
612
+ }
613
+ function filterMcpTools(tools, filter) {
614
+ const allow = filter?.allow ? new Set(filter.allow) : undefined;
615
+ const block = new Set(filter?.block ?? []);
616
+ return tools.filter((tool) => (allow === undefined || allow.has(tool.name)) && !block.has(tool.name));
617
+ }
618
+
619
+ // src/tools/connections/openapi.ts
620
+ import { z as z3 } from "zod";
621
+
622
+ // src/tools/connections/openapi-document.ts
623
+ function resolveOpenApiDocument(document) {
624
+ let nodes = 0;
625
+ const visit = (value, refs, depth) => {
626
+ if (++nodes > 1e5 || depth > 64)
627
+ throw new TypeError("OpenAPI document expansion exceeds its limit");
628
+ if (Array.isArray(value))
629
+ return value.map((item) => visit(item, refs, depth + 1));
630
+ if (!isRecord(value))
631
+ return value;
632
+ if (typeof value.$ref === "string") {
633
+ const ref = value.$ref;
634
+ if (!ref.startsWith("#/"))
635
+ throw new TypeError("OpenAPI external references are unsupported");
636
+ if (refs.includes(ref))
637
+ throw new TypeError("OpenAPI recursive references are unsupported");
638
+ let target = document;
639
+ for (const part of ref.slice(2).split("/").map((part2) => part2.replace(/~1/g, "/").replace(/~0/g, "~"))) {
640
+ if (!isRecord(target) || !Object.hasOwn(target, part))
641
+ throw new TypeError("OpenAPI reference does not resolve");
642
+ target = target[part];
643
+ }
644
+ if (!isRecord(target))
645
+ throw new TypeError("OpenAPI reference must resolve to an object");
646
+ const { $ref: _, ...siblings } = value;
647
+ return visit({ ...target, ...siblings }, [...refs, ref], depth + 1);
648
+ }
649
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, visit(item, refs, depth + 1)]));
650
+ };
651
+ const result = visit(document, [], 0);
652
+ if (!isRecord(result))
653
+ throw new TypeError("OpenAPI document must be an object");
654
+ return result;
655
+ }
656
+ function assertOpenApiOperation(document, operation, pathItem) {
657
+ for (const servers of [document.servers, pathItem.servers, operation.servers]) {
658
+ if (Array.isArray(servers) && servers.some((server) => isRecord(server) && typeof server.url === "string" && /[{}]/.test(server.url)))
659
+ throw new TypeError("OpenAPI server variables are unsupported");
660
+ }
661
+ const security = operation.security ?? document.security;
662
+ if (Array.isArray(security) && security.some((item) => isRecord(item) && Object.keys(item).length > 1))
663
+ throw new TypeError("OpenAPI combined security schemes require a custom tool");
664
+ const body = operation.requestBody;
665
+ if (isRecord(body) && (!isRecord(body.content) || !isRecord(body.content["application/json"])))
666
+ throw new TypeError("OpenAPI request bodies must declare application/json");
667
+ const names = new Map;
668
+ for (const source of [pathItem.parameters, operation.parameters]) {
669
+ if (!Array.isArray(source))
670
+ continue;
671
+ for (const parameter of source) {
672
+ if (!isRecord(parameter) || typeof parameter.name !== "string" || typeof parameter.in !== "string")
673
+ throw new TypeError("Invalid OpenAPI parameter");
674
+ const previous = names.get(parameter.name);
675
+ if (previous && previous !== parameter.in)
676
+ throw new TypeError("OpenAPI parameter names must be unique across locations");
677
+ names.set(parameter.name, parameter.in);
678
+ if (isRecord(parameter.schema) && (parameter.schema.type === "array" || parameter.schema.type === "object"))
679
+ throw new TypeError("OpenAPI structured parameter serialization is unsupported");
680
+ if (parameter.content)
681
+ throw new TypeError("OpenAPI parameter content serialization is unsupported");
682
+ }
683
+ }
684
+ }
685
+
686
+ // src/tools/connections/openapi.ts
687
+ async function mountOpenApiConnection(connection, instanceId) {
688
+ const timeoutMs = connectionTimeoutMs(connection.timeoutMs);
689
+ const maxResponseBytes = connectionMaxResponseBytes(connection.maxResponseBytes);
690
+ const loaded = await loadOpenApiDocument(connection, timeoutMs, maxResponseBytes);
691
+ const document = resolveOpenApiDocument(loaded.document);
692
+ const baseUrl = resolveBaseUrl(connection, document, loaded.url);
693
+ const baseHost = assertConnectionUrl(baseUrl, connection.name);
694
+ const allowedHosts = connectionAllowedHosts(loaded.url && !connection.baseUrl ? new URL(loaded.url) : baseHost, loaded.url ? [...connection.allowHosts ?? [], new URL(loaded.url).host] : connection.allowHosts);
695
+ assertAllowedHost(new URL(baseUrl), allowedHosts, connection.name);
696
+ const operations = collectOperations(document);
697
+ const names = new Set;
698
+ return operations.map((entry) => {
699
+ assertOpenApiOperation(document, entry.operation, entry.pathItem);
700
+ const rawName = operationKey(entry);
701
+ const name = uniqueToolName(connection.name, rawName, names);
702
+ const description = typeof entry.operation.description === "string" ? entry.operation.description : `${entry.method} ${entry.path}`;
703
+ const schema = operationInputSchema(document, entry);
704
+ const input = zodObjectFromJsonSchema(schema);
705
+ const auth = resolveAuth(document, entry.operation);
706
+ const definition = defineRuntimeTool({
707
+ name,
708
+ description,
709
+ identity: {
710
+ serviceName: connection.name,
711
+ action: rawName,
712
+ method: entry.method
713
+ },
714
+ input,
715
+ output: z3.unknown(),
716
+ handler: (context) => withConnectionToken({ instanceId, provider: connection.token, context }, async (token) => {
717
+ const args = isRecord(context.input) ? context.input : {};
718
+ const url = buildOperationUrl(baseUrl, entry, args);
719
+ assertAllowedHost(url, allowedHosts, connection.name);
720
+ const headers = { accept: "application/json" };
721
+ for (const parameter of operationParameters(document, entry)) {
722
+ const value = args[parameter.name];
723
+ if (value === undefined)
724
+ continue;
725
+ if (parameter.in === "query")
726
+ url.searchParams.set(parameter.name, String(value));
727
+ else if (parameter.in === "header")
728
+ headers[parameter.name] = String(value);
729
+ else if (parameter.in === "cookie") {
730
+ const cookie = `${parameter.name}=${encodeURIComponent(String(value))}`;
731
+ headers.cookie = headers.cookie ? `${headers.cookie}; ${cookie}` : cookie;
732
+ }
733
+ }
734
+ const body = requestBody(entry.operation);
735
+ applyAuth(auth, token, url, headers);
736
+ return withConnectionDeadline(connection.name, timeoutMs, context.signal, async (signal) => {
737
+ const requestInit = { method: entry.method, headers, signal };
738
+ if (body && args.body !== undefined) {
739
+ headers["content-type"] = "application/json";
740
+ requestInit.body = JSON.stringify(args.body);
741
+ }
742
+ const response = await fetchConnection(url, requestInit, allowedHosts, connection.name);
743
+ if (response.status === 401) {
744
+ await response.body?.cancel();
745
+ throw new ConnectionAuthorizationRequiredError(connection.name, instanceId);
746
+ }
747
+ if (!response.ok) {
748
+ throw new ConnectionRequestError(connection.name, response.status, (await safeText(response, maxResponseBytes)).slice(0, 512));
749
+ }
750
+ return readResponse(response, maxResponseBytes, connection.name);
751
+ });
752
+ })
753
+ });
754
+ recordForeignSchemaBytes(definition, jsonSchemaBytes(schema));
755
+ return definition;
756
+ });
757
+ }
758
+ async function loadOpenApiDocument(connection, timeoutMs, maxResponseBytes) {
759
+ if (typeof connection.spec === "object" && connection.spec !== null) {
760
+ if (!isRecord(connection.spec)) {
761
+ throw new Error(`OpenAPI spec of "${connection.name}" is not an object`);
762
+ }
763
+ return { document: connection.spec };
764
+ }
765
+ const text = connection.spec.trim();
766
+ if (text.startsWith("{")) {
767
+ const parsed2 = JSON.parse(text);
768
+ if (!isRecord(parsed2))
769
+ throw new Error(`OpenAPI spec of "${connection.name}" is not an object`);
770
+ return { document: parsed2 };
771
+ }
772
+ const url = assertConnectionUrl(text, connection.name);
773
+ const parsed = await withConnectionDeadline(connection.name, timeoutMs, undefined, async (signal) => {
774
+ const response = await fetchConnection(url, { signal }, connectionAllowedHosts(url, connection.allowHosts), connection.name);
775
+ if (!response.ok) {
776
+ throw new ConnectionRequestError(connection.name, response.status, "");
777
+ }
778
+ return readBoundedJson(response, maxResponseBytes, connection.name);
779
+ });
780
+ if (!isRecord(parsed))
781
+ throw new Error(`OpenAPI spec at ${url} is not an object`);
782
+ return { document: parsed, url: url.toString() };
783
+ }
784
+ function resolveBaseUrl(connection, document, specUrl) {
785
+ if (connection.baseUrl)
786
+ return connection.baseUrl;
787
+ const servers = document.servers;
788
+ if (Array.isArray(servers)) {
789
+ for (const server of servers) {
790
+ if (isRecord(server) && typeof server.url === "string" && server.url)
791
+ return server.url;
792
+ }
793
+ }
794
+ if (specUrl)
795
+ return specUrl;
796
+ throw new Error(`OpenAPI connection "${connection.name}" declares no baseUrl or server`);
797
+ }
798
+ function collectOperations(document) {
799
+ const paths = asRecord(document.paths);
800
+ if (!paths)
801
+ return [];
802
+ const operations = [];
803
+ for (const [path, rawItem] of Object.entries(paths)) {
804
+ const pathItem = asRecord(rawItem);
805
+ if (!pathItem)
806
+ continue;
807
+ for (const [rawMethod, method] of Object.entries(HTTP_METHODS)) {
808
+ const operation = asRecord(pathItem[rawMethod]);
809
+ if (!operation)
810
+ continue;
811
+ operations.push({ method, path, operation, pathItem });
812
+ }
813
+ }
814
+ return operations;
815
+ }
816
+ var HTTP_METHODS = {
817
+ get: "GET",
818
+ head: "HEAD",
819
+ post: "POST",
820
+ put: "PUT",
821
+ patch: "PATCH",
822
+ delete: "DELETE"
823
+ };
824
+ function operationKey(entry) {
825
+ const id = entry.operation.operationId;
826
+ if (typeof id === "string" && id.trim())
827
+ return id;
828
+ return `${entry.method}_${entry.path}`;
829
+ }
830
+ function uniqueToolName(connectionName, rawName, used) {
831
+ const base = `${sanitizeToolName(connectionName)}__${sanitizeToolName(rawName)}`;
832
+ if (!used.has(base)) {
833
+ used.add(base);
834
+ return base;
835
+ }
836
+ let suffix = 2;
837
+ while (used.has(`${base}_${suffix}`))
838
+ suffix += 1;
839
+ const name = `${base}_${suffix}`;
840
+ used.add(name);
841
+ return name;
842
+ }
843
+ function operationParameters(document, entry) {
844
+ const merged = new Map;
845
+ for (const source of [entry.pathItem.parameters, entry.operation.parameters]) {
846
+ if (!Array.isArray(source))
847
+ continue;
848
+ for (const raw of source) {
849
+ const parameter = asRecord(raw);
850
+ if (!parameter || typeof parameter.name !== "string")
851
+ continue;
852
+ const location = parameter.in;
853
+ if (location !== "path" && location !== "query" && location !== "header" && location !== "cookie") {
854
+ continue;
855
+ }
856
+ const resolved = resolveRef(document, asRecord(parameter.schema));
857
+ merged.set(`${location}:${parameter.name}`, {
858
+ name: parameter.name,
859
+ in: location,
860
+ required: parameter.required === true || location === "path",
861
+ ...resolved && { schema: resolved }
862
+ });
863
+ }
864
+ }
865
+ return [...merged.values()];
866
+ }
867
+ function requestBody(operation) {
868
+ const body = asRecord(operation.requestBody);
869
+ if (!body)
870
+ return false;
871
+ const content = asRecord(body.content);
872
+ return Boolean(content && isRecord(content["application/json"]));
873
+ }
874
+ function operationInputSchema(document, entry) {
875
+ const properties = {};
876
+ const required = [];
877
+ for (const parameter of operationParameters(document, entry)) {
878
+ properties[parameter.name] = parameter.schema ?? { type: "string" };
879
+ if (parameter.required)
880
+ required.push(parameter.name);
881
+ }
882
+ const body = asRecord(entry.operation.requestBody);
883
+ const content = body ? asRecord(body.content) : undefined;
884
+ const json = content ? asRecord(content["application/json"]) : undefined;
885
+ if (body && json) {
886
+ properties.body = resolveRef(document, asRecord(json.schema)) ?? {};
887
+ if (body.required === true)
888
+ required.push("body");
889
+ }
890
+ return {
891
+ type: "object",
892
+ properties,
893
+ ...required.length > 0 && { required }
894
+ };
895
+ }
896
+ function resolveRef(document, node) {
897
+ if (!node)
898
+ return;
899
+ const ref = node.$ref;
900
+ if (typeof ref !== "string" || !ref.startsWith("#/"))
901
+ return node;
902
+ const segments = ref.slice(2).split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
903
+ let current = document;
904
+ for (const segment of segments) {
905
+ if (!isRecord(current))
906
+ return;
907
+ current = current[segment];
908
+ }
909
+ return asRecord(current);
910
+ }
911
+ function resolveAuth(document, operation) {
912
+ const security = operation.security ?? document.security;
913
+ if (!Array.isArray(security))
914
+ return;
915
+ for (const requirement of security) {
916
+ const record = asRecord(requirement);
917
+ if (!record)
918
+ continue;
919
+ const schemeName = Object.keys(record)[0];
920
+ if (!schemeName)
921
+ return;
922
+ const scheme = asRecord(asRecord(asRecord(document.components)?.securitySchemes)?.[schemeName]);
923
+ if (!scheme)
924
+ continue;
925
+ const type = scheme.type;
926
+ if (type === "apiKey") {
927
+ const placement = scheme.in;
928
+ const headerName = scheme.name;
929
+ if (typeof headerName === "string" && (placement === "header" || placement === "query" || placement === "cookie")) {
930
+ return { placement, name: headerName, scheme: "apikey" };
931
+ }
932
+ continue;
933
+ }
934
+ if (type === "http") {
935
+ const httpScheme = typeof scheme.scheme === "string" ? scheme.scheme.toLowerCase() : "";
936
+ if (httpScheme === "basic")
937
+ return { placement: "header", name: "authorization", scheme: "basic" };
938
+ if (httpScheme === "bearer")
939
+ return { placement: "header", name: "authorization", scheme: "bearer" };
940
+ continue;
941
+ }
942
+ if (type === "oauth2" || type === "openIdConnect") {
943
+ return { placement: "header", name: "authorization", scheme: "bearer" };
944
+ }
945
+ }
946
+ if (security.length > 0)
947
+ throw new TypeError("OpenAPI security has no supported alternative");
948
+ return;
949
+ }
950
+ function applyAuth(auth, token, url, headers) {
951
+ if (!auth || token === undefined)
952
+ return;
953
+ const value = auth.scheme === "basic" ? `Basic ${btoa(token)}` : auth.scheme === "bearer" ? `Bearer ${token}` : token;
954
+ if (auth.placement === "query")
955
+ url.searchParams.set(auth.name, value);
956
+ else if (auth.placement === "cookie") {
957
+ headers.cookie = headers.cookie ? `${headers.cookie}; ${auth.name}=${value}` : `${auth.name}=${value}`;
958
+ } else
959
+ headers[auth.name] = value;
960
+ }
961
+ function operationServerUrl(entry) {
962
+ for (const source of [entry.operation.servers, entry.pathItem.servers]) {
963
+ if (!Array.isArray(source))
964
+ continue;
965
+ for (const server of source) {
966
+ if (isRecord(server) && typeof server.url === "string" && server.url)
967
+ return server.url;
968
+ }
969
+ }
970
+ return;
971
+ }
972
+ function buildOperationUrl(baseUrl, entry, args) {
973
+ let path = entry.path;
974
+ for (const parameter of operationParameters({}, entry)) {
975
+ if (parameter.in !== "path")
976
+ continue;
977
+ const value = args[parameter.name];
978
+ if (value === undefined)
979
+ continue;
980
+ path = path.replace(`{${parameter.name}}`, encodeURIComponent(String(value)));
981
+ }
982
+ const origin = operationServerUrl(entry) ?? baseUrl;
983
+ const resolved = `${origin.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
984
+ return new URL(resolved);
985
+ }
986
+ async function readResponse(response, maxBytes, connectionName) {
987
+ if (response.status === 204)
988
+ return null;
989
+ const contentType = response.headers.get("content-type") ?? "";
990
+ if (contentType.includes("json"))
991
+ return readBoundedJson(response, maxBytes, connectionName);
992
+ return readBoundedText(response, maxBytes, connectionName);
993
+ }
994
+ async function safeText(response, maxBytes) {
995
+ try {
996
+ return await readBoundedText(response, maxBytes, "response");
997
+ } catch {
998
+ return "";
999
+ }
1000
+ }
1001
+ function asRecord(value) {
1002
+ return isRecord(value) ? value : undefined;
1003
+ }
1004
+
1005
+ // src/tools/connections/mount.ts
1006
+ async function mountConnections(connections, options = {}) {
1007
+ const tools = [];
1008
+ for (const connection of connections) {
1009
+ const instanceId = instanceIdOf(connection);
1010
+ const mounted = connection.kind === "mcp" ? await mountMcpConnection(connection, instanceId) : await mountOpenApiConnection(connection, instanceId);
1011
+ tools.push(...mounted);
1012
+ }
1013
+ const limit = options.budget?.maxTools;
1014
+ if (limit !== undefined && tools.length > limit) {
1015
+ throw new ConnectionBudgetExceededError(limit, tools.length);
1016
+ }
1017
+ const schemaLimit = options.budget?.maxSchemaBytes;
1018
+ if (schemaLimit !== undefined) {
1019
+ const schemaBytes2 = tools.reduce((total, tool) => total + foreignSchemaBytes(tool), 0);
1020
+ if (schemaBytes2 > schemaLimit) {
1021
+ throw new ConnectionBudgetExceededError(schemaLimit, schemaBytes2, "schema bytes");
1022
+ }
1023
+ }
1024
+ return tools;
1025
+ }
1026
+ function instanceIdOf(connection) {
1027
+ let url;
1028
+ if (connection.kind === "mcp") {
1029
+ url = connection.transport.url;
1030
+ } else if (connection.baseUrl) {
1031
+ url = connection.baseUrl;
1032
+ } else if (typeof connection.spec === "string") {
1033
+ url = connection.spec;
1034
+ } else {
1035
+ url = JSON.stringify(connection.spec);
1036
+ }
1037
+ return connectionInstanceId(connection.name, url, connection.instanceKey);
1038
+ }
1039
+ export {
1040
+ mountConnections,
1041
+ defineOpenApiConnection,
1042
+ defineMcpClientConnection,
1043
+ ConnectionUrlError,
1044
+ ConnectionRequestError,
1045
+ ConnectionBudgetExceededError,
1046
+ ConnectionAuthorizationRequiredError
1047
+ };