@volter/world-core 2.0.36 → 3.0.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.
- package/README.md +4 -5
- package/app-route.cjs +12 -6
- package/app-route.d.cts +1 -1
- package/dist/app-route.cjs +12 -6
- package/dist/app-route.d.cts +1 -1
- package/dist/generated/pack-facts.json +1410 -3069
- package/dist/inject.cjs +64 -9
- package/dist/pack-facts.cjs +44 -0
- package/dist/src/actions.d.ts +3 -3
- package/dist/src/actions.js +22 -16
- package/dist/src/ancestry.d.ts +14 -2
- package/dist/src/ancestry.js +92 -2
- package/dist/src/anthropic-wire.d.ts +39 -0
- package/dist/src/anthropic-wire.js +136 -0
- package/dist/src/bytes.d.ts +7 -0
- package/dist/src/bytes.js +35 -0
- package/dist/src/changeset.d.ts +1 -1
- package/dist/src/changeset.js +0 -0
- package/dist/src/clickhouse/index.d.ts +3 -0
- package/dist/src/clickhouse/index.js +6 -0
- package/dist/src/clickhouse/sql.d.ts +233 -0
- package/dist/src/clickhouse/sql.js +4329 -0
- package/dist/src/clickhouse/types.d.ts +18 -0
- package/dist/src/clickhouse/types.js +47 -0
- package/dist/src/clickhouse/values.d.ts +146 -0
- package/dist/src/clickhouse/values.js +858 -0
- package/dist/src/client-bundle.js +2 -3
- package/dist/src/cors.d.ts +15 -0
- package/dist/src/cors.js +31 -0
- package/dist/src/derived-core.d.ts +487 -24
- package/dist/src/derived-core.js +788 -144
- package/dist/src/derived-real.d.ts +13 -0
- package/dist/src/derived-real.js +518 -0
- package/dist/src/derived.d.ts +35 -1
- package/dist/src/derived.js +61 -9
- package/dist/src/emit.js +1 -2
- package/dist/src/events.d.ts +206 -0
- package/dist/src/events.js +341 -0
- package/dist/src/executor.d.ts +3 -0
- package/dist/src/executor.js +19 -2
- package/dist/src/file-response.d.ts +6 -0
- package/dist/src/file-response.js +30 -0
- package/dist/src/fork.js +3 -2
- package/dist/src/git/history.d.ts +7 -0
- package/dist/src/git/history.js +24 -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 +28 -0
- package/dist/src/git/lfs.js +66 -0
- package/dist/src/git/objects.js +3 -8
- package/dist/src/git/smart-http.d.ts +3 -1
- package/dist/src/git/smart-http.js +67 -6
- package/dist/src/graphql-wire.d.ts +29 -0
- package/dist/src/graphql-wire.js +101 -0
- package/dist/src/grpc-wire.d.ts +67 -0
- package/dist/src/grpc-wire.js +170 -0
- package/dist/src/h2.d.ts +40 -0
- package/dist/src/h2.js +656 -0
- package/dist/src/head.d.ts +32 -3
- package/dist/src/head.js +161 -40
- package/dist/src/history.d.ts +1 -1
- package/dist/src/history.js +6 -6
- package/dist/src/hpack.json +1 -0
- package/dist/src/index.d.ts +64 -75
- package/dist/src/index.js +58 -101
- package/dist/src/log.js +28 -19
- package/dist/src/machines.d.ts +50 -0
- package/dist/src/machines.js +151 -0
- package/dist/src/managed-database.d.ts +86 -0
- package/dist/src/managed-database.js +283 -0
- package/dist/src/multipart.d.ts +11 -0
- package/dist/src/multipart.js +51 -0
- package/dist/src/observe.d.ts +15 -5
- package/dist/src/observe.js +23 -9
- package/dist/src/openai-wire.d.ts +108 -0
- package/dist/src/openai-wire.js +337 -0
- package/dist/src/pack-assets.d.ts +3 -4
- package/dist/src/pack-assets.js +15 -10
- package/dist/src/pack-fetch.d.ts +77 -0
- package/dist/src/pack-fetch.js +449 -0
- package/dist/src/pack-paths.d.ts +12 -0
- package/dist/src/pack-paths.js +86 -0
- package/dist/src/packRegistry.d.ts +69 -162
- package/dist/src/packRegistry.js +55 -20
- package/dist/src/people.d.ts +13 -0
- package/dist/src/people.js +18 -0
- package/dist/src/placeholder-image.d.ts +5 -0
- package/dist/src/placeholder-image.js +114 -0
- package/dist/src/protobuf.d.ts +28 -0
- package/dist/src/protobuf.js +332 -0
- package/dist/src/redis/engine.js +1 -1
- package/dist/src/request-scope.d.ts +1 -1
- package/dist/src/request-scope.js +6 -4
- package/dist/src/resource-blob.d.ts +5 -0
- package/dist/src/resource-blob.js +11 -0
- package/dist/src/runtime.d.ts +85 -0
- package/dist/src/runtime.js +104 -0
- package/dist/src/s3/wire.d.ts +60 -0
- package/dist/src/s3/wire.js +157 -0
- package/dist/src/scenario.d.ts +3 -0
- package/dist/src/scenario.js +2 -0
- package/dist/src/schema-sample.d.ts +1 -0
- package/dist/src/schema-sample.js +21 -0
- package/dist/src/sealed-box.d.ts +14 -0
- package/dist/src/sealed-box.js +225 -0
- package/dist/src/serve-http.d.ts +14 -0
- package/dist/src/serve-http.js +27 -3
- package/dist/src/serve.d.ts +6 -0
- package/dist/src/serve.js +69 -14
- package/dist/src/signing.d.ts +135 -0
- package/dist/src/signing.js +222 -0
- package/dist/src/sigv4.d.ts +48 -0
- package/dist/src/sigv4.js +167 -0
- package/dist/src/smtp.d.ts +16 -0
- package/dist/src/smtp.js +72 -0
- package/dist/src/sockets.d.ts +51 -0
- package/dist/src/sockets.js +90 -0
- package/dist/src/state-system.d.ts +1 -0
- package/dist/src/state-system.js +1 -1
- package/dist/src/storage.d.ts +1 -1
- package/dist/src/storage.js +3 -3
- package/dist/src/trace-context.js +1 -1
- package/dist/src/twin-fetch.d.ts +0 -7
- package/dist/src/twin-fetch.js +0 -14
- package/dist/src/vendor-call.d.ts +6 -0
- package/dist/src/vendor-call.js +41 -0
- package/dist/src/world-store.js +1 -1
- package/dist/vendor-hosts.cjs +36 -125
- package/dist/vendor-hosts.d.cts +8 -0
- package/generated/pack-facts.json +1410 -3069
- package/inject.cjs +64 -9
- package/pack-facts.cjs +44 -0
- package/package.json +17 -3
- package/src/actions.ts +23 -16
- package/src/ancestry.ts +74 -2
- package/src/anthropic-wire.ts +137 -0
- package/src/bytes.ts +42 -0
- package/src/changeset.ts +5 -5
- package/src/clickhouse/index.ts +6 -0
- package/src/clickhouse/sql.ts +3059 -0
- package/src/clickhouse/types.ts +44 -0
- package/src/clickhouse/values.ts +697 -0
- package/src/client-bundle.ts +2 -3
- package/src/cors.ts +34 -0
- package/src/derived-core.ts +1013 -146
- package/src/derived-real.ts +434 -0
- package/src/derived.ts +73 -3
- package/src/emit.ts +1 -2
- package/src/events.ts +449 -0
- package/src/executor.ts +24 -2
- package/src/file-response.ts +27 -0
- package/src/fork.ts +3 -2
- package/src/git/history.ts +19 -0
- package/src/git/index.ts +1 -0
- package/src/git/lfs.ts +67 -0
- package/src/git/objects.ts +3 -5
- package/src/git/smart-http.ts +56 -6
- package/src/graphql-wire.ts +106 -0
- package/src/grpc-wire.ts +159 -0
- package/src/h2.ts +627 -0
- package/src/head.ts +132 -41
- package/src/history.ts +6 -6
- package/src/hpack.json +1 -0
- package/src/index.ts +82 -329
- package/src/log.ts +27 -18
- package/src/machines.ts +151 -0
- package/src/managed-database.ts +299 -0
- package/src/multipart.ts +51 -0
- package/src/observe.ts +31 -15
- package/src/openai-wire.ts +371 -0
- package/src/pack-assets.ts +15 -11
- package/src/pack-fetch.ts +458 -0
- package/src/pack-paths.ts +72 -0
- package/src/packRegistry.ts +79 -167
- package/src/people.ts +31 -0
- package/src/placeholder-image.ts +88 -0
- package/src/protobuf.ts +251 -0
- package/src/redis/engine.ts +1 -1
- package/src/request-scope.ts +8 -4
- package/src/resource-blob.ts +13 -0
- package/src/runtime.ts +344 -0
- package/src/s3/wire.ts +172 -0
- package/src/scenario.ts +4 -0
- package/src/schema-sample.ts +24 -0
- package/src/sealed-box.ts +182 -0
- package/src/serve-http.ts +31 -3
- package/src/serve.ts +58 -14
- package/src/signing.ts +231 -0
- package/src/sigv4.ts +158 -0
- package/src/smtp.ts +76 -0
- package/src/sockets.ts +140 -0
- package/src/state-system.ts +2 -2
- package/src/storage.ts +3 -3
- package/src/trace-context.ts +1 -1
- package/src/twin-fetch.ts +0 -20
- package/src/vendor-call.ts +41 -0
- package/src/world-store.ts +1 -1
- package/vendor-hosts.cjs +36 -125
- package/vendor-hosts.d.cts +8 -0
- package/dist/src/mirror-shell.d.ts +0 -2
- package/dist/src/mirror-shell.js +0 -13
- package/dist/src/v1-removed.d.ts +0 -159
- package/dist/src/v1-removed.js +0 -124
- package/src/mirror-shell.ts +0 -15
- package/src/v1-removed.ts +0 -172
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// THE WORLD'S MANAGED DATABASE, as a pack reaches it (architecture.md, "The engine slot" and managed infrastructure): a
|
|
2
|
+
// vendor whose data plane is a server in front of Postgres (Supabase's PostgREST, Storage and Auth; a Neon or a
|
|
3
|
+
// PlanetScale) serves it over the World's own Postgres, whose URL the runtime hands the pack (the descriptor's
|
|
4
|
+
// `managedDatabase`: `serve --database <url>`, or the colocated host's `database` option). A protocol-3 handler reaches
|
|
5
|
+
// it as `ctx.engine`; a pack's own server in front of it (a PostgREST) through `managedDatabase(url)`. Its terms:
|
|
6
|
+
//
|
|
7
|
+
// - Nothing here reads the environment, and the tree references none of the rows it writes.
|
|
8
|
+
// - Each batch is ONE transaction sent as ONE extended-protocol pipeline ending in ONE Sync: `BEGIN`, the role
|
|
9
|
+
// (`SET LOCAL ROLE`), the transaction-local settings (`set_config(…, true)`), the statements and `COMMIT`, every value a
|
|
10
|
+
// bind parameter. The PGlite host runs a connection's messages up to its Sync under one lock and lets another
|
|
11
|
+
// connection's read into a session idle inside a transaction; a batch that never sits idle inside its transaction lets
|
|
12
|
+
// nobody in, so its role and settings reach no other connection.
|
|
13
|
+
// - The role, which Postgres takes only as SQL text, is quoted as an identifier; which roles a caller may take is the
|
|
14
|
+
// pack's rule (Supabase's three API roles), checked before a batch is made.
|
|
15
|
+
// - A batch that fails leaves its transaction aborted (the backend skips to the Sync); it is ended with `ROLLBACK` before
|
|
16
|
+
// the connection is released, and a connection that cannot be rolled back is destroyed, never reused.
|
|
17
|
+
//
|
|
18
|
+
// The Postgres driver is node-postgres (`pg`), loaded the first time a database is bound and installed by the package that
|
|
19
|
+
// declares a managed database (its package.json): the kernel's dependencies are zod and ws alone (architecture B1), and a
|
|
20
|
+
// browser bundle imports the kernel without a database. A host may register another (`useDatabaseDriver`).
|
|
21
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
22
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
23
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
24
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return path;
|
|
28
|
+
};
|
|
29
|
+
/** A Postgres error as the backend reported it (SQLSTATE and its fields), what a vendor's server passes through. */
|
|
30
|
+
export class EngineDatabaseError extends Error {
|
|
31
|
+
code;
|
|
32
|
+
detail;
|
|
33
|
+
hint;
|
|
34
|
+
constructor(message, fields) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = 'EngineDatabaseError';
|
|
37
|
+
this.code = fields.code ?? 'XX000';
|
|
38
|
+
this.detail = fields.detail ?? null;
|
|
39
|
+
this.hint = fields.hint ?? null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** The World's database cannot be reached: none is bound, or it does not answer. */
|
|
43
|
+
export class EngineUnavailableError extends Error {
|
|
44
|
+
constructor(message) { super(message); this.name = 'EngineUnavailableError'; }
|
|
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
|
+
/** Quote an identifier the way Postgres's quote_ident does for a name that needs it. */
|
|
80
|
+
export function quoteIdent(name) {
|
|
81
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
82
|
+
}
|
|
83
|
+
/** pg's Submittable (the extension point pg-cursor and pg-query-stream use): the client hands it the connection once
|
|
84
|
+
* it is its turn, it writes every statement's Parse/Bind/Describe/Execute and ONE Sync, and the client hands it each
|
|
85
|
+
* backend message until ReadyForQuery. */
|
|
86
|
+
class Pipeline {
|
|
87
|
+
statements;
|
|
88
|
+
done;
|
|
89
|
+
results = [];
|
|
90
|
+
current = null;
|
|
91
|
+
error = null;
|
|
92
|
+
settled = false;
|
|
93
|
+
constructor(statements, done) {
|
|
94
|
+
this.statements = statements;
|
|
95
|
+
this.done = done;
|
|
96
|
+
}
|
|
97
|
+
submit(connection) {
|
|
98
|
+
for (const statement of this.statements) {
|
|
99
|
+
connection.parse({ text: statement.text, types: [] });
|
|
100
|
+
connection.bind({ values: statement.values ?? [] });
|
|
101
|
+
connection.describe({ type: 'P' });
|
|
102
|
+
connection.execute({ rows: 0 });
|
|
103
|
+
}
|
|
104
|
+
connection.sync();
|
|
105
|
+
}
|
|
106
|
+
handleRowDescription(message) {
|
|
107
|
+
this.current = { fields: message.fields.map((field) => field.name), rows: [] };
|
|
108
|
+
}
|
|
109
|
+
handleDataRow(message) {
|
|
110
|
+
(this.current ??= { fields: [], rows: [] }).rows.push(message.fields);
|
|
111
|
+
}
|
|
112
|
+
handleCommandComplete(message) {
|
|
113
|
+
this.results.push({ ...(this.current ?? { fields: [], rows: [] }), command: String(message.text ?? '') });
|
|
114
|
+
this.current = null;
|
|
115
|
+
}
|
|
116
|
+
handleEmptyQuery() {
|
|
117
|
+
this.results.push({ fields: [], rows: [], command: '' });
|
|
118
|
+
}
|
|
119
|
+
handlePortalSuspended() { }
|
|
120
|
+
handleCopyInResponse(connection) { connection.sendCopyFail('COPY FROM STDIN is not served by a managed database batch'); }
|
|
121
|
+
handleCopyData() { }
|
|
122
|
+
handleError(error) {
|
|
123
|
+
// An ErrorResponse (the backend's, with a SQLSTATE) or a failed connection. pg ends the submittable's turn here:
|
|
124
|
+
// it drops it as the active query, so the ReadyForQuery the backend sends after skipping to the Sync goes to no
|
|
125
|
+
// one, and the next query this client is given (the ROLLBACK) is sent once that ReadyForQuery arrives.
|
|
126
|
+
this.error ??= error;
|
|
127
|
+
this.finish();
|
|
128
|
+
}
|
|
129
|
+
handleReadyForQuery() { this.finish(); }
|
|
130
|
+
finish() {
|
|
131
|
+
if (this.settled)
|
|
132
|
+
return;
|
|
133
|
+
this.settled = true;
|
|
134
|
+
this.done(this.error, this.results);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function asDatabaseError(error) {
|
|
138
|
+
const fields = error;
|
|
139
|
+
if (fields.severity === undefined)
|
|
140
|
+
return new EngineUnavailableError(`the World's Postgres failed: ${error.message}`);
|
|
141
|
+
return new EngineDatabaseError(error.message, fields);
|
|
142
|
+
}
|
|
143
|
+
let driver;
|
|
144
|
+
/** A Postgres driver a host registers in place of node-postgres. */
|
|
145
|
+
export function useDatabaseDriver(given) {
|
|
146
|
+
driver = given;
|
|
147
|
+
}
|
|
148
|
+
/** The driver: the registered one, else node-postgres, loaded on first use (a name the bundler does not resolve). */
|
|
149
|
+
async function loadDriver() {
|
|
150
|
+
if (driver !== undefined)
|
|
151
|
+
return driver;
|
|
152
|
+
const name = 'pg';
|
|
153
|
+
try {
|
|
154
|
+
const mod = (await import(__rewriteRelativeImportExtension(name)));
|
|
155
|
+
driver = mod.default ?? mod;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
throw new EngineUnavailableError(`no Postgres driver: node-postgres (pg) is not installed beside the pack that declares a managed database (${error.message})`);
|
|
159
|
+
}
|
|
160
|
+
return driver;
|
|
161
|
+
}
|
|
162
|
+
const pools = new Map(); // cache: one pool per bound database URL — the connections this process holds to the World's Postgres, never state
|
|
163
|
+
async function poolFor(url) {
|
|
164
|
+
let pool = pools.get(url);
|
|
165
|
+
if (pool === undefined) {
|
|
166
|
+
const { Pool } = await loadDriver();
|
|
167
|
+
// another request may have made it while the driver loaded
|
|
168
|
+
const made = pools.get(url);
|
|
169
|
+
if (made !== undefined)
|
|
170
|
+
return made;
|
|
171
|
+
// a few connections: the PGlite host serializes them anyway, and a container Postgres needs no more for a twin
|
|
172
|
+
pool = new Pool({ connectionString: url, max: 4, idleTimeoutMillis: 10_000, connectionTimeoutMillis: 10_000 });
|
|
173
|
+
// an idle connection that the database ends (a World's Postgres restarting) is dropped from the pool, not thrown
|
|
174
|
+
pool.on('error', () => { });
|
|
175
|
+
pools.set(url, pool);
|
|
176
|
+
}
|
|
177
|
+
return pool;
|
|
178
|
+
}
|
|
179
|
+
async function checkout(url) {
|
|
180
|
+
const pool = await poolFor(url);
|
|
181
|
+
try {
|
|
182
|
+
return await pool.connect();
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
throw new EngineUnavailableError(`the World's Postgres is not answering: ${error.message}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function pipeline(client, statements) {
|
|
189
|
+
return new Promise((resolve) => {
|
|
190
|
+
client.query(new Pipeline(statements, (error, results) => resolve({ error, results })));
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/** Run one transaction as one pipeline with one Sync; on failure, ROLLBACK before the connection goes back. */
|
|
194
|
+
async function transaction(url, statements, lead) {
|
|
195
|
+
const client = await checkout(url);
|
|
196
|
+
let broken;
|
|
197
|
+
try {
|
|
198
|
+
const { error, results } = await pipeline(client, statements);
|
|
199
|
+
if (error !== null) {
|
|
200
|
+
if (error.severity !== undefined) {
|
|
201
|
+
try {
|
|
202
|
+
await client.query('ROLLBACK');
|
|
203
|
+
}
|
|
204
|
+
catch (rollback) {
|
|
205
|
+
broken = rollback;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else
|
|
209
|
+
broken = error;
|
|
210
|
+
throw asDatabaseError(error);
|
|
211
|
+
}
|
|
212
|
+
// the answer is the batch's own statements: not BEGIN, the role, the settings, or its end
|
|
213
|
+
return results.slice(lead, statements.length - 1);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
client.release(broken);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function settingsStatement(settings) {
|
|
220
|
+
const names = Object.keys(settings);
|
|
221
|
+
if (names.length === 0)
|
|
222
|
+
return null;
|
|
223
|
+
const values = [];
|
|
224
|
+
const calls = names.map((name) => {
|
|
225
|
+
values.push(name, settings[name]);
|
|
226
|
+
return `set_config($${values.length - 1}, $${values.length}, true)`;
|
|
227
|
+
});
|
|
228
|
+
return { text: `SELECT ${calls.join(', ')}`, values };
|
|
229
|
+
}
|
|
230
|
+
/** 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`. */
|
|
233
|
+
export function managedDatabase(url, options = {}) {
|
|
234
|
+
return {
|
|
235
|
+
bound: true,
|
|
236
|
+
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 ?? {}) });
|
|
239
|
+
const head = [
|
|
240
|
+
{ text: batch.readOnly || options.readOnly ? 'BEGIN READ ONLY' : 'BEGIN' },
|
|
241
|
+
...(batch.role === undefined ? [] : [{ text: `SET LOCAL ROLE ${quoteIdent(batch.role)}` }]),
|
|
242
|
+
...(set === null ? [] : [set]),
|
|
243
|
+
];
|
|
244
|
+
return transaction(url, [...head, ...batch.statements, { text: batch.rollback ? 'ROLLBACK' : 'COMMIT' }], head.length);
|
|
245
|
+
},
|
|
246
|
+
async script(text) {
|
|
247
|
+
const client = await checkout(url);
|
|
248
|
+
let broken;
|
|
249
|
+
try {
|
|
250
|
+
await client.query(text);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (error.severity === undefined)
|
|
254
|
+
broken = error;
|
|
255
|
+
else {
|
|
256
|
+
try {
|
|
257
|
+
await client.query('ROLLBACK');
|
|
258
|
+
}
|
|
259
|
+
catch (rollback) {
|
|
260
|
+
broken = rollback;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
throw asDatabaseError(error);
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
client.release(broken);
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/** A handler's database when the World binds none: every use is the refusal naming what to add. */
|
|
272
|
+
export function unboundDatabase(service) {
|
|
273
|
+
const refuse = () => {
|
|
274
|
+
throw new EngineUnavailableError(`This World binds no managed Postgres to the ${service} twin, so what it serves over one is refused, not served: add a postgres service to the World's managed infrastructure (architecture: managed infrastructure)`);
|
|
275
|
+
};
|
|
276
|
+
return { bound: false, batch: async () => refuse(), script: async () => refuse() };
|
|
277
|
+
}
|
|
278
|
+
/** Close every connection this process holds (a test's teardown; a served twin's connections end with it). */
|
|
279
|
+
export async function closeManagedDatabases() {
|
|
280
|
+
const held = [...pools.values()];
|
|
281
|
+
pools.clear();
|
|
282
|
+
await Promise.all(held.map((pool) => pool.end().catch(() => { })));
|
|
283
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** One part: its field name (possibly empty), its filename when it is a file, its media type as sent, its bytes. */
|
|
2
|
+
export type MultipartPart = {
|
|
3
|
+
name: string;
|
|
4
|
+
filename: string | null;
|
|
5
|
+
type: string | null;
|
|
6
|
+
body: Uint8Array;
|
|
7
|
+
};
|
|
8
|
+
/** The boundary a `multipart/form-data` content type names, or null (not multipart, or no boundary). */
|
|
9
|
+
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. */
|
|
11
|
+
export declare function multipartParts(bytes: Uint8Array, boundary: string): MultipartPart[];
|
|
@@ -0,0 +1,51 @@
|
|
|
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). */
|
|
5
|
+
export function multipartBoundary(contentType) {
|
|
6
|
+
if (!contentType || !/^multipart\/form-data/i.test(contentType))
|
|
7
|
+
return null;
|
|
8
|
+
const hit = /boundary=(?:"([^"]+)"|([^;\s]+))/i.exec(contentType);
|
|
9
|
+
return hit ? (hit[1] ?? hit[2]) : null;
|
|
10
|
+
}
|
|
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;
|
|
17
|
+
}
|
|
18
|
+
return -1;
|
|
19
|
+
}
|
|
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();
|
|
23
|
+
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)
|
|
38
|
+
break;
|
|
39
|
+
const head = decoder.decode(bytes.subarray(start, headerEnd));
|
|
40
|
+
const disposition = /content-disposition:[^\r\n]*/i.exec(head)?.[0] ?? '';
|
|
41
|
+
parts.push({
|
|
42
|
+
name: /\bname="([^"]*)"/i.exec(disposition)?.[1] ?? '',
|
|
43
|
+
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),
|
|
47
|
+
});
|
|
48
|
+
at = next;
|
|
49
|
+
}
|
|
50
|
+
return parts;
|
|
51
|
+
}
|
package/dist/src/observe.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type SubjectFields } from './hash.js';
|
|
2
|
+
/** `adopts`: the local id of a subject a deploy settled with no vendor id, which this observation is (refresh's
|
|
3
|
+
* adoption, architecture "Refresh"): the observation lands as that subject's alias, and the local one stands. */
|
|
2
4
|
export type ObservedResource = {
|
|
3
5
|
type: string;
|
|
4
6
|
id: string;
|
|
5
7
|
fields: SubjectFields;
|
|
6
8
|
deleted?: boolean;
|
|
9
|
+
adopts?: string;
|
|
7
10
|
};
|
|
8
11
|
export type ObserveReport = {
|
|
9
12
|
observed: number;
|
|
@@ -18,7 +21,7 @@ export type Observation = {
|
|
|
18
21
|
resource: ObservedResource;
|
|
19
22
|
} | {
|
|
20
23
|
resources: ObservedResource[];
|
|
21
|
-
complete?:
|
|
24
|
+
complete?: Completed[];
|
|
22
25
|
});
|
|
23
26
|
/** Run `fn` with every `observeResource` inside it COLLECTED instead of folded; the caller folds the
|
|
24
27
|
* batch (`observeResources`) once `fn` returns. The kernel's refresh does this around an adapter. */
|
|
@@ -26,11 +29,18 @@ export declare function collectObservations<T>(fn: () => Promise<T>): Promise<{
|
|
|
26
29
|
value: T;
|
|
27
30
|
observations: Observation[];
|
|
28
31
|
}>;
|
|
32
|
+
/** A type a batch listed in full: all of it, or (a type under a parent) the subjects whose `field` names one of the
|
|
33
|
+
* parents listed `within`. */
|
|
34
|
+
export type Completed = string | {
|
|
35
|
+
type: string;
|
|
36
|
+
field: string;
|
|
37
|
+
within: string[];
|
|
38
|
+
};
|
|
29
39
|
/** Fold a collected batch: one `observeResources` per service, `complete` applied to each. */
|
|
30
40
|
export declare function foldObservations(observations: Observation[], opts?: {
|
|
31
41
|
root?: string;
|
|
32
42
|
at?: string;
|
|
33
|
-
complete?:
|
|
43
|
+
complete?: Completed[];
|
|
34
44
|
batch?: string;
|
|
35
45
|
}): ObserveReport;
|
|
36
46
|
/** One observed resource: collected when a refresh scope is open, else folded onto the root's log now. */
|
|
@@ -39,11 +49,11 @@ export declare function observeResource(service: string, resource: ObservedResou
|
|
|
39
49
|
at?: string;
|
|
40
50
|
}): ObserveReport;
|
|
41
51
|
/** The fold: diff each resource against upstream state, append what changed to the root's log. `complete`
|
|
42
|
-
* names the types this batch listed in full — a subject of such a type the
|
|
43
|
-
* tombstoned. Entry ids are content-addressed, so the same observation folds once. */
|
|
52
|
+
* names the types this batch listed in full (or in full under the parents it names) — a subject of such a type the
|
|
53
|
+
* batch does not hold is tombstoned. Entry ids are content-addressed, so the same observation folds once. */
|
|
44
54
|
export declare function observeResources(service: string, resources: ObservedResource[], opts?: {
|
|
45
55
|
root?: string;
|
|
46
56
|
at?: string;
|
|
47
|
-
complete?:
|
|
57
|
+
complete?: Completed[];
|
|
48
58
|
batch?: string;
|
|
49
59
|
}): ObserveReport;
|
package/dist/src/observe.js
CHANGED
|
@@ -7,16 +7,17 @@
|
|
|
7
7
|
// unchanged resource appends nothing; a listing the adapter declares complete tombstones what it
|
|
8
8
|
// no longer holds. A refresh runs inside a scope that collects the adapter's observations and folds
|
|
9
9
|
// them as one batch; an ingest observes directly, one resource at a time.
|
|
10
|
-
import {
|
|
10
|
+
import { withHistoryLock } from "./ancestry.js";
|
|
11
11
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
12
12
|
import { appendParentEntry, readParentTreeMap } from "./log.js";
|
|
13
13
|
import { hashFieldValue } from "./hash.js";
|
|
14
14
|
import { worldNow } from "./world-clock.js";
|
|
15
|
+
import { worldPaths } from "./storage.js";
|
|
15
16
|
// ONE sink per process, whichever copy of the kernel a pack resolved: a pack installed beside a
|
|
16
17
|
// world carries its own `@volter/world-core`, and its observations must reach the scope the runtime
|
|
17
18
|
// opened. A module-level instance would be one per copy; a process-global symbol is one per process.
|
|
18
19
|
const SINK = Symbol.for('volter.observe.sink');
|
|
19
|
-
// created on first use, never at import: a
|
|
20
|
+
// created on first use, never at import: a browser bundle carrying the kernel carries this module
|
|
20
21
|
// through the pack's shared helpers, and a browser has no AsyncLocalStorage — constructing one at load
|
|
21
22
|
// would kill the whole client ("not a constructor") for a scope the browser never opens
|
|
22
23
|
const sink = () => (globalThis[SINK] ??= new AsyncLocalStorage());
|
|
@@ -75,8 +76,8 @@ function stable(value) {
|
|
|
75
76
|
return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${stable(o[k])}`).join(',')}}`;
|
|
76
77
|
}
|
|
77
78
|
/** The fold: diff each resource against upstream state, append what changed to the root's log. `complete`
|
|
78
|
-
* names the types this batch listed in full — a subject of such a type the
|
|
79
|
-
* tombstoned. Entry ids are content-addressed, so the same observation folds once. */
|
|
79
|
+
* names the types this batch listed in full (or in full under the parents it names) — a subject of such a type the
|
|
80
|
+
* batch does not hold is tombstoned. Entry ids are content-addressed, so the same observation folds once. */
|
|
80
81
|
export function observeResources(service, resources, opts = {}) {
|
|
81
82
|
const collecting = typeof AsyncLocalStorage === 'function' ? sink().getStore() : undefined;
|
|
82
83
|
if (collecting) {
|
|
@@ -86,17 +87,17 @@ export function observeResources(service, resources, opts = {}) {
|
|
|
86
87
|
return foldResources(service, resources, opts);
|
|
87
88
|
}
|
|
88
89
|
function foldResources(service, resources, opts) {
|
|
89
|
-
return
|
|
90
|
+
return withHistoryLock(worldPaths(service, opts.root).dir, () => {
|
|
90
91
|
const at = opts.at ?? worldNow();
|
|
91
92
|
// one look is one batch: every entry it appends carries the same id, and no position may split it
|
|
92
93
|
const batch = opts.batch ?? `obs:${service}:${at}`;
|
|
93
94
|
const tree = readParentTreeMap(service, opts.root);
|
|
94
95
|
const report = { observed: resources.length, appended: 0, unchanged: 0, removed: 0 };
|
|
95
96
|
const seen = new Set();
|
|
96
|
-
const append = (subject, fields) => {
|
|
97
|
+
const append = (subject, fields, aliasOf) => {
|
|
97
98
|
const entry = {
|
|
98
|
-
id: `observed:${service}:${subject.type}:${subject.id}:${hashFieldValue({ subject, fields, at })}`,
|
|
99
|
-
service, op: 'set', operation: 'observed', subject, occurredAt: at, actor: { kind: 'system', id: 'vendor' }, fields, batch,
|
|
99
|
+
id: `observed:${service}:${subject.type}:${subject.id}:${hashFieldValue({ subject, fields, at, ...(aliasOf ? { aliasOf } : {}) })}`,
|
|
100
|
+
service, op: 'set', operation: 'observed', subject, occurredAt: at, actor: { kind: 'system', id: 'vendor' }, fields, batch, ...(aliasOf ? { aliasOf } : {}),
|
|
100
101
|
};
|
|
101
102
|
const { appended } = appendParentEntry(entry, opts.root);
|
|
102
103
|
const key = `${subject.type}:${subject.id}`;
|
|
@@ -110,6 +111,15 @@ function foldResources(service, resources, opts) {
|
|
|
110
111
|
for (const r of resources) {
|
|
111
112
|
const key = `${r.type}:${r.id}`;
|
|
112
113
|
seen.add(key);
|
|
114
|
+
// an adoption: the vendor's subject is the local one a deploy made with no id, aliased to it
|
|
115
|
+
if (r.adopts !== undefined && !tree.has(key)) {
|
|
116
|
+
seen.add(`${r.type}:${r.adopts}`);
|
|
117
|
+
if (append({ type: r.type, id: r.id }, r.fields, r.adopts))
|
|
118
|
+
report.appended += 1;
|
|
119
|
+
else
|
|
120
|
+
report.unchanged += 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
113
123
|
const existing = tree.get(key);
|
|
114
124
|
if (r.deleted) {
|
|
115
125
|
if (existing && existing.fields.deleted !== true) {
|
|
@@ -135,10 +145,14 @@ function foldResources(service, resources, opts) {
|
|
|
135
145
|
else
|
|
136
146
|
report.unchanged += 1;
|
|
137
147
|
}
|
|
138
|
-
for (const
|
|
148
|
+
for (const c of opts.complete ?? []) {
|
|
149
|
+
const type = typeof c === 'string' ? c : c.type;
|
|
150
|
+
const within = typeof c === 'string' ? undefined : new Set(c.within);
|
|
139
151
|
for (const s of tree.values()) {
|
|
140
152
|
if (s.type !== type || seen.has(`${s.type}:${s.id}`) || s.fields.deleted === true)
|
|
141
153
|
continue;
|
|
154
|
+
if (within && typeof c !== 'string' && !within.has(String(s.fields[c.field])))
|
|
155
|
+
continue;
|
|
142
156
|
if (append({ type: s.type, id: s.id }, { deleted: true }))
|
|
143
157
|
report.removed += 1;
|
|
144
158
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { PackScenario } from './pack-fetch.js';
|
|
2
|
+
import type { ScenarioDecision } from './scenario.js';
|
|
3
|
+
type Row = Record<string, unknown>;
|
|
4
|
+
/** A turn: the text, or tool calls, the reasoning when there is some, and why it finished. */
|
|
5
|
+
export type WireTurn = {
|
|
6
|
+
text?: string | null;
|
|
7
|
+
toolCalls?: Array<{
|
|
8
|
+
name: string;
|
|
9
|
+
arguments: Row;
|
|
10
|
+
}>;
|
|
11
|
+
reasoning?: string | null;
|
|
12
|
+
finishReason?: string;
|
|
13
|
+
};
|
|
14
|
+
/** Tokens, estimated at about four characters a token: a twin's count, not a tokenizer's. */
|
|
15
|
+
export declare const estimateTokens: (text: string) => number;
|
|
16
|
+
/** A message's content as text: a string, or its text parts joined. */
|
|
17
|
+
export declare const chatText: (content: unknown) => string;
|
|
18
|
+
/** The last user message's text. */
|
|
19
|
+
export declare function chatLastUserText(messages: Row[]): string;
|
|
20
|
+
/** The tool results the last message carries (a `tool` message), by its name or call id. */
|
|
21
|
+
export declare const chatToolResults: (messages: Row[]) => string[];
|
|
22
|
+
/** The labeled stub for a chat request: with tools offered (`tool_choice` not `none`) and the last message not a tool's
|
|
23
|
+
* result, it calls the tool the choice names, else the first, with arguments its schema admits; with a JSON response
|
|
24
|
+
* format, it answers a value the schema admits; otherwise it echoes the last user message. `label` names the twin. */
|
|
25
|
+
export declare function chatStubTurn(body: Row, label: string): WireTurn;
|
|
26
|
+
export type ChatShape = {
|
|
27
|
+
id: string;
|
|
28
|
+
model: string;
|
|
29
|
+
created: number;
|
|
30
|
+
turn: WireTurn;
|
|
31
|
+
messages: Row[];
|
|
32
|
+
/** the request's max_tokens / max_completion_tokens: a longer text is cut there and finishes `length` */
|
|
33
|
+
maxTokens?: number;
|
|
34
|
+
/** each tool call's id */
|
|
35
|
+
callId: (i: number) => string;
|
|
36
|
+
/** fields a vendor adds at the top of the answer and of each chunk (OpenRouter's `provider`) */
|
|
37
|
+
extra?: Row;
|
|
38
|
+
/** top-level fields on interim stream chunks instead of extra; terminal chunks retain extra */
|
|
39
|
+
interimExtra?: Row;
|
|
40
|
+
/** fields a vendor adds to the usage (OpenRouter's `cost`) */
|
|
41
|
+
usageExtra?: Row;
|
|
42
|
+
/** the vendor also answers the provider's own finish reason (`native_finish_reason`) */
|
|
43
|
+
nativeFinish?: boolean;
|
|
44
|
+
/** the name the reasoning takes in a message (`reasoning`, or `reasoning_content`) */
|
|
45
|
+
reasoningField?: string;
|
|
46
|
+
};
|
|
47
|
+
/** A chat completion's body (`object: chat.completion`), one choice. */
|
|
48
|
+
export declare function chatCompletion(s: ChatShape): Row;
|
|
49
|
+
/** A chat completion streamed: its chunks (`object: chat.completion.chunk`) as Server-Sent Events, the text in two
|
|
50
|
+
* deltas, the last chunk carrying the finish reason and the usage, then `data: [DONE]`. `comment` is a keep-alive a
|
|
51
|
+
* vendor sends first (OpenRouter's `: OPENROUTER PROCESSING`). */
|
|
52
|
+
export declare function chatCompletionSSE(s: ChatShape & {
|
|
53
|
+
comment?: string;
|
|
54
|
+
}): string;
|
|
55
|
+
/** A V4 call's options as the chat completions request they mean: its prompt as messages, its tools, tool choice,
|
|
56
|
+
* response format and output limit; so the chat stub and the wire's scenario read it. */
|
|
57
|
+
export declare function aiSdkChatBody(call: Row, model: string): Row;
|
|
58
|
+
/** A V4 generate result: the turn's content (reasoning, text, tool calls with their input as JSON text), its finish
|
|
59
|
+
* reason (unified, and the raw one) and its usage; `extra` is what the server adds (the gateway's provider metadata). */
|
|
60
|
+
export declare function languageModelResult(s: ChatShape): Row;
|
|
61
|
+
/** A V4 stream: its parts as Server-Sent Events (stream-start, response-metadata, the reasoning, the text in two deltas,
|
|
62
|
+
* each tool call's input then the call, and the finish), with no `[DONE]`: the SDK reads every event as a part. */
|
|
63
|
+
export declare function languageModelSSE(s: ChatShape): string;
|
|
64
|
+
/** A Responses request's input as items: a string is one user message. */
|
|
65
|
+
export declare const responsesItems: (input: unknown) => Row[];
|
|
66
|
+
/** The last user message's text in a Responses input. */
|
|
67
|
+
export declare function responsesLastUserText(input: unknown): string;
|
|
68
|
+
/** The function-call outputs the input ends with. */
|
|
69
|
+
export declare const responsesToolResults: (input: unknown) => string[];
|
|
70
|
+
/** The labeled stub for a Responses request: a function tool is called (unless the input already carries its output or
|
|
71
|
+
* `tool_choice` is `none`), a JSON schema format answers a value it admits, otherwise the last user text is echoed. */
|
|
72
|
+
export declare function responsesStubTurn(body: Row, label: string): WireTurn;
|
|
73
|
+
export type ResponsesShape = {
|
|
74
|
+
id: string;
|
|
75
|
+
model: string;
|
|
76
|
+
createdAt: number;
|
|
77
|
+
turn: WireTurn;
|
|
78
|
+
body: Row;
|
|
79
|
+
/** ids for the output items: the message, the reasoning, each function call and its call id */
|
|
80
|
+
itemId: (kind: 'msg' | 'rs' | 'fc' | 'call', i: number) => string;
|
|
81
|
+
extra?: Row;
|
|
82
|
+
};
|
|
83
|
+
/** A Responses object (`object: response`, `status: completed`): its output items (the reasoning when there is some,
|
|
84
|
+
* the message with its output text, or function calls) and its usage. */
|
|
85
|
+
export declare function responsesAnswer(s: ResponsesShape): Row;
|
|
86
|
+
/** A Responses object streamed: the events the wire sends (`response.created`, `response.in_progress`, each output
|
|
87
|
+
* item's added, its text deltas and done, `response.completed`), each `event:` and `data:` with its sequence number. */
|
|
88
|
+
export declare function responsesSSE(s: ResponsesShape): string;
|
|
89
|
+
export type WireRequest = {
|
|
90
|
+
model: string;
|
|
91
|
+
text: string;
|
|
92
|
+
lastUser: string;
|
|
93
|
+
tools: string[];
|
|
94
|
+
results: string[];
|
|
95
|
+
toolsForbidden?: boolean;
|
|
96
|
+
};
|
|
97
|
+
/** Whether a request forbids every tool call: OpenAI's `tool_choice: 'none'`, Anthropic's `{ type: 'none' }`. */
|
|
98
|
+
export declare const forbidsTools: (choice: unknown) => boolean;
|
|
99
|
+
/** The turn a scenario's decision scripts (`respond`: text, toolCalls, reasoning, finishReason), or undefined. */
|
|
100
|
+
export declare function scenarioTurn(decision: ScenarioDecision | undefined): WireTurn | undefined;
|
|
101
|
+
/** A request as the scenario reads it: its model, its text, its last user text, the tools it offers and the tool
|
|
102
|
+
* results it ends with, from a chat completion's messages or a Responses input. */
|
|
103
|
+
export declare function wireRequest(body: Row): WireRequest;
|
|
104
|
+
/** The scenario of a vendor on this wire: its operations, and its error body for a scripted fault. `readers` reads an
|
|
105
|
+
* operation whose request is another wire's (a gateway's Anthropic Messages surface: anthropicWire's
|
|
106
|
+
* `messagesWireRequest`), so one scenario scripts every surface's turns alike. */
|
|
107
|
+
export declare function scenario(vendor: string, operations: string[], faultBody: (status: number, message: string | undefined) => unknown, readers?: Record<string, (body: Row) => WireRequest>): PackScenario<WireRequest>;
|
|
108
|
+
export {};
|