@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.
- package/app-route.cjs +26 -25
- package/app-route.d.cts +1 -0
- package/dist/app-route.cjs +26 -25
- package/dist/app-route.d.cts +1 -0
- package/dist/generated/pack-facts.json +409 -352
- package/dist/inject.cjs +474 -95
- package/dist/network-policy.cjs +108 -13
- package/dist/network-policy.d.cts +8 -3
- package/dist/src/actions.js +2 -1
- package/dist/src/anthropic-wire.d.ts +2 -0
- package/dist/src/anthropic-wire.js +5 -0
- package/dist/src/attestation.d.ts +6 -0
- package/dist/src/attestation.js +27 -0
- package/dist/src/bytes.d.ts +3 -0
- package/dist/src/bytes.js +17 -0
- package/dist/src/changeset.js +2 -1
- package/dist/src/clickhouse/sql.js +73 -14
- package/dist/src/derived-core.d.ts +210 -32
- package/dist/src/derived-core.js +510 -244
- package/dist/src/derived-real.d.ts +1 -1
- package/dist/src/derived-real.js +704 -93
- package/dist/src/derived.d.ts +18 -2
- package/dist/src/derived.js +21 -2
- package/dist/src/events.d.ts +28 -45
- package/dist/src/events.js +40 -58
- package/dist/src/exact-json.d.ts +10 -0
- package/dist/src/exact-json.js +98 -0
- package/dist/src/executor.d.ts +17 -0
- package/dist/src/executor.js +91 -30
- package/dist/src/file-response.js +1 -1
- package/dist/src/form.d.ts +8 -0
- package/dist/src/form.js +76 -0
- package/dist/src/git/format.d.ts +16 -0
- package/dist/src/git/format.js +110 -0
- package/dist/src/git/index.d.ts +1 -0
- package/dist/src/git/index.js +1 -0
- package/dist/src/git/lfs.d.ts +1 -1
- package/dist/src/git/lfs.js +4 -3
- package/dist/src/graphql-wire.d.ts +6 -0
- package/dist/src/graphql-wire.js +6 -3
- package/dist/src/grpc-wire.js +2 -1
- package/dist/src/head.d.ts +1 -0
- package/dist/src/head.js +15 -4
- package/dist/src/held-reads.d.ts +6 -0
- package/dist/src/held-reads.js +60 -0
- package/dist/src/history.js +5 -4
- package/dist/src/host-port.d.ts +5 -0
- package/dist/src/host-port.js +14 -0
- package/dist/src/index.d.ts +11 -3
- package/dist/src/index.js +9 -4
- package/dist/src/log.js +12 -7
- package/dist/src/machines.d.ts +81 -5
- package/dist/src/machines.js +125 -9
- package/dist/src/managed-database.d.ts +15 -11
- package/dist/src/managed-database.js +38 -37
- package/dist/src/multipart.d.ts +21 -3
- package/dist/src/multipart.js +112 -38
- package/dist/src/observe.d.ts +3 -0
- package/dist/src/observe.js +6 -2
- package/dist/src/openai-wire.d.ts +16 -0
- package/dist/src/openai-wire.js +43 -12
- package/dist/src/pack-fetch.d.ts +4 -6
- package/dist/src/pack-fetch.js +189 -57
- package/dist/src/packRegistry.d.ts +7 -1
- package/dist/src/packRegistry.js +20 -3
- package/dist/src/private-transport.d.ts +1 -0
- package/dist/src/private-transport.js +11 -0
- package/dist/src/protobuf.js +10 -4
- package/dist/src/rateBudget.d.ts +8 -1
- package/dist/src/rateBudget.js +1 -0
- package/dist/src/redis/engine.d.ts +14 -5
- package/dist/src/redis/engine.js +271 -72
- package/dist/src/redis/frames.d.ts +17 -0
- package/dist/src/redis/frames.js +123 -0
- package/dist/src/redis/index.d.ts +6 -0
- package/dist/src/redis/index.js +6 -0
- package/dist/src/redis/json.d.ts +2 -0
- package/dist/src/redis/json.js +14 -0
- package/dist/src/redis/lua-libs.d.ts +4 -0
- package/dist/src/redis/lua-libs.js +173 -0
- package/dist/src/redis/lua.d.ts +1 -0
- package/dist/src/redis/lua.js +36 -0
- package/dist/src/redis/member-storage.d.ts +5 -0
- package/dist/src/redis/member-storage.js +75 -0
- package/dist/src/redis/resp2.d.ts +4 -0
- package/dist/src/redis/resp2.js +29 -0
- package/dist/src/redis/stream.d.ts +14 -0
- package/dist/src/redis/stream.js +335 -0
- package/dist/src/remote-execute.d.ts +2 -2
- package/dist/src/request-body.d.ts +11 -0
- package/dist/src/request-body.js +11 -0
- package/dist/src/runtime.d.ts +6 -5
- package/dist/src/runtime.js +5 -5
- package/dist/src/s3/wire.d.ts +4 -4
- package/dist/src/s3/wire.js +5 -3
- package/dist/src/scenario.d.ts +22 -0
- package/dist/src/scenario.js +35 -31
- package/dist/src/serve-http.d.ts +2 -1
- package/dist/src/serve-http.js +10 -1
- package/dist/src/serve.js +7 -4
- package/dist/src/signing.d.ts +23 -5
- package/dist/src/signing.js +74 -11
- package/dist/src/sigv4.d.ts +3 -1
- package/dist/src/sigv4.js +4 -2
- package/dist/src/smtp.js +2 -1
- package/dist/src/sockets.js +8 -3
- package/dist/src/state-system.d.ts +3 -0
- package/dist/src/storage.js +5 -2
- package/dist/src/twin-fetch.d.ts +3 -1
- package/dist/src/twin-fetch.js +3 -2
- package/dist/src/vendor-call.d.ts +10 -3
- package/dist/src/vendor-call.js +58 -14
- package/dist/test-fixtures/attestation.SOURCE.md +1 -0
- package/dist/test-fixtures/attestation.json +50 -0
- package/generated/pack-facts.json +409 -352
- package/inject.cjs +474 -95
- package/network-policy.cjs +108 -13
- package/network-policy.d.cts +8 -3
- package/package.json +11 -4
- package/src/actions.ts +2 -1
- package/src/anthropic-wire.ts +6 -0
- package/src/attestation.ts +23 -0
- package/src/bytes.ts +21 -0
- package/src/changeset.ts +2 -1
- package/src/clickhouse/sql.ts +50 -16
- package/src/derived-core.ts +573 -249
- package/src/derived-real.ts +552 -42
- package/src/derived.ts +23 -4
- package/src/events.ts +54 -85
- package/src/exact-json.ts +71 -0
- package/src/executor.ts +86 -27
- package/src/file-response.ts +1 -1
- package/src/form.ts +66 -0
- package/src/git/format.ts +89 -0
- package/src/git/index.ts +1 -0
- package/src/git/lfs.ts +5 -4
- package/src/graphql-wire.ts +8 -3
- package/src/grpc-wire.ts +2 -1
- package/src/head.ts +15 -5
- package/src/held-reads.ts +54 -0
- package/src/history.ts +5 -4
- package/src/host-port.ts +9 -0
- package/src/index.ts +10 -4
- package/src/log.ts +11 -7
- package/src/machines.ts +135 -13
- package/src/managed-database.ts +48 -40
- package/src/multipart.ts +110 -35
- package/src/observe.ts +6 -3
- package/src/openai-wire.ts +45 -14
- package/src/pack-fetch.ts +173 -50
- package/src/packRegistry.ts +27 -4
- package/src/private-transport.ts +8 -0
- package/src/protobuf.ts +8 -4
- package/src/rateBudget.ts +6 -1
- package/src/redis/engine.ts +168 -63
- package/src/redis/frames.ts +53 -0
- package/src/redis/index.ts +6 -0
- package/src/redis/json.ts +13 -0
- package/src/redis/lua-libs.ts +62 -0
- package/src/redis/lua.ts +20 -0
- package/src/redis/member-storage.ts +58 -0
- package/src/redis/resp2.ts +25 -0
- package/src/redis/stream.ts +102 -0
- package/src/remote-execute.ts +2 -2
- package/src/request-body.ts +12 -0
- package/src/runtime.ts +6 -5
- package/src/s3/wire.ts +8 -5
- package/src/scenario.ts +34 -13
- package/src/serve-http.ts +11 -2
- package/src/serve.ts +4 -3
- package/src/signing.ts +71 -17
- package/src/sigv4.ts +5 -3
- package/src/smtp.ts +3 -2
- package/src/sockets.ts +8 -3
- package/src/state-system.ts +3 -0
- package/src/storage.ts +5 -2
- package/src/twin-fetch.ts +4 -3
- package/src/vendor-call.ts +59 -12
- 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)
|
|
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
|
-
|
|
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 = () => {
|
package/dist/src/multipart.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
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[];
|
package/dist/src/multipart.js
CHANGED
|
@@ -1,51 +1,125 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
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
|
|
5
|
+
if (!contentType || !/^multipart\//i.test(contentType))
|
|
7
6
|
return null;
|
|
8
|
-
const hit =
|
|
7
|
+
const hit = /\bboundary\s*=\s*(?:"([^"]+)"|([^;\s]+))/i.exec(contentType);
|
|
9
8
|
return hit ? (hit[1] ?? hit[2]) : null;
|
|
10
9
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
20
|
+
return lines;
|
|
19
21
|
}
|
|
20
|
-
/**
|
|
21
|
-
export function
|
|
22
|
-
const
|
|
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
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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:
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/src/observe.d.ts
CHANGED
|
@@ -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?: {
|
package/dist/src/observe.js
CHANGED
|
@@ -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 {};
|
package/dist/src/openai-wire.js
CHANGED
|
@@ -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
|
-
...(
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/src/pack-fetch.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type DerivedManifest, type Handler, type HandlerContext, type VendorManifest } from './derived-core.js';
|
|
2
|
-
import { type DerivedFetch, type
|
|
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>):
|
|
85
|
-
owners: () => Record<string, DerivedOwner>;
|
|
86
|
-
workspaces: () => Workspace[];
|
|
87
|
-
};
|
|
85
|
+
export declare function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): DerivedFetch;
|