@volter/world-core 2.0.37 → 3.0.1
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,337 @@
|
|
|
1
|
+
import { sampleFromSchema } from "./schema-sample.js";
|
|
2
|
+
const obj = (v) => (v && typeof v === 'object' && !Array.isArray(v) ? v : {});
|
|
3
|
+
const arr = (v) => (Array.isArray(v) ? v : []);
|
|
4
|
+
/** Tokens, estimated at about four characters a token: a twin's count, not a tokenizer's. */
|
|
5
|
+
export const estimateTokens = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
6
|
+
// ── chat completions: requests ─────────────────────────────────────────────────────────────────
|
|
7
|
+
/** A message's content as text: a string, or its text parts joined. */
|
|
8
|
+
export const chatText = (content) => (typeof content === 'string' ? content : arr(content).map((p) => (typeof p.text === 'string' ? p.text : '')).join(''));
|
|
9
|
+
/** The last user message's text. */
|
|
10
|
+
export function chatLastUserText(messages) {
|
|
11
|
+
for (let i = messages.length - 1; i >= 0; i--)
|
|
12
|
+
if (messages[i].role === 'user')
|
|
13
|
+
return chatText(messages[i].content);
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
/** The tool results the last message carries (a `tool` message), by its name or call id. */
|
|
17
|
+
export const chatToolResults = (messages) => (messages.at(-1)?.role === 'tool' ? [String(messages.at(-1).name ?? messages.at(-1).tool_call_id ?? '')] : []);
|
|
18
|
+
/** The labeled stub for a chat request: with tools offered (`tool_choice` not `none`) and the last message not a tool's
|
|
19
|
+
* result, it calls the tool the choice names, else the first, with arguments its schema admits; with a JSON response
|
|
20
|
+
* format, it answers a value the schema admits; otherwise it echoes the last user message. `label` names the twin. */
|
|
21
|
+
export function chatStubTurn(body, label) {
|
|
22
|
+
const model = String(body.model);
|
|
23
|
+
const messages = arr(body.messages);
|
|
24
|
+
const tools = arr(body.tools).filter((t) => obj(t.function).name !== undefined);
|
|
25
|
+
const choice = body.tool_choice ?? 'auto';
|
|
26
|
+
const named = typeof choice === 'object' ? String(obj(obj(choice).function).name ?? '') : '';
|
|
27
|
+
if (tools.length && choice !== 'none' && (chatToolResults(messages).length === 0 || choice === 'required' || named)) {
|
|
28
|
+
const f = obj(((named ? tools.find((t) => obj(t.function).name === named) : undefined) ?? tools[0]).function);
|
|
29
|
+
return { toolCalls: [{ name: String(f.name), arguments: sampleFromSchema(f.parameters, String(f.name)) }] };
|
|
30
|
+
}
|
|
31
|
+
const format = obj(body.response_format);
|
|
32
|
+
if (format.type === 'json_schema')
|
|
33
|
+
return { text: JSON.stringify(sampleFromSchema(obj(format.json_schema).schema)) };
|
|
34
|
+
if (format.type === 'json_object')
|
|
35
|
+
return { text: JSON.stringify({ twin_stub: `deterministic stub from the ${label} (${model}): no model runs` }) };
|
|
36
|
+
const said = chatLastUserText(messages).trim();
|
|
37
|
+
return { text: `[twin-stub:${model}] This is a deterministic stub from the ${label} (no model runs). Your last message: ${said.length > 200 ? `${said.slice(0, 200)}…` : said || '(empty)'}` };
|
|
38
|
+
}
|
|
39
|
+
function chatParts(s) {
|
|
40
|
+
let text = s.turn.text ?? null;
|
|
41
|
+
let finish = s.turn.finishReason ?? (s.turn.toolCalls?.length ? 'tool_calls' : 'stop');
|
|
42
|
+
if (text !== null && s.maxTokens !== undefined && estimateTokens(text) > s.maxTokens) {
|
|
43
|
+
text = text.slice(0, s.maxTokens * 4);
|
|
44
|
+
finish = 'length';
|
|
45
|
+
}
|
|
46
|
+
const calls = (s.turn.toolCalls ?? []).map((c, i) => ({ id: s.callId(i), type: 'function', function: { name: c.name, arguments: JSON.stringify(c.arguments ?? {}) } }));
|
|
47
|
+
const reasoning = s.turn.reasoning ?? null;
|
|
48
|
+
const prompt = estimateTokens(s.messages.map((m) => chatText(m.content)).join('\n'));
|
|
49
|
+
const completion = estimateTokens([text ?? '', ...calls.map((c) => c.function.arguments)].join(''));
|
|
50
|
+
const reasoningTokens = reasoning ? estimateTokens(reasoning) : 0;
|
|
51
|
+
const usage = {
|
|
52
|
+
prompt_tokens: prompt, completion_tokens: completion + reasoningTokens, total_tokens: prompt + completion + reasoningTokens,
|
|
53
|
+
prompt_tokens_details: { cached_tokens: 0 }, completion_tokens_details: { reasoning_tokens: reasoningTokens }, ...(s.usageExtra ?? {}),
|
|
54
|
+
};
|
|
55
|
+
return { text, finish, calls, usage, reasoning };
|
|
56
|
+
}
|
|
57
|
+
/** A chat completion's body (`object: chat.completion`), one choice. */
|
|
58
|
+
export function chatCompletion(s) {
|
|
59
|
+
const p = chatParts(s);
|
|
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 } : {}) };
|
|
62
|
+
return {
|
|
63
|
+
id: s.id, ...(s.extra ?? {}), model: s.model, object: 'chat.completion', created: s.created,
|
|
64
|
+
choices: [{ index: 0, message, logprobs: null, finish_reason: p.finish, ...(s.nativeFinish ? { native_finish_reason: p.finish } : {}) }], usage: p.usage,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** A chat completion streamed: its chunks (`object: chat.completion.chunk`) as Server-Sent Events, the text in two
|
|
68
|
+
* deltas, the last chunk carrying the finish reason and the usage, then `data: [DONE]`. `comment` is a keep-alive a
|
|
69
|
+
* vendor sends first (OpenRouter's `: OPENROUTER PROCESSING`). */
|
|
70
|
+
export function chatCompletionSSE(s) {
|
|
71
|
+
const p = chatParts(s);
|
|
72
|
+
const field = s.reasoningField ?? 'reasoning';
|
|
73
|
+
const chunk = (delta, last) => `data: ${JSON.stringify({
|
|
74
|
+
id: s.id, ...((last ? s.extra : s.interimExtra ?? s.extra) ?? {}), model: s.model, object: 'chat.completion.chunk', created: s.created,
|
|
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 } : {}),
|
|
77
|
+
})}\n\n`;
|
|
78
|
+
const half = p.text ? Math.ceil(p.text.length / 2) : 0;
|
|
79
|
+
return [
|
|
80
|
+
...(s.comment ? [`: ${s.comment}\n\n`] : []),
|
|
81
|
+
...(p.reasoning ? [chunk({ role: 'assistant', content: '', [field]: p.reasoning }, false)] : []),
|
|
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
|
+
...(p.calls.length ? [chunk({ role: 'assistant', content: null, tool_calls: p.calls.map((c, i) => ({ index: i, ...c })) }, false)] : []),
|
|
84
|
+
chunk({}, true),
|
|
85
|
+
'data: [DONE]\n\n',
|
|
86
|
+
].join('');
|
|
87
|
+
}
|
|
88
|
+
// ── the AI SDK's language-model protocol (Vercel's AI Gateway) ─────────────────────────────────────
|
|
89
|
+
// `@ai-sdk/gateway` posts a model call's options as `@ai-sdk/provider`'s LanguageModelV4 defines them (the model in the
|
|
90
|
+
// `ai-language-model-id` header) and reads back its generate result, or its stream parts as Server-Sent Events
|
|
91
|
+
// (https://unpkg.com/@ai-sdk/provider@4.0.14/dist/index.d.ts, `LanguageModelV4GenerateResult`, `LanguageModelV4StreamPart`).
|
|
92
|
+
/** A V4 call's options as the chat completions request they mean: its prompt as messages, its tools, tool choice,
|
|
93
|
+
* response format and output limit; so the chat stub and the wire's scenario read it. */
|
|
94
|
+
export function aiSdkChatBody(call, model) {
|
|
95
|
+
const text = (content) => (typeof content === 'string' ? content : arr(content).filter((p) => p.type === 'text' || p.type === 'reasoning').map((p) => String(p.text ?? '')).join(''));
|
|
96
|
+
const messages = [];
|
|
97
|
+
for (const m of arr(call.prompt)) {
|
|
98
|
+
if (m.role === 'tool')
|
|
99
|
+
for (const r of arr(m.content).filter((p) => p.type === 'tool-result'))
|
|
100
|
+
messages.push({ role: 'tool', tool_call_id: r.toolCallId, name: r.toolName, content: JSON.stringify(r.output ?? null) });
|
|
101
|
+
else if (m.role === 'assistant') {
|
|
102
|
+
const calls = arr(m.content).filter((p) => p.type === 'tool-call').map((c) => ({ id: c.toolCallId, type: 'function', function: { name: c.toolName, arguments: typeof c.input === 'string' ? c.input : JSON.stringify(c.input ?? {}) } }));
|
|
103
|
+
messages.push({ role: 'assistant', content: text(m.content), ...(calls.length ? { tool_calls: calls } : {}) });
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
messages.push({ role: String(m.role ?? 'user'), content: text(m.content) });
|
|
107
|
+
}
|
|
108
|
+
const tools = arr(call.tools).filter((x) => x.type === 'function').map((x) => ({ type: 'function', function: { name: x.name, description: x.description, parameters: x.inputSchema } }));
|
|
109
|
+
const choice = obj(call.toolChoice);
|
|
110
|
+
const format = obj(call.responseFormat);
|
|
111
|
+
return {
|
|
112
|
+
model, messages,
|
|
113
|
+
...(tools.length ? { tools } : {}),
|
|
114
|
+
...(choice.type === 'tool' ? { tool_choice: { type: 'function', function: { name: choice.toolName } } } : typeof choice.type === 'string' ? { tool_choice: choice.type } : {}),
|
|
115
|
+
...(format.type === 'json' ? { response_format: format.schema ? { type: 'json_schema', json_schema: { name: format.name ?? 'response', schema: format.schema } } : { type: 'json_object' } } : {}),
|
|
116
|
+
...(typeof call.maxOutputTokens === 'number' ? { max_tokens: call.maxOutputTokens } : {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const UNIFIED = { stop: 'stop', length: 'length', tool_calls: 'tool-calls', content_filter: 'content-filter' };
|
|
120
|
+
function lmParts(s) {
|
|
121
|
+
const p = chatParts(s);
|
|
122
|
+
const u = p.usage;
|
|
123
|
+
return {
|
|
124
|
+
content: [
|
|
125
|
+
...(p.reasoning ? [{ type: 'reasoning', text: p.reasoning }] : []),
|
|
126
|
+
...(p.text !== null ? [{ type: 'text', text: p.text }] : []),
|
|
127
|
+
...p.calls.map((c) => ({ type: 'tool-call', toolCallId: c.id, toolName: c.function.name, input: c.function.arguments })),
|
|
128
|
+
],
|
|
129
|
+
finishReason: { unified: UNIFIED[p.finish] ?? 'other', raw: p.finish },
|
|
130
|
+
usage: {
|
|
131
|
+
inputTokens: { total: u.prompt_tokens, noCache: u.prompt_tokens, cacheRead: 0, cacheWrite: 0 },
|
|
132
|
+
outputTokens: { total: u.completion_tokens, text: u.completion_tokens - u.completion_tokens_details.reasoning_tokens, reasoning: u.completion_tokens_details.reasoning_tokens },
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** A V4 generate result: the turn's content (reasoning, text, tool calls with their input as JSON text), its finish
|
|
137
|
+
* reason (unified, and the raw one) and its usage; `extra` is what the server adds (the gateway's provider metadata). */
|
|
138
|
+
export function languageModelResult(s) {
|
|
139
|
+
const r = lmParts(s);
|
|
140
|
+
return { ...r, response: { id: s.id, modelId: s.model, timestamp: new Date(s.created * 1000).toISOString() }, warnings: [], ...(s.extra ?? {}) };
|
|
141
|
+
}
|
|
142
|
+
/** A V4 stream: its parts as Server-Sent Events (stream-start, response-metadata, the reasoning, the text in two deltas,
|
|
143
|
+
* each tool call's input then the call, and the finish), with no `[DONE]`: the SDK reads every event as a part. */
|
|
144
|
+
export function languageModelSSE(s) {
|
|
145
|
+
const r = lmParts(s);
|
|
146
|
+
const out = [{ type: 'stream-start', warnings: [] }, { type: 'response-metadata', id: s.id, modelId: s.model, timestamp: new Date(s.created * 1000).toISOString() }];
|
|
147
|
+
for (const [i, c] of r.content.entries()) {
|
|
148
|
+
if (c.type === 'reasoning')
|
|
149
|
+
out.push({ type: 'reasoning-start', id: `reasoning-${i}` }, { type: 'reasoning-delta', id: `reasoning-${i}`, delta: c.text }, { type: 'reasoning-end', id: `reasoning-${i}` });
|
|
150
|
+
else if (c.type === 'text') {
|
|
151
|
+
const text = String(c.text);
|
|
152
|
+
const half = Math.ceil(text.length / 2);
|
|
153
|
+
out.push({ type: 'text-start', id: `text-${i}` }, { type: 'text-delta', id: `text-${i}`, delta: text.slice(0, half) }, ...(text.length > half ? [{ type: 'text-delta', id: `text-${i}`, delta: text.slice(half) }] : []), { type: 'text-end', id: `text-${i}` });
|
|
154
|
+
}
|
|
155
|
+
else
|
|
156
|
+
out.push({ type: 'tool-input-start', id: c.toolCallId, toolName: c.toolName }, { type: 'tool-input-delta', id: c.toolCallId, delta: c.input }, { type: 'tool-input-end', id: c.toolCallId }, c);
|
|
157
|
+
}
|
|
158
|
+
out.push({ type: 'finish', finishReason: r.finishReason, usage: r.usage, ...(s.extra ?? {}) });
|
|
159
|
+
return out.map((part) => `data: ${JSON.stringify(part)}\n\n`).join('');
|
|
160
|
+
}
|
|
161
|
+
// ── Responses: requests ────────────────────────────────────────────────────────────────────────
|
|
162
|
+
/** A Responses request's input as items: a string is one user message. */
|
|
163
|
+
export const responsesItems = (input) => (typeof input === 'string' ? [{ type: 'message', role: 'user', content: input }] : arr(input));
|
|
164
|
+
const itemText = (content) => (typeof content === 'string' ? content : arr(content).map((p) => (typeof p.text === 'string' ? p.text : '')).join(''));
|
|
165
|
+
/** The last user message's text in a Responses input. */
|
|
166
|
+
export function responsesLastUserText(input) {
|
|
167
|
+
const items = responsesItems(input);
|
|
168
|
+
for (let i = items.length - 1; i >= 0; i--)
|
|
169
|
+
if ((items[i].role ?? (items[i].type === 'message' ? 'user' : '')) === 'user')
|
|
170
|
+
return itemText(items[i].content);
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
/** The function-call outputs the input ends with. */
|
|
174
|
+
export const responsesToolResults = (input) => responsesItems(input).filter((i) => i.type === 'function_call_output').map((i) => String(i.call_id ?? ''));
|
|
175
|
+
/** The labeled stub for a Responses request: a function tool is called (unless the input already carries its output or
|
|
176
|
+
* `tool_choice` is `none`), a JSON schema format answers a value it admits, otherwise the last user text is echoed. */
|
|
177
|
+
export function responsesStubTurn(body, label) {
|
|
178
|
+
const model = String(body.model);
|
|
179
|
+
const tools = arr(body.tools).filter((t) => t.type === 'function' && typeof t.name === 'string');
|
|
180
|
+
const choice = body.tool_choice ?? 'auto';
|
|
181
|
+
const named = typeof choice === 'object' ? String(obj(choice).name ?? '') : '';
|
|
182
|
+
if (tools.length && choice !== 'none' && (responsesToolResults(body.input).length === 0 || choice === 'required' || named)) {
|
|
183
|
+
const t = (named ? tools.find((x) => x.name === named) : undefined) ?? tools[0];
|
|
184
|
+
return { toolCalls: [{ name: String(t.name), arguments: sampleFromSchema(t.parameters, String(t.name)) }] };
|
|
185
|
+
}
|
|
186
|
+
const format = obj(obj(body.text).format);
|
|
187
|
+
if (format.type === 'json_schema')
|
|
188
|
+
return { text: JSON.stringify(sampleFromSchema(format.schema)) };
|
|
189
|
+
if (format.type === 'json_object')
|
|
190
|
+
return { text: JSON.stringify({ twin_stub: `deterministic stub from the ${label} (${model}): no model runs` }) };
|
|
191
|
+
const said = responsesLastUserText(body.input).trim();
|
|
192
|
+
return { text: `[twin-stub:${model}] This is a deterministic stub from the ${label} (no model runs). Your last message: ${said.length > 200 ? `${said.slice(0, 200)}…` : said || '(empty)'}` };
|
|
193
|
+
}
|
|
194
|
+
/** A Responses object (`object: response`, `status: completed`): its output items (the reasoning when there is some,
|
|
195
|
+
* the message with its output text, or function calls) and its usage. */
|
|
196
|
+
export function responsesAnswer(s) {
|
|
197
|
+
const b = s.body;
|
|
198
|
+
let text = s.turn.text ?? null;
|
|
199
|
+
const max = typeof b.max_output_tokens === 'number' ? b.max_output_tokens : undefined;
|
|
200
|
+
let incomplete = null;
|
|
201
|
+
if (text !== null && max !== undefined && estimateTokens(text) > max) {
|
|
202
|
+
text = text.slice(0, max * 4);
|
|
203
|
+
incomplete = { reason: 'max_output_tokens' };
|
|
204
|
+
}
|
|
205
|
+
const output = [
|
|
206
|
+
...(s.turn.reasoning ? [{ type: 'reasoning', id: s.itemId('rs', 0), summary: [{ type: 'summary_text', text: s.turn.reasoning }], status: 'completed' }] : []),
|
|
207
|
+
...(text !== null ? [{ type: 'message', id: s.itemId('msg', 0), status: incomplete ? 'incomplete' : 'completed', role: 'assistant', content: [{ type: 'output_text', text, annotations: [], logprobs: [] }] }] : []),
|
|
208
|
+
...(s.turn.toolCalls ?? []).map((c, i) => ({ type: 'function_call', id: s.itemId('fc', i), call_id: s.itemId('call', i), name: c.name, arguments: JSON.stringify(c.arguments ?? {}), status: 'completed' })),
|
|
209
|
+
];
|
|
210
|
+
const input = estimateTokens([String(b.instructions ?? ''), ...responsesItems(b.input).map((i) => itemText(i.content ?? i.output))].join('\n'));
|
|
211
|
+
const out = estimateTokens([text ?? '', ...(s.turn.toolCalls ?? []).map((c) => JSON.stringify(c.arguments ?? {}))].join(''));
|
|
212
|
+
const reasoningTokens = s.turn.reasoning ? estimateTokens(s.turn.reasoning) : 0;
|
|
213
|
+
return {
|
|
214
|
+
id: s.id, object: 'response', created_at: s.createdAt, status: incomplete ? 'incomplete' : 'completed', ...(s.extra ?? {}),
|
|
215
|
+
error: null, incomplete_details: incomplete, instructions: b.instructions ?? null, max_output_tokens: max ?? null, model: s.model, output,
|
|
216
|
+
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
|
+
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 ?? {},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/** A Responses object streamed: the events the wire sends (`response.created`, `response.in_progress`, each output
|
|
224
|
+
* item's added, its text deltas and done, `response.completed`), each `event:` and `data:` with its sequence number. */
|
|
225
|
+
export function responsesSSE(s) {
|
|
226
|
+
const done = responsesAnswer(s);
|
|
227
|
+
const pending = { ...done, status: 'in_progress', output: [], usage: null };
|
|
228
|
+
const events = [{ type: 'response.created', response: pending }, { type: 'response.in_progress', response: pending }];
|
|
229
|
+
for (const [i, item] of done.output.entries()) {
|
|
230
|
+
if (item.type === 'message') {
|
|
231
|
+
const text = String((item.content[0] ?? {}).text ?? '');
|
|
232
|
+
events.push({ type: 'response.output_item.added', output_index: i, item: { ...item, status: 'in_progress', content: [] } });
|
|
233
|
+
events.push({ type: 'response.content_part.added', item_id: item.id, output_index: i, content_index: 0, part: { type: 'output_text', text: '', annotations: [] } });
|
|
234
|
+
const half = Math.ceil(text.length / 2);
|
|
235
|
+
for (const delta of [text.slice(0, half), text.slice(half)].filter(Boolean))
|
|
236
|
+
events.push({ type: 'response.output_text.delta', item_id: item.id, output_index: i, content_index: 0, delta, logprobs: [] });
|
|
237
|
+
events.push({ type: 'response.output_text.done', item_id: item.id, output_index: i, content_index: 0, text, logprobs: [] });
|
|
238
|
+
events.push({ type: 'response.content_part.done', item_id: item.id, output_index: i, content_index: 0, part: item.content[0] });
|
|
239
|
+
}
|
|
240
|
+
else if (item.type === 'function_call') {
|
|
241
|
+
events.push({ type: 'response.output_item.added', output_index: i, item: { ...item, arguments: '', status: 'in_progress' } });
|
|
242
|
+
events.push({ type: 'response.function_call_arguments.delta', item_id: item.id, output_index: i, delta: item.arguments });
|
|
243
|
+
events.push({ type: 'response.function_call_arguments.done', item_id: item.id, output_index: i, arguments: item.arguments });
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
events.push({ type: 'response.output_item.added', output_index: i, item });
|
|
247
|
+
}
|
|
248
|
+
events.push({ type: 'response.output_item.done', output_index: i, item });
|
|
249
|
+
}
|
|
250
|
+
events.push({ type: done.status === 'incomplete' ? 'response.incomplete' : 'response.completed', response: done });
|
|
251
|
+
return events.map((e, n) => `event: ${String(e.type)}\ndata: ${JSON.stringify({ ...e, sequence_number: n })}\n\n`).join('');
|
|
252
|
+
}
|
|
253
|
+
/** Whether a request forbids every tool call: OpenAI's `tool_choice: 'none'`, Anthropic's `{ type: 'none' }`. */
|
|
254
|
+
export const forbidsTools = (choice) => choice === 'none' || obj(choice).type === 'none';
|
|
255
|
+
/** The turn a scenario's decision scripts (`respond`: text, toolCalls, reasoning, finishReason), or undefined. */
|
|
256
|
+
export function scenarioTurn(decision) {
|
|
257
|
+
if (!decision || decision.kind !== 'handler')
|
|
258
|
+
return undefined;
|
|
259
|
+
const r = obj(decision.respond);
|
|
260
|
+
return {
|
|
261
|
+
...(typeof r.text === 'string' ? { text: r.text } : {}),
|
|
262
|
+
...(r.toolCalls !== undefined ? { toolCalls: (Array.isArray(r.toolCalls) ? r.toolCalls : [r.toolCalls]) } : {}),
|
|
263
|
+
...(typeof r.reasoning === 'string' ? { reasoning: r.reasoning } : {}),
|
|
264
|
+
...(typeof r.finishReason === 'string' ? { finishReason: r.finishReason } : {}),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
|
|
268
|
+
const RESPOND_KEYS = new Set(['text', 'toolCalls', 'reasoning', 'finishReason']);
|
|
269
|
+
/** A request as the scenario reads it: its model, its text, its last user text, the tools it offers and the tool
|
|
270
|
+
* results it ends with, from a chat completion's messages or a Responses input. */
|
|
271
|
+
export function wireRequest(body) {
|
|
272
|
+
const messages = arr(body.messages);
|
|
273
|
+
const tools = arr(body.tools).map((t) => String(obj(t.function).name ?? t.name ?? '')).filter(Boolean);
|
|
274
|
+
const forbidden = forbidsTools(body.tool_choice) ? { toolsForbidden: true } : {};
|
|
275
|
+
if (messages.length)
|
|
276
|
+
return { model: String(body.model ?? ''), text: messages.map((m) => chatText(m.content)).join('\n'), lastUser: chatLastUserText(messages), tools, results: chatToolResults(messages), ...forbidden };
|
|
277
|
+
const items = responsesItems(body.input);
|
|
278
|
+
return { model: String(body.model ?? ''), text: [String(body.instructions ?? ''), ...items.map((i) => itemText(i.content))].join('\n'), lastUser: responsesLastUserText(body.input), tools, results: responsesToolResults(body.input), ...forbidden };
|
|
279
|
+
}
|
|
280
|
+
/** The scenario of a vendor on this wire: its operations, and its error body for a scripted fault. `readers` reads an
|
|
281
|
+
* operation whose request is another wire's (a gateway's Anthropic Messages surface: anthropicWire's
|
|
282
|
+
* `messagesWireRequest`), so one scenario scripts every surface's turns alike. */
|
|
283
|
+
export function scenario(vendor, operations, faultBody, readers = {}) {
|
|
284
|
+
const adapter = {
|
|
285
|
+
vendor,
|
|
286
|
+
renderFault: (f) => ({ body: faultBody(f.status, f.message) }),
|
|
287
|
+
features: (r) => ({ model: r.model, lastUserText: r.lastUser.slice(0, 300), tools: r.tools, lastMessageIsToolResult: r.results.length > 0, toolResultFor: r.results }),
|
|
288
|
+
matchers: {
|
|
289
|
+
modelEquals: (r, c) => nonEmpty(c) && r.model === c,
|
|
290
|
+
userTextIncludes: (r, c) => nonEmpty(c) && r.lastUser.toLowerCase().includes(c.toLowerCase()),
|
|
291
|
+
anyTextIncludes: (r, c) => nonEmpty(c) && r.text.toLowerCase().includes(c.toLowerCase()),
|
|
292
|
+
lastMessageIsToolResult: (r, c) => typeof c === 'boolean' && (r.results.length > 0) === c,
|
|
293
|
+
toolResultFor: (r, c) => nonEmpty(c) && r.results.includes(c),
|
|
294
|
+
hasTool: (r, c) => nonEmpty(c) && r.tools.includes(c),
|
|
295
|
+
},
|
|
296
|
+
text: (r) => r.text,
|
|
297
|
+
// source: https://platform.openai.com/docs/api-reference/chat/create "none means the model will not call any tool and instead generates a message"
|
|
298
|
+
admits: (r, respond) => !(r.toolsForbidden && obj(respond).toolCalls !== undefined),
|
|
299
|
+
validateOn: (on) => {
|
|
300
|
+
for (const k of ['modelEquals', 'userTextIncludes', 'anyTextIncludes', 'toolResultFor', 'hasTool'])
|
|
301
|
+
if (on[k] !== undefined && !nonEmpty(on[k]))
|
|
302
|
+
return `on.${k} is a non-empty string`;
|
|
303
|
+
if (on.lastMessageIsToolResult !== undefined && typeof on.lastMessageIsToolResult !== 'boolean')
|
|
304
|
+
return 'on.lastMessageIsToolResult is a boolean';
|
|
305
|
+
return null;
|
|
306
|
+
},
|
|
307
|
+
validateRespond: (respond) => {
|
|
308
|
+
if (typeof respond !== 'object' || respond === null || Array.isArray(respond))
|
|
309
|
+
return 'respond is an object { text?, toolCalls?, reasoning?, finishReason? }';
|
|
310
|
+
const r = respond;
|
|
311
|
+
for (const k of Object.keys(r))
|
|
312
|
+
if (!RESPOND_KEYS.has(k))
|
|
313
|
+
return `respond: unknown key "${k}" (valid: ${[...RESPOND_KEYS].join(', ')})`;
|
|
314
|
+
if (r.text !== undefined && typeof r.text !== 'string')
|
|
315
|
+
return 'respond.text is a string';
|
|
316
|
+
if (r.reasoning !== undefined && typeof r.reasoning !== 'string')
|
|
317
|
+
return 'respond.reasoning is a string';
|
|
318
|
+
for (const c of r.toolCalls === undefined ? [] : Array.isArray(r.toolCalls) ? r.toolCalls : [r.toolCalls]) {
|
|
319
|
+
const u = c;
|
|
320
|
+
if (!u || typeof u !== 'object' || Array.isArray(u) || !nonEmpty(u.name))
|
|
321
|
+
return 'respond.toolCalls[].name is a non-empty string';
|
|
322
|
+
}
|
|
323
|
+
if (r.text === undefined && r.toolCalls === undefined)
|
|
324
|
+
return 'respond needs text or toolCalls';
|
|
325
|
+
return null;
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
// a V4 call (the AI SDK's protocol) is read as the chat request it means, its model in its header
|
|
329
|
+
const request = (ctx) => {
|
|
330
|
+
const body = (ctx.body ?? {});
|
|
331
|
+
const read = Object.hasOwn(readers, ctx.call.operation.id) ? readers[ctx.call.operation.id] : undefined;
|
|
332
|
+
if (read)
|
|
333
|
+
return read(body);
|
|
334
|
+
return wireRequest(Array.isArray(body.prompt) ? aiSdkChatBody(body, ctx.call.request.headers.get('ai-language-model-id') ?? '') : body);
|
|
335
|
+
};
|
|
336
|
+
return { adapter, operations, request };
|
|
337
|
+
}
|
|
@@ -7,15 +7,14 @@ export interface PackAssetStore {
|
|
|
7
7
|
export declare function assetContentType(relPath: string): string;
|
|
8
8
|
/** A relative path confined to a pack's tree: no absolute, no `..`, no empty segment. */
|
|
9
9
|
export declare function confinedAssetPath(relPath: string): string | null;
|
|
10
|
-
/** The
|
|
11
|
-
* `twinDir` ends with a slash. */
|
|
10
|
+
/** The host's store: a pack-directory resolver, or a catalog root ending in a slash. */
|
|
12
11
|
export declare class LocalPackAssets implements PackAssetStore {
|
|
13
12
|
private readonly twinDir;
|
|
14
|
-
constructor(twinDir: string);
|
|
13
|
+
constructor(twinDir: string | ((vendor: string) => string | undefined | Promise<string | undefined>));
|
|
15
14
|
fetch(vendor: string, relPath: string): Promise<Response | null>;
|
|
16
15
|
}
|
|
17
16
|
/** The workerd shell's store: the static-assets binding, where the deploy step places each
|
|
18
|
-
* pack's declared assets under `/{vendor}/pack/<relPath
|
|
17
|
+
* pack's declared assets under `/{vendor}/pack/<relPath>`. */
|
|
19
18
|
export declare class BindingPackAssets implements PackAssetStore {
|
|
20
19
|
private readonly assets;
|
|
21
20
|
constructor(assets: {
|
package/dist/src/pack-assets.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
// THE PACK-ASSET SEAM (runtime contract R20). A pack may ship bytes it serves byte-for-byte —
|
|
2
|
-
// clerk's vendored clerk-js dist (136 files, 14.5 MB)
|
|
2
|
+
// clerk's vendored clerk-js dist (136 files, 14.5 MB) — that are neither
|
|
3
3
|
// committed text (size) nor world-state blobs (R11's seam is keyed under a world's state root
|
|
4
4
|
// and filled by requests). They are PACK assets: read-only, versioned with the package, the
|
|
5
5
|
// same on every world. This seam is how a pack reaches them without touching a host: the
|
|
6
6
|
// pack declares them on its descriptor (`assets`), reads them through `getActivePackAssets()`,
|
|
7
7
|
// and the SHELL binds the store — the Bun shell reads the pack's own tree, workerd the
|
|
8
|
-
// static-assets binding
|
|
8
|
+
// static-assets binding. A pack that reads its tree
|
|
9
9
|
// with Bun.file or import.meta is host-bound (R12b); one that reads through here is not.
|
|
10
|
-
// No node builtins at module scope: `@volter/world-core`'s index re-exports this module and
|
|
11
|
-
//
|
|
12
|
-
// broke every mirror bundle the day the seam landed. The local store reads through Bun's file
|
|
10
|
+
// No node builtins at module scope: `@volter/world-core`'s index re-exports this module and a browser
|
|
11
|
+
// bundle pulls the index in, so a static `node:fs`/`node:url` import here would break it. The local store reads through Bun's file
|
|
13
12
|
// API at call time; the worker store never touches a filesystem.
|
|
14
13
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
15
14
|
const CONTENT_TYPES = {
|
|
@@ -30,8 +29,7 @@ export function confinedAssetPath(relPath) {
|
|
|
30
29
|
return null;
|
|
31
30
|
return segs.join('/');
|
|
32
31
|
}
|
|
33
|
-
/** The
|
|
34
|
-
* `twinDir` ends with a slash. */
|
|
32
|
+
/** The host's store: a pack-directory resolver, or a catalog root ending in a slash. */
|
|
35
33
|
export class LocalPackAssets {
|
|
36
34
|
twinDir;
|
|
37
35
|
constructor(twinDir) {
|
|
@@ -41,14 +39,17 @@ export class LocalPackAssets {
|
|
|
41
39
|
const safe = confinedAssetPath(relPath);
|
|
42
40
|
if (safe === null || !/^[a-z][a-z0-9-]*$/.test(vendor))
|
|
43
41
|
return null;
|
|
44
|
-
const
|
|
42
|
+
const dir = typeof this.twinDir === 'string' ? `${this.twinDir}${vendor}` : await this.twinDir(vendor);
|
|
43
|
+
if (dir === undefined)
|
|
44
|
+
return null;
|
|
45
|
+
const file = `${dir}/${safe}`; // confined: no `..`, no absolute, no empty segment (above)
|
|
45
46
|
if (!existsSync(file) || !statSync(file).isFile())
|
|
46
47
|
return null;
|
|
47
48
|
return new Response(readFileSync(file), { status: 200, headers: { 'content-type': assetContentType(safe) } });
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
/** The workerd shell's store: the static-assets binding, where the deploy step places each
|
|
51
|
-
* pack's declared assets under `/{vendor}/pack/<relPath
|
|
52
|
+
* pack's declared assets under `/{vendor}/pack/<relPath>`. */
|
|
52
53
|
export class BindingPackAssets {
|
|
53
54
|
assets;
|
|
54
55
|
constructor(assets) {
|
|
@@ -68,7 +69,11 @@ let active;
|
|
|
68
69
|
/** The bound store — the Bun shell's local tree unless a shell bound another. */
|
|
69
70
|
export function getActivePackAssets() {
|
|
70
71
|
if (active === undefined)
|
|
71
|
-
active = new LocalPackAssets(
|
|
72
|
+
active = new LocalPackAssets(async (vendor) => {
|
|
73
|
+
const { vendorPackageDir } = await import("./pack-paths.js");
|
|
74
|
+
const { fileURLToPath } = await import('node:url');
|
|
75
|
+
return vendorPackageDir(process.cwd(), vendor) ?? vendorPackageDir(fileURLToPath(new URL('.', import.meta.url)), vendor);
|
|
76
|
+
});
|
|
72
77
|
return active;
|
|
73
78
|
}
|
|
74
79
|
export function setActivePackAssets(store) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type DerivedManifest, type Handler, type HandlerContext, type VendorManifest } from './derived-core.js';
|
|
2
|
+
import { type DerivedFetch, type DerivedOwner, type DerivedSurface } from './derived.js';
|
|
3
|
+
import { type PackScenarioAdapter } from './scenario.js';
|
|
4
|
+
import { type SocketEngine } from './sockets.js';
|
|
5
|
+
import { type GraphqlPart } from './graphql-wire.js';
|
|
6
|
+
/** What a pack declares and writes, and nothing it assembles itself. */
|
|
7
|
+
export type PackParts = {
|
|
8
|
+
manifest: DerivedManifest;
|
|
9
|
+
surface: DerivedSurface;
|
|
10
|
+
/** `semantics/index.ts`: the handlers, by operationId. */
|
|
11
|
+
handlers?: Record<string, Handler>;
|
|
12
|
+
/** `semantics/doors.ts`: a door's answer, by the id the manifest's `doors` gives it. */
|
|
13
|
+
doors?: Record<string, Handler>;
|
|
14
|
+
/** Each `screens/<id>.tsx`'s answer, by the id the manifest's `screens` gives it. */
|
|
15
|
+
screens?: Record<string, Handler>;
|
|
16
|
+
/** `semantics/around.ts`: what the vendor does around every request to its API, one the surface names or not (a key
|
|
17
|
+
* checked, a plan's limits, a quota counted after a success), over a context whose operation is the request's (or
|
|
18
|
+
* `unmatched`); `next` answers the request itself, or the one it is given (the vendor's own rewrite of it: a query
|
|
19
|
+
* token moved to its header, a caller resolved into a header only the vendor's own front sets). */
|
|
20
|
+
around?: (ctx: HandlerContext, next: (request?: Request) => Promise<Response>) => Promise<Response>;
|
|
21
|
+
/** `semantics/clock.ts`: the vendor's own moves the World clock has made due, caught up before anything is answered.
|
|
22
|
+
* They are the vendor's, not the caller's: they land under a read-only request too. */
|
|
23
|
+
clock?: (ctx: HandlerContext) => Promise<void>;
|
|
24
|
+
/** `semantics/scenario.ts`: how the World's scenario decides a model vendor's turn — the operations it decides, the
|
|
25
|
+
* request each is as the scenario reads it, and the vendor's own matchers and fault body (the adapter). */
|
|
26
|
+
scenario?: PackScenario;
|
|
27
|
+
/** `semantics/gap.ts`: the vendor's answer for what it does not serve, where its manifest's `gap` cannot say it (Clerk's
|
|
28
|
+
* Frontend API: Go's plain-text 404 for a path it does not have, `signed_out` for an operation asked without a
|
|
29
|
+
* client), over a context whose operation is the one matched, or `unmatched`. */
|
|
30
|
+
gap?: (ctx: HandlerContext) => Response | Promise<Response>;
|
|
31
|
+
/** `semantics/tenant.ts`: the tenant the caller acts in, which the core scopes the manifest's `tenant` by. */
|
|
32
|
+
tenant?: (ctx: HandlerContext) => string | undefined | Promise<string | undefined>;
|
|
33
|
+
/** The vendor's lanes, by the name the manifest's `lanes` routes give them: each its own pack fetch and manifest. */
|
|
34
|
+
lanes?: Record<string, LanePart>;
|
|
35
|
+
/** `semantics/graphql.ts` with the vendor's schema (`generated/graphql-sdl.gen.json`): the GraphQL wire over the same
|
|
36
|
+
* state, served at the manifest's `graphql.paths` (graphql-wire.ts). */
|
|
37
|
+
graphql?: GraphqlPart<HandlerContext> & {
|
|
38
|
+
sdl: string;
|
|
39
|
+
};
|
|
40
|
+
/** `semantics/sockets.ts`: each socket's engine, by the id the manifest's `sockets` gives it (sockets.ts). */
|
|
41
|
+
sockets?: Record<string, SocketEngine<HandlerContext>>;
|
|
42
|
+
};
|
|
43
|
+
/** A pack's scenario (`semantics/scenario.ts` exports `scenario`): the adapter the kernel's engine reads the World's
|
|
44
|
+
* scenario document with, the operations it decides a turn of, and the request an operation is to it (undefined when
|
|
45
|
+
* the request is not one it decides: the handler answers the vendor's refusal). */
|
|
46
|
+
export type PackScenario<Req = any> = {
|
|
47
|
+
adapter: PackScenarioAdapter<Req>;
|
|
48
|
+
operations: ReadonlyArray<string>;
|
|
49
|
+
request: (ctx: HandlerContext) => Req | undefined;
|
|
50
|
+
};
|
|
51
|
+
/** A lane as its vendor's fetch is given it: its own pack fetch, and its manifest (the doors it declares). */
|
|
52
|
+
export type LanePart = {
|
|
53
|
+
fetch: ((request: Request) => Promise<Response>) & {
|
|
54
|
+
serves?: (method: string, path: string) => boolean;
|
|
55
|
+
};
|
|
56
|
+
manifest: {
|
|
57
|
+
doors?: ReadonlyArray<{
|
|
58
|
+
method: string;
|
|
59
|
+
path: string;
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
/** Where a pack serves: its World's root, whether it was started read-only, the World instant when a caller pins it, the
|
|
64
|
+
* World's managed Postgres the runtime binds a pack declaring `managedDatabase` to (a handler's `ctx.engine`), and the
|
|
65
|
+
* World's scenario file for a model vendor's turns (`scenarioPath`, the runtime's `--scenario`). */
|
|
66
|
+
export type PackFetchOptions = {
|
|
67
|
+
root?: string;
|
|
68
|
+
readOnly?: boolean;
|
|
69
|
+
clock?: () => string;
|
|
70
|
+
database?: string;
|
|
71
|
+
scenarioPath?: string;
|
|
72
|
+
};
|
|
73
|
+
/** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
|
|
74
|
+
export declare function createPackFetch(parts: PackParts, options?: PackFetchOptions): DerivedFetch;
|
|
75
|
+
export declare function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): ((request: Request) => Promise<Response>) & {
|
|
76
|
+
owners: () => Record<string, DerivedOwner>;
|
|
77
|
+
};
|