stitchkit 0.67.0 → 0.68.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 (83) hide show
  1. package/dist/agent-runtime.js +5 -6
  2. package/dist/application/admission.d.ts +131 -0
  3. package/dist/application/admission.d.ts.map +1 -0
  4. package/dist/application/channel.d.ts +106 -0
  5. package/dist/application/channel.d.ts.map +1 -0
  6. package/dist/application/latest-sink.d.ts.map +1 -1
  7. package/dist/application-grammy.js +1 -1
  8. package/dist/application-opentelemetry.js +1 -1
  9. package/dist/application.d.ts +2 -0
  10. package/dist/application.d.ts.map +1 -1
  11. package/dist/application.js +386 -17
  12. package/dist/browser/client.d.ts +2 -3
  13. package/dist/browser/client.d.ts.map +1 -1
  14. package/dist/browser/contract-stream.d.ts +5 -0
  15. package/dist/browser/contract-stream.d.ts.map +1 -0
  16. package/dist/browser/http.d.ts +3 -0
  17. package/dist/browser/http.d.ts.map +1 -1
  18. package/dist/browser/stream.d.ts +4 -1
  19. package/dist/browser/stream.d.ts.map +1 -1
  20. package/dist/browser/transport.d.ts +3 -0
  21. package/dist/browser/transport.d.ts.map +1 -0
  22. package/dist/cli.js +6 -7
  23. package/dist/contract/define.d.ts +34 -2
  24. package/dist/contract/define.d.ts.map +1 -1
  25. package/dist/contract/errors.d.ts +4 -0
  26. package/dist/contract/errors.d.ts.map +1 -1
  27. package/dist/contract/index.d.ts +2 -1
  28. package/dist/contract/index.d.ts.map +1 -1
  29. package/dist/contract/index.js +5 -1
  30. package/dist/contract/stream.d.ts +19 -0
  31. package/dist/contract/stream.d.ts.map +1 -0
  32. package/dist/{index-xxye8j3k.js → index-0w9abg87.js} +4 -0
  33. package/dist/index-69m2278y.js +115 -0
  34. package/dist/{index-physbf4z.js → index-6wzr93cg.js} +1 -1
  35. package/dist/{index-1pgeyyee.js → index-7pn02bgv.js} +4 -6
  36. package/dist/index-9ky9hhg3.js +484 -0
  37. package/dist/{index-wrhf06ak.js → index-avjd67h6.js} +8 -8
  38. package/dist/{index-413xk7ga.js → index-cq9q73nf.js} +50 -1
  39. package/dist/{index-6taryy00.js → index-dafax5md.js} +1 -1
  40. package/dist/{index-8mxadm34.js → index-nymn7wbg.js} +1092 -19
  41. package/dist/{index-y91zd0ch.js → index-pr0qsmjy.js} +3 -2
  42. package/dist/index-v5bayx1z.js +138 -0
  43. package/dist/{index-mfw1pec7.js → index-wqyrvhzz.js} +3 -3
  44. package/dist/{index-svqhcrrj.js → index-zd4v31v2.js} +156 -10
  45. package/dist/{index-x8v8b7rc.js → index-zpyj7hsv.js} +330 -58
  46. package/dist/index.js +270 -105
  47. package/dist/internal/bounded-lines.d.ts +4 -0
  48. package/dist/internal/bounded-lines.d.ts.map +1 -0
  49. package/dist/node.d.ts +1 -0
  50. package/dist/node.d.ts.map +1 -1
  51. package/dist/node.js +11 -8
  52. package/dist/observability/index.js +6 -7
  53. package/dist/remote.js +5 -6
  54. package/dist/server/contract-stream.d.ts +5 -0
  55. package/dist/server/contract-stream.d.ts.map +1 -0
  56. package/dist/server/create.d.ts.map +1 -1
  57. package/dist/server/implement.d.ts.map +1 -1
  58. package/dist/server/index.d.ts +1 -0
  59. package/dist/server/index.d.ts.map +1 -1
  60. package/dist/server/index.js +41 -283
  61. package/dist/server/openapi.d.ts.map +1 -1
  62. package/dist/server/stream.d.ts +3 -1
  63. package/dist/server/stream.d.ts.map +1 -1
  64. package/dist/server/streaming-route.d.ts +2 -0
  65. package/dist/server/streaming-route.d.ts.map +1 -1
  66. package/dist/server/types.d.ts +13 -3
  67. package/dist/server/types.d.ts.map +1 -1
  68. package/dist/server/unix-client-body.d.ts +4 -0
  69. package/dist/server/unix-client-body.d.ts.map +1 -0
  70. package/dist/server/unix-client-bun.d.ts +13 -0
  71. package/dist/server/unix-client-bun.d.ts.map +1 -0
  72. package/dist/server/unix-client-error.d.ts +9 -0
  73. package/dist/server/unix-client-error.d.ts.map +1 -0
  74. package/dist/server/unix-client.d.ts +25 -0
  75. package/dist/server/unix-client.d.ts.map +1 -0
  76. package/dist/testing.js +6 -5
  77. package/dist/tools.js +14 -16
  78. package/llms-full.txt +337 -17
  79. package/package.json +1 -1
  80. package/dist/index-48ffdxgk.js +0 -6
  81. package/dist/index-pzyt11ch.js +0 -127
  82. package/dist/index-tvwcrx2d.js +0 -224
  83. package/dist/index-wd8g8z6e.js +0 -206
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  isUnsafeKey,
3
3
  mergeMeta
4
- } from "./index-wd8g8z6e.js";
4
+ } from "./index-9ky9hhg3.js";
5
5
  import {
6
6
  callRuntimeHandler,
7
7
  isRecord,
@@ -144,11 +144,12 @@ function bindContract(contract, handlers) {
144
144
  serviceName: contract.meta.prefix,
145
145
  key: String(key),
146
146
  toolName: "toolName" in endpoint ? endpoint.toolName : undefined,
147
- expose: endpoint.rawResponse || endpoint.rawBody || endpoint.responseMeta ? HTTP_ONLY : endpoint.expose,
147
+ expose: endpoint.rawResponse || endpoint.rawBody || endpoint.responseMeta || "stream" in endpoint && endpoint.stream ? HTTP_ONLY : endpoint.expose,
148
148
  scope: endpoint.scope ?? groupScope,
149
149
  paramsSchema: endpoint.params,
150
150
  inputSchema: endpoint.input,
151
151
  outputSchema: endpoint.output,
152
+ stream: "stream" in endpoint ? endpoint.stream : undefined,
152
153
  multipart: endpoint.multipart,
153
154
  multipartReceivers: streamingHandler?.receivers,
154
155
  maxJsonBodyBytes: endpoint.maxJsonBodyBytes,
@@ -0,0 +1,138 @@
1
+ import {
2
+ normalizeError
3
+ } from "./index-9ky9hhg3.js";
4
+
5
+ // src/internal/bounded-lines.ts
6
+ var DEFAULT_STREAM_LINE_BYTES = 1024 * 1024;
7
+ function lineLimit(value) {
8
+ const resolved = value ?? DEFAULT_STREAM_LINE_BYTES;
9
+ if (!Number.isSafeInteger(resolved) || resolved <= 0) {
10
+ throw new TypeError("maxLineBytes must be a positive safe integer");
11
+ }
12
+ return resolved;
13
+ }
14
+ function joinBytes(left, right) {
15
+ if (left.byteLength === 0)
16
+ return right.slice();
17
+ const result = new Uint8Array(left.byteLength + right.byteLength);
18
+ result.set(left);
19
+ result.set(right, left.byteLength);
20
+ return result;
21
+ }
22
+ async function* readBoundedUtf8Lines(response, maxLineBytes) {
23
+ const reader = response.body?.getReader();
24
+ if (!reader)
25
+ return;
26
+ const limit = lineLimit(maxLineBytes);
27
+ const decoder = new TextDecoder("utf-8", { fatal: true });
28
+ let pending = new Uint8Array;
29
+ try {
30
+ for (;; ) {
31
+ const chunk = await reader.read();
32
+ if (chunk.done)
33
+ break;
34
+ let start = 0;
35
+ for (let index = 0;index < chunk.value.byteLength; index += 1) {
36
+ if (chunk.value[index] !== 10)
37
+ continue;
38
+ const lineBytes = joinBytes(pending, chunk.value.slice(start, index));
39
+ if (lineBytes.byteLength > limit) {
40
+ throw new RangeError(`Stream line exceeds the ${limit} byte limit`);
41
+ }
42
+ yield decoder.decode(lineBytes);
43
+ pending = new Uint8Array;
44
+ start = index + 1;
45
+ }
46
+ pending = joinBytes(pending, chunk.value.slice(start));
47
+ if (pending.byteLength > limit) {
48
+ throw new RangeError(`Stream line exceeds the ${limit} byte limit`);
49
+ }
50
+ }
51
+ if (pending.byteLength > 0)
52
+ yield decoder.decode(pending);
53
+ } finally {
54
+ await reader.cancel().catch(() => {
55
+ return;
56
+ });
57
+ reader.releaseLock();
58
+ }
59
+ }
60
+
61
+ // src/server/stream.ts
62
+ function streamSSE(generator) {
63
+ const encoder = new TextEncoder;
64
+ let cancelled = false;
65
+ const stream = new ReadableStream({
66
+ async start(controller) {
67
+ try {
68
+ for await (const chunk of generator) {
69
+ if (cancelled)
70
+ return;
71
+ const data = JSON.stringify(chunk);
72
+ controller.enqueue(encoder.encode(`data: ${data}
73
+
74
+ `));
75
+ }
76
+ if (cancelled)
77
+ return;
78
+ controller.enqueue(encoder.encode(`data: [DONE]
79
+
80
+ `));
81
+ controller.close();
82
+ } catch (err) {
83
+ if (cancelled)
84
+ return;
85
+ const envelope = normalizeError(err).toJSON();
86
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(envelope)}
87
+
88
+ `));
89
+ controller.close();
90
+ }
91
+ },
92
+ cancel() {
93
+ cancelled = true;
94
+ generator.return(undefined).catch(() => {
95
+ return;
96
+ });
97
+ }
98
+ });
99
+ return new Response(stream, {
100
+ headers: {
101
+ "Content-Type": "text/event-stream",
102
+ "Cache-Control": "no-cache",
103
+ Connection: "keep-alive"
104
+ }
105
+ });
106
+ }
107
+ async function* parseSSE(response, options) {
108
+ try {
109
+ for await (const rawLine of readBoundedUtf8Lines(response, options?.maxLineBytes ?? DEFAULT_STREAM_LINE_BYTES)) {
110
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
111
+ if (!line.startsWith("data:"))
112
+ continue;
113
+ const data = line.slice(5).replace(/^ /, "");
114
+ if (data === "[DONE]")
115
+ return;
116
+ try {
117
+ yield JSON.parse(data);
118
+ } catch (error) {
119
+ const failure = error instanceof Error ? error : new Error(String(error));
120
+ if (!options?.onParseError)
121
+ throw failure;
122
+ options.onParseError(data, failure);
123
+ }
124
+ }
125
+ } catch (error) {
126
+ const failure = error instanceof Error ? error : new Error(String(error));
127
+ if (!options?.onParseError)
128
+ throw failure;
129
+ options.onParseError("", failure);
130
+ }
131
+ }
132
+
133
+ // src/internal/http-input.ts
134
+ function inputIsQuery(method) {
135
+ return method === "GET" || method === "DELETE";
136
+ }
137
+
138
+ export { inputIsQuery, DEFAULT_STREAM_LINE_BYTES, readBoundedUtf8Lines, streamSSE, parseSSE };
@@ -2,20 +2,20 @@ import {
2
2
  contextContributionDelta,
3
3
  mergeContextContribution,
4
4
  prepareContextContribution
5
- } from "./index-y91zd0ch.js";
5
+ } from "./index-pr0qsmjy.js";
6
6
  import {
7
7
  base64UrlToBytes,
8
8
  bytesToBase64Url,
9
9
  isUnsafeKey,
10
10
  safeJsonParse
11
- } from "./index-wd8g8z6e.js";
11
+ } from "./index-9ky9hhg3.js";
12
12
  import {
13
13
  isRecord
14
14
  } from "./index-smpbdg6k.js";
15
15
  import {
16
16
  forbidden,
17
17
  unauthorized
18
- } from "./index-xxye8j3k.js";
18
+ } from "./index-0w9abg87.js";
19
19
 
20
20
  // src/server/middleware/cookies.ts
21
21
  function parseCookies(header) {
@@ -1,9 +1,14 @@
1
1
  import {
2
- inputIsQuery
3
- } from "./index-48ffdxgk.js";
2
+ DEFAULT_STREAM_LINE_BYTES,
3
+ inputIsQuery,
4
+ parseSSE,
5
+ readBoundedUtf8Lines
6
+ } from "./index-v5bayx1z.js";
4
7
  import {
8
+ ContractStreamFrameSchema,
9
+ DEFAULT_CONTRACT_STREAM_FRAME_BYTES,
5
10
  parseTrailingWildcard
6
- } from "./index-pzyt11ch.js";
11
+ } from "./index-9ky9hhg3.js";
7
12
  import {
8
13
  isRecord,
9
14
  mapObject,
@@ -277,10 +282,103 @@ function parseApiErrorBody(body) {
277
282
  hint: typeof error.hint === "string" ? error.hint : undefined
278
283
  };
279
284
  }
285
+ // src/browser/stream.ts
286
+ function parseFailure(raw, error, onParseError) {
287
+ const failure = error instanceof Error ? error : new Error(String(error));
288
+ if (!onParseError)
289
+ throw failure;
290
+ onParseError(raw, failure);
291
+ }
292
+ async function* parseNDJSON(response, options) {
293
+ try {
294
+ for await (const rawLine of readBoundedUtf8Lines(response, options?.maxLineBytes ?? DEFAULT_STREAM_LINE_BYTES)) {
295
+ const line = (rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine).trim();
296
+ if (line === "")
297
+ continue;
298
+ try {
299
+ yield JSON.parse(line);
300
+ } catch (error) {
301
+ parseFailure(line, error, options?.onParseError);
302
+ }
303
+ }
304
+ } catch (error) {
305
+ parseFailure("", error, options?.onParseError);
306
+ }
307
+ }
308
+
309
+ // src/browser/contract-stream.ts
310
+ function sourceFor(response, descriptor) {
311
+ const options = {
312
+ maxLineBytes: descriptor.maxFrameBytes ?? DEFAULT_CONTRACT_STREAM_FRAME_BYTES
313
+ };
314
+ return descriptor.format === "sse" ? parseSSE(response, options) : parseNDJSON(response, options);
315
+ }
316
+ async function* readContractStream(response, descriptor, abort) {
317
+ let ended = false;
318
+ let terminalSeen = descriptor.terminal === undefined;
319
+ try {
320
+ for await (const raw of sourceFor(response, descriptor)) {
321
+ const parsedFrame = ContractStreamFrameSchema.safeParse(raw);
322
+ if (!parsedFrame.success) {
323
+ throw new ApiError("STREAM_PROTOCOL_ERROR", 0, undefined, "Stream frame did not match the protocol envelope");
324
+ }
325
+ const frame = parsedFrame.data;
326
+ if (frame.type === "error") {
327
+ throw new ApiError(frame.error.code, 0, frame.error.details, frame.error.message, frame.error.hint);
328
+ }
329
+ if (frame.type === "end") {
330
+ if (!terminalSeen) {
331
+ throw new ApiError("STREAM_TERMINAL_MISSING", 0, undefined, "Stream completed before its declared terminal item");
332
+ }
333
+ ended = true;
334
+ return;
335
+ }
336
+ const parsedItem = descriptor.item.safeParse(frame.data);
337
+ if (!parsedItem.success) {
338
+ throw new ApiError("STREAM_ITEM_INVALID", 0, undefined, "Stream item did not match its contract");
339
+ }
340
+ const item = parsedItem.data;
341
+ if (descriptor.terminal?.safeParse(item).success)
342
+ terminalSeen = true;
343
+ yield item;
344
+ }
345
+ if (!ended) {
346
+ throw new ApiError("STREAM_TRUNCATED", 0, undefined, "Stream ended without its protocol terminal frame");
347
+ }
348
+ } finally {
349
+ abort();
350
+ }
351
+ }
352
+
353
+ class OwnedContractStream {
354
+ #inner;
355
+ #abort;
356
+ constructor(inner, abort) {
357
+ this.#inner = inner;
358
+ this.#abort = abort;
359
+ }
360
+ [Symbol.asyncIterator]() {
361
+ return this;
362
+ }
363
+ next() {
364
+ return this.#inner.next();
365
+ }
366
+ return() {
367
+ this.#abort();
368
+ return this.#inner.return(undefined);
369
+ }
370
+ throw(error) {
371
+ this.#abort();
372
+ return this.#inner.throw(error);
373
+ }
374
+ }
375
+ function parseContractStream(response, descriptor, abort) {
376
+ return new OwnedContractStream(readContractStream(response, descriptor, abort), abort);
377
+ }
280
378
 
281
379
  // src/browser/client.ts
282
380
  function withTimeout(options, endpoint) {
283
- const responseType = endpoint.rawResponse ? "response" : endpoint.output ? undefined : "void";
381
+ const responseType = endpoint.rawResponse || "stream" in endpoint ? "response" : endpoint.output ? undefined : "void";
284
382
  if (endpoint.timeout === undefined && responseType === undefined)
285
383
  return options;
286
384
  return {
@@ -289,9 +387,23 @@ function withTimeout(options, endpoint) {
289
387
  ...responseType && { responseType }
290
388
  };
291
389
  }
292
- function withOutput(endpoint, result) {
390
+ function withOutput(endpoint, result, abortStream) {
293
391
  if (endpoint.rawResponse)
294
392
  return result;
393
+ if ("stream" in endpoint && endpoint.stream) {
394
+ return result.then((value) => {
395
+ if (!(value instanceof Response)) {
396
+ abortStream?.();
397
+ throw new Error("Streaming endpoint did not return a Response");
398
+ }
399
+ return parseContractStream(value, endpoint.stream, abortStream ?? (() => {
400
+ return;
401
+ }));
402
+ }, (error) => {
403
+ abortStream?.();
404
+ throw error;
405
+ });
406
+ }
295
407
  const schema = endpoint.output;
296
408
  return result.then((value) => {
297
409
  if (!schema) {
@@ -341,20 +453,26 @@ function createHttpExecutor(endpoint, prefix, client, config) {
341
453
  const httpMethod = endpoint.method.toLowerCase();
342
454
  return (requestArgs, options) => {
343
455
  const plan = planClientRequest(endpoint, prefix, requestArgs, config);
456
+ const streamAbort = "stream" in endpoint ? new AbortController : undefined;
457
+ const requestOptions = streamAbort ? {
458
+ ...options,
459
+ signal: options?.signal ? AbortSignal.any([options.signal, streamAbort.signal]) : streamAbort.signal
460
+ } : options;
461
+ const finishStream = streamAbort ? () => streamAbort.abort() : undefined;
344
462
  if (endpoint.multipart) {
345
463
  if (httpMethod === "get" || httpMethod === "head" || httpMethod === "delete") {
346
464
  throw new Error(`Multipart endpoint ${endpoint.method} ${endpoint.path} must be POST / PUT / PATCH`);
347
465
  }
348
466
  const formData = buildMultipartForm(endpoint.multipart, plan.remainingArgs);
349
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(options, endpoint)));
467
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(requestOptions, endpoint)), finishStream);
350
468
  }
351
469
  if (httpMethod === "get" || httpMethod === "head") {
352
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(options, endpoint)));
470
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(requestOptions, endpoint)), finishStream);
353
471
  }
354
472
  if (httpMethod === "delete") {
355
- return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(options, endpoint)));
473
+ return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(requestOptions, endpoint)), finishStream);
356
474
  }
357
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(options, endpoint)));
475
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(requestOptions, endpoint)), finishStream);
358
476
  };
359
477
  }
360
478
  function createFetchExecutor(endpoint, prefix, config, contractConfig) {
@@ -366,7 +484,32 @@ function createFetchExecutor(endpoint, prefix, config, contractConfig) {
366
484
  Accept: "application/json",
367
485
  ...typeof config.headers === "function" ? config.headers() : config.headers
368
486
  };
369
- const cancellation = createRequestCancellation(options?.signal, endpoint.timeout ?? config.timeout ?? 30000);
487
+ const streamAbort = "stream" in endpoint ? new AbortController : undefined;
488
+ const requestSignal = streamAbort ? options?.signal ? AbortSignal.any([options.signal, streamAbort.signal]) : streamAbort.signal : options?.signal;
489
+ const openTimeoutMs = endpoint.timeout ?? config.timeout ?? 30000;
490
+ const cancellation = streamAbort ? {
491
+ signal: requestSignal,
492
+ async run(operation) {
493
+ let timedOut = false;
494
+ const timer = setTimeout(() => {
495
+ timedOut = true;
496
+ streamAbort.abort(new DOMException("Request timed out", "TimeoutError"));
497
+ }, openTimeoutMs);
498
+ try {
499
+ if (options?.signal?.aborted)
500
+ throw new RequestCancellationError("caller");
501
+ return await operation(requestSignal);
502
+ } catch (error) {
503
+ if (timedOut)
504
+ throw new RequestCancellationError("timeout");
505
+ if (options?.signal?.aborted)
506
+ throw new RequestCancellationError("caller");
507
+ throw error;
508
+ } finally {
509
+ clearTimeout(timer);
510
+ }
511
+ }
512
+ } : createRequestCancellation(requestSignal, openTimeoutMs);
370
513
  const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && Object.keys(plan.remainingArgs).length > 0;
371
514
  if (hasBody)
372
515
  headers["Content-Type"] = "application/json";
@@ -385,6 +528,9 @@ function createFetchExecutor(endpoint, prefix, config, contractConfig) {
385
528
  }
386
529
  if (endpoint.rawResponse)
387
530
  return res;
531
+ if ("stream" in endpoint && endpoint.stream) {
532
+ return parseContractStream(res, endpoint.stream, () => streamAbort?.abort());
533
+ }
388
534
  if (!endpoint.output) {
389
535
  const text = await res.text();
390
536
  if (text.length > 0) {