@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +12 -1
- package/README.md +4 -1
- package/dist/aws-event-stream.cjs +20 -23
- package/dist/aws-event-stream.cjs.map +1 -1
- package/dist/aws-event-stream.d.cts +0 -1
- package/dist/aws-event-stream.d.ts +0 -1
- package/dist/aws-event-stream.js +20 -24
- package/dist/aws-event-stream.js.map +1 -1
- package/dist/bedrock-converse.cjs +79 -82
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +80 -83
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +112 -121
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +113 -122
- package/dist/bedrock.js.map +1 -1
- package/dist/cohere.cjs +58 -106
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +60 -108
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/fixture-loader.cjs +103 -2
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +104 -4
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +63 -121
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +65 -123
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +242 -155
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +244 -157
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +400 -7
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +51 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +51 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +391 -8
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/messages.cjs +159 -141
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +161 -143
- package/dist/messages.js.map +1 -1
- package/dist/misbehavior.cjs +62 -3
- package/dist/misbehavior.cjs.map +1 -1
- package/dist/misbehavior.d.cts.map +1 -1
- package/dist/misbehavior.d.ts.map +1 -1
- package/dist/misbehavior.js +63 -4
- package/dist/misbehavior.js.map +1 -1
- package/dist/ndjson-writer.cjs +21 -23
- package/dist/ndjson-writer.cjs.map +1 -1
- package/dist/ndjson-writer.d.cts +0 -1
- package/dist/ndjson-writer.d.cts.map +1 -1
- package/dist/ndjson-writer.d.ts +0 -1
- package/dist/ndjson-writer.d.ts.map +1 -1
- package/dist/ndjson-writer.js +21 -24
- package/dist/ndjson-writer.js.map +1 -1
- package/dist/ollama.cjs +153 -147
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +155 -149
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-chat.cjs +1 -1
- package/dist/openrouter-chat.js +1 -1
- package/dist/raw-validation.cjs +177 -0
- package/dist/raw-validation.cjs.map +1 -0
- package/dist/raw-validation.js +176 -0
- package/dist/raw-validation.js.map +1 -0
- package/dist/response/deliver.cjs +144 -0
- package/dist/response/deliver.cjs.map +1 -0
- package/dist/response/deliver.js +143 -0
- package/dist/response/deliver.js.map +1 -0
- package/dist/response/frame-loop.cjs +102 -0
- package/dist/response/frame-loop.cjs.map +1 -0
- package/dist/response/frame-loop.js +100 -0
- package/dist/response/frame-loop.js.map +1 -0
- package/dist/response/lifecycle.cjs +223 -0
- package/dist/response/lifecycle.cjs.map +1 -0
- package/dist/response/lifecycle.js +211 -0
- package/dist/response/lifecycle.js.map +1 -0
- package/dist/response/proxy.cjs +71 -0
- package/dist/response/proxy.cjs.map +1 -0
- package/dist/response/proxy.js +70 -0
- package/dist/response/proxy.js.map +1 -0
- package/dist/response/raw.cjs +228 -0
- package/dist/response/raw.cjs.map +1 -0
- package/dist/response/raw.js +227 -0
- package/dist/response/raw.js.map +1 -0
- package/dist/response/timing.cjs +157 -0
- package/dist/response/timing.cjs.map +1 -0
- package/dist/response/timing.js +150 -0
- package/dist/response/timing.js.map +1 -0
- package/dist/responses.cjs +269 -148
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +270 -149
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +20 -4
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +7 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +7 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +21 -5
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +89 -120
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +91 -122
- package/dist/server.js.map +1 -1
- package/dist/sse-writer.cjs +49 -30
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts +0 -1
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +0 -1
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +49 -31
- package/dist/sse-writer.js.map +1 -1
- package/dist/transcription.cjs +19 -33
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +20 -34
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +173 -10
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +173 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ws-framing.cjs +12 -6
- package/dist/ws-framing.cjs.map +1 -1
- package/dist/ws-framing.d.cts +2 -0
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +2 -0
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-framing.js +12 -6
- package/dist/ws-framing.js.map +1 -1
- package/dist/ws-gemini-live.cjs +120 -93
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.js +120 -93
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +80 -70
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +80 -70
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +72 -53
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +73 -54
- package/dist/ws-responses.js.map +1 -1
- package/package.json +1 -1
- package/skills/write-fixtures/SKILL.md +34 -2
package/dist/responses.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { renderError } from "./response/errors.js";
|
|
2
2
|
import { isReasoningModel } from "./model-utils.js";
|
|
3
3
|
import { resolveRequestDecisions, strictOverrideField } from "./response/policy.js";
|
|
4
|
-
import { assertCustomToolCalls, assertResponsesToolCalls, extractOverrides, flattenHeaders, generateId, generateToolCallId, getContext, getTestId, isCombinedFixtureResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, prepareOpenAIChatMisbehavior, resolveFixtureBlocks, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveResponse, resolveServedBlockOutcome, serializeErrorResponse, servedToolCalls, strictNoMatchLogLine, strictNoMatchMessage, toolCallFixtureBlock, withoutLegacyNamespaces } from "./helpers.js";
|
|
5
|
-
import {
|
|
4
|
+
import { UnsupportedGeneratedFileError, assertCustomToolCalls, assertResponsesToolCalls, extractOverrides, fixtureCitations, fixtureFiles, flattenHeaders, generateId, generateToolCallId, getContext, getTestId, isCombinedFixtureResponse, isErrorResponse, isJsonObject, isRawResponse, isTextResponse, isToolCallResponse, prepareOpenAIChatMisbehavior, resolveFixtureBlocks, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveResponse, resolveServedBlockOutcome, serializeErrorResponse, servedToolCalls, strictNoMatchLogLine, strictNoMatchMessage, surrogateSafeChunks, toolCallFixtureBlock, withoutLegacyNamespaces } from "./helpers.js";
|
|
5
|
+
import { typedSseCodec, writeErrorResponse } from "./sse-writer.js";
|
|
6
6
|
import { applyChaosAsync } from "./chaos.js";
|
|
7
7
|
import { planMisbehavior, recordMisbehaviorOutcome } from "./misbehavior.js";
|
|
8
8
|
import { matchFixtureDiagnostic, recordMatchOptions, setResponsesOfferedTools } from "./router.js";
|
|
9
9
|
import { proxyAndRecord } from "./recorder.js";
|
|
10
|
-
import {
|
|
10
|
+
import { extendRecordedTimings, misbehaviorPayloadLength, nonStreamingTimingEnabled, responsesOutputFrames, responsesPayloadLength } from "./response/timing.js";
|
|
11
|
+
import { deliverJson, deliverStream } from "./response/deliver.js";
|
|
12
|
+
import { proxyInterruption, withHangUpDetection } from "./response/proxy.js";
|
|
13
|
+
import { serveRawResponse } from "./response/raw.js";
|
|
11
14
|
import { render } from "./response/usage/codecs/responses.js";
|
|
12
15
|
import { mapResponsesFinish } from "./response/finish.js";
|
|
13
16
|
import { randomBytes } from "node:crypto";
|
|
@@ -324,7 +327,7 @@ function itemId() {
|
|
|
324
327
|
}
|
|
325
328
|
function buildTextStreamEvents(content, model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
326
329
|
const { respId, created, events, prefixOutputItems, nextOutputIndex } = buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
327
|
-
const { events: msgEvents, msgItem } = buildMessageOutputEvents(content, chunkSize, nextOutputIndex, overrides?.phase);
|
|
330
|
+
const { events: msgEvents, msgItem } = buildMessageOutputEvents(content, chunkSize, nextOutputIndex, overrides?.phase, buildAnnotations(content, overrides?.citations));
|
|
328
331
|
events.push(...msgEvents);
|
|
329
332
|
events.push({
|
|
330
333
|
type: "response.completed",
|
|
@@ -433,6 +436,7 @@ function buildToolCallStreamEvents(toolCalls, model, chunkSize, reasoning, webSe
|
|
|
433
436
|
}
|
|
434
437
|
function toolCallStreamEvents(toolCalls, model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
435
438
|
assertResponsesToolCalls(toolCalls);
|
|
439
|
+
fixtureCitations("", overrides?.citations);
|
|
436
440
|
const { respId, created, events, prefixOutputItems, nextOutputIndex } = buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
437
441
|
const fcOutputItems = [];
|
|
438
442
|
for (let idx = 0; idx < toolCalls.length; idx++) {
|
|
@@ -669,6 +673,79 @@ function buildWebSearchStreamEvents(queries, startOutputIndex) {
|
|
|
669
673
|
}
|
|
670
674
|
return events;
|
|
671
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* A Responses `image_generation_call` output item for one fixture file (see
|
|
678
|
+
* `ResponseOutputItem.ImageGenerationCall` in the openai SDK): `result` holds
|
|
679
|
+
* the base64 bytes once completed and is null while in progress.
|
|
680
|
+
*/
|
|
681
|
+
function imageGenerationCallItem(id, file, status) {
|
|
682
|
+
return {
|
|
683
|
+
type: "image_generation_call",
|
|
684
|
+
id,
|
|
685
|
+
status,
|
|
686
|
+
result: status === "completed" ? file.data : null
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* A fixture file that is not `image/*` reached the Responses API, which
|
|
691
|
+
* carries generated files only as `image_generation_call` items. Surfaces as
|
|
692
|
+
* the same coded 500 (`aimock_unsupported_generated_file`), naming `mimeType`.
|
|
693
|
+
*/
|
|
694
|
+
var NonImageGeneratedFileError = class extends UnsupportedGeneratedFileError {
|
|
695
|
+
constructor(mimeType) {
|
|
696
|
+
super("the OpenAI Responses API");
|
|
697
|
+
this.message = `aimock: fixture "files" on the OpenAI Responses API carry only image/* files (as image_generation_call items); got mimeType "${mimeType}"`;
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
/**
|
|
701
|
+
* Throw {@link NonImageGeneratedFileError} for the first fixture file whose
|
|
702
|
+
* `mimeType` is not `image/*`. Called before any output item is built, so
|
|
703
|
+
* nothing reaches the client.
|
|
704
|
+
*/
|
|
705
|
+
function requireImageFiles(files) {
|
|
706
|
+
const nonImage = files.find((f) => !f.mimeType.toLowerCase().startsWith("image/"));
|
|
707
|
+
if (nonImage) throw new NonImageGeneratedFileError(nonImage.mimeType);
|
|
708
|
+
}
|
|
709
|
+
/** Fixture `files` as completed `image_generation_call` items (non-streaming). */
|
|
710
|
+
function imageGenerationCallItems(files) {
|
|
711
|
+
const validated = fixtureFiles(files);
|
|
712
|
+
requireImageFiles(validated);
|
|
713
|
+
return validated.map((f) => imageGenerationCallItem(generateId("ig"), f, "completed"));
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Streamed form of {@link imageGenerationCallItems}: per file, the item's
|
|
717
|
+
* `output_item.added`, the `image_generation_call` `in_progress`,
|
|
718
|
+
* `generating` and `completed` events, then `output_item.done` carrying the
|
|
719
|
+
* completed item.
|
|
720
|
+
*/
|
|
721
|
+
function buildImageGenerationStreamEvents(files, startOutputIndex) {
|
|
722
|
+
requireImageFiles(files);
|
|
723
|
+
const events = [];
|
|
724
|
+
files.forEach((file, i) => {
|
|
725
|
+
const id = generateId("ig");
|
|
726
|
+
const outputIndex = startOutputIndex + i;
|
|
727
|
+
events.push({
|
|
728
|
+
type: "response.output_item.added",
|
|
729
|
+
output_index: outputIndex,
|
|
730
|
+
item: imageGenerationCallItem(id, file, "in_progress")
|
|
731
|
+
});
|
|
732
|
+
for (const phase of [
|
|
733
|
+
"in_progress",
|
|
734
|
+
"generating",
|
|
735
|
+
"completed"
|
|
736
|
+
]) events.push({
|
|
737
|
+
type: `response.image_generation_call.${phase}`,
|
|
738
|
+
output_index: outputIndex,
|
|
739
|
+
item_id: id
|
|
740
|
+
});
|
|
741
|
+
events.push({
|
|
742
|
+
type: "response.output_item.done",
|
|
743
|
+
output_index: outputIndex,
|
|
744
|
+
item: imageGenerationCallItem(id, file, "completed")
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
return events;
|
|
748
|
+
}
|
|
672
749
|
function buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false, forceReasoning = false) {
|
|
673
750
|
const respId = overrides?.id ?? responseId();
|
|
674
751
|
const created = overrides?.created ?? Math.floor(Date.now() / 1e3);
|
|
@@ -712,6 +789,13 @@ function buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrid
|
|
|
712
789
|
for (const de of doneEvents) prefixOutputItems.push(de.item);
|
|
713
790
|
nextOutputIndex += webSearches.length;
|
|
714
791
|
}
|
|
792
|
+
const files = fixtureFiles(overrides?.files);
|
|
793
|
+
if (files.length > 0) {
|
|
794
|
+
const fileEvents = buildImageGenerationStreamEvents(files, nextOutputIndex);
|
|
795
|
+
events.push(...fileEvents);
|
|
796
|
+
for (const e of fileEvents) if (e.type === "response.output_item.done") prefixOutputItems.push(e.item);
|
|
797
|
+
nextOutputIndex += files.length;
|
|
798
|
+
}
|
|
715
799
|
return {
|
|
716
800
|
respId,
|
|
717
801
|
created,
|
|
@@ -720,7 +804,84 @@ function buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrid
|
|
|
720
804
|
nextOutputIndex
|
|
721
805
|
};
|
|
722
806
|
}
|
|
723
|
-
|
|
807
|
+
/**
|
|
808
|
+
* Fixture `citations` as `output_text` annotations for each text in `texts`
|
|
809
|
+
* (one per message item), in fixture order: a URL citation becomes
|
|
810
|
+
* `url_citation`, a file citation (`type: "file"`) becomes `file_citation`
|
|
811
|
+
* (`file_id`, `filename`, `index`; see `ResponseOutputText.FileCitation` in
|
|
812
|
+
* the openai SDK). Offsets index the joined text of all texts, as on every
|
|
813
|
+
* other provider. A URL citation is a range and goes only to the text holding
|
|
814
|
+
* its `startIndex`. A file citation is a point after the cited text, so it
|
|
815
|
+
* goes to the last text starting before its `index`: an `index` equal to a
|
|
816
|
+
* text's end stays at the end of that text (not at 0 of the next text), and
|
|
817
|
+
* an `index` of 0 goes to the first text. Each citation is rebased onto its
|
|
818
|
+
* text since Responses offsets are per `output_text`, with `end_index`
|
|
819
|
+
* clamped to it. Anthropic and Gemini need no point rule: they have no file
|
|
820
|
+
* citation. Anthropic URL citations follow the same start-owns-range rule;
|
|
821
|
+
* Gemini does not, and splits a citation that spans several text parts into
|
|
822
|
+
* one grounding support per part (see geminiGroundingSupports).
|
|
823
|
+
* Missing offsets take the defaults `resolveCitations` applies: a URL
|
|
824
|
+
* citation starts at 0 and ends at the end of the joined text, so one without
|
|
825
|
+
* offsets goes on the first text and covers all of it; a file citation's
|
|
826
|
+
* `index` defaults to the end of the joined text (the last text).
|
|
827
|
+
*/
|
|
828
|
+
function blockAnnotations(texts, citations) {
|
|
829
|
+
const perText = texts.map(() => []);
|
|
830
|
+
if (texts.length === 0) {
|
|
831
|
+
fixtureCitations("", citations);
|
|
832
|
+
return perText;
|
|
833
|
+
}
|
|
834
|
+
const starts = [];
|
|
835
|
+
let offset = 0;
|
|
836
|
+
for (const text of texts) {
|
|
837
|
+
starts.push(offset);
|
|
838
|
+
offset += text.length;
|
|
839
|
+
}
|
|
840
|
+
const joinedLength = offset;
|
|
841
|
+
const ownerOf = (start) => {
|
|
842
|
+
let owner = 0;
|
|
843
|
+
for (let k = 1; k < starts.length; k++) if (starts[k] <= start) owner = k;
|
|
844
|
+
return owner;
|
|
845
|
+
};
|
|
846
|
+
const pointOwnerOf = (index) => {
|
|
847
|
+
let owner = 0;
|
|
848
|
+
for (let k = 1; k < starts.length; k++) if (starts[k] < index) owner = k;
|
|
849
|
+
return owner;
|
|
850
|
+
};
|
|
851
|
+
for (const c of fixtureCitations(texts.join(""), citations)) {
|
|
852
|
+
if (c.type === "file") {
|
|
853
|
+
const index = c.index ?? joinedLength;
|
|
854
|
+
const owner = pointOwnerOf(index);
|
|
855
|
+
perText[owner].push({
|
|
856
|
+
type: "file_citation",
|
|
857
|
+
file_id: c.fileId,
|
|
858
|
+
filename: c.filename,
|
|
859
|
+
index: index - starts[owner]
|
|
860
|
+
});
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
const start = c.startIndex ?? 0;
|
|
864
|
+
const end = c.endIndex ?? joinedLength;
|
|
865
|
+
const owner = ownerOf(start);
|
|
866
|
+
perText[owner].push({
|
|
867
|
+
type: "url_citation",
|
|
868
|
+
url: c.url,
|
|
869
|
+
title: c.title ?? c.url,
|
|
870
|
+
start_index: start - starts[owner],
|
|
871
|
+
end_index: Math.min(end - starts[owner], texts[owner].length)
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
return perText;
|
|
875
|
+
}
|
|
876
|
+
/** {@link blockAnnotations} for a turn with a single text. */
|
|
877
|
+
function buildAnnotations(content, citations) {
|
|
878
|
+
return blockAnnotations([content], citations)[0];
|
|
879
|
+
}
|
|
880
|
+
/** The annotations of each text block in `blocks`, in block order. */
|
|
881
|
+
function orderedBlockAnnotations(blocks, citations) {
|
|
882
|
+
return blockAnnotations(blocks.flatMap((b) => b.type === "text" ? [b.text] : []), citations);
|
|
883
|
+
}
|
|
884
|
+
function buildMessageOutputEvents(content, chunkSize, outputIndex, phase, annotations) {
|
|
724
885
|
const msgId = itemId();
|
|
725
886
|
const events = [];
|
|
726
887
|
events.push({
|
|
@@ -760,6 +921,16 @@ function buildMessageOutputEvents(content, chunkSize, outputIndex, phase) {
|
|
|
760
921
|
content_index: 0,
|
|
761
922
|
text: content
|
|
762
923
|
});
|
|
924
|
+
annotations.forEach((annotation, annotationIndex) => {
|
|
925
|
+
events.push({
|
|
926
|
+
type: "response.output_text.annotation.added",
|
|
927
|
+
item_id: msgId,
|
|
928
|
+
output_index: outputIndex,
|
|
929
|
+
content_index: 0,
|
|
930
|
+
annotation_index: annotationIndex,
|
|
931
|
+
annotation
|
|
932
|
+
});
|
|
933
|
+
});
|
|
763
934
|
events.push({
|
|
764
935
|
type: "response.content_part.done",
|
|
765
936
|
item_id: msgId,
|
|
@@ -768,7 +939,7 @@ function buildMessageOutputEvents(content, chunkSize, outputIndex, phase) {
|
|
|
768
939
|
part: {
|
|
769
940
|
type: "output_text",
|
|
770
941
|
text: content,
|
|
771
|
-
annotations
|
|
942
|
+
annotations
|
|
772
943
|
}
|
|
773
944
|
});
|
|
774
945
|
const msgItem = {
|
|
@@ -780,7 +951,7 @@ function buildMessageOutputEvents(content, chunkSize, outputIndex, phase) {
|
|
|
780
951
|
content: [{
|
|
781
952
|
type: "output_text",
|
|
782
953
|
text: content,
|
|
783
|
-
annotations
|
|
954
|
+
annotations
|
|
784
955
|
}]
|
|
785
956
|
};
|
|
786
957
|
events.push({
|
|
@@ -835,24 +1006,6 @@ function buildFunctionCallOutputEvents(toolCall, chunkSize, outputIndex) {
|
|
|
835
1006
|
};
|
|
836
1007
|
}
|
|
837
1008
|
/**
|
|
838
|
-
* `text` cut into slices of at most `chunkSize` UTF-16 units, never between
|
|
839
|
-
* the two halves of a surrogate pair: a cut that would land inside a pair
|
|
840
|
-
* moves back before it, or, when that would leave the slice empty
|
|
841
|
-
* (`chunkSize` 1), forward past it. The slices rejoin to `text` exactly.
|
|
842
|
-
*/
|
|
843
|
-
function surrogateSafeChunks(text, chunkSize) {
|
|
844
|
-
const chunks = [];
|
|
845
|
-
for (let i = 0; i < text.length;) {
|
|
846
|
-
let end = Math.min(i + chunkSize, text.length);
|
|
847
|
-
const high = text.charCodeAt(end - 1);
|
|
848
|
-
const low = text.charCodeAt(end);
|
|
849
|
-
if (high >= 55296 && high <= 56319 && low >= 56320 && low <= 57343) end = end - 1 > i ? end - 1 : end + 1;
|
|
850
|
-
chunks.push(text.slice(i, end));
|
|
851
|
-
i = end;
|
|
852
|
-
}
|
|
853
|
-
return chunks;
|
|
854
|
-
}
|
|
855
|
-
/**
|
|
856
1009
|
* Emit output_item.added → custom_tool_call_input.delta×N →
|
|
857
1010
|
* custom_tool_call_input.done → output_item.done for one `custom_tool_call` at
|
|
858
1011
|
* `outputIndex`, returning the completed item. An empty `input`
|
|
@@ -895,7 +1048,7 @@ function buildCustomToolCallOutputEvents(call, chunkSize, outputIndex) {
|
|
|
895
1048
|
function buildToolCallOutputEvents(call, chunkSize, outputIndex) {
|
|
896
1049
|
return isCustomEmittable(call) ? buildCustomToolCallOutputEvents(call, chunkSize, outputIndex) : buildFunctionCallOutputEvents(call, chunkSize, outputIndex);
|
|
897
1050
|
}
|
|
898
|
-
function buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false, phase) {
|
|
1051
|
+
function buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false, phase, citations, files) {
|
|
899
1052
|
const output = [];
|
|
900
1053
|
if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) output.push(buildReasoningOutputItem(generateId("rs"), {
|
|
901
1054
|
summaryText: reasoning,
|
|
@@ -910,6 +1063,7 @@ function buildOutputPrefix(content, model, reasoning, webSearches, emitEncrypted
|
|
|
910
1063
|
query
|
|
911
1064
|
}
|
|
912
1065
|
});
|
|
1066
|
+
output.push(...imageGenerationCallItems(files));
|
|
913
1067
|
output.push({
|
|
914
1068
|
type: "message",
|
|
915
1069
|
id: itemId(),
|
|
@@ -919,7 +1073,7 @@ function buildOutputPrefix(content, model, reasoning, webSearches, emitEncrypted
|
|
|
919
1073
|
content: [{
|
|
920
1074
|
type: "output_text",
|
|
921
1075
|
text: content,
|
|
922
|
-
annotations:
|
|
1076
|
+
annotations: buildAnnotations(content, citations)
|
|
923
1077
|
}]
|
|
924
1078
|
});
|
|
925
1079
|
return output;
|
|
@@ -936,10 +1090,11 @@ function buildResponseEnvelope(model, output, overrides) {
|
|
|
936
1090
|
};
|
|
937
1091
|
}
|
|
938
1092
|
function buildTextResponse(content, model, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
939
|
-
return buildResponseEnvelope(model, buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning, synthesizeSummarylessReasoning, overrides?.phase), overrides);
|
|
1093
|
+
return buildResponseEnvelope(model, buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning, synthesizeSummarylessReasoning, overrides?.phase, overrides?.citations, overrides?.files), overrides);
|
|
940
1094
|
}
|
|
941
1095
|
function buildToolCallResponse(toolCalls, model, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
942
1096
|
assertResponsesToolCalls(toolCalls);
|
|
1097
|
+
fixtureCitations("", overrides?.citations);
|
|
943
1098
|
const output = [];
|
|
944
1099
|
if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) output.push(buildReasoningOutputItem(generateId("rs"), {
|
|
945
1100
|
summaryText: reasoning,
|
|
@@ -954,6 +1109,7 @@ function buildToolCallResponse(toolCalls, model, reasoning, webSearches, overrid
|
|
|
954
1109
|
query
|
|
955
1110
|
}
|
|
956
1111
|
});
|
|
1112
|
+
output.push(...imageGenerationCallItems(overrides?.files));
|
|
957
1113
|
for (const tc of toolCalls) output.push(buildToolCallOutputItem(tc));
|
|
958
1114
|
return buildResponseEnvelope(model, output, overrides);
|
|
959
1115
|
}
|
|
@@ -971,9 +1127,11 @@ function contentWithToolCallsStreamEvents(content, toolCalls, model, chunkSize,
|
|
|
971
1127
|
const orderedOutputItems = [];
|
|
972
1128
|
if (ordered && ordered.length > 0) {
|
|
973
1129
|
let outputIndex = nextOutputIndex;
|
|
1130
|
+
const textAnnotations = orderedBlockAnnotations(ordered, overrides?.citations);
|
|
1131
|
+
let textPos = 0;
|
|
974
1132
|
for (const block of ordered) {
|
|
975
1133
|
if (block.type === "text") {
|
|
976
|
-
const { events: msgEvents, msgItem } = buildMessageOutputEvents(block.text, chunkSize, outputIndex, overrides?.phase);
|
|
1134
|
+
const { events: msgEvents, msgItem } = buildMessageOutputEvents(block.text, chunkSize, outputIndex, overrides?.phase, textAnnotations[textPos++]);
|
|
977
1135
|
events.push(...msgEvents);
|
|
978
1136
|
orderedOutputItems.push(msgItem);
|
|
979
1137
|
} else {
|
|
@@ -985,7 +1143,7 @@ function contentWithToolCallsStreamEvents(content, toolCalls, model, chunkSize,
|
|
|
985
1143
|
}
|
|
986
1144
|
} else {
|
|
987
1145
|
assertResponsesToolCalls(toolCalls);
|
|
988
|
-
const { events: msgEvents, msgItem } = buildMessageOutputEvents(content, chunkSize, nextOutputIndex, overrides?.phase);
|
|
1146
|
+
const { events: msgEvents, msgItem } = buildMessageOutputEvents(content, chunkSize, nextOutputIndex, overrides?.phase, buildAnnotations(content, overrides?.citations));
|
|
989
1147
|
events.push(...msgEvents);
|
|
990
1148
|
orderedOutputItems.push(msgItem);
|
|
991
1149
|
for (let idx = 0; idx < toolCalls.length; idx++) {
|
|
@@ -1013,7 +1171,7 @@ function buildToolCallOutputItem(tc) {
|
|
|
1013
1171
|
if (isCustomEmittable(tc)) return customToolCallItem(tc, customToolCallItemId(), tc.id || generateToolCallId(), requireFixtureToolInput(tc.input), "completed");
|
|
1014
1172
|
return functionCallItem(tc, generateId("fc"), tc.id || generateToolCallId(), requireFixtureToolArguments(tc.arguments), "completed");
|
|
1015
1173
|
}
|
|
1016
|
-
function buildMessageOutputItem(content, phase) {
|
|
1174
|
+
function buildMessageOutputItem(content, phase, annotations) {
|
|
1017
1175
|
return {
|
|
1018
1176
|
type: "message",
|
|
1019
1177
|
id: itemId(),
|
|
@@ -1023,7 +1181,7 @@ function buildMessageOutputItem(content, phase) {
|
|
|
1023
1181
|
content: [{
|
|
1024
1182
|
type: "output_text",
|
|
1025
1183
|
text: content,
|
|
1026
|
-
annotations
|
|
1184
|
+
annotations
|
|
1027
1185
|
}]
|
|
1028
1186
|
};
|
|
1029
1187
|
}
|
|
@@ -1043,40 +1201,18 @@ function buildContentWithToolCallsResponse(content, toolCalls, model, reasoning,
|
|
|
1043
1201
|
query
|
|
1044
1202
|
}
|
|
1045
1203
|
});
|
|
1046
|
-
|
|
1204
|
+
output.push(...imageGenerationCallItems(overrides?.files));
|
|
1205
|
+
const textAnnotations = orderedBlockAnnotations(ordered, overrides?.citations);
|
|
1206
|
+
let textPos = 0;
|
|
1207
|
+
for (const block of ordered) if (block.type === "text") output.push(buildMessageOutputItem(block.text, overrides?.phase, textAnnotations[textPos++]));
|
|
1047
1208
|
else output.push(buildToolCallOutputItem(block));
|
|
1048
1209
|
return buildResponseEnvelope(model, output, overrides);
|
|
1049
1210
|
}
|
|
1050
1211
|
assertResponsesToolCalls(toolCalls);
|
|
1051
|
-
const output = buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning, synthesizeSummarylessReasoning, overrides?.phase);
|
|
1212
|
+
const output = buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning, synthesizeSummarylessReasoning, overrides?.phase, overrides?.citations, overrides?.files);
|
|
1052
1213
|
for (const tc of toolCalls) output.push(buildToolCallOutputItem(tc));
|
|
1053
1214
|
return buildResponseEnvelope(model, output, overrides);
|
|
1054
1215
|
}
|
|
1055
|
-
async function writeResponsesSSEStream(res, events, optionsOrLatency) {
|
|
1056
|
-
const opts = typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : optionsOrLatency ?? {};
|
|
1057
|
-
const latency = opts.latency ?? 0;
|
|
1058
|
-
const profile = opts.streamingProfile;
|
|
1059
|
-
const { recordedTimings, replaySpeed } = opts;
|
|
1060
|
-
const signal = opts.signal;
|
|
1061
|
-
const onChunkSent = opts.onChunkSent;
|
|
1062
|
-
if (res.writableEnded) return true;
|
|
1063
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
1064
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
1065
|
-
res.setHeader("Connection", "keep-alive");
|
|
1066
|
-
let chunkIndex = 0;
|
|
1067
|
-
for (const event of events) {
|
|
1068
|
-
const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
|
|
1069
|
-
if (chunkDelay > 0) await delay(chunkDelay, signal);
|
|
1070
|
-
if (signal?.aborted) return false;
|
|
1071
|
-
if (res.writableEnded) return true;
|
|
1072
|
-
res.write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1073
|
-
onChunkSent?.();
|
|
1074
|
-
if (signal?.aborted) return false;
|
|
1075
|
-
chunkIndex++;
|
|
1076
|
-
}
|
|
1077
|
-
if (!res.writableEnded) res.end();
|
|
1078
|
-
return true;
|
|
1079
|
-
}
|
|
1080
1216
|
async function handleResponses(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
|
|
1081
1217
|
setCorsHeaders(res);
|
|
1082
1218
|
let responsesReq;
|
|
@@ -1158,7 +1294,10 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1158
1294
|
const emitEncryptedReasoning = requestWantsEncryptedReasoning(responsesReq);
|
|
1159
1295
|
const synthesizeSummarylessReasoning = requestIncludesEncryptedReasoning(responsesReq);
|
|
1160
1296
|
const testId = getTestId(req);
|
|
1161
|
-
const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform,
|
|
1297
|
+
const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, {
|
|
1298
|
+
...recordMatchOptions(!!defaults.record, defaults.logger),
|
|
1299
|
+
wire: "openai-responses"
|
|
1300
|
+
});
|
|
1162
1301
|
if (fixture) {
|
|
1163
1302
|
defaults.logger.debug(`Responses fixture matched for ${req.method ?? "POST"} ${req.url ?? "/v1/responses"}`);
|
|
1164
1303
|
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
@@ -1193,7 +1332,7 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1193
1332
|
return;
|
|
1194
1333
|
}
|
|
1195
1334
|
if (defaults.record) {
|
|
1196
|
-
const outcome = await proxyAndRecord(req, res, completionReq, "openai", req.url ?? "/v1/responses", fixtures, defaults, raw);
|
|
1335
|
+
const { outcome, clientHungUp } = await withHangUpDetection(req, res, () => proxyAndRecord(req, res, completionReq, "openai", req.url ?? "/v1/responses", fixtures, defaults, raw));
|
|
1197
1336
|
if (outcome === "handled_by_hook") return;
|
|
1198
1337
|
if (outcome !== "not_configured") {
|
|
1199
1338
|
journal.add({
|
|
@@ -1204,7 +1343,8 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1204
1343
|
response: {
|
|
1205
1344
|
status: res.statusCode ?? 200,
|
|
1206
1345
|
fixture: null,
|
|
1207
|
-
source: "proxy"
|
|
1346
|
+
source: "proxy",
|
|
1347
|
+
...proxyInterruption(res, clientHungUp)
|
|
1208
1348
|
}
|
|
1209
1349
|
});
|
|
1210
1350
|
return;
|
|
@@ -1269,9 +1409,23 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1269
1409
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
1270
1410
|
const fixtureTimings = fixture.recordedTimings;
|
|
1271
1411
|
const effectiveReplaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
|
|
1412
|
+
const streamTiming = {
|
|
1413
|
+
latency,
|
|
1414
|
+
streamingProfile: fixture.streamingProfile,
|
|
1415
|
+
recordedTimings: fixtureTimings,
|
|
1416
|
+
replaySpeed: effectiveReplaySpeed
|
|
1417
|
+
};
|
|
1418
|
+
const timedJson = nonStreamingTimingEnabled(fixture, "other");
|
|
1419
|
+
const jsonPayloadLength = (r, reasoning) => responsesPayloadLength(r, reasoning) + responsesOutputFrames(r) * chunkSize;
|
|
1420
|
+
const jsonOpts = (payloadLength, duplicateId = false) => ({
|
|
1421
|
+
surface: "other",
|
|
1422
|
+
payloadLength: timedJson ? payloadLength() : 0,
|
|
1423
|
+
chunkSize,
|
|
1424
|
+
recordedTimings: fixtureTimings,
|
|
1425
|
+
duplicateId,
|
|
1426
|
+
replaySpeed: effectiveReplaySpeed
|
|
1427
|
+
});
|
|
1272
1428
|
if (misbehavior.kind === "applied") {
|
|
1273
|
-
const prepared = prepareResponsesMisbehavior(misbehavior);
|
|
1274
|
-
const output = buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning);
|
|
1275
1429
|
const entry = journal.add({
|
|
1276
1430
|
method: req.method ?? "POST",
|
|
1277
1431
|
path: req.url ?? "/v1/responses",
|
|
@@ -1282,41 +1436,50 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1282
1436
|
fixture
|
|
1283
1437
|
}
|
|
1284
1438
|
});
|
|
1285
|
-
|
|
1439
|
+
const prepared = prepareResponsesMisbehavior(misbehavior);
|
|
1440
|
+
const build = () => buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning);
|
|
1441
|
+
const record = () => recordMisbehaviorOutcome({
|
|
1286
1442
|
entry,
|
|
1287
1443
|
summary: prepared.summary,
|
|
1288
1444
|
defaults,
|
|
1289
1445
|
testId
|
|
1290
1446
|
});
|
|
1447
|
+
const output = timedJson && responsesReq.stream !== true ? void 0 : build();
|
|
1448
|
+
if (output) record();
|
|
1291
1449
|
if (responsesReq.stream !== true) {
|
|
1292
|
-
|
|
1293
|
-
res
|
|
1450
|
+
const servedReasoning = prepared.reasoning ?? ("reasoning" in prepared.response ? prepared.response.reasoning : void 0);
|
|
1451
|
+
await deliverJson(res, entry, fixture, () => {
|
|
1452
|
+
if (output) return output.response;
|
|
1453
|
+
const built = build();
|
|
1454
|
+
record();
|
|
1455
|
+
return built.response;
|
|
1456
|
+
}, jsonOpts(() => misbehaviorPayloadLength(prepared, servedReasoning, jsonPayloadLength), !!prepared.duplicateId));
|
|
1294
1457
|
} else {
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
const lastGap = appliedTimings.interChunkDelaysMs.at(-1) ?? 0;
|
|
1301
|
-
while (appliedTimings.interChunkDelaysMs.length < output.events.length - 1) appliedTimings.interChunkDelaysMs.push(lastGap);
|
|
1302
|
-
}
|
|
1303
|
-
const interruption = createInterruptionSignal(fixture);
|
|
1304
|
-
if (!await writeResponsesSSEStream(res, output.events, {
|
|
1305
|
-
latency,
|
|
1306
|
-
streamingProfile: fixture.streamingProfile,
|
|
1307
|
-
recordedTimings: appliedTimings,
|
|
1308
|
-
replaySpeed: effectiveReplaySpeed,
|
|
1309
|
-
signal: interruption?.signal,
|
|
1310
|
-
onChunkSent: interruption?.tick
|
|
1311
|
-
})) {
|
|
1312
|
-
if (!res.writableEnded) res.destroy();
|
|
1313
|
-
entry.response.interrupted = true;
|
|
1314
|
-
entry.response.interruptReason = interruption?.reason();
|
|
1315
|
-
}
|
|
1316
|
-
interruption?.cleanup();
|
|
1458
|
+
const { events } = output ?? build();
|
|
1459
|
+
await deliverStream(res, entry, fixture, typedSseCodec(), events, {
|
|
1460
|
+
...streamTiming,
|
|
1461
|
+
recordedTimings: extendRecordedTimings(fixtureTimings, events.length, !!prepared.duplicateId)
|
|
1462
|
+
});
|
|
1317
1463
|
}
|
|
1318
1464
|
return;
|
|
1319
1465
|
}
|
|
1466
|
+
if (isRawResponse(response)) {
|
|
1467
|
+
await serveRawResponse({
|
|
1468
|
+
wire: "openai-responses",
|
|
1469
|
+
stream: responsesReq.stream === true,
|
|
1470
|
+
req,
|
|
1471
|
+
res,
|
|
1472
|
+
fixture,
|
|
1473
|
+
response,
|
|
1474
|
+
body: completionReq,
|
|
1475
|
+
journal,
|
|
1476
|
+
defaults,
|
|
1477
|
+
testId,
|
|
1478
|
+
misbehaviorSummary: misbehavior.summary,
|
|
1479
|
+
errorWire: "openai"
|
|
1480
|
+
});
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1320
1483
|
if (isErrorResponse(response)) {
|
|
1321
1484
|
const status = response.status ?? 500;
|
|
1322
1485
|
recordMisbehaviorOutcome({
|
|
@@ -1360,26 +1523,10 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1360
1523
|
const ordered = resolveServedBlockOutcome(response)?.ordered;
|
|
1361
1524
|
if (!ordered) assertCustomToolCalls(response);
|
|
1362
1525
|
const toolCalls = servedToolCalls(response);
|
|
1363
|
-
if (responsesReq.stream !== true)
|
|
1364
|
-
|
|
1365
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1366
|
-
res.end(JSON.stringify(body));
|
|
1367
|
-
} else {
|
|
1526
|
+
if (responsesReq.stream !== true) await deliverJson(res, journalEntry, fixture, () => buildContentWithToolCallsResponse(response.content ?? "", toolCalls, completionReq.model, effReasoning, response.webSearches, overrides, ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning), jsonOpts(() => jsonPayloadLength(response, effReasoning)));
|
|
1527
|
+
else {
|
|
1368
1528
|
const events = contentWithToolCallsStreamEvents(response.content ?? "", toolCalls, completionReq.model, chunkSize, effReasoning, response.webSearches, overrides, ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
1369
|
-
|
|
1370
|
-
if (!await writeResponsesSSEStream(res, events, {
|
|
1371
|
-
latency,
|
|
1372
|
-
streamingProfile: fixture.streamingProfile,
|
|
1373
|
-
recordedTimings: fixtureTimings,
|
|
1374
|
-
replaySpeed: effectiveReplaySpeed,
|
|
1375
|
-
signal: interruption?.signal,
|
|
1376
|
-
onChunkSent: interruption?.tick
|
|
1377
|
-
})) {
|
|
1378
|
-
if (!res.writableEnded) res.destroy();
|
|
1379
|
-
journalEntry.response.interrupted = true;
|
|
1380
|
-
journalEntry.response.interruptReason = interruption?.reason();
|
|
1381
|
-
}
|
|
1382
|
-
interruption?.cleanup();
|
|
1529
|
+
await deliverStream(res, journalEntry, fixture, typedSseCodec(), events, streamTiming);
|
|
1383
1530
|
}
|
|
1384
1531
|
return;
|
|
1385
1532
|
}
|
|
@@ -1403,26 +1550,10 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1403
1550
|
defaults,
|
|
1404
1551
|
testId
|
|
1405
1552
|
});
|
|
1406
|
-
if (responsesReq.stream !== true)
|
|
1407
|
-
|
|
1408
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1409
|
-
res.end(JSON.stringify(body));
|
|
1410
|
-
} else {
|
|
1553
|
+
if (responsesReq.stream !== true) await deliverJson(res, journalEntry, fixture, () => buildTextResponse(response.content, completionReq.model, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning), jsonOpts(() => jsonPayloadLength(response, effReasoning)));
|
|
1554
|
+
else {
|
|
1411
1555
|
const events = buildTextStreamEvents(response.content, completionReq.model, chunkSize, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
1412
|
-
|
|
1413
|
-
if (!await writeResponsesSSEStream(res, events, {
|
|
1414
|
-
latency,
|
|
1415
|
-
streamingProfile: fixture.streamingProfile,
|
|
1416
|
-
recordedTimings: fixtureTimings,
|
|
1417
|
-
replaySpeed: effectiveReplaySpeed,
|
|
1418
|
-
signal: interruption?.signal,
|
|
1419
|
-
onChunkSent: interruption?.tick
|
|
1420
|
-
})) {
|
|
1421
|
-
if (!res.writableEnded) res.destroy();
|
|
1422
|
-
journalEntry.response.interrupted = true;
|
|
1423
|
-
journalEntry.response.interruptReason = interruption?.reason();
|
|
1424
|
-
}
|
|
1425
|
-
interruption?.cleanup();
|
|
1556
|
+
await deliverStream(res, journalEntry, fixture, typedSseCodec(), events, streamTiming);
|
|
1426
1557
|
}
|
|
1427
1558
|
return;
|
|
1428
1559
|
}
|
|
@@ -1447,26 +1578,10 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1447
1578
|
testId
|
|
1448
1579
|
});
|
|
1449
1580
|
assertCustomToolCalls(response);
|
|
1450
|
-
if (responsesReq.stream !== true)
|
|
1451
|
-
|
|
1452
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1453
|
-
res.end(JSON.stringify(body));
|
|
1454
|
-
} else {
|
|
1581
|
+
if (responsesReq.stream !== true) await deliverJson(res, journalEntry, fixture, () => buildToolCallResponse(servedToolCalls(response), completionReq.model, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning), jsonOpts(() => jsonPayloadLength(response, effReasoning)));
|
|
1582
|
+
else {
|
|
1455
1583
|
const events = toolCallStreamEvents(servedToolCalls(response), completionReq.model, chunkSize, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
1456
|
-
|
|
1457
|
-
if (!await writeResponsesSSEStream(res, events, {
|
|
1458
|
-
latency,
|
|
1459
|
-
streamingProfile: fixture.streamingProfile,
|
|
1460
|
-
recordedTimings: fixtureTimings,
|
|
1461
|
-
replaySpeed: effectiveReplaySpeed,
|
|
1462
|
-
signal: interruption?.signal,
|
|
1463
|
-
onChunkSent: interruption?.tick
|
|
1464
|
-
})) {
|
|
1465
|
-
if (!res.writableEnded) res.destroy();
|
|
1466
|
-
journalEntry.response.interrupted = true;
|
|
1467
|
-
journalEntry.response.interruptReason = interruption?.reason();
|
|
1468
|
-
}
|
|
1469
|
-
interruption?.cleanup();
|
|
1584
|
+
await deliverStream(res, journalEntry, fixture, typedSseCodec(), events, streamTiming);
|
|
1470
1585
|
}
|
|
1471
1586
|
return;
|
|
1472
1587
|
}
|
|
@@ -1501,7 +1616,11 @@ function buildResponsesMisbehavior(plan, request, chunkSize, emitEncryptedReason
|
|
|
1501
1616
|
const overrides = extractOverrides(response);
|
|
1502
1617
|
const reasoning = plan.reasoning ?? ("reasoning" in response ? response.reasoning : void 0);
|
|
1503
1618
|
const webSearches = "webSearches" in response ? response.webSearches : void 0;
|
|
1504
|
-
const
|
|
1619
|
+
const outputWithheld = plan.refusal !== void 0 || plan.summary.fault === "empty-response" || plan.stop === "content_filter" || plan.reasoning !== void 0;
|
|
1620
|
+
const { respId, created, events, prefixOutputItems, nextOutputIndex } = buildResponsePreamble(request.model, chunkSize, reasoning, webSearches, outputWithheld ? {
|
|
1621
|
+
...overrides,
|
|
1622
|
+
files: void 0
|
|
1623
|
+
} : overrides, emitEncryptedReasoning, false, plan.reasoning !== void 0);
|
|
1505
1624
|
const output = [...prefixOutputItems];
|
|
1506
1625
|
let outputIndex = nextOutputIndex;
|
|
1507
1626
|
const incomplete = plan.stop === "length" || plan.stop === "content_filter";
|
|
@@ -1559,16 +1678,18 @@ function buildResponsesMisbehavior(plan, request, chunkSize, emitEncryptedReason
|
|
|
1559
1678
|
item
|
|
1560
1679
|
});
|
|
1561
1680
|
output.push(item);
|
|
1562
|
-
} else if (
|
|
1681
|
+
} else if (!outputWithheld) {
|
|
1563
1682
|
const combined = isCombinedFixtureResponse(response);
|
|
1564
1683
|
const blocks = (combined ? resolveServedBlockOutcome(response) : void 0)?.ordered ?? [..."content" in response && typeof response.content === "string" ? [{
|
|
1565
1684
|
type: "text",
|
|
1566
1685
|
text: response.content
|
|
1567
1686
|
}] : [], ...combined || isToolCallResponse(response) ? servedToolCalls(response).map(toolCallFixtureBlock) : []];
|
|
1568
1687
|
let callIndex = 0;
|
|
1688
|
+
const textAnnotations = orderedBlockAnnotations(blocks, overrides.citations);
|
|
1689
|
+
let textPos = 0;
|
|
1569
1690
|
for (const block of blocks) {
|
|
1570
1691
|
if (block.type === "text") {
|
|
1571
|
-
const message = buildMessageOutputEvents(block.text, chunkSize, outputIndex, overrides.phase);
|
|
1692
|
+
const message = buildMessageOutputEvents(block.text, chunkSize, outputIndex, overrides.phase, textAnnotations[textPos++]);
|
|
1572
1693
|
events.push(...message.events);
|
|
1573
1694
|
output.push(message.msgItem);
|
|
1574
1695
|
} else {
|