@volter/world-core 3.0.37 → 3.0.39

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 (179) hide show
  1. package/app-route.cjs +26 -25
  2. package/app-route.d.cts +1 -0
  3. package/dist/app-route.cjs +26 -25
  4. package/dist/app-route.d.cts +1 -0
  5. package/dist/generated/pack-facts.json +409 -352
  6. package/dist/inject.cjs +474 -95
  7. package/dist/network-policy.cjs +108 -13
  8. package/dist/network-policy.d.cts +8 -3
  9. package/dist/src/actions.js +2 -1
  10. package/dist/src/anthropic-wire.d.ts +2 -0
  11. package/dist/src/anthropic-wire.js +5 -0
  12. package/dist/src/attestation.d.ts +6 -0
  13. package/dist/src/attestation.js +27 -0
  14. package/dist/src/bytes.d.ts +3 -0
  15. package/dist/src/bytes.js +17 -0
  16. package/dist/src/changeset.js +2 -1
  17. package/dist/src/clickhouse/sql.js +73 -14
  18. package/dist/src/derived-core.d.ts +210 -32
  19. package/dist/src/derived-core.js +510 -244
  20. package/dist/src/derived-real.d.ts +1 -1
  21. package/dist/src/derived-real.js +704 -93
  22. package/dist/src/derived.d.ts +18 -2
  23. package/dist/src/derived.js +21 -2
  24. package/dist/src/events.d.ts +28 -45
  25. package/dist/src/events.js +40 -58
  26. package/dist/src/exact-json.d.ts +10 -0
  27. package/dist/src/exact-json.js +98 -0
  28. package/dist/src/executor.d.ts +17 -0
  29. package/dist/src/executor.js +91 -30
  30. package/dist/src/file-response.js +1 -1
  31. package/dist/src/form.d.ts +8 -0
  32. package/dist/src/form.js +76 -0
  33. package/dist/src/git/format.d.ts +16 -0
  34. package/dist/src/git/format.js +110 -0
  35. package/dist/src/git/index.d.ts +1 -0
  36. package/dist/src/git/index.js +1 -0
  37. package/dist/src/git/lfs.d.ts +1 -1
  38. package/dist/src/git/lfs.js +4 -3
  39. package/dist/src/graphql-wire.d.ts +6 -0
  40. package/dist/src/graphql-wire.js +6 -3
  41. package/dist/src/grpc-wire.js +2 -1
  42. package/dist/src/head.d.ts +1 -0
  43. package/dist/src/head.js +15 -4
  44. package/dist/src/held-reads.d.ts +6 -0
  45. package/dist/src/held-reads.js +60 -0
  46. package/dist/src/history.js +5 -4
  47. package/dist/src/host-port.d.ts +5 -0
  48. package/dist/src/host-port.js +14 -0
  49. package/dist/src/index.d.ts +11 -3
  50. package/dist/src/index.js +9 -4
  51. package/dist/src/log.js +12 -7
  52. package/dist/src/machines.d.ts +81 -5
  53. package/dist/src/machines.js +125 -9
  54. package/dist/src/managed-database.d.ts +15 -11
  55. package/dist/src/managed-database.js +38 -37
  56. package/dist/src/multipart.d.ts +21 -3
  57. package/dist/src/multipart.js +112 -38
  58. package/dist/src/observe.d.ts +3 -0
  59. package/dist/src/observe.js +6 -2
  60. package/dist/src/openai-wire.d.ts +16 -0
  61. package/dist/src/openai-wire.js +43 -12
  62. package/dist/src/pack-fetch.d.ts +4 -6
  63. package/dist/src/pack-fetch.js +189 -57
  64. package/dist/src/packRegistry.d.ts +7 -1
  65. package/dist/src/packRegistry.js +20 -3
  66. package/dist/src/private-transport.d.ts +1 -0
  67. package/dist/src/private-transport.js +11 -0
  68. package/dist/src/protobuf.js +10 -4
  69. package/dist/src/rateBudget.d.ts +8 -1
  70. package/dist/src/rateBudget.js +1 -0
  71. package/dist/src/redis/engine.d.ts +14 -5
  72. package/dist/src/redis/engine.js +271 -72
  73. package/dist/src/redis/frames.d.ts +17 -0
  74. package/dist/src/redis/frames.js +123 -0
  75. package/dist/src/redis/index.d.ts +6 -0
  76. package/dist/src/redis/index.js +6 -0
  77. package/dist/src/redis/json.d.ts +2 -0
  78. package/dist/src/redis/json.js +14 -0
  79. package/dist/src/redis/lua-libs.d.ts +4 -0
  80. package/dist/src/redis/lua-libs.js +173 -0
  81. package/dist/src/redis/lua.d.ts +1 -0
  82. package/dist/src/redis/lua.js +36 -0
  83. package/dist/src/redis/member-storage.d.ts +5 -0
  84. package/dist/src/redis/member-storage.js +75 -0
  85. package/dist/src/redis/resp2.d.ts +4 -0
  86. package/dist/src/redis/resp2.js +29 -0
  87. package/dist/src/redis/stream.d.ts +14 -0
  88. package/dist/src/redis/stream.js +335 -0
  89. package/dist/src/remote-execute.d.ts +2 -2
  90. package/dist/src/request-body.d.ts +11 -0
  91. package/dist/src/request-body.js +11 -0
  92. package/dist/src/runtime.d.ts +6 -5
  93. package/dist/src/runtime.js +5 -5
  94. package/dist/src/s3/wire.d.ts +4 -4
  95. package/dist/src/s3/wire.js +5 -3
  96. package/dist/src/scenario.d.ts +22 -0
  97. package/dist/src/scenario.js +35 -31
  98. package/dist/src/serve-http.d.ts +2 -1
  99. package/dist/src/serve-http.js +10 -1
  100. package/dist/src/serve.js +7 -4
  101. package/dist/src/signing.d.ts +23 -5
  102. package/dist/src/signing.js +74 -11
  103. package/dist/src/sigv4.d.ts +3 -1
  104. package/dist/src/sigv4.js +4 -2
  105. package/dist/src/smtp.js +2 -1
  106. package/dist/src/sockets.js +8 -3
  107. package/dist/src/state-system.d.ts +3 -0
  108. package/dist/src/storage.js +5 -2
  109. package/dist/src/twin-fetch.d.ts +3 -1
  110. package/dist/src/twin-fetch.js +3 -2
  111. package/dist/src/vendor-call.d.ts +10 -3
  112. package/dist/src/vendor-call.js +58 -14
  113. package/dist/test-fixtures/attestation.SOURCE.md +1 -0
  114. package/dist/test-fixtures/attestation.json +50 -0
  115. package/generated/pack-facts.json +409 -352
  116. package/inject.cjs +474 -95
  117. package/network-policy.cjs +108 -13
  118. package/network-policy.d.cts +8 -3
  119. package/package.json +11 -4
  120. package/src/actions.ts +2 -1
  121. package/src/anthropic-wire.ts +6 -0
  122. package/src/attestation.ts +23 -0
  123. package/src/bytes.ts +21 -0
  124. package/src/changeset.ts +2 -1
  125. package/src/clickhouse/sql.ts +50 -16
  126. package/src/derived-core.ts +573 -249
  127. package/src/derived-real.ts +552 -42
  128. package/src/derived.ts +23 -4
  129. package/src/events.ts +54 -85
  130. package/src/exact-json.ts +71 -0
  131. package/src/executor.ts +86 -27
  132. package/src/file-response.ts +1 -1
  133. package/src/form.ts +66 -0
  134. package/src/git/format.ts +89 -0
  135. package/src/git/index.ts +1 -0
  136. package/src/git/lfs.ts +5 -4
  137. package/src/graphql-wire.ts +8 -3
  138. package/src/grpc-wire.ts +2 -1
  139. package/src/head.ts +15 -5
  140. package/src/held-reads.ts +54 -0
  141. package/src/history.ts +5 -4
  142. package/src/host-port.ts +9 -0
  143. package/src/index.ts +10 -4
  144. package/src/log.ts +11 -7
  145. package/src/machines.ts +135 -13
  146. package/src/managed-database.ts +48 -40
  147. package/src/multipart.ts +110 -35
  148. package/src/observe.ts +6 -3
  149. package/src/openai-wire.ts +45 -14
  150. package/src/pack-fetch.ts +173 -50
  151. package/src/packRegistry.ts +27 -4
  152. package/src/private-transport.ts +8 -0
  153. package/src/protobuf.ts +8 -4
  154. package/src/rateBudget.ts +6 -1
  155. package/src/redis/engine.ts +168 -63
  156. package/src/redis/frames.ts +53 -0
  157. package/src/redis/index.ts +6 -0
  158. package/src/redis/json.ts +13 -0
  159. package/src/redis/lua-libs.ts +62 -0
  160. package/src/redis/lua.ts +20 -0
  161. package/src/redis/member-storage.ts +58 -0
  162. package/src/redis/resp2.ts +25 -0
  163. package/src/redis/stream.ts +102 -0
  164. package/src/remote-execute.ts +2 -2
  165. package/src/request-body.ts +12 -0
  166. package/src/runtime.ts +6 -5
  167. package/src/s3/wire.ts +8 -5
  168. package/src/scenario.ts +34 -13
  169. package/src/serve-http.ts +11 -2
  170. package/src/serve.ts +4 -3
  171. package/src/signing.ts +71 -17
  172. package/src/sigv4.ts +5 -3
  173. package/src/smtp.ts +3 -2
  174. package/src/sockets.ts +8 -3
  175. package/src/state-system.ts +3 -0
  176. package/src/storage.ts +5 -2
  177. package/src/twin-fetch.ts +4 -3
  178. package/src/vendor-call.ts +59 -12
  179. package/test-fixtures/attestation.json +50 -0
@@ -43,39 +43,6 @@ export class EngineDatabaseError extends Error {
43
43
  export class EngineUnavailableError extends Error {
44
44
  constructor(message) { super(message); this.name = 'EngineUnavailableError'; }
45
45
  }
46
- /** The transaction-local setting every batch a handler makes carries: the World clock at the call (ctx.occurredAt). */
47
- export const WORLD_CLOCK_SETTING = 'volter.world_clock';
48
- /** The World clock inside the database, for the schemas a vendor lays (its migrations stamp rows with Postgres's
49
- * `now()`, which reads the wall clock): `volter.now()`, the World clock the batch set or the wall clock where none is
50
- * set, and every column default in `schemas` that reads `now()` or `CURRENT_TIMESTAMP` (alone or inside an expression,
51
- * GoTrue's `timezone('utc', now())`) rewritten to read it. Run after the schemas are laid; running it again changes
52
- * nothing. A trigger or function that calls `now()` itself is the pack's to redefine. */
53
- export function worldClockSql(schemas) {
54
- const list = schemas.map((s) => `'${s.replace(/'/g, "''")}'`).join(', ');
55
- return `CREATE SCHEMA IF NOT EXISTS volter;
56
- CREATE OR REPLACE FUNCTION volter.now() RETURNS timestamptz LANGUAGE sql STABLE AS $fn$
57
- SELECT coalesce(nullif(current_setting('${WORLD_CLOCK_SETTING}', true), '')::timestamptz, pg_catalog.now())
58
- $fn$;
59
- GRANT USAGE ON SCHEMA volter TO PUBLIC;
60
- GRANT EXECUTE ON FUNCTION volter.now() TO PUBLIC;
61
- DO $do$
62
- DECLARE d record; rewritten text;
63
- BEGIN
64
- FOR d IN
65
- SELECT n.nspname AS schema, c.relname AS tbl, a.attname AS col, pg_get_expr(ad.adbin, ad.adrelid) AS expr
66
- FROM pg_attrdef ad
67
- JOIN pg_class c ON c.oid = ad.adrelid
68
- JOIN pg_namespace n ON n.oid = c.relnamespace
69
- JOIN pg_attribute a ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
70
- WHERE n.nspname IN (${list}) AND c.relkind IN ('r', 'p')
71
- AND pg_get_expr(ad.adbin, ad.adrelid) ~* '(^|[^.[:alnum:]_])(now\\(\\)|current_timestamp)'
72
- LOOP
73
- rewritten := regexp_replace(d.expr, '(^|[^.[:alnum:]_])(now\\(\\)|current_timestamp)', '\\1volter.now()', 'gi');
74
- EXECUTE format('ALTER TABLE %I.%I ALTER COLUMN %I SET DEFAULT %s', d.schema, d.tbl, d.col, rewritten);
75
- END LOOP;
76
- END
77
- $do$;`;
78
- }
79
46
  /** Quote an identifier the way Postgres's quote_ident does for a name that needs it. */
80
47
  export function quoteIdent(name) {
81
48
  return `"${name.replace(/"/g, '""')}"`;
@@ -228,14 +195,13 @@ function settingsStatement(settings) {
228
195
  return { text: `SELECT ${calls.join(', ')}`, values };
229
196
  }
230
197
  /** The World's Postgres at `url` (the runtime's binding, never the environment); with `readOnly`, every transaction is
231
- * READ ONLY whatever the batch asks (a read-only twin or request: Postgres itself refuses a write, 25006); with `clock`,
232
- * every batch carries the World clock as `volter.world_clock`. */
198
+ * READ ONLY whatever the batch asks (a read-only twin or request: Postgres itself refuses a write, 25006). */
233
199
  export function managedDatabase(url, options = {}) {
234
200
  return {
235
201
  bound: true,
202
+ url,
236
203
  async batch(batch) {
237
- // the World clock at the call, which volter.now() and the defaults worldClockSql rewrote read
238
- const set = settingsStatement({ ...(options.clock !== undefined ? { [WORLD_CLOCK_SETTING]: options.clock } : {}), ...(batch.settings ?? {}) });
204
+ const set = settingsStatement(batch.settings ?? {});
239
205
  const head = [
240
206
  { text: batch.readOnly || options.readOnly ? 'BEGIN READ ONLY' : 'BEGIN' },
241
207
  ...(batch.role === undefined ? [] : [{ text: `SET LOCAL ROLE ${quoteIdent(batch.role)}` }]),
@@ -268,6 +234,41 @@ export function managedDatabase(url, options = {}) {
268
234
  },
269
235
  };
270
236
  }
237
+ /** The World's Postgres at `url` as one connection held until `end()`. */
238
+ export async function databaseSession(url) {
239
+ const { Pool } = await loadDriver();
240
+ const pool = new Pool({ connectionString: url, max: 1, idleTimeoutMillis: 0, connectionTimeoutMillis: 10_000 });
241
+ pool.on('error', () => { });
242
+ let client;
243
+ try {
244
+ client = await pool.connect();
245
+ }
246
+ catch (error) {
247
+ await pool.end().catch(() => { });
248
+ throw new EngineUnavailableError(`the World's Postgres is not answering: ${error.message}`);
249
+ }
250
+ // every value as the wire carries it: a row an array of texts, no type parsed
251
+ const raw = { getTypeParser: () => (value) => value };
252
+ return {
253
+ async query(text, values) {
254
+ try {
255
+ const answer = (await client.query({ text, ...(values === undefined ? {} : { values }), rowMode: 'array', types: raw }));
256
+ return (Array.isArray(answer) ? answer : [answer]).map((r) => ({
257
+ fields: (r.fields ?? []).map((f) => f.name),
258
+ rows: r.rows ?? [],
259
+ command: [r.command ?? '', r.command === 'INSERT' ? '0' : '', r.rowCount ?? ''].filter((part) => part !== '').join(' '),
260
+ }));
261
+ }
262
+ catch (error) {
263
+ throw asDatabaseError(error);
264
+ }
265
+ },
266
+ async end() {
267
+ client.release();
268
+ await pool.end().catch(() => { });
269
+ },
270
+ };
271
+ }
271
272
  /** A handler's database when the World binds none: every use is the refusal naming what to add. */
272
273
  export function unboundDatabase(service) {
273
274
  const refuse = () => {
@@ -1,11 +1,29 @@
1
- /** One part: its field name (possibly empty), its filename when it is a file, its media type as sent, its bytes. */
2
1
  export type MultipartPart = {
3
2
  name: string;
4
3
  filename: string | null;
5
4
  type: string | null;
6
5
  body: Uint8Array;
7
6
  };
8
- /** The boundary a `multipart/form-data` content type names, or null (not multipart, or no boundary). */
7
+ export type MultipartFramedPart = MultipartPart & {
8
+ headers: Uint8Array;
9
+ leading: Uint8Array;
10
+ separator: Uint8Array;
11
+ trailing: Uint8Array;
12
+ };
13
+ export type MultipartEnvelope = {
14
+ complete: boolean;
15
+ preamble: Uint8Array;
16
+ epilogue: Uint8Array;
17
+ delimiters: Uint8Array[];
18
+ parts: MultipartFramedPart[];
19
+ };
20
+ /** The boundary named by any multipart media type. */
9
21
  export declare function multipartBoundary(contentType: string | null | undefined): string | null;
10
- /** The parts of a body delimited by `boundary`, in order; a malformed tail ends the reading. */
22
+ /** Used both by the byte reader and by consumers guarding placeholder spans within a part. */
23
+ export declare function multipartDelimiterPattern(boundary: string): string;
24
+ /** Infer only a boundary with a corresponding closing line; Content-Type remains authoritative when supplied. */
25
+ export declare function inferMultipartBoundary(bytes: Uint8Array): string | null;
26
+ /** Raw MIME framing and interpreted parts. A malformed tail ends reading and leaves `complete` false. */
27
+ export declare function multipartEnvelope(bytes: Uint8Array, boundary: string): MultipartEnvelope;
28
+ /** The request's parts, in order, using the same framing reader as wire comparison. */
11
29
  export declare function multipartParts(bytes: Uint8Array, boundary: string): MultipartPart[];
@@ -1,51 +1,125 @@
1
- // A multipart/form-data body's parts, read from its bytes (RFC 7578): the one reading every pack's uploads go through.
2
- // A runtime's own form parser is not used: Bun's drops a part whose field name is empty, which a browser's or
3
- // supabase-js's Blob upload sends. Dependency-free and side-effect free (a browser bundle imports it).
4
- /** The boundary a `multipart/form-data` content type names, or null (not multipart, or no boundary). */
1
+ // The one MIME multipart reader for requests and published examples. Dependency-free, side-effect-free and
2
+ // byte-preserving: a browser bundle imports it, and empty field names and binary uploads must survive reading.
3
+ /** The boundary named by any multipart media type. */
5
4
  export function multipartBoundary(contentType) {
6
- if (!contentType || !/^multipart\/form-data/i.test(contentType))
5
+ if (!contentType || !/^multipart\//i.test(contentType))
7
6
  return null;
8
- const hit = /boundary=(?:"([^"]+)"|([^;\s]+))/i.exec(contentType);
7
+ const hit = /\bboundary\s*=\s*(?:"([^"]+)"|([^;\s]+))/i.exec(contentType);
9
8
  return hit ? (hit[1] ?? hit[2]) : null;
10
9
  }
11
- function indexOf(haystack, needle, from) {
12
- outer: for (let i = from; i <= haystack.length - needle.length; i += 1) {
13
- for (let j = 0; j < needle.length; j += 1)
14
- if (haystack[i + j] !== needle[j])
15
- continue outer;
16
- return i;
10
+ function byteLines(bytes) {
11
+ const lines = [];
12
+ for (let at = 0; at < bytes.length;) {
13
+ let end = at;
14
+ while (end < bytes.length && bytes[end] !== 13 && bytes[end] !== 10)
15
+ end++;
16
+ const after = end === bytes.length ? end : end + (bytes[end] === 13 && bytes[end + 1] === 10 ? 2 : 1);
17
+ lines.push({ at, end, after });
18
+ at = after;
17
19
  }
18
- return -1;
20
+ return lines;
19
21
  }
20
- /** The parts of a body delimited by `boundary`, in order; a malformed tail ends the reading. */
21
- export function multipartParts(bytes, boundary) {
22
- const encoder = new TextEncoder();
22
+ /** Used both by the byte reader and by consumers guarding placeholder spans within a part. */
23
+ export function multipartDelimiterPattern(boundary) {
24
+ const literal = boundary.replace(/[.*+?^$(){}|[\]\\]/g, '\\$&');
25
+ // Include malformed whitespace-before-close lines in the guard: they cannot be hidden in a value either.
26
+ return '(?:^|(?<=[\\r\\n]))--' + literal + '(?:--|[ \\t]+--)?[ \\t]*(?=\\r\\n|\\n|\\r|$)';
27
+ }
28
+ /** Infer only a boundary with a corresponding closing line; Content-Type remains authoritative when supplied. */
29
+ export function inferMultipartBoundary(bytes) {
23
30
  const decoder = new TextDecoder();
24
- const delimiter = encoder.encode(`--${boundary}`);
25
- const blank = encoder.encode('\r\n\r\n');
26
- const parts = [];
27
- let at = indexOf(bytes, delimiter, 0);
28
- while (at >= 0) {
29
- const start = at + delimiter.length;
30
- // `--` after the delimiter closes the body
31
- if (bytes[start] === 0x2d && bytes[start + 1] === 0x2d)
32
- break;
33
- const headerEnd = indexOf(bytes, blank, start);
34
- if (headerEnd < 0)
35
- break;
36
- const next = indexOf(bytes, delimiter, headerEnd + 4);
37
- if (next < 0)
31
+ const lines = byteLines(bytes).map(({ at, end }) => decoder.decode(bytes.subarray(at, end)));
32
+ for (const line of lines) {
33
+ if (!line.startsWith('--'))
34
+ continue;
35
+ const boundary = line.slice(2).replace(/[ \t]+$/, '');
36
+ if (boundary && lines.some((closing) => closing.replace(/[ \t]+$/, '') === '--' + boundary + '--'))
37
+ return boundary;
38
+ }
39
+ return null;
40
+ }
41
+ /** Read MIME fields once, unfolding continuations for interpretation while retaining raw bytes in the envelope. */
42
+ function mimeHeaders(raw) {
43
+ const fields = new Map();
44
+ let previous;
45
+ for (const line of new TextDecoder().decode(raw).split(/\r\n|\n|\r/)) {
46
+ if (!line)
47
+ continue;
48
+ if (/^[ \t]/.test(line)) {
49
+ if (!previous)
50
+ return undefined;
51
+ fields.set(previous, fields.get(previous) + ' ' + line.trim());
52
+ }
53
+ else {
54
+ const field = /^([^\s:]+):[ \t]*(.*)$/.exec(line);
55
+ if (!field)
56
+ return undefined;
57
+ previous = field[1].toLowerCase();
58
+ fields.set(previous, field[2].trim());
59
+ }
60
+ }
61
+ return fields;
62
+ }
63
+ /** Raw MIME framing and interpreted parts. A malformed tail ends reading and leaves `complete` false. */
64
+ export function multipartEnvelope(bytes, boundary) {
65
+ const empty = new Uint8Array();
66
+ const result = { complete: false, preamble: bytes.slice(), epilogue: empty, delimiters: [], parts: [] };
67
+ const lines = byteLines(bytes);
68
+ const decoder = new TextDecoder();
69
+ const prefix = '--' + boundary;
70
+ const hits = [];
71
+ for (const line of lines) {
72
+ const text = decoder.decode(bytes.subarray(line.at, line.end));
73
+ if (!text.startsWith(prefix))
74
+ continue;
75
+ const suffix = text.slice(prefix.length);
76
+ const closing = /^--[ \t]*$/.test(suffix);
77
+ const valid = closing || /^[ \t]*$/.test(suffix);
78
+ if (!valid && !/^[ \t]+--[ \t]*$/.test(suffix))
79
+ continue;
80
+ hits.push({ ...line, closing, valid });
81
+ if (closing || !valid)
38
82
  break;
39
- const head = decoder.decode(bytes.subarray(start, headerEnd));
40
- const disposition = /content-disposition:[^\r\n]*/i.exec(head)?.[0] ?? '';
41
- parts.push({
83
+ }
84
+ if (!hits.length)
85
+ return result;
86
+ result.preamble = bytes.slice(0, hits[0].at);
87
+ result.delimiters = hits.map(({ at, end }) => bytes.slice(at, end));
88
+ for (let i = 0; i < hits.length - 1; i++) {
89
+ const current = hits[i];
90
+ const next = hits[i + 1];
91
+ if (!current.valid || current.closing || current.after === current.end || !next.valid)
92
+ return result;
93
+ const headerLines = lines.filter((line) => line.at >= current.after && line.at < next.at);
94
+ const blank = headerLines.find((line) => line.at === line.end);
95
+ if (!blank)
96
+ return result;
97
+ const previous = headerLines.find((line) => line.after === blank.at);
98
+ const headersEnd = previous?.end ?? blank.at;
99
+ const headers = bytes.slice(current.after, headersEnd);
100
+ const fields = mimeHeaders(headers);
101
+ if (!fields)
102
+ return result;
103
+ const beforeNext = lines.find((line) => line.after === next.at);
104
+ // The line break immediately before a delimiter belongs to framing, never to the uploaded bytes.
105
+ const bodyEnd = Math.max(blank.after, beforeNext?.end ?? next.at);
106
+ const disposition = fields.get('content-disposition') ?? '';
107
+ result.parts.push({
42
108
  name: /\bname="([^"]*)"/i.exec(disposition)?.[1] ?? '',
43
109
  filename: /\bfilename="([^"]*)"/i.exec(disposition)?.[1] ?? null,
44
- type: /content-type:\s*([^\r\n]+)/i.exec(head)?.[1]?.trim() ?? null,
45
- // the CRLF before the next delimiter is the delimiter's, not the part's
46
- body: bytes.slice(headerEnd + 4, next - 2),
110
+ type: fields.get('content-type')?.trim() ?? null,
111
+ body: bytes.slice(blank.after, bodyEnd), headers,
112
+ leading: bytes.slice(current.end, current.after), separator: bytes.slice(headersEnd, blank.after),
113
+ trailing: bytes.slice(bodyEnd, next.at),
47
114
  });
48
- at = next;
49
115
  }
50
- return parts;
116
+ const last = hits.at(-1);
117
+ result.complete = last.valid && last.closing;
118
+ if (result.complete)
119
+ result.epilogue = bytes.slice(last.end);
120
+ return result;
121
+ }
122
+ /** The request's parts, in order, using the same framing reader as wire comparison. */
123
+ export function multipartParts(bytes, boundary) {
124
+ return multipartEnvelope(bytes, boundary).parts.map(({ name, filename, type, body }) => ({ name, filename, type, body }));
51
125
  }
@@ -35,6 +35,9 @@ export type Completed = string | {
35
35
  type: string;
36
36
  field: string;
37
37
  within: string[];
38
+ } | {
39
+ type: string;
40
+ ids: string[];
38
41
  };
39
42
  /** Fold a collected batch: one `observeResources` per service, `complete` applied to each. */
40
43
  export declare function foldObservations(observations: Observation[], opts?: {
@@ -147,11 +147,15 @@ function foldResources(service, resources, opts) {
147
147
  }
148
148
  for (const c of opts.complete ?? []) {
149
149
  const type = typeof c === 'string' ? c : c.type;
150
- const within = typeof c === 'string' ? undefined : new Set(c.within);
150
+ const within = typeof c === 'string' || !('within' in c) ? undefined : new Set(c.within);
151
+ // subjects read one by one (a refresh of the known): only those read are whole, an unseen one is gone
152
+ const ids = typeof c !== 'string' && 'ids' in c ? new Set(c.ids) : undefined;
151
153
  for (const s of tree.values()) {
152
154
  if (s.type !== type || seen.has(`${s.type}:${s.id}`) || s.fields.deleted === true)
153
155
  continue;
154
- if (within && typeof c !== 'string' && !within.has(String(s.fields[c.field])))
156
+ if (within && typeof c !== 'string' && 'field' in c && !within.has(String(s.fields[c.field])))
157
+ continue;
158
+ if (ids && !ids.has(s.id))
155
159
  continue;
156
160
  if (append({ type: s.type, id: s.id }, { deleted: true }))
157
161
  report.removed += 1;
@@ -9,6 +9,7 @@ export type WireTurn = {
9
9
  arguments: Row;
10
10
  }>;
11
11
  reasoning?: string | null;
12
+ reasoningDetails?: Row[];
12
13
  finishReason?: string;
13
14
  };
14
15
  /** Tokens, estimated at about four characters a token: a twin's count, not a tokenizer's. */
@@ -51,6 +52,7 @@ export declare function chatCompletion(s: ChatShape): Row;
51
52
  * vendor sends first (OpenRouter's `: OPENROUTER PROCESSING`). */
52
53
  export declare function chatCompletionSSE(s: ChatShape & {
53
54
  comment?: string;
55
+ separateUsageChunk?: boolean;
54
56
  }): string;
55
57
  /** A V4 call's options as the chat completions request they mean: its prompt as messages, its tools, tool choice,
56
58
  * response format and output limit; so the chat stub and the wire's scenario read it. */
@@ -79,6 +81,8 @@ export type ResponsesShape = {
79
81
  /** ids for the output items: the message, the reasoning, each function call and its call id */
80
82
  itemId: (kind: 'msg' | 'rs' | 'fc' | 'call', i: number) => string;
81
83
  extra?: Row;
84
+ /** Vendor defaults; explicit request values win, including nullable gateway defaults. */
85
+ defaults?: Row;
82
86
  };
83
87
  /** A Responses object (`object: response`, `status: completed`): its output items (the reasoning when there is some,
84
88
  * the message with its output text, or function calls) and its usage. */
@@ -105,4 +109,16 @@ export declare function wireRequest(body: Row): WireRequest;
105
109
  * operation whose request is another wire's (a gateway's Anthropic Messages surface: anthropicWire's
106
110
  * `messagesWireRequest`), so one scenario scripts every surface's turns alike. */
107
111
  export declare function scenario(vendor: string, operations: string[], faultBody: (status: number, message: string | undefined) => unknown, readers?: Record<string, (body: Row) => WireRequest>): PackScenario<WireRequest>;
112
+ /** An admitted generation that fails: partial JSON or actual Chat Completions error frames.
113
+ * The pack supplies its error envelope/type; the shared wire owns the SDK framing. */
114
+ export declare function chatCompletionFailure(s: {
115
+ id: string;
116
+ model: string;
117
+ created: number;
118
+ error: Row;
119
+ text?: string;
120
+ extra?: Row;
121
+ }, stream: boolean): Row | string;
122
+ /** Responses generation failures keep the response's canonical fields and native error code. */
123
+ export declare function responsesFailure(response: Row, stream: boolean, event?: string): Row | string;
108
124
  export {};
@@ -58,7 +58,7 @@ function chatParts(s) {
58
58
  export function chatCompletion(s) {
59
59
  const p = chatParts(s);
60
60
  const field = s.reasoningField ?? 'reasoning';
61
- const message = { role: 'assistant', content: p.text, refusal: null, ...(p.reasoning ? { [field]: p.reasoning } : {}), ...(p.calls.length ? { tool_calls: p.calls } : {}) };
61
+ const message = { role: 'assistant', content: p.text, refusal: null, ...(p.reasoning ? { [field]: p.reasoning } : {}), ...(s.turn.reasoningDetails ? { reasoning_details: s.turn.reasoningDetails } : {}), ...(p.calls.length ? { tool_calls: p.calls } : {}) };
62
62
  return {
63
63
  id: s.id, ...(s.extra ?? {}), model: s.model, object: 'chat.completion', created: s.created,
64
64
  choices: [{ index: 0, message, logprobs: null, finish_reason: p.finish, ...(s.nativeFinish ? { native_finish_reason: p.finish } : {}) }], usage: p.usage,
@@ -70,18 +70,19 @@ export function chatCompletion(s) {
70
70
  export function chatCompletionSSE(s) {
71
71
  const p = chatParts(s);
72
72
  const field = s.reasoningField ?? 'reasoning';
73
- const chunk = (delta, last) => `data: ${JSON.stringify({
73
+ const chunk = (delta, last, usage = last) => `data: ${JSON.stringify({
74
74
  id: s.id, ...((last ? s.extra : s.interimExtra ?? s.extra) ?? {}), model: s.model, object: 'chat.completion.chunk', created: s.created,
75
75
  choices: [{ index: 0, delta, logprobs: null, finish_reason: last ? p.finish : null, ...(s.nativeFinish ? { native_finish_reason: last ? p.finish : null } : {}) }],
76
- ...(last ? { usage: p.usage } : {}),
76
+ ...(usage ? { usage: p.usage } : {}),
77
77
  })}\n\n`;
78
78
  const half = p.text ? Math.ceil(p.text.length / 2) : 0;
79
79
  return [
80
80
  ...(s.comment ? [`: ${s.comment}\n\n`] : []),
81
- ...(p.reasoning ? [chunk({ role: 'assistant', content: '', [field]: p.reasoning }, false)] : []),
81
+ ...(p.reasoning || s.turn.reasoningDetails ? [chunk({ role: 'assistant', content: '', ...(p.reasoning ? { [field]: p.reasoning } : {}), ...(s.turn.reasoningDetails ? { reasoning_details: s.turn.reasoningDetails } : {}) }, false)] : []),
82
82
  ...(p.text ? [chunk({ role: 'assistant', content: p.text.slice(0, half) }, false), ...(p.text.length > half ? [chunk({ content: p.text.slice(half) }, false)] : [])] : []),
83
83
  ...(p.calls.length ? [chunk({ role: 'assistant', content: null, tool_calls: p.calls.map((c, i) => ({ index: i, ...c })) }, false)] : []),
84
- chunk({}, true),
84
+ chunk(s.separateUsageChunk ? { role: 'assistant', content: '' } : {}, true, !s.separateUsageChunk),
85
+ ...(s.separateUsageChunk ? [chunk({ role: 'assistant', content: '' }, true, true)] : []),
85
86
  'data: [DONE]\n\n',
86
87
  ].join('');
87
88
  }
@@ -195,6 +196,7 @@ export function responsesStubTurn(body, label) {
195
196
  * the message with its output text, or function calls) and its usage. */
196
197
  export function responsesAnswer(s) {
197
198
  const b = s.body;
199
+ const defaultOf = (field, fallback) => s.defaults && Object.hasOwn(s.defaults, field) ? s.defaults[field] : fallback;
198
200
  let text = s.turn.text ?? null;
199
201
  const max = typeof b.max_output_tokens === 'number' ? b.max_output_tokens : undefined;
200
202
  let incomplete = null;
@@ -214,10 +216,10 @@ export function responsesAnswer(s) {
214
216
  id: s.id, object: 'response', created_at: s.createdAt, status: incomplete ? 'incomplete' : 'completed', ...(s.extra ?? {}),
215
217
  error: null, incomplete_details: incomplete, instructions: b.instructions ?? null, max_output_tokens: max ?? null, model: s.model, output,
216
218
  parallel_tool_calls: b.parallel_tool_calls ?? true, previous_response_id: b.previous_response_id ?? null,
217
- reasoning: { effort: obj(b.reasoning).effort ?? null, summary: obj(b.reasoning).summary ?? null }, store: b.store ?? true,
218
- temperature: b.temperature ?? 1, text: b.text ?? { format: { type: 'text' } }, tool_choice: b.tool_choice ?? 'auto', tools: b.tools ?? [], top_p: b.top_p ?? 1,
219
+ reasoning: { effort: obj(b.reasoning).effort ?? null, summary: obj(b.reasoning).summary ?? null }, store: b.store ?? defaultOf('store', true),
220
+ temperature: b.temperature ?? defaultOf('temperature', 1), text: b.text ?? { format: { type: 'text' } }, tool_choice: b.tool_choice ?? 'auto', tools: b.tools ?? [], top_p: b.top_p ?? defaultOf('top_p', 1),
219
221
  usage: { input_tokens: input, input_tokens_details: { cached_tokens: 0 }, output_tokens: out + reasoningTokens, output_tokens_details: { reasoning_tokens: reasoningTokens }, total_tokens: input + out + reasoningTokens },
220
- metadata: b.metadata ?? {},
222
+ metadata: b.metadata ?? defaultOf('metadata', {}),
221
223
  };
222
224
  }
223
225
  /** A Responses object streamed: the events the wire sends (`response.created`, `response.in_progress`, each output
@@ -243,7 +245,14 @@ export function responsesSSE(s) {
243
245
  events.push({ type: 'response.function_call_arguments.done', item_id: item.id, output_index: i, arguments: item.arguments });
244
246
  }
245
247
  else {
246
- events.push({ type: 'response.output_item.added', output_index: i, item });
248
+ // announced empty, as a message item is: an SDK's accumulator appends each summary part the events below add
249
+ events.push({ type: 'response.output_item.added', output_index: i, item: { ...item, summary: [], status: 'in_progress' } });
250
+ for (const [n, summary] of arr(item.summary).entries()) {
251
+ events.push({ type: 'response.reasoning_summary_part.added', item_id: item.id, output_index: i, summary_index: n, part: { type: 'summary_text', text: '' } });
252
+ events.push({ type: 'response.reasoning_summary_text.delta', item_id: item.id, output_index: i, summary_index: n, delta: summary.text });
253
+ events.push({ type: 'response.reasoning_summary_text.done', item_id: item.id, output_index: i, summary_index: n, text: summary.text });
254
+ events.push({ type: 'response.reasoning_summary_part.done', item_id: item.id, output_index: i, summary_index: n, part: summary });
255
+ }
247
256
  }
248
257
  events.push({ type: 'response.output_item.done', output_index: i, item });
249
258
  }
@@ -254,18 +263,19 @@ export function responsesSSE(s) {
254
263
  export const forbidsTools = (choice) => choice === 'none' || obj(choice).type === 'none';
255
264
  /** The turn a scenario's decision scripts (`respond`: text, toolCalls, reasoning, finishReason), or undefined. */
256
265
  export function scenarioTurn(decision) {
257
- if (!decision || decision.kind !== 'handler')
266
+ if (!decision || decision.kind !== 'handler' || decision.fault?.kind === 'generation')
258
267
  return undefined;
259
268
  const r = obj(decision.respond);
260
269
  return {
261
270
  ...(typeof r.text === 'string' ? { text: r.text } : {}),
262
271
  ...(r.toolCalls !== undefined ? { toolCalls: (Array.isArray(r.toolCalls) ? r.toolCalls : [r.toolCalls]) } : {}),
263
272
  ...(typeof r.reasoning === 'string' ? { reasoning: r.reasoning } : {}),
273
+ ...(Array.isArray(r.reasoningDetails) ? { reasoningDetails: r.reasoningDetails } : {}),
264
274
  ...(typeof r.finishReason === 'string' ? { finishReason: r.finishReason } : {}),
265
275
  };
266
276
  }
267
277
  const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
268
- const RESPOND_KEYS = new Set(['text', 'toolCalls', 'reasoning', 'finishReason']);
278
+ const RESPOND_KEYS = new Set(['text', 'toolCalls', 'reasoning', 'reasoningDetails', 'finishReason']);
269
279
  /** A request as the scenario reads it: its model, its text, its last user text, the tools it offers and the tool
270
280
  * results it ends with, from a chat completion's messages or a Responses input. */
271
281
  export function wireRequest(body) {
@@ -306,13 +316,15 @@ export function scenario(vendor, operations, faultBody, readers = {}) {
306
316
  },
307
317
  validateRespond: (respond) => {
308
318
  if (typeof respond !== 'object' || respond === null || Array.isArray(respond))
309
- return 'respond is an object { text?, toolCalls?, reasoning?, finishReason? }';
319
+ return 'respond is an object { text?, toolCalls?, reasoning?, reasoningDetails?, finishReason? }';
310
320
  const r = respond;
311
321
  for (const k of Object.keys(r))
312
322
  if (!RESPOND_KEYS.has(k))
313
323
  return `respond: unknown key "${k}" (valid: ${[...RESPOND_KEYS].join(', ')})`;
314
324
  if (r.text !== undefined && typeof r.text !== 'string')
315
325
  return 'respond.text is a string';
326
+ if (r.reasoningDetails !== undefined && (!Array.isArray(r.reasoningDetails) || r.reasoningDetails.some((d) => !d || typeof d !== 'object' || Array.isArray(d))))
327
+ return 'respond.reasoningDetails is an array of JSON objects';
316
328
  if (r.reasoning !== undefined && typeof r.reasoning !== 'string')
317
329
  return 'respond.reasoning is a string';
318
330
  for (const c of r.toolCalls === undefined ? [] : Array.isArray(r.toolCalls) ? r.toolCalls : [r.toolCalls]) {
@@ -335,3 +347,22 @@ export function scenario(vendor, operations, faultBody, readers = {}) {
335
347
  };
336
348
  return { adapter, operations, request };
337
349
  }
350
+ /** An admitted generation that fails: partial JSON or actual Chat Completions error frames.
351
+ * The pack supplies its error envelope/type; the shared wire owns the SDK framing. */
352
+ export function chatCompletionFailure(s, stream) {
353
+ const common = { id: s.id, model: s.model, created: s.created, ...s.extra };
354
+ if (!stream)
355
+ return { ...common, object: 'chat.completion', choices: [{ index: 0, message: { role: 'assistant', content: s.text ?? '' }, finish_reason: 'error', error: s.error }] };
356
+ const frames = [];
357
+ if (s.text)
358
+ frames.push({ ...common, object: 'chat.completion.chunk', choices: [{ index: 0, delta: { role: 'assistant', content: s.text }, finish_reason: null }] });
359
+ frames.push({ ...common, object: 'chat.completion.chunk', choices: [{ index: 0, delta: { content: '' }, finish_reason: 'error' }], error: s.error });
360
+ return frames.map((f) => `data: ${JSON.stringify(f)}\n\n`).join('') + 'data: [DONE]\n\n';
361
+ }
362
+ /** Responses generation failures keep the response's canonical fields and native error code. */
363
+ export function responsesFailure(response, stream, event = 'response.failed') {
364
+ if (!stream)
365
+ return response;
366
+ const data = event === 'response.failed' ? { type: event, response } : { type: event, error: response.error };
367
+ return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
368
+ }
@@ -1,8 +1,9 @@
1
1
  import { type DerivedManifest, type Handler, type HandlerContext, type VendorManifest } from './derived-core.js';
2
- import { type DerivedFetch, type DerivedOwner, type DerivedSurface, type Workspace, type BoardFrame } from './derived.js';
2
+ import { type DerivedFetch, type DerivedSurface, type BoardFrame } from './derived.js';
3
3
  import { type PackScenarioAdapter } from './scenario.js';
4
4
  import type { ApplicationStandIn } from './events.js';
5
5
  import { type SocketEngine } from './sockets.js';
6
+ import type { WebSocketUpgrade } from './serve-http.js';
6
7
  import { type GraphqlPart } from './graphql-wire.js';
7
8
  /** What a pack declares and writes, and nothing it assembles itself. */
8
9
  export type PackParts = {
@@ -59,6 +60,7 @@ export type LanePart = {
59
60
  draws?: (url: string) => boolean;
60
61
  /** the hosts the lane's built content screens name: its vendor's router sends `<base>/@<host>/` there */
61
62
  contentHosts?: ReadonlyArray<string>;
63
+ upgrade?: WebSocketUpgrade;
62
64
  };
63
65
  manifest: {
64
66
  doors?: ReadonlyArray<{
@@ -79,9 +81,5 @@ export type PackFetchOptions = {
79
81
  /** the application a walk stands in for (a life's `application` answers): this fetch's `ctx.ask` is answered by it */
80
82
  application?: ApplicationStandIn;
81
83
  };
82
- /** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
83
84
  export declare function createPackFetch(parts: PackParts, options?: PackFetchOptions): DerivedFetch;
84
- export declare function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): ((request: Request) => Promise<Response>) & {
85
- owners: () => Record<string, DerivedOwner>;
86
- workspaces: () => Workspace[];
87
- };
85
+ export declare function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): DerivedFetch;