@copilotkit/aimock 1.46.0 → 1.47.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.
Files changed (183) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +12 -1
  4. package/README.md +4 -1
  5. package/dist/aws-event-stream.cjs +20 -23
  6. package/dist/aws-event-stream.cjs.map +1 -1
  7. package/dist/aws-event-stream.d.cts +0 -1
  8. package/dist/aws-event-stream.d.ts +0 -1
  9. package/dist/aws-event-stream.js +20 -24
  10. package/dist/aws-event-stream.js.map +1 -1
  11. package/dist/bedrock-converse.cjs +79 -82
  12. package/dist/bedrock-converse.cjs.map +1 -1
  13. package/dist/bedrock-converse.d.cts.map +1 -1
  14. package/dist/bedrock-converse.d.ts.map +1 -1
  15. package/dist/bedrock-converse.js +80 -83
  16. package/dist/bedrock-converse.js.map +1 -1
  17. package/dist/bedrock.cjs +112 -121
  18. package/dist/bedrock.cjs.map +1 -1
  19. package/dist/bedrock.d.cts.map +1 -1
  20. package/dist/bedrock.d.ts.map +1 -1
  21. package/dist/bedrock.js +113 -122
  22. package/dist/bedrock.js.map +1 -1
  23. package/dist/cohere.cjs +58 -106
  24. package/dist/cohere.cjs.map +1 -1
  25. package/dist/cohere.d.cts.map +1 -1
  26. package/dist/cohere.d.ts.map +1 -1
  27. package/dist/cohere.js +60 -108
  28. package/dist/cohere.js.map +1 -1
  29. package/dist/config-loader.d.cts.map +1 -1
  30. package/dist/config-loader.d.ts.map +1 -1
  31. package/dist/fixture-loader.cjs +103 -2
  32. package/dist/fixture-loader.cjs.map +1 -1
  33. package/dist/fixture-loader.d.cts.map +1 -1
  34. package/dist/fixture-loader.d.ts.map +1 -1
  35. package/dist/fixture-loader.js +104 -4
  36. package/dist/fixture-loader.js.map +1 -1
  37. package/dist/gemini-interactions.cjs +63 -121
  38. package/dist/gemini-interactions.cjs.map +1 -1
  39. package/dist/gemini-interactions.d.cts.map +1 -1
  40. package/dist/gemini-interactions.d.ts.map +1 -1
  41. package/dist/gemini-interactions.js +65 -123
  42. package/dist/gemini-interactions.js.map +1 -1
  43. package/dist/gemini.cjs +242 -155
  44. package/dist/gemini.cjs.map +1 -1
  45. package/dist/gemini.d.cts.map +1 -1
  46. package/dist/gemini.d.ts.map +1 -1
  47. package/dist/gemini.js +244 -157
  48. package/dist/gemini.js.map +1 -1
  49. package/dist/helpers.cjs +400 -7
  50. package/dist/helpers.cjs.map +1 -1
  51. package/dist/helpers.d.cts +51 -1
  52. package/dist/helpers.d.cts.map +1 -1
  53. package/dist/helpers.d.ts +51 -1
  54. package/dist/helpers.d.ts.map +1 -1
  55. package/dist/helpers.js +391 -8
  56. package/dist/helpers.js.map +1 -1
  57. package/dist/index.cjs +4 -2
  58. package/dist/index.d.cts +4 -4
  59. package/dist/index.d.ts +4 -4
  60. package/dist/index.js +4 -4
  61. package/dist/mcp-fakes.d.cts.map +1 -1
  62. package/dist/messages.cjs +159 -141
  63. package/dist/messages.cjs.map +1 -1
  64. package/dist/messages.d.cts.map +1 -1
  65. package/dist/messages.d.ts.map +1 -1
  66. package/dist/messages.js +161 -143
  67. package/dist/messages.js.map +1 -1
  68. package/dist/misbehavior.cjs +62 -3
  69. package/dist/misbehavior.cjs.map +1 -1
  70. package/dist/misbehavior.d.cts.map +1 -1
  71. package/dist/misbehavior.d.ts.map +1 -1
  72. package/dist/misbehavior.js +63 -4
  73. package/dist/misbehavior.js.map +1 -1
  74. package/dist/ndjson-writer.cjs +21 -23
  75. package/dist/ndjson-writer.cjs.map +1 -1
  76. package/dist/ndjson-writer.d.cts +0 -1
  77. package/dist/ndjson-writer.d.cts.map +1 -1
  78. package/dist/ndjson-writer.d.ts +0 -1
  79. package/dist/ndjson-writer.d.ts.map +1 -1
  80. package/dist/ndjson-writer.js +21 -24
  81. package/dist/ndjson-writer.js.map +1 -1
  82. package/dist/ollama.cjs +153 -147
  83. package/dist/ollama.cjs.map +1 -1
  84. package/dist/ollama.d.cts.map +1 -1
  85. package/dist/ollama.d.ts.map +1 -1
  86. package/dist/ollama.js +155 -149
  87. package/dist/ollama.js.map +1 -1
  88. package/dist/openrouter-chat.cjs +1 -1
  89. package/dist/openrouter-chat.js +1 -1
  90. package/dist/raw-validation.cjs +177 -0
  91. package/dist/raw-validation.cjs.map +1 -0
  92. package/dist/raw-validation.js +176 -0
  93. package/dist/raw-validation.js.map +1 -0
  94. package/dist/response/deliver.cjs +144 -0
  95. package/dist/response/deliver.cjs.map +1 -0
  96. package/dist/response/deliver.js +143 -0
  97. package/dist/response/deliver.js.map +1 -0
  98. package/dist/response/frame-loop.cjs +102 -0
  99. package/dist/response/frame-loop.cjs.map +1 -0
  100. package/dist/response/frame-loop.js +100 -0
  101. package/dist/response/frame-loop.js.map +1 -0
  102. package/dist/response/lifecycle.cjs +223 -0
  103. package/dist/response/lifecycle.cjs.map +1 -0
  104. package/dist/response/lifecycle.js +211 -0
  105. package/dist/response/lifecycle.js.map +1 -0
  106. package/dist/response/proxy.cjs +71 -0
  107. package/dist/response/proxy.cjs.map +1 -0
  108. package/dist/response/proxy.js +70 -0
  109. package/dist/response/proxy.js.map +1 -0
  110. package/dist/response/raw.cjs +228 -0
  111. package/dist/response/raw.cjs.map +1 -0
  112. package/dist/response/raw.js +227 -0
  113. package/dist/response/raw.js.map +1 -0
  114. package/dist/response/timing.cjs +157 -0
  115. package/dist/response/timing.cjs.map +1 -0
  116. package/dist/response/timing.js +150 -0
  117. package/dist/response/timing.js.map +1 -0
  118. package/dist/responses.cjs +269 -148
  119. package/dist/responses.cjs.map +1 -1
  120. package/dist/responses.d.cts.map +1 -1
  121. package/dist/responses.d.ts.map +1 -1
  122. package/dist/responses.js +270 -149
  123. package/dist/responses.js.map +1 -1
  124. package/dist/router.cjs +20 -4
  125. package/dist/router.cjs.map +1 -1
  126. package/dist/router.d.cts +7 -1
  127. package/dist/router.d.cts.map +1 -1
  128. package/dist/router.d.ts +7 -1
  129. package/dist/router.d.ts.map +1 -1
  130. package/dist/router.js +21 -5
  131. package/dist/router.js.map +1 -1
  132. package/dist/server.cjs +89 -120
  133. package/dist/server.cjs.map +1 -1
  134. package/dist/server.d.cts.map +1 -1
  135. package/dist/server.d.ts.map +1 -1
  136. package/dist/server.js +91 -122
  137. package/dist/server.js.map +1 -1
  138. package/dist/sse-writer.cjs +49 -30
  139. package/dist/sse-writer.cjs.map +1 -1
  140. package/dist/sse-writer.d.cts +0 -1
  141. package/dist/sse-writer.d.cts.map +1 -1
  142. package/dist/sse-writer.d.ts +0 -1
  143. package/dist/sse-writer.d.ts.map +1 -1
  144. package/dist/sse-writer.js +49 -31
  145. package/dist/sse-writer.js.map +1 -1
  146. package/dist/transcription.cjs +19 -33
  147. package/dist/transcription.cjs.map +1 -1
  148. package/dist/transcription.d.cts.map +1 -1
  149. package/dist/transcription.d.ts.map +1 -1
  150. package/dist/transcription.js +20 -34
  151. package/dist/transcription.js.map +1 -1
  152. package/dist/types.cjs.map +1 -1
  153. package/dist/types.d.cts +173 -10
  154. package/dist/types.d.cts.map +1 -1
  155. package/dist/types.d.ts +173 -10
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js.map +1 -1
  158. package/dist/ws-framing.cjs +12 -6
  159. package/dist/ws-framing.cjs.map +1 -1
  160. package/dist/ws-framing.d.cts +2 -0
  161. package/dist/ws-framing.d.cts.map +1 -1
  162. package/dist/ws-framing.d.ts +2 -0
  163. package/dist/ws-framing.d.ts.map +1 -1
  164. package/dist/ws-framing.js +12 -6
  165. package/dist/ws-framing.js.map +1 -1
  166. package/dist/ws-gemini-live.cjs +120 -93
  167. package/dist/ws-gemini-live.cjs.map +1 -1
  168. package/dist/ws-gemini-live.js +120 -93
  169. package/dist/ws-gemini-live.js.map +1 -1
  170. package/dist/ws-realtime.cjs +80 -70
  171. package/dist/ws-realtime.cjs.map +1 -1
  172. package/dist/ws-realtime.d.cts.map +1 -1
  173. package/dist/ws-realtime.d.ts.map +1 -1
  174. package/dist/ws-realtime.js +80 -70
  175. package/dist/ws-realtime.js.map +1 -1
  176. package/dist/ws-responses.cjs +72 -53
  177. package/dist/ws-responses.cjs.map +1 -1
  178. package/dist/ws-responses.d.cts.map +1 -1
  179. package/dist/ws-responses.d.ts.map +1 -1
  180. package/dist/ws-responses.js +73 -54
  181. package/dist/ws-responses.js.map +1 -1
  182. package/package.json +1 -1
  183. package/skills/write-fixtures/SKILL.md +34 -2
package/dist/helpers.cjs CHANGED
@@ -216,12 +216,331 @@ function requireEmittedFunctionToolCalls(response, wire) {
216
216
  if (Array.isArray(response.blocks) && response.blocks.length > 0) return [];
217
217
  return requireFunctionToolCalls(response.toolCalls ?? []);
218
218
  }
219
- /** True for the fixture tool-call errors a handler surfaces as a coded 500. */
219
+ /**
220
+ * A fixture's model-generated `files` reached a wire that has no faithful
221
+ * projection for them. Gemini (`inlineData` parts) and the OpenAI Responses
222
+ * API (`image_generation_call` items, `image/*` only) serve generated files.
223
+ * Chat Completions (OpenAI, Azure OpenAI, OpenRouter and BytePlus ModelArk)
224
+ * and Anthropic Messages raise this through {@link rejectGeneratedFiles}, and
225
+ * the Responses API raises a subclass for a non-`image/*` file, before any
226
+ * content is written. The handler surfaces it as a coded 500
227
+ * (`aimock_unsupported_generated_file`) like {@link UnsupportedToolCallError}.
228
+ */
229
+ var UnsupportedGeneratedFileError = class extends Error {
230
+ wire;
231
+ constructor(wire) {
232
+ super(`aimock: fixture "files" (model-generated files) are only supported on Gemini and the OpenAI Responses API; this request arrived on ${wire}`);
233
+ this.name = "UnsupportedGeneratedFileError";
234
+ this.wire = wire;
235
+ }
236
+ };
237
+ /**
238
+ * Reject a fixture that sets a non-empty `files` on a wire that cannot carry
239
+ * model-generated files. Throws {@link UnsupportedGeneratedFileError} naming
240
+ * `wire`. A malformed `files` value (see {@link fixtureFiles}) throws
241
+ * {@link InvalidFixtureOutputError} (`aimock_invalid_fixture_file`) first.
242
+ * Chat Completions and Anthropic Messages call this. Gemini and OpenAI
243
+ * Responses serve `files`; other wires ignore the field.
244
+ */
245
+ function rejectGeneratedFiles(response, wire) {
246
+ if (fixtureFiles(response.files).length > 0) throw new UnsupportedGeneratedFileError(wire);
247
+ }
248
+ /**
249
+ * A fixture's `citations` or `files` is malformed: not an array, an entry
250
+ * missing a required field, a non-integer offset, citation offsets outside
251
+ * `0 <= startIndex <= endIndex <= text.length`, a file citation `index`
252
+ * outside `0 <= index <= text.length`, an offset or `index` that splits a
253
+ * UTF-16 surrogate pair, a citation that mixes URL and file citation fields,
254
+ * or any `citations` but an empty array on a turn with no text (tool calls
255
+ * only, `blocks` with no text block, or empty content). The wires that read
256
+ * these keys (Chat Completions, Anthropic Messages, Gemini and OpenAI
257
+ * Responses) read them
258
+ * through {@link fixtureFiles} and {@link fixtureCitations}, which throw this
259
+ * at serve time, before any byte is written; the handler surfaces it as a
260
+ * coded 500 like {@link UnsupportedGeneratedFileError}. Other wires ignore
261
+ * `citations` and `files`, so they never throw this. The message names the
262
+ * offending entry and how to fix the fixture.
263
+ */
264
+ var InvalidFixtureOutputError = class extends Error {
265
+ code;
266
+ constructor(code, problem) {
267
+ super(`aimock: invalid fixture "${code === "aimock_invalid_fixture_file" ? "files" : "citations"}": ${problem}`);
268
+ this.name = "InvalidFixtureOutputError";
269
+ this.code = code;
270
+ }
271
+ };
272
+ /**
273
+ * A short description of a fixture value for an error message. Never throws:
274
+ * a programmatic fixture can hold values JSON cannot carry (BigInt, Symbol,
275
+ * function, NaN, Infinity), and each gets a readable name.
276
+ */
277
+ function describeFixtureValue(value) {
278
+ if (value === void 0) return "undefined";
279
+ if (Array.isArray(value)) return "an array";
280
+ if (value !== null && typeof value === "object") return "an object";
281
+ if (typeof value === "function") return "a function";
282
+ if (typeof value === "symbol") return value.toString();
283
+ if (typeof value === "bigint") return `${value}n`;
284
+ if (typeof value === "number" && !Number.isFinite(value)) return String(value);
285
+ const json = JSON.stringify(value);
286
+ return json.length > 40 ? `${json.slice(0, 37)}...` : json;
287
+ }
288
+ /**
289
+ * The UTF-16 index `at` falls between the two halves of a surrogate pair, so
290
+ * a span that starts or ends there cuts a character in half.
291
+ */
292
+ function splitsSurrogatePair(text, at) {
293
+ if (at <= 0 || at >= text.length) return false;
294
+ const before = text.charCodeAt(at - 1);
295
+ const after = text.charCodeAt(at);
296
+ return before >= 55296 && before <= 56319 && after >= 56320 && after <= 57343;
297
+ }
298
+ /**
299
+ * `text` cut into slices of at most `chunkSize` UTF-16 units, never between
300
+ * the two halves of a surrogate pair: a cut that would land inside a pair
301
+ * moves back before it, or, when that would leave the slice empty
302
+ * (`chunkSize` 1), forward past it. The slices rejoin to `text` exactly.
303
+ */
304
+ function surrogateSafeChunks(text, chunkSize) {
305
+ const chunks = [];
306
+ for (let i = 0; i < text.length;) {
307
+ let end = Math.min(i + chunkSize, text.length);
308
+ const high = text.charCodeAt(end - 1);
309
+ const low = text.charCodeAt(end);
310
+ if (high >= 55296 && high <= 56319 && low >= 56320 && low <= 57343) end = end - 1 > i ? end - 1 : end + 1;
311
+ chunks.push(text.slice(i, end));
312
+ i = end;
313
+ }
314
+ return chunks;
315
+ }
316
+ /** The message for a citation offset that splits a surrogate pair. */
317
+ function surrogateSplitProblem(where, at) {
318
+ return `${where} ${at} splits a UTF-16 surrogate pair (one character, e.g. an emoji); fix the offset to fall before or after the whole character`;
319
+ }
320
+ /** Fields only a URL citation has. */
321
+ const URL_CITATION_FIELDS = [
322
+ "url",
323
+ "title",
324
+ "startIndex",
325
+ "endIndex"
326
+ ];
327
+ /** Fields only a file citation has. */
328
+ const FILE_CITATION_FIELDS = [
329
+ "fileId",
330
+ "filename",
331
+ "index"
332
+ ];
333
+ /**
334
+ * A citation that mixes URL and file citation fields, or a file-shaped one
335
+ * (`fileId` / `filename` / `index`) with no `type`, which would otherwise be
336
+ * read as a URL citation and lose its file data. Undefined when the fields
337
+ * agree.
338
+ */
339
+ function mixedCitationProblem(citation, i) {
340
+ const present = (names) => names.filter((n) => citation[n] != null);
341
+ const fileFields = present(FILE_CITATION_FIELDS);
342
+ if (fileFields.length === 0) return void 0;
343
+ const urlFields = present(URL_CITATION_FIELDS);
344
+ const fix = citation.type === "file" ? `remove the URL citation fields (${urlFields.join(", ")}) from this file citation` : citation.type === "url" ? `remove the file citation fields (${fileFields.join(", ")}) from this URL citation, or set type: "file" for a file citation` : `set type: "file" for a file citation, or remove the stray fields`;
345
+ if (urlFields.length > 0) return `citations[${i}] mixes URL citation fields (${urlFields.join(", ")}) with file citation fields (${fileFields.join(", ")}); ${fix}`;
346
+ if (citation.type == null) return `citations[${i}] has file citation fields (${fileFields.join(", ")}) but no type; ${fix}`;
347
+ if (citation.type === "url") return `citations[${i}] has type "url" but file citation fields (${fileFields.join(", ")}); ${fix}`;
348
+ }
349
+ /** "an object", "a string", ... for a value that should have been an array. */
350
+ function describeFixtureKind(value) {
351
+ if (value === null) return "null";
352
+ if (value !== null && typeof value === "object") return "an object";
353
+ return typeof value === "string" ? "a string" : `a ${typeof value}`;
354
+ }
355
+ /**
356
+ * What is wrong with a fixture `files` value, or undefined when it is absent
357
+ * or an array of `{ mimeType, data }` string pairs. Shared by the serve-time
358
+ * check ({@link fixtureFiles}) and fixture load validation.
359
+ */
360
+ function fixtureFilesProblem(files) {
361
+ if (files === void 0 || files === null) return void 0;
362
+ if (!Array.isArray(files)) return `"files" must be an array of { mimeType, data } objects, got ${describeFixtureKind(files)}; wrap a single file in [ ]`;
363
+ for (let i = 0; i < files.length; i++) {
364
+ const file = files[i];
365
+ if (file === null || typeof file !== "object" || Array.isArray(file)) return `files[${i}] must be a { mimeType, data } object, got ${describeFixtureValue(file)}`;
366
+ const { mimeType, data } = file;
367
+ if (typeof mimeType !== "string" || mimeType.length === 0) return `files[${i}].mimeType must be a non-empty string, got ${describeFixtureValue(mimeType)}; set the file's MIME type, e.g. "image/png"`;
368
+ if (typeof data !== "string") return `files[${i}].data must be a string, got ${describeFixtureValue(data)}; set the base64-encoded file bytes`;
369
+ }
370
+ }
371
+ /**
372
+ * What is wrong with a fixture `citations` value, or undefined when it is
373
+ * absent or valid: an array of citation objects. A URL citation (no `type`,
374
+ * or `type: "url"`) has a non-empty string `url`, an optional string `title`
375
+ * and optional integer offsets. A file citation (`type: "file"`) has a
376
+ * non-empty string `fileId` and `filename` and an optional integer `index`;
377
+ * it has no `url`. When `text` is given, the resolved offsets must also
378
+ * satisfy `0 <= startIndex <= endIndex <= text.length`, and a file citation's
379
+ * `index` must satisfy `0 <= index <= text.length`, and no offset may split a
380
+ * UTF-16 surrogate pair. A citation must not mix URL and file citation
381
+ * fields, and one with file fields needs `type: "file"`. A `null` value, or a
382
+ * `null` optional field, is treated as absent. An empty `text` is a turn with
383
+ * no text (tool calls only, `blocks` with no text block, or empty content): a
384
+ * well-formed non-empty array is then a problem, since there is nothing to
385
+ * cite; a malformed value is reported by its shape first. Shared by the
386
+ * serve-time check ({@link fixtureCitations}) and fixture load validation.
387
+ */
388
+ function fixtureCitationsProblem(citations, text) {
389
+ if (citations === void 0 || citations === null) return void 0;
390
+ if (!Array.isArray(citations)) return `"citations" must be an array of { url, title?, startIndex?, endIndex? } or { type: "file", fileId, filename, index? } objects, got ${describeFixtureKind(citations)}; wrap a single citation in [ ]`;
391
+ const shape = citationEntriesProblem(citations, void 0);
392
+ if (shape !== void 0 || text === void 0) return shape;
393
+ if (text === "" && citations.length > 0) return "\"citations\" has nothing to cite: this turn has no text (only tool calls, or empty content); add text to the response or remove the citations";
394
+ return citationEntriesProblem(citations, text);
395
+ }
396
+ /**
397
+ * What is wrong with the entries of a `citations` array, or undefined when
398
+ * all are valid. Offsets are checked against `text` only when it is given. A
399
+ * `null` optional field (`type`, `title`, `startIndex`, `endIndex`, `index`)
400
+ * is treated as absent, as a `null` top-level key is.
401
+ */
402
+ function citationEntriesProblem(citations, text) {
403
+ for (let i = 0; i < citations.length; i++) {
404
+ const citation = citations[i];
405
+ if (citation === null || typeof citation !== "object" || Array.isArray(citation)) return `citations[${i}] must be a { url, title?, startIndex?, endIndex? } or { type: "file", fileId, filename, index? } object, got ${describeFixtureValue(citation)}`;
406
+ const fields = citation;
407
+ const url = fields.url;
408
+ const type = fields.type ?? void 0;
409
+ const title = fields.title ?? void 0;
410
+ const startIndex = fields.startIndex ?? void 0;
411
+ const endIndex = fields.endIndex ?? void 0;
412
+ if (type === void 0 || type === "url" || type === "file") {
413
+ const mixed = mixedCitationProblem(citation, i);
414
+ if (mixed !== void 0) return mixed;
415
+ }
416
+ if (type === "file") {
417
+ const problem = fixtureFileCitationProblem(citation, i, text);
418
+ if (problem !== void 0) return problem;
419
+ continue;
420
+ }
421
+ if (type !== void 0 && type !== "url") return `citations[${i}].type must be "url" or "file" when set, got ${describeFixtureValue(type)}; omit it for a URL citation`;
422
+ if (typeof url !== "string" || url.length === 0) return `citations[${i}].url must be a non-empty string, got ${describeFixtureValue(url)}; set the cited source URL`;
423
+ if (title !== void 0 && typeof title !== "string") return `citations[${i}].title must be a string when set, got ${describeFixtureValue(title)}`;
424
+ for (const [name, value] of [["startIndex", startIndex], ["endIndex", endIndex]]) if (value !== void 0 && !Number.isInteger(value)) return `citations[${i}].${name} must be an integer when set, got ${describeFixtureValue(value)}; give a character offset into the text, or omit it`;
425
+ if (text === void 0) continue;
426
+ const start = startIndex ?? 0;
427
+ const end = endIndex ?? text.length;
428
+ if (start < 0 || start > end || end > text.length) return `citations[${i}] offsets must satisfy 0 <= startIndex <= endIndex <= text length (${text.length}), got startIndex ${start}, endIndex ${end}; fix the offsets, or omit them to cite the whole text`;
429
+ if (splitsSurrogatePair(text, start)) return surrogateSplitProblem(`citations[${i}].startIndex`, start);
430
+ if (splitsSurrogatePair(text, end)) return surrogateSplitProblem(`citations[${i}].endIndex`, end);
431
+ }
432
+ }
433
+ /** What is wrong with the file citation `citations[i]`, or undefined when valid. */
434
+ function fixtureFileCitationProblem(citation, i, text) {
435
+ const { fileId, filename } = citation;
436
+ const index = citation.index ?? void 0;
437
+ if (typeof fileId !== "string" || fileId.length === 0) return `citations[${i}].fileId must be a non-empty string, got ${describeFixtureValue(fileId)}; set the cited file's ID, e.g. "file-abc123"`;
438
+ if (typeof filename !== "string" || filename.length === 0) return `citations[${i}].filename must be a non-empty string, got ${describeFixtureValue(filename)}; set the cited file's name`;
439
+ if (index !== void 0 && !Number.isInteger(index)) return `citations[${i}].index must be an integer when set, got ${describeFixtureValue(index)}; give a character position in the text, or omit it`;
440
+ if (text === void 0) return void 0;
441
+ const at = index ?? text.length;
442
+ if (at < 0 || at > text.length) return `citations[${i}].index must satisfy 0 <= index <= text length (${text.length}), got ${at}; fix the index, or omit it to cite the end of the text`;
443
+ if (splitsSurrogatePair(text, at)) return surrogateSplitProblem(`citations[${i}].index`, at);
444
+ }
445
+ /**
446
+ * Fixture `citations`, checked against the text they cite: the array itself,
447
+ * or empty when absent. Throws {@link InvalidFixtureOutputError}
448
+ * (`aimock_invalid_fixture_citation`) for every problem
449
+ * {@link fixtureCitationsProblem} reports: a malformed value, an offset
450
+ * outside the text or one that splits a surrogate pair, mixed URL and file
451
+ * citation fields, or citations on a turn with no text (`content` is empty:
452
+ * tool calls only, `blocks` with no text block, or empty content). Every
453
+ * provider that reads `citations` goes through this.
454
+ */
455
+ function fixtureCitations(content, citations) {
456
+ const problem = fixtureCitationsProblem(citations, content);
457
+ if (problem !== void 0) throw new InvalidFixtureOutputError("aimock_invalid_fixture_citation", problem);
458
+ return citations ?? [];
459
+ }
460
+ /**
461
+ * Fixture `files`, checked: the array itself, or empty when absent. Throws
462
+ * {@link InvalidFixtureOutputError} (`aimock_invalid_fixture_file`) for a
463
+ * malformed value. Every provider that reads `files` goes through this.
464
+ */
465
+ function fixtureFiles(files) {
466
+ const problem = fixtureFilesProblem(files);
467
+ if (problem !== void 0) throw new InvalidFixtureOutputError("aimock_invalid_fixture_file", problem);
468
+ return files ?? [];
469
+ }
470
+ /**
471
+ * Fixture URL `citations` against the text they cite, with defaults applied:
472
+ * the title falls back to the URL and the offsets to the whole text. Empty
473
+ * when the fixture sets none. File citations (`type: "file"`) are skipped:
474
+ * only OpenAI Responses has a wire shape for them (`file_citation`), and it
475
+ * builds those itself. Chat Completions, Anthropic Messages and Gemini, which
476
+ * resolve through here, serve URL citations only. This reads `citations`
477
+ * through {@link fixtureCitations}, so it is the serve-time check for those
478
+ * three wires: every problem {@link fixtureCitationsProblem} reports against
479
+ * `content`, including one in a file citation it then skips, throws
480
+ * {@link InvalidFixtureOutputError} (`aimock_invalid_fixture_citation`).
481
+ * OpenAI Responses calls {@link fixtureCitations} directly. Other wires
482
+ * ignore `citations`.
483
+ */
484
+ function resolveCitations(content, citations) {
485
+ return fixtureCitations(content, citations).flatMap((c) => c.type === "file" ? [] : [c]).map((c) => ({
486
+ url: c.url,
487
+ title: c.title ?? c.url,
488
+ startIndex: c.startIndex ?? 0,
489
+ endIndex: c.endIndex ?? content.length
490
+ }));
491
+ }
492
+ /**
493
+ * Fixture `citations` as Chat Completions `url_citation` annotations. File
494
+ * citations are skipped (see {@link resolveCitations}): Chat Completions has no
495
+ * `file_citation` annotation.
496
+ */
497
+ function chatCompletionAnnotations(content, citations) {
498
+ return resolveCitations(content, citations).map((c) => ({
499
+ type: "url_citation",
500
+ url_citation: {
501
+ url: c.url,
502
+ title: c.title,
503
+ start_index: c.startIndex,
504
+ end_index: c.endIndex
505
+ }
506
+ }));
507
+ }
508
+ /** `{ annotations }` for a Chat Completions message, or nothing when the fixture has no citations. */
509
+ function annotationsField(content, overrides) {
510
+ const annotations = chatCompletionAnnotations(content, overrides?.citations);
511
+ return annotations.length > 0 ? { annotations } : {};
512
+ }
513
+ /**
514
+ * The streamed form of {@link chatCompletionAnnotations}: one chunk whose
515
+ * `delta.annotations` holds every annotation, sent after the content and
516
+ * before the finish chunk. Nothing when the fixture has no citations.
517
+ */
518
+ function chatAnnotationChunks(content, overrides, envelope) {
519
+ const annotations = chatCompletionAnnotations(content, overrides?.citations);
520
+ if (annotations.length === 0) return [];
521
+ const fingerprint = overrides?.systemFingerprint;
522
+ return [{
523
+ id: envelope.id,
524
+ object: "chat.completion.chunk",
525
+ created: envelope.created,
526
+ model: envelope.model,
527
+ choices: [{
528
+ index: 0,
529
+ delta: { annotations },
530
+ logprobs: null,
531
+ finish_reason: null
532
+ }],
533
+ ...fingerprint !== void 0 && { system_fingerprint: fingerprint }
534
+ }];
535
+ }
536
+ /** True for the fixture errors a handler surfaces as a coded 500. */
220
537
  function isFixtureToolCallError(error) {
221
- return error instanceof InvalidToolArgumentsError || error instanceof UnsupportedToolCallError || error instanceof InvalidFixtureToolCallError;
538
+ return error instanceof InvalidToolArgumentsError || error instanceof UnsupportedToolCallError || error instanceof InvalidFixtureToolCallError || error instanceof UnsupportedGeneratedFileError || error instanceof InvalidFixtureOutputError;
222
539
  }
223
540
  /** The aimock error code carried in every provider envelope for a fixture tool-call error. */
224
541
  function fixtureToolCallErrorCode(error) {
542
+ if (error instanceof UnsupportedGeneratedFileError) return "aimock_unsupported_generated_file";
543
+ if (error instanceof InvalidFixtureOutputError) return error.code;
225
544
  if (error instanceof UnsupportedToolCallError) return "aimock_unsupported_tool_call";
226
545
  if (error instanceof InvalidFixtureToolCallError) return error.code;
227
546
  return "aimock_invalid_tool_arguments";
@@ -240,9 +559,10 @@ function googleFixtureToolCallErrorDetails(code) {
240
559
  }];
241
560
  }
242
561
  /**
243
- * Run a WebSocket preflight `check` that may raise a fixture tool-call error
244
- * ({@link InvalidToolArgumentsError}, {@link UnsupportedToolCallError} or
245
- * {@link InvalidFixtureToolCallError}).
562
+ * Run a WebSocket preflight `check` that may raise a fixture error
563
+ * ({@link InvalidToolArgumentsError}, {@link UnsupportedToolCallError},
564
+ * {@link InvalidFixtureToolCallError}, {@link UnsupportedGeneratedFileError}
565
+ * or {@link InvalidFixtureOutputError}).
246
566
  * Such an error marks the already-journaled entry as a 500 carrying the
247
567
  * message, then propagates to the transport's error handler.
248
568
  */
@@ -1067,9 +1387,47 @@ function isVideoResponse(r) {
1067
1387
  function isJSONResponse(r) {
1068
1388
  return "json" in r && r.json !== void 0;
1069
1389
  }
1390
+ /**
1391
+ * Every key some other response guard reads. A response carrying any of them
1392
+ * is that type (with a stray `raw` key the loader has always ignored), never
1393
+ * a raw response — so adding raw support changes nothing for such fixtures.
1394
+ */
1395
+ const NON_RAW_RESPONSE_MARKERS = [
1396
+ "content",
1397
+ "toolCalls",
1398
+ "blocks",
1399
+ "responsesBlocks",
1400
+ "customToolCalls",
1401
+ "error",
1402
+ "embedding",
1403
+ "image",
1404
+ "images",
1405
+ "audio",
1406
+ "transcription",
1407
+ "video",
1408
+ "json",
1409
+ "live"
1410
+ ];
1411
+ /**
1412
+ * @internal Whether `r` is a raw passthrough response: `raw` is a non-null,
1413
+ * non-array object and no other response type's marker key is present. The
1414
+ * shape of `raw` itself is checked by the loader and again at serve time.
1415
+ * Exported for in-repo use only; not part of the public API.
1416
+ *
1417
+ * A fixture added in code can hold a response that is not an object (null, a
1418
+ * string). `in` throws on such a value, so it is simply not raw.
1419
+ */
1420
+ function isRawResponse(r) {
1421
+ if (typeof r !== "object" || r === null || !("raw" in r)) return false;
1422
+ const raw = r.raw;
1423
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return false;
1424
+ return !NON_RAW_RESPONSE_MARKERS.some((marker) => marker in r);
1425
+ }
1070
1426
  function extractOverrides(response) {
1071
1427
  const r = response;
1072
1428
  return {
1429
+ ...r.citations !== void 0 && { citations: r.citations },
1430
+ ...r.files !== void 0 && { files: r.files },
1073
1431
  ...r.id !== void 0 && { id: r.id },
1074
1432
  ...r.created !== void 0 && { created: r.created },
1075
1433
  ...r.model !== void 0 && { model: r.model },
@@ -1179,6 +1537,11 @@ function buildTextChunks(content, model, chunkSize, reasoning, overrides) {
1179
1537
  ...fingerprint !== void 0 && { system_fingerprint: fingerprint }
1180
1538
  });
1181
1539
  }
1540
+ chunks.push(...chatAnnotationChunks(content, overrides, {
1541
+ id,
1542
+ created,
1543
+ model: effectiveModel
1544
+ }));
1182
1545
  chunks.push({
1183
1546
  id,
1184
1547
  object: "chat.completion.chunk",
@@ -1198,6 +1561,7 @@ function buildTextChunks(content, model, chunkSize, reasoning, overrides) {
1198
1561
  function buildOpenAIReasoningChunks(reasoning, model, chunkSize, exposeReasoning, overrides) {
1199
1562
  return buildTextChunks(exposeReasoning ? reasoning : "", model, chunkSize, void 0, {
1200
1563
  ...overrides,
1564
+ citations: void 0,
1201
1565
  role: "assistant",
1202
1566
  finishReason: "length"
1203
1567
  }).map((chunk) => ({
@@ -1226,6 +1590,7 @@ function buildOpenAIReasoningCompletion(reasoning, model, exposeReasoning, overr
1226
1590
  const visible = exposeReasoning ? reasoning : "";
1227
1591
  const completion = buildTextCompletion(visible, model, void 0, {
1228
1592
  ...overrides,
1593
+ citations: void 0,
1229
1594
  role: "assistant",
1230
1595
  finishReason: "length",
1231
1596
  usage: void 0
@@ -1255,6 +1620,7 @@ function buildOpenAIReasoningCompletion(reasoning, model, exposeReasoning, overr
1255
1620
  function buildOpenAIContentFilterChunks(model, overrides) {
1256
1621
  return buildTextChunks("", model, 1, void 0, {
1257
1622
  ...overrides,
1623
+ citations: void 0,
1258
1624
  role: "assistant",
1259
1625
  finishReason: "content_filter"
1260
1626
  }).map((chunk) => ({
@@ -1272,6 +1638,7 @@ function buildOpenAIContentFilterChunks(model, overrides) {
1272
1638
  function buildOpenAIContentFilterCompletion(model, overrides, requestMessages) {
1273
1639
  const completion = buildTextCompletion("", model, void 0, {
1274
1640
  ...overrides,
1641
+ citations: void 0,
1275
1642
  role: "assistant",
1276
1643
  finishReason: "content_filter",
1277
1644
  usage: void 0
@@ -1292,6 +1659,7 @@ function buildOpenAIContentFilterCompletion(model, overrides, requestMessages) {
1292
1659
  function buildOpenAIRefusalChunks(refusal, model, chunkSize, overrides) {
1293
1660
  return buildTextChunks(refusal, model, chunkSize, void 0, {
1294
1661
  ...overrides,
1662
+ citations: void 0,
1295
1663
  role: "assistant",
1296
1664
  finishReason: "stop"
1297
1665
  }).map((chunk) => ({
@@ -1312,6 +1680,7 @@ function buildOpenAIRefusalChunks(refusal, model, chunkSize, overrides) {
1312
1680
  function buildOpenAIRefusalCompletion(refusal, model, overrides, requestMessages) {
1313
1681
  const completion = buildTextCompletion(refusal, model, void 0, {
1314
1682
  ...overrides,
1683
+ citations: void 0,
1315
1684
  role: "assistant",
1316
1685
  finishReason: "stop",
1317
1686
  usage: void 0
@@ -1329,6 +1698,7 @@ function buildOpenAIRefusalCompletion(refusal, model, overrides, requestMessages
1329
1698
  };
1330
1699
  }
1331
1700
  function buildToolCallChunks(toolCalls, model, chunkSize, reasoning, overrides) {
1701
+ fixtureCitations("", overrides?.citations);
1332
1702
  const id = overrides?.id ?? generateId();
1333
1703
  const created = overrides?.created ?? Math.floor(Date.now() / 1e3);
1334
1704
  const effectiveModel = overrides?.model ?? model;
@@ -1439,7 +1809,8 @@ function buildTextCompletion(content, model, reasoning, overrides, requestMessag
1439
1809
  role: overrides?.role ?? "assistant",
1440
1810
  content,
1441
1811
  refusal: null,
1442
- ...reasoning ? { reasoning_content: reasoning } : {}
1812
+ ...reasoning ? { reasoning_content: reasoning } : {},
1813
+ ...annotationsField(content, overrides)
1443
1814
  },
1444
1815
  logprobs: null,
1445
1816
  finish_reason: overrides?.finishReason ?? "stop"
@@ -1449,6 +1820,7 @@ function buildTextCompletion(content, model, reasoning, overrides, requestMessag
1449
1820
  };
1450
1821
  }
1451
1822
  function buildToolCallCompletion(toolCalls, model, reasoning, overrides, requestMessages) {
1823
+ fixtureCitations("", overrides?.citations);
1452
1824
  const promptText = requestMessages ? requestMessages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => p.text ?? "").join("") : "").join("") : "";
1453
1825
  const completionText = toolCalls.map((tc) => tc.name + tc.arguments).join("");
1454
1826
  return {
@@ -1585,6 +1957,11 @@ function buildContentWithToolCallsChunks(content, toolCalls, model, chunkSize, r
1585
1957
  }
1586
1958
  tcIdx++;
1587
1959
  }
1960
+ chunks.push(...chatAnnotationChunks(outcome.content, overrides, {
1961
+ id,
1962
+ created,
1963
+ model: effectiveModel
1964
+ }));
1588
1965
  chunks.push({
1589
1966
  id,
1590
1967
  object: "chat.completion.chunk",
@@ -1696,6 +2073,11 @@ function buildContentWithToolCallsChunks(content, toolCalls, model, chunkSize, r
1696
2073
  });
1697
2074
  }
1698
2075
  }
2076
+ chunks.push(...chatAnnotationChunks(content, overrides, {
2077
+ id,
2078
+ created,
2079
+ model: effectiveModel
2080
+ }));
1699
2081
  chunks.push({
1700
2082
  id,
1701
2083
  object: "chat.completion.chunk",
@@ -1733,7 +2115,8 @@ function buildContentWithToolCallsCompletion(content, toolCalls, model, reasonin
1733
2115
  name: tc.name,
1734
2116
  arguments: tc.arguments
1735
2117
  }
1736
- }))
2118
+ })),
2119
+ ...annotationsField(content, overrides)
1737
2120
  },
1738
2121
  logprobs: null,
1739
2122
  finish_reason: overrides?.finishReason ?? "tool_calls"
@@ -2376,10 +2759,12 @@ function parseMcpUndeclared(raw) {
2376
2759
 
2377
2760
  //#endregion
2378
2761
  exports.FORMAT_TO_CONTENT_TYPE = FORMAT_TO_CONTENT_TYPE;
2762
+ exports.InvalidFixtureOutputError = InvalidFixtureOutputError;
2379
2763
  exports.InvalidFixtureToolCallError = InvalidFixtureToolCallError;
2380
2764
  exports.InvalidToolArgumentsError = InvalidToolArgumentsError;
2381
2765
  exports.MAX_EMBEDDING_DIMENSIONS = MAX_EMBEDDING_DIMENSIONS;
2382
2766
  exports.RequestBodyTooLargeError = RequestBodyTooLargeError;
2767
+ exports.UnsupportedGeneratedFileError = UnsupportedGeneratedFileError;
2383
2768
  exports.UnsupportedToolCallError = UnsupportedToolCallError;
2384
2769
  exports.assertCustomToolCalls = assertCustomToolCalls;
2385
2770
  exports.assertResponsesToolCalls = assertResponsesToolCalls;
@@ -2403,6 +2788,10 @@ exports.describeMatch = describeMatch;
2403
2788
  exports.estimatePromptTokens = estimatePromptTokens;
2404
2789
  exports.estimateTokens = estimateTokens;
2405
2790
  exports.extractOverrides = extractOverrides;
2791
+ exports.fixtureCitations = fixtureCitations;
2792
+ exports.fixtureCitationsProblem = fixtureCitationsProblem;
2793
+ exports.fixtureFiles = fixtureFiles;
2794
+ exports.fixtureFilesProblem = fixtureFilesProblem;
2406
2795
  exports.fixtureListTarget = fixtureListTarget;
2407
2796
  exports.fixtureToolCallErrorCode = fixtureToolCallErrorCode;
2408
2797
  exports.flattenHeaders = flattenHeaders;
@@ -2427,6 +2816,7 @@ exports.isImageResponse = isImageResponse;
2427
2816
  exports.isJSONResponse = isJSONResponse;
2428
2817
  exports.isJsonObject = isJsonObject;
2429
2818
  exports.isLiveResponse = isLiveResponse;
2819
+ exports.isRawResponse = isRawResponse;
2430
2820
  exports.isResponseFactory = isResponseFactory;
2431
2821
  exports.isServerOwnedFixtures = isServerOwnedFixtures;
2432
2822
  exports.isTextResponse = isTextResponse;
@@ -2446,10 +2836,12 @@ exports.prepareOpenAIChatMisbehavior = prepareOpenAIChatMisbehavior;
2446
2836
  exports.readBody = readBody;
2447
2837
  exports.readBodyBufferBounded = readBodyBufferBounded;
2448
2838
  exports.rebuildOrderedBlocks = rebuildOrderedBlocks;
2839
+ exports.rejectGeneratedFiles = rejectGeneratedFiles;
2449
2840
  exports.rejectResponsesOnlyToolCalls = rejectResponsesOnlyToolCalls;
2450
2841
  exports.requireEmittedFunctionToolCalls = requireEmittedFunctionToolCalls;
2451
2842
  exports.requireFunctionToolCalls = requireFunctionToolCalls;
2452
2843
  exports.requireServedFunctionToolCalls = requireServedFunctionToolCalls;
2844
+ exports.resolveCitations = resolveCitations;
2453
2845
  exports.resolveFixtureBlockOutcome = resolveFixtureBlockOutcome;
2454
2846
  exports.resolveFixtureBlocks = resolveFixtureBlocks;
2455
2847
  exports.resolveMcpIdentity = resolveMcpIdentity;
@@ -2471,6 +2863,7 @@ exports.slugifyTestId = slugifyTestId;
2471
2863
  exports.strictNoMatchLogLine = strictNoMatchLogLine;
2472
2864
  exports.strictNoMatchMessage = strictNoMatchMessage;
2473
2865
  exports.strictToolArgumentsEnabled = strictToolArgumentsEnabled;
2866
+ exports.surrogateSafeChunks = surrogateSafeChunks;
2474
2867
  exports.toolArgsForWire = toolArgsForWire;
2475
2868
  exports.toolCallFixtureBlock = toolCallFixtureBlock;
2476
2869
  exports.validateChatMessages = validateChatMessages;