@volter/twin-openai 0.1.2 → 2.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.
Files changed (120) hide show
  1. package/README.md +33 -30
  2. package/defaults/handlers.json +10 -0
  3. package/dist/defaults/handlers.json +10 -0
  4. package/dist/src/cli.d.ts +2 -0
  5. package/dist/src/cli.js +29 -0
  6. package/dist/src/generated/surface.gen.json +1 -0
  7. package/dist/src/generated/ui.gen.json +1 -0
  8. package/dist/src/index.d.ts +19 -0
  9. package/dist/src/index.js +72 -0
  10. package/dist/src/manifest.d.ts +6 -0
  11. package/dist/src/manifest.js +323 -0
  12. package/dist/src/openai-budget.d.ts +53 -0
  13. package/dist/src/openai-budget.js +147 -0
  14. package/dist/src/openai-capabilities.d.ts +4 -0
  15. package/dist/src/openai-capabilities.js +1569 -0
  16. package/dist/src/openai-conformance.d.ts +13 -0
  17. package/dist/src/openai-conformance.js +116 -0
  18. package/dist/src/openai-connector.d.ts +86 -0
  19. package/dist/src/openai-connector.js +291 -0
  20. package/dist/src/openai-media.d.ts +43 -0
  21. package/dist/src/openai-media.js +257 -0
  22. package/dist/src/openai-models.d.ts +74 -0
  23. package/dist/src/openai-models.js +148 -0
  24. package/dist/src/openai-scenario.d.ts +51 -0
  25. package/dist/src/openai-scenario.js +166 -0
  26. package/dist/src/openai-server.d.ts +40 -0
  27. package/dist/src/openai-server.js +126 -0
  28. package/dist/src/openai-stub.d.ts +82 -0
  29. package/dist/src/openai-stub.js +256 -0
  30. package/dist/src/openai-twin.d.ts +182 -0
  31. package/dist/src/openai-twin.js +1117 -0
  32. package/dist/src/openai-types.d.ts +194 -0
  33. package/dist/src/openai-types.js +4 -0
  34. package/dist/src/openai-webhooks.d.ts +47 -0
  35. package/dist/src/openai-webhooks.js +99 -0
  36. package/dist/src/screens/api-keys.d.ts +16 -0
  37. package/dist/src/screens/api-keys.js +131 -0
  38. package/dist/src/screens/session.d.ts +22 -0
  39. package/dist/src/screens/session.js +115 -0
  40. package/dist/src/semantics/assistants.d.ts +2 -0
  41. package/dist/src/semantics/assistants.js +331 -0
  42. package/dist/src/semantics/audio.d.ts +2 -0
  43. package/dist/src/semantics/audio.js +27 -0
  44. package/dist/src/semantics/batches.d.ts +4 -0
  45. package/dist/src/semantics/batches.js +86 -0
  46. package/dist/src/semantics/chat-completions.d.ts +3 -0
  47. package/dist/src/semantics/chat-completions.js +58 -0
  48. package/dist/src/semantics/containers.d.ts +2 -0
  49. package/dist/src/semantics/containers.js +147 -0
  50. package/dist/src/semantics/embeddings.d.ts +2 -0
  51. package/dist/src/semantics/embeddings.js +13 -0
  52. package/dist/src/semantics/evals.d.ts +2 -0
  53. package/dist/src/semantics/evals.js +173 -0
  54. package/dist/src/semantics/files.d.ts +13 -0
  55. package/dist/src/semantics/files.js +59 -0
  56. package/dist/src/semantics/fine-tuning.d.ts +4 -0
  57. package/dist/src/semantics/fine-tuning.js +178 -0
  58. package/dist/src/semantics/images.d.ts +2 -0
  59. package/dist/src/semantics/images.js +18 -0
  60. package/dist/src/semantics/index.d.ts +8 -0
  61. package/dist/src/semantics/index.js +46 -0
  62. package/dist/src/semantics/models.d.ts +2 -0
  63. package/dist/src/semantics/models.js +34 -0
  64. package/dist/src/semantics/moderations.d.ts +2 -0
  65. package/dist/src/semantics/moderations.js +12 -0
  66. package/dist/src/semantics/organization.d.ts +2 -0
  67. package/dist/src/semantics/organization.js +67 -0
  68. package/dist/src/semantics/progress.d.ts +22 -0
  69. package/dist/src/semantics/progress.js +63 -0
  70. package/dist/src/semantics/responses.d.ts +3 -0
  71. package/dist/src/semantics/responses.js +153 -0
  72. package/dist/src/semantics/shared.d.ts +32 -0
  73. package/dist/src/semantics/shared.js +69 -0
  74. package/dist/src/semantics/uploads.d.ts +2 -0
  75. package/dist/src/semantics/uploads.js +84 -0
  76. package/dist/src/semantics/vector-stores.d.ts +2 -0
  77. package/dist/src/semantics/vector-stores.js +281 -0
  78. package/dist/test-fixtures/openai-openapi-operations.SOURCE.md +18 -0
  79. package/dist/test-fixtures/openai-openapi-operations.json +1849 -0
  80. package/package.json +21 -10
  81. package/src/cli.ts +9 -7
  82. package/src/generated/surface.gen.json +1 -0
  83. package/src/generated/ui.gen.json +1 -0
  84. package/src/index.ts +20 -10
  85. package/src/manifest.ts +343 -0
  86. package/src/openai-budget.ts +4 -4
  87. package/src/openai-capabilities.ts +177 -195
  88. package/src/openai-conformance.ts +1 -1
  89. package/src/openai-connector.ts +40 -43
  90. package/src/openai-media.ts +225 -0
  91. package/src/openai-models.ts +145 -15
  92. package/src/openai-scenario.ts +46 -10
  93. package/src/openai-server.ts +65 -108
  94. package/src/openai-stub.ts +54 -30
  95. package/src/openai-twin.ts +760 -1665
  96. package/src/openai-types.ts +24 -6
  97. package/src/openai-webhooks.ts +3 -2
  98. package/src/screens/api-keys.tsx +138 -0
  99. package/src/screens/session.tsx +131 -0
  100. package/src/semantics/assistants.ts +336 -0
  101. package/src/semantics/audio.ts +31 -0
  102. package/src/semantics/batches.ts +88 -0
  103. package/src/semantics/chat-completions.ts +66 -0
  104. package/src/semantics/containers.ts +151 -0
  105. package/src/semantics/embeddings.ts +19 -0
  106. package/src/semantics/evals.ts +182 -0
  107. package/src/semantics/files.ts +67 -0
  108. package/src/semantics/fine-tuning.ts +185 -0
  109. package/src/semantics/images.ts +23 -0
  110. package/src/semantics/index.ts +52 -0
  111. package/src/semantics/models.ts +41 -0
  112. package/src/semantics/moderations.ts +14 -0
  113. package/src/semantics/organization.ts +76 -0
  114. package/src/semantics/progress.ts +72 -0
  115. package/src/semantics/responses.ts +151 -0
  116. package/src/semantics/shared.ts +82 -0
  117. package/src/semantics/uploads.ts +92 -0
  118. package/src/semantics/vector-stores.ts +279 -0
  119. package/test-fixtures/openai-openapi-operations.SOURCE.md +4 -5
  120. package/test-fixtures/openai-openapi-operations.json +224 -1334
@@ -0,0 +1,256 @@
1
+ // THE GENERATIVE-STUB CORE (the twin's answer to generation).
2
+ //
3
+ // The twin CANNOT run the model — there are no weights here. So `POST /v1/chat/completions`
4
+ // and `POST /v1/responses` return a DETERMINISTIC STUB completion that is CLEARLY a twin stub,
5
+ // NEVER pretending to be real model output. `POST /v1/embeddings` returns DETERMINISTIC
6
+ // pseudo-vectors (seeded from the input hash) — never real embedding values. What IS faithful
7
+ // is the ENTIRE PROTOCOL ENVELOPE: the response shape, streaming chunk sequence, tool_calls
8
+ // shape, finish_reason, and deterministic token counts.
9
+ //
10
+ // Pinned by `openai.chat.stub_labeled` and `openai.embeddings.deterministic` in the manifest.
11
+ // capability manifest and the README ## Coverage: the protocol is real; the generation is a stub.
12
+ /** Deterministic token estimate for a string: ~1 token per 4 chars (faithful order of
13
+ * magnitude; deterministic so usage counts are assertable, like the vendor's tokenizer on a
14
+ * fixed input). Never zero for non-empty text. */
15
+ export function estimateTokens(text) {
16
+ if (!text)
17
+ return 0;
18
+ return Math.max(1, Math.ceil(text.length / 4));
19
+ }
20
+ /** Flatten a chat message's content (string OR content-part array) to its text for token
21
+ * counting / echo. Non-text parts contribute their JSON length so the count is deterministic
22
+ * and reflects payload size. */
23
+ export function contentToText(content) {
24
+ if (typeof content === 'string')
25
+ return content;
26
+ if (content === null || content === undefined)
27
+ return '';
28
+ if (!Array.isArray(content))
29
+ return '';
30
+ return content
31
+ .map((part) => {
32
+ if (part && typeof part === 'object' && part.type === 'text') {
33
+ return String(part.text ?? '');
34
+ }
35
+ return JSON.stringify(part);
36
+ })
37
+ .join('\n');
38
+ }
39
+ /** Deterministic prompt-token count for the full set of messages. */
40
+ export function countPromptTokens(messages) {
41
+ let total = 0;
42
+ for (const m of messages) {
43
+ total += estimateTokens(contentToText(m.content));
44
+ if (m.name)
45
+ total += estimateTokens(m.name);
46
+ for (const tc of m.tool_calls ?? [])
47
+ total += estimateTokens(JSON.stringify(tc));
48
+ }
49
+ return total;
50
+ }
51
+ /** The last user turn's text — the thing the stub echoes (deterministic, clearly labeled). */
52
+ export function lastUserText(messages) {
53
+ // No user turn (e.g. only system) → fall back to the last message's text.
54
+ const turn = [...messages].reverse().find((m) => m.role === 'user') ?? messages.at(-1);
55
+ return turn ? contentToText(turn.content) : '';
56
+ }
57
+ /**
58
+ * Build the deterministic stub ASSISTANT text. It is unmistakably a twin stub: it carries the
59
+ * `[twin-stub:<model>]` marker and echoes the prompt, so no caller can mistake it for real
60
+ * model output. Deterministic for a given prompt → assertable in tests.
61
+ */
62
+ export function stubAssistantText(messages, model) {
63
+ const prompt = lastUserText(messages).trim();
64
+ const echo = prompt.length > 200 ? `${prompt.slice(0, 200)}…` : prompt;
65
+ return `[twin-stub:${model}] This is a deterministic stub from the OpenAI twin (no model weights are run). Echoing your last message: ${echo || '(empty)'}`;
66
+ }
67
+ /** Extract a tool's function name from either a Chat Completions tool
68
+ * (`{ type:'function', function:{ name } }`) or a legacy `functions` entry (`{ name }`). */
69
+ function toolName(t) {
70
+ const o = t;
71
+ return o?.function && typeof o.function.name === 'string' ? o.function.name : typeof o?.name === 'string' ? o.name : 'unknown_function';
72
+ }
73
+ function placeholderForSchema(def) {
74
+ const d = def;
75
+ if (Array.isArray(d?.enum) && d.enum.length)
76
+ return d.enum[0];
77
+ switch (d?.type) {
78
+ case 'number':
79
+ case 'integer': return 0;
80
+ case 'boolean': return false;
81
+ case 'array': return [];
82
+ case 'object': return {};
83
+ default: return '';
84
+ }
85
+ }
86
+ // Words that name nothing a tool could take as a value.
87
+ const STOPWORDS = new Set(['a', 'an', 'the', 'do', 'does', 'you', 'your', 'have', 'has', 'is', 'are', 'for', 'me', 'my', 'please', 'this', 'that', 'one', 'like', 'with', 'what', 'which', 'how', 'can', 'could', 'would', 'and', 'or', 'of', 'to', 'in', 'on', 'it', 'its', 'small', 'large', 'big', 'some', 'any']);
88
+ /** The value the user's message names for a string parameter: its longest content word (the thing asked about: "Do you
89
+ * have a small monstera?" names `monstera`), or none. */
90
+ function namedIn(userText) {
91
+ // a link the message carries (an image's URL) is not what it asks about
92
+ const words = (userText.replace(/\S+:\/\/\S+/g, ' ').toLowerCase().match(/[a-z][a-z0-9-]*/g) ?? []).filter((w) => !STOPWORDS.has(w));
93
+ return words.reduce((best, w) => (!best || w.length > best.length ? w : best), undefined);
94
+ }
95
+ /**
96
+ * Build a deterministic stub argument string for a tool. When the tool declares a JSON-schema
97
+ * `parameters` object (especially with `strict:true`), real models emit arguments that validate
98
+ * against the schema; the twin synthesizes a deterministic object containing every declared
99
+ * property with a type-appropriate placeholder so `strict` callers parse it cleanly. A required
100
+ * string is never left empty: an empty value is schema-valid in type but names nothing, so an
101
+ * application's tool could not act on it and a story could not follow the call. It takes what the
102
+ * user's message names (`namedIn`), else a deterministic value of the parameter's own name; an enum
103
+ * takes the value the message names, else its first.
104
+ */
105
+ export function stubToolArguments(tool, userText = '') {
106
+ const o = tool;
107
+ const schema = (o?.function?.parameters ?? o?.parameters);
108
+ const props = schema && typeof schema === 'object' ? schema.properties : undefined;
109
+ if (!props || typeof props !== 'object')
110
+ return '{}';
111
+ const required = new Set(Array.isArray(schema.required) ? schema.required.map(String) : []);
112
+ const words = new Set(userText.toLowerCase().match(/[a-z0-9-]+/g) ?? []);
113
+ const out = {};
114
+ for (const [key, def] of Object.entries(props)) {
115
+ const d = def;
116
+ const named = Array.isArray(d?.enum) ? d.enum.find((e) => typeof e === 'string' && words.has(e.toLowerCase())) : undefined;
117
+ out[key] = named ?? (required.has(key) && d?.type === 'string' && !Array.isArray(d?.enum) ? namedIn(userText) ?? `twin-${key}` : placeholderForSchema(def));
118
+ }
119
+ return JSON.stringify(out);
120
+ }
121
+ /**
122
+ * When tools/functions are provided, real models may respond with `tool_calls` and
123
+ * `finish_reason:'tool_calls'`. The stub deterministically "calls" the tool selected by
124
+ * `forcedName` (a named tool_choice) or the FIRST provided tool. Arguments are synthesized
125
+ * from the tool's JSON schema so strict callers parse them — clearly a stub, but a
126
+ * vendor-faithful tool_calls envelope. Returns the tool_call, or null when no tools provided.
127
+ */
128
+ export function stubToolCall(tools, seq, forcedName, userText = '') {
129
+ if (!Array.isArray(tools) || tools.length === 0)
130
+ return null;
131
+ const chosen = forcedName ? (tools.find((t) => toolName(t) === forcedName) ?? tools[0]) : tools[0];
132
+ return { id: `call_twin_${seq}`, type: 'function', function: { name: toolName(chosen), arguments: stubToolArguments(chosen, userText) } };
133
+ }
134
+ /**
135
+ * Build a deterministic JSON-object stub for `response_format` json_object / json_schema. The
136
+ * model's job is to emit parseable JSON; the twin returns a clearly-labeled deterministic object
137
+ * (and, for json_schema, fills every declared property with a schema-typed placeholder so the
138
+ * caller's strict parse succeeds). Always valid JSON.
139
+ */
140
+ export function stubJsonObject(messages, model, jsonSchema) {
141
+ const schema = jsonSchema;
142
+ const props = schema?.schema?.properties ?? schema?.properties;
143
+ if (!props || typeof props !== 'object')
144
+ return freeformJson(messages, model);
145
+ const out = {};
146
+ for (const [key, def] of Object.entries(props))
147
+ out[key] = placeholderForSchema(def);
148
+ return JSON.stringify(out);
149
+ }
150
+ /** JSON mode with no schema (`json_object`): a labeled object echoing the request. */
151
+ function freeformJson(messages, model) {
152
+ return JSON.stringify({ _twin_stub: true, model, echo: lastUserText(messages).slice(0, 200) });
153
+ }
154
+ /** A whitespace-preserving token split: deterministic chunks of the stub text whose `token`
155
+ * fields re-join into the exact text. NOT a real BPE tokenizer. */
156
+ function splitForLogprobs(text) {
157
+ if (!text)
158
+ return [];
159
+ return text.match(/\s+|\S+/g) ?? [];
160
+ }
161
+ /**
162
+ * Build deterministic pseudo-logprobs for the stub completion text. Real models return a per-token
163
+ * logprob (a negative number) plus `top_logprobs` alternatives; the twin synthesizes a deterministic
164
+ * negative logprob per token (seeded from the token text) and `topN` alternatives. NOT real
165
+ * probabilities — the SHAPE and DETERMINISM are faithful, the values carry no model meaning.
166
+ * Re-joining `content[].token` reconstructs the full text exactly.
167
+ */
168
+ export function buildLogprobs(text, topN) {
169
+ const tokens = splitForLogprobs(text);
170
+ const content = tokens.map((tok) => {
171
+ const seed = fnv1a(tok);
172
+ const lp = -((seed % 5000) / 1000); // deterministic logprob in (-5, 0]
173
+ const bytes = Array.from(new TextEncoder().encode(tok));
174
+ // the token itself, then `topN` labeled alternatives, each less likely
175
+ const top = [{ token: tok, logprob: lp, bytes }, ...Array.from({ length: topN }, (_, i) => ({ token: `«alt${i}»`, logprob: lp - 1 - (fnv1a(`${tok}#${i}`) % 3000) / 1000, bytes: [] }))];
176
+ return { token: tok, logprob: lp, bytes, top_logprobs: top.slice(0, Math.max(1, topN)) };
177
+ });
178
+ return { content };
179
+ }
180
+ // ── Embeddings: deterministic pseudo-vectors ────────────────────────────────────────────
181
+ /** A small deterministic 32-bit hash (FNV-1a) of a string — the seed for a pseudo-vector. */
182
+ function fnv1a(text) {
183
+ let h = 0x811c9dc5;
184
+ for (let i = 0; i < text.length; i++) {
185
+ h ^= text.charCodeAt(i);
186
+ h = Math.imul(h, 0x01000193);
187
+ }
188
+ return h >>> 0;
189
+ }
190
+ /** A deterministic, reproducible L2-normalized pseudo-embedding vector for `text`. NOT a real
191
+ * embedding — the values carry no semantic meaning; only the SHAPE and DETERMINISM are
192
+ * faithful (`openai.embeddings.deterministic`). Same text → same vector. */
193
+ export function pseudoEmbedding(text, dimensions) {
194
+ const dim = Math.max(1, Math.floor(dimensions));
195
+ let state = fnv1a(text) || 1;
196
+ const raw = new Array(dim);
197
+ let norm = 0;
198
+ for (let i = 0; i < dim; i++) {
199
+ // xorshift32 PRNG seeded from the text hash → deterministic per (text, index).
200
+ state ^= state << 13;
201
+ state >>>= 0;
202
+ state ^= state >> 17;
203
+ state ^= state << 5;
204
+ state >>>= 0;
205
+ // map to [-1, 1)
206
+ const v = (state / 0xffffffff) * 2 - 1;
207
+ raw[i] = v;
208
+ norm += v * v;
209
+ }
210
+ norm = Math.sqrt(norm) || 1;
211
+ for (let i = 0; i < dim; i++)
212
+ raw[i] = raw[i] / norm;
213
+ return raw;
214
+ }
215
+ // ── Moderations: deterministic classifier ───────────────────────────────────────────────
216
+ /** The moderation categories the twin reports (faithful key set). */
217
+ export const MODERATION_CATEGORIES = [
218
+ 'hate', 'hate/threatening', 'harassment', 'harassment/threatening', 'illicit', 'illicit/violent',
219
+ 'self-harm', 'self-harm/intent', 'self-harm/instructions',
220
+ 'sexual', 'sexual/minors', 'violence', 'violence/graphic',
221
+ ];
222
+ /** The categories an image is classified in besides text (the spec's `category_applied_input_types`; the moderation
223
+ * guide, https://platform.openai.com/docs/guides/moderation). */
224
+ const IMAGE_CATEGORIES = new Set(['self-harm', 'self-harm/intent', 'self-harm/instructions', 'sexual', 'violence', 'violence/graphic']);
225
+ // Deterministic keyword → category map. The twin can't run the real classifier, so it flags
226
+ // on a fixed keyword list (clearly a heuristic). Shape is faithful; the decision is a stub.
227
+ const MODERATION_KEYWORDS = {
228
+ kill: 'violence', murder: 'violence', attack: 'violence',
229
+ hate: 'hate', hateful: 'hate',
230
+ harass: 'harassment',
231
+ suicide: 'self-harm', 'self-harm': 'self-harm',
232
+ };
233
+ /** Deterministically moderate one input (its text, and whether it held an image) into the faithful result shape: each
234
+ * category with the input types its score applies to, as every result carries them
235
+ * (https://platform.openai.com/docs/api-reference/moderations/object). */
236
+ export function moderateText(text, image = false) {
237
+ const lower = text.toLowerCase();
238
+ const categories = {};
239
+ const scores = {};
240
+ for (const c of MODERATION_CATEGORIES) {
241
+ categories[c] = false;
242
+ scores[c] = 0;
243
+ }
244
+ let flagged = false;
245
+ for (const [kw, cat] of Object.entries(MODERATION_KEYWORDS)) {
246
+ if (lower.includes(kw)) {
247
+ categories[cat] = true;
248
+ scores[cat] = 0.99;
249
+ flagged = true;
250
+ }
251
+ }
252
+ const applied = {};
253
+ for (const c of MODERATION_CATEGORIES)
254
+ applied[c] = image && IMAGE_CATEGORIES.has(c) ? ['text', 'image'] : ['text'];
255
+ return { flagged, categories, category_scores: scores, category_applied_input_types: applied };
256
+ }
@@ -0,0 +1,182 @@
1
+ import { type ScenarioDecision } from '@volter/world-core';
2
+ import { type OpenAIScenarioEngine } from './openai-scenario.js';
3
+ import type { ChatCompletion, ChatMessageParam, OpenAIResponse, SseSink } from './openai-types.js';
4
+ export type OpenAIRequest = {
5
+ /** The scenario engine (kernel grammar + this pack's vocabulary) — scripts chat turns. */
6
+ scenarioEngine?: OpenAIScenarioEngine;
7
+ method: string;
8
+ path: string;
9
+ /** JSON text, or a multipart form (an upload) */
10
+ body?: string | FormData;
11
+ occurredAt?: string;
12
+ root?: string;
13
+ readOnly?: boolean;
14
+ /** The credential the caller presents (the SDK's bearer `Authorization` header). When a request
15
+ * carries an auth SURFACE (this field set, or `headers` present), the twin holds it to the real
16
+ * vendor rule: a credential is required → 401 on missing/invalid. In-process trusted calls
17
+ * (capability verify, connector) omit BOTH and are not auth-gated — the twin can't validate
18
+ * against real keys, so the modeled failure is the CHECKABLE missing/sentinel-invalid case. */
19
+ apiKey?: string;
20
+ /** Lower-cased request headers (e.g. `authorization`) the HTTP server passes through so the
21
+ * handler can model auth (401) and the rate-limit trigger (429). */
22
+ headers?: Record<string, string>;
23
+ /** When set on a streaming POST, chunks are written here (no sockets). */
24
+ sseSink?: SseSink;
25
+ };
26
+ /** The handler response. `headers` (when present) are response headers the HTTP server should set
27
+ * — e.g. `Retry-After` + the `x-ratelimit-*` family on a modeled 429. */
28
+ export type OpenAIResponseEnvelope = {
29
+ status: number;
30
+ body: unknown;
31
+ headers?: Record<string, string>;
32
+ };
33
+ export declare function usageCost(model: string, inputTokens: number, outputTokens: number): number;
34
+ /** The query a report answers: its window, bucket width, page size, where the page starts, and its groups. */
35
+ export type ReportQuery = {
36
+ start: number;
37
+ end: number;
38
+ width: number;
39
+ limit: number;
40
+ from: number;
41
+ groupBy: string[];
42
+ };
43
+ /** A report's query from its parameters; Costs buckets by day only and pages up to 180 of them. */
44
+ export declare function reportQuery(params: Record<string, unknown>, now: number, costs: boolean): ReportQuery;
45
+ /** Buckets of one page of the query, each holding `results(rows in its window)`. */
46
+ export declare function bucketed(records: Array<Record<string, unknown>>, q: ReportQuery, results: (rows: Array<Record<string, unknown>>, at: number, until: number) => unknown[]): Record<string, unknown>;
47
+ /** The usage report over the recorded usage rows, for one kind ('completions', 'embeddings', …). */
48
+ export declare function usageReport(records: Array<Record<string, unknown>>, kind: string | undefined, q: ReportQuery): Record<string, unknown>;
49
+ /** The costs report: the recorded usage priced per model, per bucket, by line item (usage kind) when grouped so. */
50
+ export declare function costsReport(records: Array<Record<string, unknown>>, q: ReportQuery): Record<string, unknown>;
51
+ type ToolChoice = 'auto' | 'none' | 'required' | {
52
+ name: string;
53
+ };
54
+ type ResponseFormat = {
55
+ kind: 'text';
56
+ } | {
57
+ kind: 'json_object';
58
+ } | {
59
+ kind: 'json_schema';
60
+ schema: unknown;
61
+ };
62
+ type ChatArgs = {
63
+ model: string;
64
+ messages: ChatMessageParam[];
65
+ tools?: unknown;
66
+ functions?: unknown;
67
+ n: number;
68
+ maxTokens?: number;
69
+ stop?: string[];
70
+ stream: boolean;
71
+ toolChoice?: ToolChoice;
72
+ parallelToolCalls: boolean;
73
+ responseFormat: ResponseFormat;
74
+ includeUsage: boolean;
75
+ logprobs: boolean;
76
+ topLogprobs?: number;
77
+ seed?: number;
78
+ logitBias?: Record<string, number>;
79
+ prediction?: string;
80
+ /** the reasoning effort a reasoning model spends (the request's `reasoning_effort`, else the default) */
81
+ reasoningEffort?: string;
82
+ store: boolean;
83
+ metadata?: Record<string, unknown>;
84
+ /** modalities: ['text'] (default) or ['text','audio'] — audio asks for a spoken output. */
85
+ audioOutput?: {
86
+ voice: string;
87
+ format: string;
88
+ };
89
+ };
90
+ export declare function validateChat(params: Record<string, unknown>): {
91
+ args: ChatArgs;
92
+ } | {
93
+ error: OpenAIResponseEnvelope;
94
+ };
95
+ export declare function buildChatCompletion(args: ChatArgs, occurredAt?: string, decision?: ScenarioDecision): ChatCompletion;
96
+ /**
97
+ * Emit the vendor-faithful Chat Completions streaming sequence into the injected sink (NO
98
+ * sockets, NO setTimeout). Real order: a first chunk with `delta:{role:'assistant'}`, then
99
+ * `delta:{content}` chunks (or tool_calls deltas), then a final chunk with `finish_reason`,
100
+ * then `[DONE]`. Deterministic + synchronous so a collector can assert the full sequence.
101
+ */
102
+ export declare function streamChat(args: ChatArgs, sink: SseSink, occurredAt?: string, decision?: ScenarioDecision): ChatCompletion;
103
+ export type ResponsesArgs = {
104
+ inputItems: Record<string, unknown>[];
105
+ instructions?: string;
106
+ model: string;
107
+ inputText: string;
108
+ messages: ChatMessageParam[];
109
+ stream: boolean;
110
+ store: boolean;
111
+ previousResponseId?: string;
112
+ /** reasoning.effort ('minimal'|'low'|'medium'|'high') → emit a reasoning item + reasoning_tokens. */
113
+ reasoningEffort?: string;
114
+ /** background:true → the response is created `queued` and processed asynchronously (poll + cancel). */
115
+ background: boolean;
116
+ /** The caller's tools, as sent (Responses shape `{type:'function', name, parameters}`); the scenario reads their names. */
117
+ tools?: unknown;
118
+ /** max_output_tokens, when sent — the output cap a scenario handler may key on. */
119
+ maxTokens?: number;
120
+ /** the request's settings a response answers back as sent, or OpenAI's defaults */
121
+ echo: {
122
+ metadata: unknown;
123
+ temperature: unknown;
124
+ top_p: unknown;
125
+ parallel_tool_calls: unknown;
126
+ tool_choice: unknown;
127
+ text: unknown;
128
+ truncation: unknown;
129
+ user: unknown;
130
+ max_tool_calls: unknown;
131
+ top_logprobs: unknown;
132
+ service_tier: unknown;
133
+ };
134
+ /** reasoning.summary, when sent: a reasoning item carries a summary only when one is asked for */
135
+ reasoningSummary?: string;
136
+ };
137
+ export declare function responseMessages(items: Record<string, unknown>[], instructions?: string): ChatMessageParam[];
138
+ export declare function validateResponses(params: Record<string, unknown>): {
139
+ args: ResponsesArgs;
140
+ } | {
141
+ error: OpenAIResponseEnvelope;
142
+ };
143
+ export declare function buildResponse(args: ResponsesArgs, occurredAt?: string, idSuffix?: string, decision?: ScenarioDecision): OpenAIResponse;
144
+ export declare function responseSuffix(args: ResponsesArgs): string;
145
+ export declare function createStoredResponse(args: ResponsesArgs, req: OpenAIRequest, decision?: ScenarioDecision): Promise<OpenAIResponse | Record<string, unknown>>;
146
+ export declare function emitResponse(resp: OpenAIResponse, sink: SseSink): OpenAIResponse;
147
+ export declare function handleEmbeddings(params: Record<string, unknown>): OpenAIResponseEnvelope;
148
+ export declare function handleModerations(params: Record<string, unknown>, occurredAt?: string): OpenAIResponseEnvelope;
149
+ /** An image answer, and its events when streamed. */
150
+ export type ImageAnswer = OpenAIResponseEnvelope & {
151
+ events?: Array<{
152
+ event: string;
153
+ data: unknown;
154
+ }>;
155
+ };
156
+ export declare function handleImages(params: Record<string, unknown>, occurredAt?: string): ImageAnswer;
157
+ export declare function handleImageEdit(params: Record<string, unknown>, occurredAt?: string): ImageAnswer;
158
+ export declare function handleImageVariation(params: Record<string, unknown>, occurredAt?: string): OpenAIResponseEnvelope;
159
+ export type Transcription = {
160
+ status: 200;
161
+ body: unknown;
162
+ seconds: number;
163
+ events?: Array<{
164
+ data: unknown;
165
+ }>;
166
+ } | OpenAIResponseEnvelope;
167
+ /** A transcription (or translation) of the uploaded audio: a labeled transcript naming the file, in the response
168
+ * format asked (json, text, verbose_json with the word or segment timestamps asked, or diarized_json), with the
169
+ * token log probabilities when `include[]=logprobs`, and as `transcript.text.delta` events then
170
+ * `transcript.text.done` when `stream=true` on a model that streams (https://platform.openai.com/docs/api-reference/audio/createTranscription). */
171
+ export declare function handleTranscription(params: Record<string, unknown>, translate: boolean): Transcription;
172
+ export declare function handleSpeech(params: Record<string, unknown>): OpenAIResponseEnvelope | {
173
+ status: 200;
174
+ audio: Uint8Array;
175
+ type: string;
176
+ };
177
+ /** OpenAI's API as one call: the request goes through the pack's own dispatch (the derived dispatch over its
178
+ * semantics, its derived core and the hand-written routes below), so a caller that holds no HTTP
179
+ * server reaches exactly what an SDK does. A caller that presents no credential is a trusted
180
+ * in-process caller and is not held to the auth rule; `sseSink` receives a stream's events. */
181
+ export declare function handleOpenAITwinRequest(req: OpenAIRequest): Promise<OpenAIResponseEnvelope>;
182
+ export {};