stitchkit 0.54.0 → 0.55.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 (100) hide show
  1. package/README.md +4 -1
  2. package/dist/browser/socket-io.d.ts +11 -1
  3. package/dist/browser/socket-io.d.ts.map +1 -1
  4. package/dist/cli.js +14 -9
  5. package/dist/contract/file-ref.d.ts +13 -0
  6. package/dist/contract/file-ref.d.ts.map +1 -0
  7. package/dist/contract/index.d.ts +1 -0
  8. package/dist/contract/index.d.ts.map +1 -1
  9. package/dist/contract/index.js +21 -17
  10. package/dist/files/boundary.d.ts +52 -0
  11. package/dist/files/boundary.d.ts.map +1 -0
  12. package/dist/files.d.ts +3 -0
  13. package/dist/files.d.ts.map +1 -0
  14. package/dist/files.js +16 -0
  15. package/dist/{index-7vb44hy8.js → index-0gv7k0ra.js} +127 -92
  16. package/dist/index-1bx83sw4.js +4 -0
  17. package/dist/index-1k16zv57.js +269 -0
  18. package/dist/index-6k1937bx.js +20 -0
  19. package/dist/{index-bmnhqtya.js → index-7eq9va6e.js} +1 -4
  20. package/dist/{index-yxpe3phd.js → index-cby4ar3v.js} +1 -18
  21. package/dist/index-h60df7rj.js +110 -0
  22. package/dist/{index-yez419px.js → index-js7yexng.js} +19 -6
  23. package/dist/index-k4dftwf7.js +66 -0
  24. package/dist/{index-z7761jk2.js → index-npye5kt7.js} +16 -22
  25. package/dist/{index-b15rtha1.js → index-pzc32v75.js} +61 -3
  26. package/dist/{index-e1es808r.js → index-qh4xevf5.js} +1 -1
  27. package/dist/{index-gvha32jh.js → index-qx84bqzk.js} +2 -2
  28. package/dist/{index-pfqjb5xy.js → index-rgd18hx3.js} +23 -5
  29. package/dist/index-sbdmyz75.js +18 -0
  30. package/dist/{index-173tvqcp.js → index-v4bbq3p4.js} +9 -114
  31. package/dist/index.js +162 -35
  32. package/dist/node.js +30 -27
  33. package/dist/observability/index.js +28 -25
  34. package/dist/react.js +2 -2
  35. package/dist/realtime/contract.d.ts +9 -0
  36. package/dist/realtime/contract.d.ts.map +1 -1
  37. package/dist/realtime/index.d.ts +2 -1
  38. package/dist/realtime/index.d.ts.map +1 -1
  39. package/dist/realtime/request.d.ts +21 -0
  40. package/dist/realtime/request.d.ts.map +1 -0
  41. package/dist/realtime/socket.d.ts +3 -0
  42. package/dist/realtime/socket.d.ts.map +1 -1
  43. package/dist/remote.d.ts +9 -0
  44. package/dist/remote.d.ts.map +1 -0
  45. package/dist/remote.js +79 -0
  46. package/dist/server/context-contribution.d.ts +10 -0
  47. package/dist/server/context-contribution.d.ts.map +1 -0
  48. package/dist/server/context.d.ts.map +1 -1
  49. package/dist/server/index.d.ts +2 -1
  50. package/dist/server/index.d.ts.map +1 -1
  51. package/dist/server/index.js +133 -88
  52. package/dist/server/lifecycle.d.ts +4 -0
  53. package/dist/server/lifecycle.d.ts.map +1 -0
  54. package/dist/server/middleware/auth.d.ts +35 -7
  55. package/dist/server/middleware/auth.d.ts.map +1 -1
  56. package/dist/server/types.d.ts +1 -1
  57. package/dist/server/types.d.ts.map +1 -1
  58. package/dist/testing/surface-conformance.d.ts +47 -0
  59. package/dist/testing/surface-conformance.d.ts.map +1 -0
  60. package/dist/testing/surface-manifest.d.ts +156 -0
  61. package/dist/testing/surface-manifest.d.ts.map +1 -0
  62. package/dist/testing.d.ts +2 -0
  63. package/dist/testing.d.ts.map +1 -1
  64. package/dist/testing.js +366 -3
  65. package/dist/tools/agent.d.ts.map +1 -1
  66. package/dist/tools/async-operation.d.ts +168 -0
  67. package/dist/tools/async-operation.d.ts.map +1 -0
  68. package/dist/tools/cli-args.d.ts +1 -1
  69. package/dist/tools/cli-args.d.ts.map +1 -1
  70. package/dist/tools/cli-format.d.ts +2 -2
  71. package/dist/tools/cli-format.d.ts.map +1 -1
  72. package/dist/tools/cli-wait.d.ts +11 -2
  73. package/dist/tools/cli-wait.d.ts.map +1 -1
  74. package/dist/tools/cli.d.ts +2 -0
  75. package/dist/tools/cli.d.ts.map +1 -1
  76. package/dist/tools/define-download-tool.d.ts +6 -5
  77. package/dist/tools/define-download-tool.d.ts.map +1 -1
  78. package/dist/tools/define-upload-tool.d.ts +3 -1
  79. package/dist/tools/define-upload-tool.d.ts.map +1 -1
  80. package/dist/tools/define-wait-tool.d.ts +1 -1
  81. package/dist/tools/define-wait-tool.d.ts.map +1 -1
  82. package/dist/tools/download-core.d.ts +5 -10
  83. package/dist/tools/download-core.d.ts.map +1 -1
  84. package/dist/tools/lifecycle.d.ts +4 -0
  85. package/dist/tools/lifecycle.d.ts.map +1 -0
  86. package/dist/tools/mount-download.d.ts +5 -4
  87. package/dist/tools/mount-download.d.ts.map +1 -1
  88. package/dist/tools/mount-upload.d.ts +4 -2
  89. package/dist/tools/mount-upload.d.ts.map +1 -1
  90. package/dist/tools/upload-core.d.ts +3 -2
  91. package/dist/tools/upload-core.d.ts.map +1 -1
  92. package/dist/tools/view-file.d.ts +4 -3
  93. package/dist/tools/view-file.d.ts.map +1 -1
  94. package/dist/tools/wait-core.d.ts +6 -1
  95. package/dist/tools/wait-core.d.ts.map +1 -1
  96. package/dist/tools.d.ts +3 -2
  97. package/dist/tools.d.ts.map +1 -1
  98. package/dist/tools.js +385 -244
  99. package/llms-full.txt +417 -27
  100. package/package.json +10 -2
package/dist/tools.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  signJwt,
3
3
  verifyPkce
4
- } from "./index-yez419px.js";
4
+ } from "./index-js7yexng.js";
5
5
  import {
6
6
  DEFAULT_CORS_ALLOW_HEADERS,
7
7
  DEFAULT_PROCESS_SIGNALS,
@@ -9,10 +9,9 @@ import {
9
9
  defaultSignalSource,
10
10
  guardSignalCallback,
11
11
  reportSignalError
12
- } from "./index-b15rtha1.js";
12
+ } from "./index-pzc32v75.js";
13
13
  import {
14
- assertToolName,
15
- assertUniqueToolName,
14
+ WaitTimeoutError,
16
15
  buildToolPresentationSchema,
17
16
  coerceJsonArgs,
18
17
  collectToolSurface,
@@ -32,33 +31,39 @@ import {
32
31
  readCapped,
33
32
  runWaitOperation,
34
33
  toolErrorFromResult,
35
- toolResultFromError,
36
- writeDownload
37
- } from "./index-7vb44hy8.js";
34
+ toolResultFromError
35
+ } from "./index-0gv7k0ra.js";
38
36
  import {
39
- isWithinDir,
40
- realPathWithinDir,
41
- toJsonSchema
42
- } from "./index-yxpe3phd.js";
37
+ ManagedFileError
38
+ } from "./index-1k16zv57.js";
39
+ import"./index-sbdmyz75.js";
43
40
  import {
44
- ApiError,
45
- createClient
46
- } from "./index-e1es808r.js";
47
- import"./index-48ffdxgk.js";
41
+ assertToolName,
42
+ assertUniqueToolName
43
+ } from "./index-k4dftwf7.js";
44
+ import {
45
+ toJsonSchema
46
+ } from "./index-cby4ar3v.js";
48
47
  import {
49
48
  redact
50
- } from "./index-gvha32jh.js";
49
+ } from "./index-qx84bqzk.js";
51
50
  import {
52
- AppError,
53
51
  getRequestContext,
54
52
  getTraceId,
55
- mergeMeta,
56
53
  runWithRequestContext
57
- } from "./index-173tvqcp.js";
54
+ } from "./index-v4bbq3p4.js";
55
+ import {
56
+ AppError
57
+ } from "./index-h60df7rj.js";
58
+ import {
59
+ ManagedFilePathSchema,
60
+ ManagedFileRefSchema
61
+ } from "./index-6k1937bx.js";
58
62
  import {
59
63
  isRecord,
60
64
  resolvePropagationContext
61
- } from "./index-bmnhqtya.js";
65
+ } from "./index-7eq9va6e.js";
66
+ import"./index-1bx83sw4.js";
62
67
 
63
68
  // src/tools/agent.ts
64
69
  import { jsonSchema, tool } from "ai";
@@ -85,10 +90,10 @@ function mountAgent(services, config = {}) {
85
90
  const inputSchema = jsonSchema(mountable.presentationSchema, {
86
91
  validate: async (value) => isRecord(value) ? { success: true, value } : { success: false, error: new Error("Tool arguments must be an object") }
87
92
  });
88
- const execute = async (rawArgs) => {
93
+ const execute = async (rawArgs, options) => {
89
94
  const args = isRecord(rawArgs) ? rawArgs : {};
90
95
  try {
91
- const result = await runTool(mountable, args);
96
+ const result = await runTool(mountable, args, { signal: options.abortSignal });
92
97
  if (result.ok)
93
98
  return result.data;
94
99
  return formatToolError(result, mountable.name, config.errorHint);
@@ -128,9 +133,239 @@ function mountAgent(services, config = {}) {
128
133
  }
129
134
  return tools;
130
135
  }
131
- // src/tools/download-core.ts
132
- import { basename, extname, resolve } from "node:path";
136
+ // src/tools/async-operation.ts
133
137
  import { z } from "zod";
138
+
139
+ // src/tools/native-definition.ts
140
+ function managedNativeIdentity(identity, method) {
141
+ return { ...identity, method };
142
+ }
143
+
144
+ // src/tools/define-wait-tool.ts
145
+ function defineWaitTool(config) {
146
+ const renderedMcp = config.render ? {
147
+ mcp: (state) => {
148
+ const rendered = config.render?.(state, false);
149
+ return {
150
+ content: [
151
+ {
152
+ type: "text",
153
+ text: rendered?.text ?? JSON.stringify(state, null, 2)
154
+ }
155
+ ]
156
+ };
157
+ }
158
+ } : undefined;
159
+ const present = renderedMcp || config.present ? { ...renderedMcp, ...config.present } : undefined;
160
+ return defineRuntimeTool({
161
+ name: config.name ?? "wait",
162
+ description: config.description,
163
+ identity: managedNativeIdentity(config.identity, "GET"),
164
+ input: config.input,
165
+ output: config.state,
166
+ transports: config.transports,
167
+ annotations: config.annotations,
168
+ present,
169
+ handler: async (context) => {
170
+ let result;
171
+ try {
172
+ result = await runWaitOperation({
173
+ input: context.input,
174
+ poll: async (input, signal) => config.state.parse(await config.poll(input, { ...context, signal })),
175
+ done: config.done,
176
+ backoff: config.backoff,
177
+ timeoutSec: config.timeoutFromInput?.(context.input) ?? config.defaultTimeout,
178
+ signal: context.signal
179
+ });
180
+ } catch (error) {
181
+ if (error instanceof WaitTimeoutError) {
182
+ throw new AppError("WAIT_TIMEOUT", "Wait timed out before the first snapshot", 408);
183
+ }
184
+ throw error;
185
+ }
186
+ const rendered = config.render?.(result.state, result.timedOut);
187
+ if (result.timedOut) {
188
+ throw new AppError("WAIT_TIMEOUT", rendered?.text ?? "Wait timed out before the operation completed", 408);
189
+ }
190
+ if (rendered?.isError) {
191
+ throw new AppError("WAIT_FAILED", rendered.text, 409);
192
+ }
193
+ return result.state;
194
+ }
195
+ });
196
+ }
197
+
198
+ // src/tools/async-operation.ts
199
+ var AsyncOperationCancelResultSchema = z.discriminatedUnion("outcome", [
200
+ z.object({ outcome: z.literal("accepted") }),
201
+ z.object({ outcome: z.literal("already_terminal") }),
202
+ z.object({ outcome: z.literal("rejected"), reason: z.string().min(1) })
203
+ ]);
204
+ function createAsyncOperationSnapshotSchema(config) {
205
+ const progress = config.progress ? { progress: config.progress.optional() } : {};
206
+ return z.discriminatedUnion("phase", [
207
+ z.object({ phase: z.literal("pending"), ...progress }),
208
+ z.object({ phase: z.literal("running"), ...progress }),
209
+ z.object({ phase: z.literal("succeeded"), ...progress }),
210
+ z.object({ phase: z.literal("failed"), failure: config.failure, ...progress }),
211
+ z.object({ phase: z.literal("cancelled"), ...progress })
212
+ ]);
213
+ }
214
+ function terminal(snapshot) {
215
+ if (typeof snapshot !== "object" || snapshot === null || !("phase" in snapshot)) {
216
+ return false;
217
+ }
218
+ return snapshot.phase === "succeeded" || snapshot.phase === "failed" || snapshot.phase === "cancelled";
219
+ }
220
+ function succeeded(snapshot) {
221
+ return typeof snapshot === "object" && snapshot !== null && "phase" in snapshot && snapshot.phase === "succeeded";
222
+ }
223
+ function defineAsyncOperation(config) {
224
+ const capabilityName = (capability) => config.names?.[capability] ?? `${config.name}_${capability}`;
225
+ const description = (capability) => config.descriptions?.[capability] ?? `${config.description}: ${capability}`;
226
+ const identity = (capability, method) => ({
227
+ serviceName: config.identity.serviceName,
228
+ action: `${config.identity.action}.${capability}`,
229
+ scope: config.scopes?.[capability] ?? config.identity.scope,
230
+ meta: config.identity.meta,
231
+ method
232
+ });
233
+ const common = (capability) => ({
234
+ name: capabilityName(capability),
235
+ description: description(capability),
236
+ transports: config.transports?.[capability],
237
+ annotations: config.annotations?.[capability] ?? (capability === "start" || capability === "cancel" ? { destructiveHint: true } : undefined)
238
+ });
239
+ const inspect = async (capability, context) => {
240
+ await config.authorize(context.input, capability, context);
241
+ const state = config.state.parse(await config.inspect(context.input, context));
242
+ const snapshot = config.snapshot.parse(await config.classify(state, context));
243
+ return { state, snapshot };
244
+ };
245
+ const start = defineRuntimeTool({
246
+ ...common("start"),
247
+ identity: identity("start", "POST"),
248
+ input: config.startInput,
249
+ output: config.id,
250
+ handler: (context) => config.start(context.input, context)
251
+ });
252
+ const status = defineRuntimeTool({
253
+ ...common("status"),
254
+ identity: identity("status", "GET"),
255
+ input: config.id,
256
+ output: config.snapshot,
257
+ handler: async (context) => (await inspect("status", context)).snapshot
258
+ });
259
+ const wait = defineWaitTool({
260
+ ...common("wait"),
261
+ identity: identity("wait", "GET"),
262
+ input: config.id,
263
+ state: config.snapshot,
264
+ poll: async (_id, context) => (await inspect("wait", context)).snapshot,
265
+ done: terminal,
266
+ backoff: config.backoff,
267
+ defaultTimeout: config.defaultTimeout,
268
+ timeoutFromInput: config.timeoutFromId
269
+ });
270
+ const definitions = { start, status, wait };
271
+ const names = new Set;
272
+ if (config.cancel) {
273
+ const capability = config.cancel;
274
+ definitions.cancel = defineRuntimeTool({
275
+ ...common("cancel"),
276
+ identity: identity("cancel", "DELETE"),
277
+ input: config.id,
278
+ output: AsyncOperationCancelResultSchema,
279
+ handler: async (context) => {
280
+ const inspected = await inspect("cancel", context);
281
+ return capability.handler(inspected.state, context);
282
+ }
283
+ });
284
+ }
285
+ if (config.result) {
286
+ const capability = config.result;
287
+ definitions.result = defineRuntimeTool({
288
+ ...common("result"),
289
+ identity: identity("result", "GET"),
290
+ input: config.id,
291
+ output: capability.output,
292
+ handler: async (context) => {
293
+ const inspected = await inspect("result", context);
294
+ if (!succeeded(inspected.snapshot)) {
295
+ throw new AppError("OPERATION_NOT_SUCCEEDED", "Operation result is not available", 409);
296
+ }
297
+ return capability.handler(inspected.state, context);
298
+ }
299
+ });
300
+ }
301
+ if (config.artifacts) {
302
+ const capability = config.artifacts;
303
+ definitions.artifacts = defineRuntimeTool({
304
+ ...common("artifacts"),
305
+ identity: identity("artifacts", "GET"),
306
+ input: config.id,
307
+ output: capability.output,
308
+ handler: async (context) => {
309
+ const inspected = await inspect("artifacts", context);
310
+ if (!succeeded(inspected.snapshot)) {
311
+ throw new AppError("OPERATION_NOT_SUCCEEDED", "Operation artifacts are not available", 409);
312
+ }
313
+ return capability.handler(inspected.state, context);
314
+ }
315
+ });
316
+ }
317
+ for (const definition of Object.values(definitions)) {
318
+ assertUniqueToolName(definition.name, names.has(definition.name), "in-process tool name");
319
+ names.add(definition.name);
320
+ }
321
+ return {
322
+ ...definitions,
323
+ runtimeTools: Object.values(definitions),
324
+ schemas: {
325
+ id: config.id,
326
+ snapshot: config.snapshot,
327
+ cancelResult: AsyncOperationCancelResultSchema
328
+ }
329
+ };
330
+ }
331
+ function bindContractAsyncOperation(config) {
332
+ const endpoint = (key) => {
333
+ const found = config.contract.endpoints[key];
334
+ if (!found)
335
+ throw new Error(`Contract async operation method "${key}" not found`);
336
+ return found;
337
+ };
338
+ const start = endpoint(config.capabilities.start);
339
+ const status = endpoint(config.capabilities.status);
340
+ const wait = endpoint(config.capabilities.wait);
341
+ if (!start.output || !status.output || !wait.output) {
342
+ throw new Error("Contract async operation requires outputs for start, status and wait");
343
+ }
344
+ if (status.input !== start.output || wait.input !== start.output) {
345
+ const capability = status.input !== start.output ? "status" : "wait";
346
+ throw new Error(`Contract async operation capability "${capability}" input must reuse the same schema instance as the start output`);
347
+ }
348
+ if (wait.output !== status.output) {
349
+ throw new Error('Contract async operation capability "wait" output must reuse the same schema instance as the status output');
350
+ }
351
+ const validateOptional = (capability) => {
352
+ const key = config.capabilities[capability];
353
+ if (key && endpoint(key).input !== start.output) {
354
+ throw new Error(`Contract async operation capability "${capability}" input must reuse the same schema instance as the start output`);
355
+ }
356
+ };
357
+ validateOptional("cancel");
358
+ validateOptional("result");
359
+ validateOptional("artifacts");
360
+ return {
361
+ contract: config.contract,
362
+ handlers: config.handlers,
363
+ capabilities: config.capabilities,
364
+ schemas: { id: start.output, snapshot: status.output }
365
+ };
366
+ }
367
+ // src/tools/download-core.ts
368
+ import { basename, extname } from "node:path";
134
369
  var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
135
370
  var MIME_EXT = {
136
371
  "image/png": ".png",
@@ -143,11 +378,6 @@ var MIME_EXT = {
143
378
  "audio/wav": ".wav",
144
379
  "audio/ogg": ".ogg"
145
380
  };
146
- var DownloadResultSchema = z.object({
147
- path: z.string(),
148
- size: z.number().int().nonnegative(),
149
- mimeType: z.string()
150
- });
151
381
 
152
382
  class DownloadOperationError extends Error {
153
383
  code;
@@ -192,19 +422,12 @@ async function runDownloadOperation(config) {
192
422
  await res.body?.cancel();
193
423
  throw new DownloadOperationError("DOWNLOAD_TOO_LARGE", `file exceeds the ${max}-byte cap`, 413);
194
424
  }
195
- const buffer = await readCapped(res, max);
196
- if (!buffer) {
197
- throw new DownloadOperationError("DOWNLOAD_TOO_LARGE", `file exceeds the ${max}-byte cap`, 413);
198
- }
199
- const dir = resolve(config.dir);
200
- const filePath = resolve(dir, `${baseNameFor(config.url)}${extensionFor(config.url, mimeType)}`);
201
- await writeDownload(dir, filePath, buffer);
202
- return { path: filePath, size: buffer.length, mimeType };
203
- }
204
-
205
- // src/tools/native-definition.ts
206
- function managedNativeIdentity(identity, method) {
207
- return { ...identity, method };
425
+ const path = config.path ?? `${baseNameFor(config.url)}${extensionFor(config.url, mimeType)}`;
426
+ return config.files.write(path, res.body ?? new Uint8Array, {
427
+ maxBytes: max,
428
+ mediaType: mimeType,
429
+ signal: config.signal
430
+ });
208
431
  }
209
432
 
210
433
  // src/tools/define-download-tool.ts
@@ -214,7 +437,7 @@ function defineDownloadTool(config) {
214
437
  description: config.description,
215
438
  identity: managedNativeIdentity(config.identity, "POST"),
216
439
  input: config.input,
217
- output: DownloadResultSchema,
440
+ output: ManagedFileRefSchema,
218
441
  transports: config.transports,
219
442
  annotations: config.annotations,
220
443
  present: config.present,
@@ -226,7 +449,8 @@ function defineDownloadTool(config) {
226
449
  try {
227
450
  return await runDownloadOperation({
228
451
  url,
229
- dir: config.dirFromInput?.(context.input) ?? config.defaultDir,
452
+ files: config.files,
453
+ path: config.pathFromInput?.(context.input),
230
454
  allowPrivateHosts: config.allowPrivateHosts,
231
455
  maxBytes: config.maxBytes,
232
456
  timeoutMs: config.timeoutMs,
@@ -245,14 +469,14 @@ function defineDownloadTool(config) {
245
469
  import { z as z2 } from "zod";
246
470
 
247
471
  // src/tools/upload-core.ts
248
- function runUploadOperation(path, upload, signal) {
472
+ function runUploadOperation(files, path, upload, signal) {
249
473
  signal?.throwIfAborted();
250
- return upload(path, signal);
474
+ return files.read(path, { signal }).then((source) => upload(source, signal));
251
475
  }
252
476
 
253
477
  // src/tools/define-upload-tool.ts
254
478
  var UploadToolInputSchema = z2.object({
255
- path: z2.string().min(1).describe("Path to a local file on this machine")
479
+ path: ManagedFilePathSchema.describe("Path relative to the configured file boundary")
256
480
  });
257
481
  function defineUploadTool(config) {
258
482
  return defineRuntimeTool({
@@ -264,12 +488,11 @@ function defineUploadTool(config) {
264
488
  transports: config.transports,
265
489
  annotations: config.annotations,
266
490
  present: config.present,
267
- handler: async (context) => runUploadOperation(context.input.path, (path) => config.upload(path, context), context.signal)
491
+ handler: async (context) => runUploadOperation(config.files, context.input.path, (source) => config.upload(source, context), context.signal)
268
492
  });
269
493
  }
270
494
  // src/tools/view-file.ts
271
- import { readFile, stat } from "node:fs/promises";
272
- import { extname as extname2, resolve as resolve2 } from "node:path";
495
+ import { extname as extname2 } from "node:path";
273
496
  import { z as z3 } from "zod";
274
497
  var MAX_INLINE_BYTES = 20 * 1024 * 1024;
275
498
  var MAX_VIEW_FILES = 20;
@@ -335,32 +558,33 @@ async function fetchSource(pathOrUrl, options, maxBytes) {
335
558
  await res.body?.cancel();
336
559
  return { tooLarge: true, mimeType, bytesRead: 0 };
337
560
  }
338
- const buffer2 = await readCapped(res, maxBytes);
339
- return buffer2 ? { buffer: buffer2, mimeType, bytesRead: buffer2.length } : { tooLarge: true, mimeType, bytesRead: maxBytes };
561
+ const buffer = await readCapped(res, maxBytes);
562
+ return buffer ? { buffer, mimeType, bytesRead: buffer.length } : { tooLarge: true, mimeType, bytesRead: maxBytes };
340
563
  }
341
- if (!options.baseDir) {
342
- throw new Error("local file paths are disabled — set baseDir to allow them");
564
+ if (!options.files) {
565
+ throw new Error("local file paths are disabled — set files to allow them");
343
566
  }
344
567
  options.signal?.throwIfAborted();
345
568
  if (!extMime) {
346
569
  throw new Error("refusing to read a non-media file");
347
570
  }
348
- const root = resolve2(options.baseDir);
349
- const target = resolve2(root, pathOrUrl);
350
- if (!isWithinDir(root, target)) {
351
- throw new Error("path escapes the allowed directory");
352
- }
353
- const realTarget = await realPathWithinDir(root, target);
354
- if (realTarget === null) {
355
- throw new Error("path escapes the allowed directory");
571
+ try {
572
+ const source = await options.files.read(pathOrUrl, {
573
+ maxBytes,
574
+ signal: options.signal
575
+ });
576
+ const buffer = Buffer.from(source.bytes);
577
+ return {
578
+ buffer,
579
+ mimeType: source.ref.mediaType ?? extMime,
580
+ bytesRead: buffer.length
581
+ };
582
+ } catch (error) {
583
+ if (error instanceof ManagedFileError && error.code === "FILE_TOO_LARGE") {
584
+ return { tooLarge: true, mimeType: extMime, bytesRead: maxBytes };
585
+ }
586
+ throw error;
356
587
  }
357
- const info = await stat(realTarget).catch(() => null);
358
- if (!info?.isFile())
359
- throw new Error("file not found");
360
- if (info.size > maxBytes)
361
- return { tooLarge: true, mimeType: extMime, bytesRead: 0 };
362
- const buffer = await readFile(realTarget, { signal: options.signal });
363
- return { buffer, mimeType: extMime, bytesRead: buffer.length };
364
588
  }
365
589
  async function resolveMediaWithinBudget(pathOrUrl, options, maxBytes) {
366
590
  if (maxBytes <= 0) {
@@ -497,57 +721,12 @@ function defineViewFileTool(config) {
497
721
  },
498
722
  present: { ...defaults, ...config.present },
499
723
  handler: (context) => runViewFileOperation(context.input.paths, {
500
- baseDir: config.baseDir,
724
+ files: config.files,
501
725
  allowPrivateHosts: config.allowPrivateHosts,
502
726
  timeoutMs: config.timeoutMs
503
727
  }, context.signal)
504
728
  });
505
729
  }
506
- // src/tools/define-wait-tool.ts
507
- function defineWaitTool(config) {
508
- const renderedMcp = config.render ? {
509
- mcp: (state) => {
510
- const rendered = config.render?.(state, false);
511
- return {
512
- content: [
513
- {
514
- type: "text",
515
- text: rendered?.text ?? JSON.stringify(state, null, 2)
516
- }
517
- ]
518
- };
519
- }
520
- } : undefined;
521
- const present = renderedMcp || config.present ? { ...renderedMcp, ...config.present } : undefined;
522
- return defineRuntimeTool({
523
- name: config.name ?? "wait",
524
- description: config.description,
525
- identity: managedNativeIdentity(config.identity, "GET"),
526
- input: config.input,
527
- output: config.state,
528
- transports: config.transports,
529
- annotations: config.annotations,
530
- present,
531
- handler: async (context) => {
532
- const result = await runWaitOperation({
533
- input: context.input,
534
- poll: async (input) => config.state.parse(await config.poll(input, context)),
535
- done: config.done,
536
- backoff: config.backoff,
537
- timeoutSec: config.timeoutFromInput?.(context.input) ?? config.defaultTimeout,
538
- signal: context.signal
539
- });
540
- const rendered = config.render?.(result.state, result.timedOut);
541
- if (result.timedOut) {
542
- throw new AppError("WAIT_TIMEOUT", rendered?.text ?? "Wait timed out before the operation completed", 408);
543
- }
544
- if (rendered?.isError) {
545
- throw new AppError("WAIT_FAILED", rendered.text, 409);
546
- }
547
- return result.state;
548
- }
549
- });
550
- }
551
730
  // src/tools/invoker.ts
552
731
  function createToolInvoker(services, config) {
553
732
  const serviceList = Array.isArray(services) ? services : [services];
@@ -595,6 +774,24 @@ function createToolInvoker(services, config) {
595
774
  }
596
775
  });
597
776
  }
777
+ // src/tools/lifecycle.ts
778
+ function composeToolLifecycle(...hooks) {
779
+ return {
780
+ beforeHandle: async (ctx, endpoint) => {
781
+ for (const hook of hooks)
782
+ await hook?.beforeHandle?.(ctx, endpoint);
783
+ },
784
+ afterHandle: async (ctx, result, endpoint) => {
785
+ let current = result;
786
+ for (const hook of hooks) {
787
+ const transformed = await hook?.afterHandle?.(ctx, current, endpoint);
788
+ if (transformed !== undefined)
789
+ current = transformed;
790
+ }
791
+ return current;
792
+ }
793
+ };
794
+ }
598
795
  // src/tools/list-names.ts
599
796
  var TOOL_TRANSPORTS = ["MCP", "AGENT", "CLI"];
600
797
  function listToolNames(surface) {
@@ -1610,8 +1807,8 @@ function createMcpHandler(config) {
1610
1807
  const authByCarrier = new WeakMap;
1611
1808
  let closed = false;
1612
1809
  let resolveClosed;
1613
- const closedSignal = new Promise((resolve3) => {
1614
- resolveClosed = resolve3;
1810
+ const closedSignal = new Promise((resolve) => {
1811
+ resolveClosed = resolve;
1615
1812
  });
1616
1813
  const sdkHandler = createSdkMcpHandler(({ authInfo }) => {
1617
1814
  const resolved = authInfo ? authByCarrier.get(authInfo) : undefined;
@@ -1714,8 +1911,8 @@ function bindStdioProcessSignals(handle, options = {}) {
1714
1911
  let rejectChain = () => {
1715
1912
  return;
1716
1913
  };
1717
- const promise = new Promise((resolve3, reject) => {
1718
- resolveChain = resolve3;
1914
+ const promise = new Promise((resolve, reject) => {
1915
+ resolveChain = resolve;
1719
1916
  rejectChain = reject;
1720
1917
  });
1721
1918
  promise.catch(() => {
@@ -1811,7 +2008,8 @@ function mountDownload(server, config) {
1811
2008
  return textResult("Nothing to download.", true);
1812
2009
  const result = await runDownloadOperation({
1813
2010
  url,
1814
- dir: config.dirFromArgs?.(args) ?? config.defaultDir,
2011
+ files: config.files,
2012
+ path: config.pathFromArgs?.(args),
1815
2013
  allowPrivateHosts: config.allowPrivateHosts,
1816
2014
  maxBytes: config.maxBytes,
1817
2015
  timeoutMs: config.timeoutMs
@@ -1823,20 +2021,21 @@ function mountDownload(server, config) {
1823
2021
  });
1824
2022
  }
1825
2023
  // src/tools/mount-upload.ts
1826
- import { z as z7 } from "zod";
1827
2024
  function mountUpload(server, config) {
1828
2025
  const name = config.name ?? "upload";
1829
2026
  assertToolName(name, "<native>", "upload");
1830
2027
  server.registerTool(name, {
1831
2028
  description: config.description,
1832
- inputSchema: { path: z7.string().describe("Path to a local file on this machine") }
2029
+ inputSchema: {
2030
+ path: ManagedFilePathSchema.describe("Path relative to the configured file boundary")
2031
+ }
1833
2032
  }, async (rawArgs) => {
1834
2033
  const args = isRecord(rawArgs) ? rawArgs : {};
1835
2034
  const path = typeof args.path === "string" ? args.path : "";
1836
2035
  if (!path)
1837
2036
  return textResult("Provide `path`.", true);
1838
2037
  try {
1839
- const uploaded = await runUploadOperation(path, config.upload);
2038
+ const uploaded = await runUploadOperation(config.files, path, config.upload);
1840
2039
  return textResult(JSON.stringify(uploaded ?? null, null, 2));
1841
2040
  } catch (err) {
1842
2041
  return textResult(`Upload failed: ${err instanceof Error ? err.message : String(err)}`, true);
@@ -1844,11 +2043,11 @@ function mountUpload(server, config) {
1844
2043
  });
1845
2044
  }
1846
2045
  // src/tools/mount-wait.ts
1847
- import { z as z8 } from "zod";
2046
+ import { z as z7 } from "zod";
1848
2047
  function mountWait(server, config) {
1849
2048
  const name = config.name ?? "wait";
1850
2049
  assertToolName(name, "<native>", "wait");
1851
- server.registerTool(name, { description: config.description, inputSchema: z8.object(config.inputSchema) }, async (rawArgs) => {
2050
+ server.registerTool(name, { description: config.description, inputSchema: z7.object(config.inputSchema) }, async (rawArgs) => {
1852
2051
  const args = isRecord(rawArgs) ? rawArgs : {};
1853
2052
  try {
1854
2053
  const { state, timedOut } = await runWaitOperation({
@@ -1867,19 +2066,19 @@ function mountWait(server, config) {
1867
2066
  }
1868
2067
  // src/tools/oauth-provider.ts
1869
2068
  import { randomUUID } from "node:crypto";
1870
- import { z as z9 } from "zod";
2069
+ import { z as z8 } from "zod";
1871
2070
  function isPlainDisplayCharacter(character) {
1872
2071
  const codePoint = character.codePointAt(0);
1873
2072
  return codePoint !== undefined && codePoint >= 32 && codePoint !== 127 && character !== "<" && character !== ">";
1874
2073
  }
1875
- var CimdClientMetadataSchema = z9.object({
1876
- client_id: z9.url(),
1877
- redirect_uris: z9.array(z9.url()).min(1),
1878
- client_name: z9.string().trim().min(1).max(200).refine((value) => Array.from(value).every(isPlainDisplayCharacter), {
2074
+ var CimdClientMetadataSchema = z8.object({
2075
+ client_id: z8.url(),
2076
+ redirect_uris: z8.array(z8.url()).min(1),
2077
+ client_name: z8.string().trim().min(1).max(200).refine((value) => Array.from(value).every(isPlainDisplayCharacter), {
1879
2078
  message: "client_name must be plain display text"
1880
2079
  }),
1881
- token_endpoint_auth_method: z9.literal("none"),
1882
- application_type: z9.enum(["native", "web"]).optional()
2080
+ token_endpoint_auth_method: z8.literal("none"),
2081
+ application_type: z8.enum(["native", "web"]).optional()
1883
2082
  });
1884
2083
  function createSecureClientMetadataFetcher(policy = {}) {
1885
2084
  assertPositiveInteger("cimd.maxBytes", policy.maxBytes ?? DEFAULT_CIMD_MAX_BYTES);
@@ -2512,67 +2711,6 @@ function mountOAuthProvider(config) {
2512
2711
  };
2513
2712
  return dcrEnabled ? [metadataRoute, registerRoute, authorizeRoute, tokenRoute] : [metadataRoute, authorizeRoute, tokenRoute];
2514
2713
  }
2515
- // src/tools/remote.ts
2516
- var HTTP_ONLY = Object.freeze(["HTTP"]);
2517
- function toArgs(ctx) {
2518
- const { params, input } = ctx;
2519
- return {
2520
- ...isRecord(params) ? params : {},
2521
- ...isRecord(input) ? input : {}
2522
- };
2523
- }
2524
- function implementRemote(contract, http, options) {
2525
- const client = createClient(contract, http);
2526
- const groupScope = contract.meta.scope ?? "public";
2527
- const methods = {};
2528
- for (const [key, endpoint] of Object.entries(contract.endpoints)) {
2529
- methods[key] = {
2530
- method: endpoint.method,
2531
- path: endpoint.path,
2532
- desc: endpoint.desc,
2533
- serviceName: contract.meta.prefix,
2534
- key,
2535
- toolName: "toolName" in endpoint ? endpoint.toolName : undefined,
2536
- expose: endpoint.rawResponse || endpoint.rawBody || endpoint.responseMeta ? HTTP_ONLY : endpoint.expose,
2537
- ui: "ui" in endpoint ? endpoint.ui : undefined,
2538
- annotations: "annotations" in endpoint ? endpoint.annotations : undefined,
2539
- meta: mergeMeta(contract.meta.meta, endpoint.meta),
2540
- scope: endpoint.scope ?? groupScope,
2541
- paramsSchema: endpoint.params,
2542
- inputSchema: endpoint.input,
2543
- outputSchema: endpoint.output,
2544
- multipart: endpoint.multipart,
2545
- maxJsonBodyBytes: endpoint.maxJsonBodyBytes,
2546
- idempotent: endpoint.idempotent,
2547
- rawResponse: endpoint.rawResponse,
2548
- rawBody: endpoint.rawBody,
2549
- responseMeta: endpoint.responseMeta,
2550
- contentType: "contentType" in endpoint ? endpoint.contentType : undefined,
2551
- handler: async (ctx) => {
2552
- const call = client[key];
2553
- if (!call) {
2554
- throw new Error(`implementRemote: endpoint "${key}" is not exposed over HTTP`);
2555
- }
2556
- const args = toArgs(ctx);
2557
- try {
2558
- const finalArgs = options?.transformArgs ? await options.transformArgs(key, args) : args;
2559
- return await call(finalArgs);
2560
- } catch (err) {
2561
- if (ApiError.is(err)) {
2562
- throw new AppError(err.code, err.message, err.status, isRecord(err.details) ? err.details : undefined, err.hint, err.traceId);
2563
- }
2564
- throw err;
2565
- }
2566
- }
2567
- };
2568
- }
2569
- return {
2570
- name: contract.meta.prefix,
2571
- prefix: contract.meta.prefix,
2572
- scope: groupScope,
2573
- methods
2574
- };
2575
- }
2576
2714
  // src/tools/tool-logger.ts
2577
2715
  function createToolLogger(config = {}) {
2578
2716
  const log = config.log ?? ((line) => console.error(line));
@@ -2673,56 +2811,59 @@ function summarizeTransports(surface) {
2673
2811
  };
2674
2812
  }
2675
2813
  export {
2676
- wwwAuthenticateHeader,
2677
- validateMcpSchemas,
2678
- summarizeTransports,
2679
- resolveMedia,
2680
- protectedResourceMetadataUrl,
2681
- oauthProtectedResourceRoute,
2682
- mountWait,
2683
- mountViewFile,
2684
- mountUpload,
2685
- mountOAuthProvider,
2686
- mountMcpResource,
2687
- mountMcp,
2688
- mountDownload,
2689
- mountAgent,
2690
- listToolNames,
2691
- listContractToolNames,
2692
- inlineMcpAppBundle,
2693
- implementRemote,
2694
- flattenToolJsonSchema,
2695
- findUntypedProperties,
2696
- findNonPortableFormats,
2697
- defineWaitTool,
2698
- defineViewFileTool,
2699
- defineUploadTool,
2700
- defineRuntimeTool,
2701
- defineDownloadTool,
2702
- defineCliCommand,
2703
- createToolkit,
2704
- createToolLogger,
2705
- createToolInvoker,
2706
- createStdioMcpServer,
2707
- createSecureClientMetadataFetcher,
2708
- createRuntimeToolFactory,
2709
- createMcpHttpRoute,
2710
- createMcpHandler,
2711
- createCli,
2712
- collectTools,
2713
- coerceJsonArgs,
2714
- buildToolManifest,
2715
- buildMcpServer,
2716
- bindStdioProcessSignals,
2717
- ViewFileOutputSchema,
2718
- ViewFileInputSchema,
2719
- ViewFileErrorSchema,
2720
- UploadToolInputSchema,
2721
- RESOURCE_MIME_TYPE,
2722
- PROTECTED_RESOURCE_PATH,
2723
- PORTABLE_JSON_SCHEMA_FORMATS,
2724
- McpMediaContentSchema,
2725
- McpAnnotationsSchema,
2814
+ AsyncOperationCancelResultSchema,
2726
2815
  EXT_APPS_BUNDLE_PLACEHOLDER,
2727
- DownloadResultSchema
2816
+ McpAnnotationsSchema,
2817
+ McpMediaContentSchema,
2818
+ PORTABLE_JSON_SCHEMA_FORMATS,
2819
+ PROTECTED_RESOURCE_PATH,
2820
+ RESOURCE_MIME_TYPE,
2821
+ UploadToolInputSchema,
2822
+ ViewFileErrorSchema,
2823
+ ViewFileInputSchema,
2824
+ ViewFileOutputSchema,
2825
+ bindContractAsyncOperation,
2826
+ bindStdioProcessSignals,
2827
+ buildMcpServer,
2828
+ buildToolManifest,
2829
+ coerceJsonArgs,
2830
+ collectTools,
2831
+ composeToolLifecycle,
2832
+ createAsyncOperationSnapshotSchema,
2833
+ createCli,
2834
+ createMcpHandler,
2835
+ createMcpHttpRoute,
2836
+ createRuntimeToolFactory,
2837
+ createSecureClientMetadataFetcher,
2838
+ createStdioMcpServer,
2839
+ createToolInvoker,
2840
+ createToolLogger,
2841
+ createToolkit,
2842
+ defineAsyncOperation,
2843
+ defineCliCommand,
2844
+ defineDownloadTool,
2845
+ defineRuntimeTool,
2846
+ defineUploadTool,
2847
+ defineViewFileTool,
2848
+ defineWaitTool,
2849
+ findNonPortableFormats,
2850
+ findUntypedProperties,
2851
+ flattenToolJsonSchema,
2852
+ inlineMcpAppBundle,
2853
+ listContractToolNames,
2854
+ listToolNames,
2855
+ mountAgent,
2856
+ mountDownload,
2857
+ mountMcp,
2858
+ mountMcpResource,
2859
+ mountOAuthProvider,
2860
+ mountUpload,
2861
+ mountViewFile,
2862
+ mountWait,
2863
+ oauthProtectedResourceRoute,
2864
+ protectedResourceMetadataUrl,
2865
+ resolveMedia,
2866
+ summarizeTransports,
2867
+ validateMcpSchemas,
2868
+ wwwAuthenticateHeader
2728
2869
  };