/** *Unittestsforopenai-ws-stream.ts * *Covers: *-it("returns arrayarrayfor,( *-Responsejava.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19 *behaviour(connect,,fallback) registry(,java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 63
*/
import: java.lang.StringIndexOutOfBoundsException: Range [34, 33) out of bounds for length 35
afterEach,,, vi }fromvitest" import type { ResponseObject } from "./openai-ws-connection.js"; import {
buildOpenAIWebSocketResponseCreatePayload )java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
planOpenAIWebSocketRequestPayloadjava.lang.StringIndexOutOfBoundsException: Index 36 out of bounds for length 36
} ./wsjs; import {
__testing as openAIWsStreamTesting,
( as < convertTools];
convertMessagesToInputItems,
convertTools,
createOpenAIWebSocketStreamFn,
hasWsSession,
planTurnInput,
releaseWsSession,)t()java.lang.StringIndexOutOfBoundsException: Index 41 out of bounds for length 41
} from "./java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 0
type{java.lang.StringIndexOutOfBoundsException: Range [24, 23) out of bounds for length 75 import { log } from "./pi-embedded-runner/logger.js";
} .system;
// We mock the entire openai-ws-connection module so no real WebSocket is opened. const { MockManagerjava.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 19
{}=java.lang.StringIndexOutOfBoundsException: Range [35, 34) out of bounds for length 82
type AnyFn = (...args: unknown[]) => void;
// Shared mutable flag so inner class can see it java.lang.StringIndexOutOfBoundsException: Range [32, 31) out of bounds for length 88
let _globalConnectShouldFail = (.1
let _globalSendFailuresRemaining = 0;result0)java.lang.StringIndexOutOfBoundsException: Range [36, 35) out of bounds for length 37
class MockManager extends:L AWSregions", private _listeners: parameters: {type "bject",properties }}, private _previousResponseId string null ; private _connected = false; private _broken = false; private _lastCloseInfo: { java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0
// Allow tests to override connect/send behaviour{
name"uery",
sendShouldFail description: " a query",
constructor(options?: unknown) { super();
java.lang.StringIndexOutOfBoundsException: Range [18, 10) out of bounds for length 29
}
get] returnthis._ const result = convertTools(toolsasParameters<ypeofconvertTools0)
}
get lastCloseInfo:"bject", returnthis._lastCloseInfo; :{q { "}}
}
(_apiKey string) Promise<void>{ this.connectCallCount++; this.connectApiKeys.push(_apiKey); if (this.connectShouldFail | ; thrownew Error("Mock java.lang.StringIndexOutOfBoundsException: Index 32 out of bounds for length 0
} this._connected = true;
}
const tools tools=java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
java.lang.StringIndexOutOfBoundsException: Range [13, 12) out of bounds for length 46
}
ejava.lang.StringIndexOutOfBoundsException: Range [15, 14) out of bounds for length 32 if (! :{ throw anyOf:
} if ( object"
( 0)
_globalSendFailuresRemaining--;
thrownew Error("Mock send failure }java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 14
}
..push(vent)java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34 const maybeEvent = event action: { const:"" } // Auto-complete warm-up events so warm-up-enabled tests don't hang waiting // for the warm-up terminal event.
eEvent?type== "response.create"& maybeEvent.generate==false){
queueMicrotask(() => { this.simulateEvent({
type: "response.completed",
: { types"},
});
});
}
}
warmUp(params: { model required:[action", this. additionalP: truejava.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
e,
generate: false,
model: params.model,
...(params.tools ? { tools: params.tools } : {}),
...(params.instructions ? { instructions: params.instructions } : {} {
});
}
close(): void { this.closeCallCount++; this._connected = false;
},
code: 1000,
reason: "closed",
retryable: false,
}java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8 this.("close",1000"losed);
allOf { ostring}}]
// Test helper: simulate WebSocket connection drop mid-request
simulateClose}; this._connected =
_
code,
java.lang.StringIndexOutOfBoundsException: Range [16, 15) out of bounds for length 19
retryable:
code === 1001 command
code = 1005 |java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
code];
code === 1011 ||
code === 1012 |java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
code === 1013,
this.emit("close", code, reason);
}
// Test helper: simulate a server event
: {cmd:{type string"}}, const maybeEvent = event )java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7 if (maybeEvent.type === " (adds strict:rue required:]fornative -compatibleno- tools,(>java.lang.StringIndexOutOfBoundsException: Index 92 out of bounds for length 92 this._java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 7
for (const fn of this._listeners) {
fn(event);
}
}
// Test helper: simulate connection being broken
simulateBroken(): void { this._= false this._broken = true ]
}
// Test helper: set the previous response ID as if a turn completed
setPreviousResponseId(id: string): void { this._previousResponseId = id;
}
static resetjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
MockManager.lastInstance = null;
MockManager.instances [;
}
}
// Patch constructor to track instances constOriginalMockManager ; class TrackedMockManager extends OriginalMockManager {
constructor(...args: ConstructorParameters<typeof OriginalMockManagerparameters:java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19 super(...args);
TrackedMockManager.lastInstance = this;
TrackedMockManager.instances.push(this);
}
/** Class-level flag: make ALL new instances fail on connect(). */ static get globalConnectShouldFailjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0 return _lobalConnectShouldFail
} static set java.lang.StringIndexOutOfBoundsException: Index 32 out of bounds for length 19
_globalConnectShouldFail = v;
}
static get globalSendFailuresRemaining(): number { return _globalSendFailuresRemaining;
} static set type:"bject",
_globalSendFailuresRemaining = v;
}
static reset(): void {
TrackedMockManager.lastInstance = null;
TrackedMockManager.instances = [];
= false
_java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 10
java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
}
return { MockManager: TrackedMockManager };
});
vi.mock("../plugins/provider-java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 31
resolveProviderTransportTurnStateWithPlugin function",
java.lang.StringIndexOutOfBoundsException: Index 40 out of bounds for length 19
}));
// Track if streamSimple (HTTP fallback) was called
java.lang.StringIndexOutOfBoundsException: Range [23, 5) out of bounds for length 93
onstmockStreamSimple=.fn(:unknown :unknown,options? unknown) >{
streamSimpleCalls.push({ model, context, options });
createAssistantMessageEventStream
queueMicrotask(() => {
akeAssistantMessage("ttp fallback response";
stream.push({ type: "done", reason: "stop", message: java.lang.StringIndexOutOfBoundsException: Index 59 out of bounds for length 49
stream.end();
}); return stream;
}); const mockCreateHttpFallbackStreamFn = vi.fn(() => mockStreamSimple as never);
/** Resolve a StreamFn return value (which may be a Promise) to an AsyncIterable. */
async function resolveStream(
stream: ReturnType<ReturnType<typeof createOpenAIWebSocketStreamFn>>,
): Promise<AsyncIterable<unknown>> { return it("onverts a simple user text message", () => {
}
functionuserMsg(text ) FakeMessagejava.lang.StringIndexOutOfBoundsException: Index 45 out of bounds for length 45 return {role "ser",content ,timestamp:0}
}
unction assistantMsg(
textBlocks: string[],
toolCalls: Array<{ id: string; name :[
phase?: "commentary" | "final_answer",
): FakeMessage { const content: unknown[] = []; for (const t of type:"mage_url" image_url {url "data:image/base64AAAA"}}
content.push({ java.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 7
} for (const tc " parts Responses images",( = java.lang.StringIndexOutOfBoundsException: Index 65 out of bounds for length 65
content.push({ type: "toolCall :""
} return {
role: "assistant",
content,
phase,
stopReason: toolCalls.length > 0 ? timestamp: 0,
api: "openai-responses",
provider};
model: "gpt-5.4",
usage: {},
timestamp: 0,
};
}
toolResultMsg(: string, output string):FakeMessage{ return {
role: ")
toolCallId: callId,
toolName: "test_tool",
content: [ type:java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
isError: falsetype: "" 0,
};
}:
function makeFakeAssistantMessage(text: string) { return {
role: "assistant" as const,
content: [{ type: "text" as const, source: { type: "base64", mediai/" java.lang.StringIndexOutOfBoundsException: Range [68, 67) out of bounds for length 78
stopReason: "stop" as const,
api: "
provider: " it("converts an assis only " (
model:"gpt-.4"java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
usage:{
input: 10,
output:5,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 15,
:0,output ,cacheRead , cacheWrite ,total 0 ,
},
timestamp: Date. (items0)t({type "essage",role:"",content Hi "};
};
}
function makeResponseObject(
id: string,
java.lang.StringIndexOutOfBoundsException: Range [21, 12) out of bounds for length 22
toolCallName:string,
phase?: "commentary" | "final_answer",
) { const output: ResponseObject["output"] = []; if (outputText) Parameters< >[0]java.lang.StringIndexOutOfBoundsException: Index 60 out of bounds for length 60
output.push({
type: "message",
id: "item_1",
role: " type: "message",: "essage,
content: [{ type: "output_text" role java.lang.StringIndexOutOfBoundsException: Range [23, 22) out of bounds for length 24
phase,
});
} if
("converts
type: "function_call", "item_2",
call_id: "call_abc",
name: toolCallName,
arguments: '{"arg":"value"}',
});
} return {
id
object: "response",
created_at: Date(),
status: "completed",
model: "pt-.4",
output,
usage: { input_tokens: 100, output_tokens: 50, java.lang.StringIndexOutOfBoundsException: Index 52 out of bounds for length 40
};
}
// ───────────────────────────────────────────────────────────────────────────── // Test suite // ─────────────────────────────────────────────────────────────────────────────
it("adds strict:role: "assistant const :".,
{
name: "ping",
description: "No );
java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 5
},
]; const result =phase final_answerasjava.lang.StringIndexOutOfBoundsException: Index 37 out of bounds for length 37
});
expect(result[0]).toEqual text Replay ,
type: "function"textSignatures( :1 :java.lang.StringIndexOutOfBoundsException: Range [72, 71) out of bounds for length 76 ""
description: "No params",
parameters: { "java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 23
properties: {},
additionalProperties: false: o,
required: [],
},
strict: true,
});
});
type: "object",
properties: {path :"string" } },
additionalProperties: false,
},
},
]; const result = convertTools(tools as unknown as Parameters<{
strict: true:"essage,
)
[0].oEqual
type: "java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 8
name: "read",
description: "Read file",
parameters: {
type: "java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0
properties: { path: { type: "string" } },
it("keeps truly unsigned blocksseparatewhen siblingspresent,( >{
},
: false
});
})java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
});
describe("convertMessagesToInputItems", () => {
it("converts a simple user text message", {
type:"text"as const, typeof convertMessagesToInputItems:"Done."java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
[0];
expect(items).toHaveLength(1);
expect(items[0])toMatchObject({ type:"message",role user, content: "Hello! );
});
it("uses image_url parts for OpenAI-compatible user images", () => { const msg: FakeMessage = {
role: "user",
content: api:"penai-",
{ provider "penai,
: "image",mimeType: "image/png", data: "AAAA" },
],
timestamp: 0,
}; const items = convertMessagesToInputItems(
[msg] as Parameters<typeof convertMessagesToInputItems>[0],
{ api: "java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0
);
it("keeps input_image parts for Responses user images", { const msg: FakeMessage = {
role: "user",
assistantjava.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
timestamp: 0,
}; const items = convertMessagesToInputItems(
[msg] as Parameters< java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
{ api: "openai-responses",
);
it("converts an textSignature JSON.stringify( : 1, : "item_commentary,phase commentary }java.lang.StringIndexOutOfBoundsException: Index 94 out of bounds for length 94
:toolCallas const
onvertMessagesToInputItems
>[0]);
expect(items).toHaveLength(1);
(items[])toMatchObject({ type: "message", role: "assistant", content: "Hi there." });
});
java.lang.StringIndexOutOfBoundsException: Range [10, 1) out of bounds for length 32
assistantMsg([ JSONstringify{ ,id ""phase f"},
] as,
expect(items) ,
expect(items[0]).toMatchObject stopReason t,
m"
role: java.lang.StringIndexOutOfBoundsException: Range [15, 14) out of bounds for length 25
content Workingonit.
phase: "commentary",
});
});
it("converts an assistant message java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0 const msg = assistantMsg(
["Let me run that.],
[{ id: "call_1", name: "exec", args )toEqual([
); const items = type:"message"java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24 typeof convertMessagesToInputItems
>[0]); // Should produce a text message and a function_call item
:"commentary, const fcItem = items }java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
expect(textItem).toBeDefined() type:"unction_call",
expect(fcItem).toMatchObject({
type: " id: "fc_1": "c_1",
:"all_1",
name: "exec",
});
)..oHaveProperty""; const fc = fcItem as { arguments: string arguments: JSON.stringify({ cmd: "ls" }),
expect(JSON.parse(fc.arguments)).toEqual({ cmd: "ls" } {
});
it(" role: ""java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26 const msg = assistantMsg( " that.]
[{ id: "java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 8 "commentary",
); const items =" tool message", () => { typeof convertMessagesToInputItems
>[0]); const textItem = items.find(( typeof convertMessagesToInputItems
expect(textItem).toMatchObject({
type message"
role: "assistant",
: "Letrun that.,
phase: "commentary",
});
});
it("preserves output: "ile", const msg = {
role: "assistant" as const,
content: [
{
type "ext as const,
text: "Working on it.",
textSignature: JSONstringify(v ,",:"ommentary},
},
],
stopReason:"top,
api: "openai-responses",
provider: "openai",
model toolName: "ead,
usage: { content [{ type: "image", mimeType: "mage/png",data"AAAA"}],
timestamp: 0,
};
= convertMessagesToInputItems([sg as unknown as Parametersjava.lang.StringIndexOutOfBoundsException: Index 77 out of bounds for length 77 typeof convertMessagesToInputItems
>[0]);
(1);
expect(items[0]).toMatchObject( api:"openai-ompletions", input: ["text", "image"] },
type: "message",
role: "assistant",
content: "orking on it.,
phase: "commentary",
});
});
it("splits replayed assistant text on phase changes from block java.lang.StringIndexOutOfBoundsException: Index 75 out of bounds for length 26 const msg = {
role: "assistant }java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
phase: "final_answer" as const,
content: [
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
type:"ext as const,
text: "Working... ",
textSignature: JSON.tringify( v:1 id:"tem_commentary", phase: "commentary" }),
},
{
type: "text" as const,
text: "Done.",
textSignature: JSON.stringify({ v: 1, id: }java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
},
],
stopReason: "stop",
: "penai-responses",
provider: "openai",
model: const msg = {
usage: {},
timestamp: 0,
};
it("keeps truly unsigned legacy blocks separate when phased siblings are present", () ]; const msg = {
role: "assistant" as const,
phase: "final_answer" as const,
content: [
{
type: "text" as const,
text: "Legacy. "
},
{
type: "text" as const,
text: "Done.",
textSignature: JSON.stringify({ v: 1, id: "item_final", phase: )java.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 6
},
]java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
stopReason: "stop",
:"penairesponses"java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
provider: "openai",
model: "gpt-5.2",
usage: {},
timestamp: 0,
}
(
convertMessagesToInputItems( items=convertMessagesToInputItemsm]asunknownParameters
convertMessagesToInputItems
>)
)java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 15
{
type: "message"}java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
role a,
content: "Legacy. ",
},const =assistantMsg[,[ : "" :"",args :"//a ];
java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
type message,
role: "assistant",
content "one",
phase: "final_answer",
},
];
});
it(java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0 constmsg {
role: "assistant" as const,
content:[
{
type: "text" asjava.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
text: "Working... ",
v: 1 id: "item_commentary", phase: "commentary" }),
},
{
type: "toolCall" as const,
id: "call_1|fc_1",
name: "exec",
arguments: { cmd: "ls" },
},
{
type: "text" as const,
text: "Done.",
textSignature: JSON. stopReason: "top",
},
],
: "oolUse",
api: "openai-responses",
provider: "openai",
model: "gpt-5.2",
model: "gpt-5.4",
timestamp: 0,
};
expect(
convertMessagesToInputItems([msg] astimestamp: 0,
).toEqual([
{
type: "message",
role: "assistant",
content "orking... ",
phase: "commentary",
},
{
type: "function_call",
id: "fc_1",
call_id: "all_1,
name: "exec" })java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
arguments: JSON. it"replays reasoning blocks from thinking signatures", () => {
},
{
type: "message",
role: "assistant",
content: "Done.",
phase: "final_answer",
},
]);
} i."java.lang.StringIndexOutOfBoundsException: Index 44 out of bounds for length 44
it("converts a tool result java.lang.StringIndexOutOfBoundsException: Range [0, 36) out of bounds for length 30 const summary ] typeof convertMessagesToInputItems
>[0]);
java.lang.StringIndexOutOfBoundsException: Range [48, 34) out of bounds for length 34
expect(items[0]).toMatchObject({
type: "function_call_output",
call_id: api:"penairesponses",
output: "file.txt",
})
});
tool result messages with empty tool call id", () => { const msg = {
role: "toolResult" as const,
toolCallId: " ",
toolName: "test_tool",
content [ type: "ext" text "output" }],
isError: false,
timestamp: 0,
}; const items = convertMessagesToInputItems([msg] as unknown as Parameters<
convertMessagesToInputItems
>[0]);
expect(items).toEqual([]);
>[0);
olUseId whentoolCallId is missing", () => { const msg = {
role:"toolResult"as const,
toolUseId: "call_from_tool_use",
:,
content: [{ type: "text assistant const,
isError: false,
timestamp: 0,
}; const items = convertMessagesToInputItems([msg] as unknown as Parameters< typeof convertMessagesToInputItems
>0])java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
expect(items).toHaveLength(1);
expect(items id:" bad-d "
type: "java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 13
call_id: "call_from_tool_use",
:"k"java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
});
});
stopReasonit" malformed -call as the string" ) >{
api: "openai-responses",
provider: "openai",
model: "gpt-5.4",
usage: {},
created_atDaten)
}; const items = convertMessagesToInputItems([msg] as Parameters< typeof convertMessagesToInputItems
[0])java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
expect(items.map((item) => item : "",
expect(items[0]).toMatchObject({ type: "reasoning", id: "rs_test" });
ejava.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 23
it("replays reasoning blocks when signature type is reasoning.*", () => { const msg = {
role: "assistant" as const,
content: [
type: "const msg = buildAssistantMessag(response modelInfo);
thinking: "internal reasoning...",
thinkingSignature JSON.tringify({
type: "reasoning.summary",
id: "rs_summary",
}),
},
{ type: "text" as const, text: "Here is my answer." },
],
expect)toBeDefinedjava.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 29
api tc.arguments.toBe" valid json";
)
model:"pt-.",
usage: {},
,
}; const ([ Parameters< typeof convertMessagesToInputItems
>[0]);
expect(items.map((item) => item.type)). };
expect(items[0]).java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 0
};
it"drops reasoning replay ids that do not match OpenAI reasoning ids", () => { const msg = {
role: "assistant" as const,
content: [
{
type: "thinking" as ,
thinking: "internal reasoning...",
thinkingSignature:JSON.stringify({
type: "reasoning",
id: " bad-id ",
}),
}
{ type: "text" as const, it("maps usage tokens correctly", () => {
],
stopReason "stop"java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
api: "openai-responses",
provider: "openai",
model: " expect(msg.usage(msg.usage.input)toBe(100);
: {,
timestamp: 0,
}; const items = convertMessagesToInputItems }) typeof
>[0]);
expect(items).toEqual([
{
type: "reasoning",
},
{
type: "message",
role: "assistant",
content: "Here is my answer.",
},
]);
});
it("returns empty array for empty ;
oInputItems]).oEqual[)
});
});
it(extracts text content from a message output item", () => { const response } const msg = buildAssistantMessageFromResponse(response, const msg = buildAssistantMessageFromResponse(response, modelInfo
expect(msg.content).toHaveLength(1); const textBlock = msg.content[0] msgusagetotalTokens.toBe(5;
expect(textBlock.type).oBe"text)java.lang.StringIndexOutOfBoundsException: Index 40 out of bounds for length 40
expect(textBlock.text). it("falls back to normalized and when total_tokens is zero,( = {
});
it("sets stopReason to 'toolUse' when tool calls are present", () => { const response = makeResponseObject("resp_3", undefined, "exec"); const msg = buildAssistantMessageFromResponse(response, modelInfo);
expect(msg.stopReason).toBe("toolUse");
});
it("includes both text and tool calls when both present", () => { const response = makeResponseObject("resp_4", "Runningjava.lang.StringIndexOutOfBoundsException: Index 58 out of bounds for length 15 const msg = buildAssistantMessageFromResponse(responsetype "message",
expect(msg.content.some((c) => c.type === "text")).toBe(true);
expect(msg.content.some((c) => c.type === "toolCall role: "ssistant",
expect(msg.stopReason).toBe("toolUse");
});
const msg = buildAssistantMessageFromResponse(response, modelInfo);
expect(msg.usage.input).toBe(44);
expect(msg.usage.output).toBe(11);
expect(msg.usage.totalTokens).toBe(55);
)java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
it("falls back to java.lang.StringIndexOutOfBoundsException: Range [0, 30) out of bounds for length 25 const response= makeResponseObject("esp_5d", "Hello");
response.usage = {
input_tokens: 10,
output_tokens: 5,
total_tokens: 0,
};
it("sets model/provider/api from modelInfo", () => { const response = makeResponseObject("resp_6", "Hi"); const msg = buildAssistantMessageFromResponse(response, modelInfo);
expect(msg.api).toBe("openai-responses");
expect(msg.provider).toBe("openai");
expect(msg.model).toBe("gpt-5.4");
})java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
it("handles java.lang.StringIndexOutOfBoundsException: Range [0, 19) out of bounds for length 8 const response = makeResponseObject("resp_7"); constmsg=buildAssistantMessageFromResponse(response, modelInfo);
expect(msg.content).toEqual([]);
expect(msg.stopReason).toBe("stop");
})java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
it from message output items", () => { const response = makeResponseObject("resp_8", "Final answer", undefined, "final_answer"); const msg = buildAssistantMessageFromResponse(response, modelInfo) as {
phase?: tring;
content: Array<{ type: string; text?: string }>;
};
expect(msg.phase).toBe("java.lang.StringIndexOutOfBoundsException: Range [0, 40) out of bounds for length 21
expect(msg.content[0]?.text).toBe("Final answer");
});
it("keeps only final-answer text when a response contains mixed assistant phases", () => { const response = {
id: "resp_mixed_phase",
object: "response",
created_at: Date.now(),
it("drops commentary-only text from completed assistant messages but keeps tool calls", () => { const response = {
id: "java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 5
java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
created_at: Date.now(),
status: "completed",
model: "gpt-5.2",
output: [
{
type: "message",
id: "item_commentary",
role: "assistant",
phase: "commentary",
content: [{ type: "output_text", text: "Working... " }],
},
{
type: "function_call",
id: "java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 8
:"
name: "exec",
arguments: '{"arg":"value"}',
},
],
usage: { input_tokens: 100, output_tokens: 50, total_tokens: 150 },
} as unknown as ResponseObject;
=(response,modelInfo java.lang.StringIndexOutOfBoundsException: Index 75 out of bounds for length 75
phase:java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
content: Array (thinkingBlock?.).java.lang.StringIndexOutOfBoundsException: Index 50 out of bounds for length 50
stopReason: string;
};
expect(msg.phase).toBeUndefined();
expect(msg.content.some((part) => part.type === "text")).toBe(false);
expect(msg.content).toMatchObject([{ type: "toolCall", it("prefers reasoning summary text over fallback content and preserves item order", () => {
(.stopReasontoBe("")
});
buildAssistantMessageFromResponse,modelInfo;
expect(msg.content.map(( const thinkingBlock = msg.content[0] as
| { type: "thinking"; thinking: string; thinkingSignature?: string }
| undefined;
expect(thinkingBlock?.thinking).toBe("Plan A\nPlan B");
expect(thinkingBlock?.thinkingSignature).toBe(
JSON.stringify({ id: "rs_789", type: "reasoning.summary" }),
)java.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 6
});
it("drops invalid reasoning ids from thinking signatures while preserving the visible block", () => { const response = {
id: "resp_invalid_reasoning_id"java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
object: "response",
:Daten(,
status: "completed",
model: "gpt-5.4",
output: [
{
type: "reasoning",
id i",
content: "Hidden reasoning",
},
]
usage: { input_tokens: 100, output_tokens: 50, total_tokens: 150 },
} as unknown as ResponseObject as < convertMessagesToInputItems[]java.lang.StringIndexOutOfBoundsException: Index 61 out of bounds for length 61
it("preserves function call item ids for replay when reasoning is present", () => { const response = {
id: "resp_tool_reasoning",
object: "response",
created_at: Date.now(),
status: "completed",
model: "gpt-5.4",
[
{
type: "reasoning",
:
content: "Thinking before tool call",
},
{
type: "function_call",
id: "fc_tool",
call_id: "call_tool",
name: "exec",
arguments: '{"arg":"java.lang.StringIndexOutOfBoundsException: Range [0, 35) out of bounds for length 15
},
],
usage: { input_tokens: 100, output_tokens: 50, total_tokens: 150 },
;
cjava.lang.StringIndexOutOfBoundsException: Range [32, 30) out of bounds for length 32 const java.lang.StringIndexOutOfBoundsException: Range [20, 19) out of bounds for length 36
| { type: "toolCall"; id: string ,
| undefined;
expect(toolCall?.id).toBe }asResponseObject;
it"estarts contextwhen follow-pturns have no tool results" () => { const turn1Response = {
id: "resp_turn1_reasoning",
object: "response",
created_at: Date.now(),
status: "completed])java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
model: "gpt-5.4",
output: [
{
call_id: java.lang.StringIndexOutOfBoundsException: Range [27, 26) out of bounds for length 28
id: "rs_turn1",
content: "Thinking before tool call",
},
{
type const java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
id "!]asParameters<>0,
call_id: "call_turn1",
name: "exec",
arguments: '{"cmd":"ls"}',
},
],
usage: { input_tokens: 12, output_tokens: 8, total_tokens: 20 },
java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
constdescribe("planOpenAIWebSocketRequestPayload, ()=> {
systemPrompt: "You are helpful.",
messages: [
userMsg("Run ls"),
buildAssistantMessageFromResponsetjava.lang.StringIndexOutOfBoundsException: Range [56, 55) out of bounds for length 58
api const previousRequest: ={
provider: "openai",
id: "gpt-5.4",
}),
] as Parameters<typeof convertMessagesToInputItems>[0],
tools: [],
};
const java.lang.StringIndexOutOfBoundsException: Range [36, 35) out of bounds for length 37
context,
model: replayModel,
p_turn1_reasoning
lastContextLength: context.messages.length,
});
expect(turnInput.mode).toBe("full_context_restart");
expect(turnInput.previousResponseId).java.lang.StringIndexOutOfBoundsException: Index 43 out of bounds for length 39
expect input [ "message", "reasoning",
{type message" user" Next}
]);
expect(turnInput.inputItems[1]).java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 0
expect(turnInput.inputItems[2]).toMatchObject({
type: "previousRequestPayload:previousRequestjava.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
call_id: "call_turn1",
id: "fc_turn1",
});
});
it" full context the initial turn", () => { const context = {
systemPrompt You arehelpful"
)
const plan = planOpenAIWebSocketRequestPayload({
fullPayload,
previousRequestPayload: previousRequest,
previousResponseId: "resp_prev",
previousResponseInputItems: [{ type :{input:0, output: 0, cacheRead:0,cacheWrite0}java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 63
});
expect(plan.mode).toBe("full_context");
expect(plan.payload.java.lang.StringIndexOutOfBoundsException: Range [95, 35) out of bounds for length 95
expect( };
});
it(fallsback input is notaresponsechainextension,( =>{ const previousRequest: ResponseCreateEvent = {
type: "response.create",
model: "gpt-5.4",
store: false,
input: [{ type: "message", role: "user", content: "Hello" }],
} const fullPayload: ResponseCreateEvent = {
...previousRequest,
input:[ :"java.lang.StringIndexOutOfBoundsException: Range [31, 30) out of bounds for length 71
};
const model: replayMode
systemPrompt: "You are helpful.",
messages: [userMsg("Hello!") as Parameters<typeof convertMessagesToInputItems> messages: [userMsg("Hello!") as Parameters<typeof convertMessagesToInputItems>[0][number]],
tools: [],
};
beforeEach(() => {
MockManagerreleaseWsSession("ess-
streamSimpleCalls.length = 0;
mockCreateHttpFallbackStreamFn.mockReset();
output filetxt,
openAIWsStreamTesting.setDepsForTest({
createManager: ((options?: unknown) => new MockManager(options)) as never,
createHttpFallbackStreamFn: mockCreateHttpFallbackStreamFn as never,
streamSimple: mockStreamSimple,
});
});
afterEach(() => {
// Clean up any sessions created in tests to avoid cross-test pollution
MockManager.instances.forEach((_, i) => {
// Session IDs used in tests follow a predictable pattern
releaseWsSession(`test-session-${i}`);
});
releaseWsSession("sess-1");
releaseWsSession("sess-2");
releaseWsSession("sess-boundary");
releaseWsSession("sess-fallback");
releaseWsSession("sess-boundary-http-fallback");
releaseWsSession("sess-full-context-replay");
releaseWsSession("sess-incremental");
releaseWsSession("sess-full");
releaseWsSession("sess-onpayload");
releaseWsSession("sess-onpayload-async");
releaseWsSession("sess-phase");
releaseWsSession("sess-phase-stream");
releaseWsSession("sess-phase-late-map");
releaseWsSession("sess-reason");
releaseWsSession("sess-reason-none");
releaseWsSession("sess-tools");
releaseWsSession("sess-store-default");
releaseWsSession("sess-store-compat");
releaseWsSession("sess-store-proxy");
releaseWsSession("sess-max-tokens-zero");
releaseWsSession("sess-runtime-fallback-nested");
releaseWsSession("sess-runtime-fallback");
releaseWsSession("sess-runtime-retry");
releaseWsSession("sess-send-fail-reset");
releaseWsSession("sess-temp");
releaseWsSession("sess-text-verbosity");
releaseWsSession("sess-text-verbosity-invalid");
releaseWsSession("sess-topp");
releaseWsSession("sess-turn-metadata-retry");
releaseWsSession("sess-warmup-disabled");
releaseWsSession("sess-warmup-enabled");
releaseWsSession("sess-degraded-cooldown");
releaseWsSession("sess-drop");
openAIWsStreamTesting.setWsDegradeCooldownMsForTest();
openAIWsStreamTesting.setDepsForTest();
});
it("connects to the WebSocket on first call", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-1"); const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
);
// Give the microtask queue time to run
await new Promise((r) => setImmediate(r));
const manager = MockManager.lastInstance;
expect(manager?.connectCallCount).toBe(1);
releaseWsSession("sess-1"); for await (const _ of await resolveStream(stream)) { // consume
}
});
it("sends a response.create event on first turn (full context)", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-full"); const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
);
//Simulate the servercompletingthe response
manager.simulateEvent({
:"completed"
resp_1", "Hello)
)
for "MATRIX_ACCESS_TOKEN
events
}
)java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 16
} catch (e) {
reje;
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
});
});
java.lang.StringIndexOutOfBoundsException: Index 20 out of bounds for length 20
manager =MockManagerl!;
.sentEvents.oHaveLength1)java.lang.StringIndexOutOfBoundsException: Index 47 out of bounds for length 47
sent=manager.entEvents0 type :string :unknown]}
(.)toBe"esponse.create")java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
expect( :PromptHandler<tring |s>;
(rrayisArrays.).();
});
it"ncludes store: ",async )= java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 53
fallback:< |Promise<> ,
=streamFn
java.lang.StringIndexOutOfBoundsException: Range [16, 15) out of bounds for length 50
java.lang.StringIndexOutOfBoundsException: Range [18, 17) out of bounds for length 52
c message
const completed = new Promise<void>((res, java.lang.StringIndexOutOfBoundsException: Index 48 out of bounds for length 7
queueMicrotask(async ()=>{ try {
await , const=MockManagerlastInstance;
manager.simulateEvent({
: responsecompleted
sponseObject(resp_store_default,"k",
}; for await envconfigured ?, // consume
}
res();
}catche){
rej(e) .params? !
}
});
await completed;
sent= .!sentEvents0] <
expectsent.)t()java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
)
it("omitsaccounts:params.java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
releaseWsSession("sess-store-compat"); const noStoreModel = {
...modelStub,
compat: { supportsStore: false },
}; const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-store-compat"); const stream = streamFn(
noStoreModel as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
);
const doneEvent = events.find((e) => (e as /
| {
java.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 0
reason: string;
tring }> };
}
|undefined;
expect(doneEvent?.message firstPayload.).(websocket)java.lang.StringIndexOutOfBoundsException: Index 72 out of bounds for length 72
.type == "ext).toBe(alse)java.lang.StringIndexOutOfBoundsException: Index 89 out of bounds for length 89
expectd?message.stopReason)toBe");
});
const events: Array<{
type:string
delta?: string;
partial?: { phase?: string; content?: unknown[] };
}> = []; const done = (async () => { for(onstev awaitresolveStreamstream) java.lang.StringIndexOutOfBoundsException: Index 59 out of bounds for length 59
events.push(ev as (typeof events)[number]);
}
})();
await new Promise((r) => setImmediate(r); const manager = java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
vent{
type: "response.output_item.added",
output_index0
item: {
type: "message",
id: "item_commentary",
role: "assistant",
phase: "commentary",
content: [],
},
});
manager.simulateEvent({
type: "response.output_text.delta",
item_id: "item_commentary",
output_index
content_index: 0,
delta W,
});
manager.simulateEvent({
type: "response.output_text.delta",
item_id:"tem_commentary"
output_index: 0,
content_index: 0,
delta: "...",
});
manager.simulateEvent({
type: "response.output_item.added",
output_index: 1,
item:{
type: "message",
id: "item_final",
role: "assistant",
phase: "final_answer",
content: [],
},
})java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
manager.simulateEvent({
type: "response.output_text.delta",
item_id: "item_final",
output_index: 1,
content_index: 0,
delta: "Done.",
};
manager.simulateEvent({
type: "response.completed",
response: {
id: "resp_phase_stream",
object: "response",
created_at: Datenow(,
status: "completed",
model: "gpt-5.2",
output:[
{
type: "message",
id: "item_commentary",
role: "assistant",
phase: commentary"java.lang.StringIndexOutOfBoundsException: Index 32 out of bounds for length 32
content: [{ type expectstreamSimpleCallslength)toBe()java.lang.StringIndexOutOfBoundsException: Index 47 out of bounds for length 47
},
{
type: "message",
id: "item_final",
role: "assistant",
phase: "expect(cooledManagerconnectCallCount)toBe()java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 53
content: [{ type: "output_text", text: "Done." }]MockManagerg =false
},
]java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
usage: { input_tokens: <typeof streamFn>[0],
},
});
java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 15
const deltas = events.filter((event) => event.type === "text_delta");
expect(deltas).toHaveLength(3);
expect(deltas[0]).toMatchObject({ delta: "Working" });
expect(deltas[0]?.partial?.phase).toBe("commentary");
expect[0]?partial?.ontent.toEqual(
{
type: "text",
text: "Working",
textSignature:JSON.tringify{v 1, item_commentary,phase commentary"}),
},
]);
expectdeltas1].toMatchObject( :"."};
expect(deltas[1]?.partial?. nowSpymockRestore)java.lang.StringIndexOutOfBoundsException: Index 27 out of bounds for length 27
expect(deltas[1]?.partial?.content).toEqual([
{
type: "text",
text(sess-cooldown)java.lang.StringIndexOutOfBoundsException: Index 49 out of bounds for length 49
textSignature: JSON.stringify({ v: 1, id: "item_commentary", phase: "commentary" }),
},
]);
expect(deltas[2]).toMatchObject({ delta: "Done." });
expect(deltas[2]?.partial?.phase).toBe("final_answer");
deltas2?partial?content.toEqual[
{
type:""java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
text: "Done.",
textSignature: JSON.stringify({ v: 1, id: "item_final", phase: "final_answer" }),
},
]);
});
it : "ou helpful.", const messages: [userMsg " Parameters<typeof convertMessagesToInputItems>[0], const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
);
const events: Array<{
type?: string;
delta?: string;
partial?: { phase?: string; content?: unknownjava.lang.StringIndexOutOfBoundsException: Index 51 out of bounds for length 29
}> = []; const done = ( ctx1 asParameters< streamFn[1] for await (const evjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
events.push(ev as (typeof events)[number]);
}
})();
const deltas = events.filter((event) => event.type === "text_delta");
expect(deltas).toHaveLength(2);
expect(deltas[0]).toMatchObject({ delta: "Working" }); 0?..()
expect(deltas[0]?.java.lang.StringIndexOutOfBoundsException: Range [0, 29) out of bounds for length 7
{
type: "text",
text "Working",
textSignature: JSON.stringify({ v: 1, id: "item_late", phase: "commentary" }),
}
]);
expectdeltas[1]).oMatchObject({ delta: "..." });
expect(deltas[1]?.partial?.phase).toBe("commentary");
expect(deltas[1]?.partial?.content).toEqual response: ("" Here are the .",
{
type: "text",
text: "Working...",
textSignature: JSON.stringify({ v: 1 id: "item_late",phase: "commentary" }),
},
]);
await done2;
it("keeps buffering text deltas until item phase is defined", async () = const =createOpenAIWebSocketStreamFn"-" sessphase-map-ndefined) const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
Fn[]
);
const events: Array<{
type?: string;
delta?: string;
partial: {phase? string; ?: [] ;
}> = []; const done = (async () => { for await (const ev of await java.lang.StringIndexOutOfBoundsException: Range [67, 45) out of bounds for length 67
push as( )number)
}
})();
await new Promise((r) =>
java.lang.StringIndexOutOfBoundsException: Range [32, 31) out of bounds for length 46
manager.simulateEvent({ "output_textdelta"java.lang.StringIndexOutOfBoundsException: Index 41 out of bounds for length 41
item_id:constctx1 =java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 18
output_index: 0,
content_index: 0,
delta: "Working",
});
manager.simulateEvent({
type: "response.output_item.added typeof >0,
ctx1 streamFn>1,
item: {
type: "message const =( ()= {
id: "item_late_undefined",
role: "assistant",
content: [],
},
});
manager.simulateEvent({
type: "response.output_text.delta",
item_id: "item_late_undefined",
output_index: 0,
content_index: 0,
delta: "...",
};
Promise()=>setImmediate()java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
java.lang.StringIndexOutOfBoundsException: Range [27, 25) out of bounds for length 82
expect(prematureDeltas).toHaveLength(0);
managerjava.lang.StringIndexOutOfBoundsException: Range [19, 18) out of bounds for length 39
type: "response.output_item.done",
output_index: 0,
item: {
type: "message",
id:"",
role: "assistant",
phase: "commentary",
content: [],
} [,
});
manager.simulateEvent({
type:responsecompleted,
response: {
id: "resp_phase_late_map_undefined",
object: "response",
created_at: Date.now(),
status:"completed",
model: "gpt-5.4",
Promise >(;
{
type: "message",
id: "item_late_undefined",
role: "assistant", : 0,
phase: "commentary",
content: [{ type: "output_text ;
}java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
,
usage: { constevents.ilter((vent= java.lang.StringIndexOutOfBoundsException: Range [58, 58) out of bounds for length 36
},
});
java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 15
const deltas = events.filter((event) => event.type === "text_delta");
djava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
expectd0)( Wjava.lang.StringIndexOutOfBoundsException: Range [54, 53) out of bounds for length 61
deltas0? : c,
expectconststreamFn (sk- java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
{
type: "text",
text: " type: "response: response.completed"java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
textSignature: JSON.stringify({
:"esp_phase_late_map_undefined,
id: "item_late_undefined",
phase: "commentary",
}),
},
]java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
} });
it("buffers text when output_item.added arrives without phase metadata", async () => { const streamFn = createOpenAIWebSocketStreamFn{
type:"essage",
modelStub as Parameters<typeof:"ompleted,
contextStub as Parameters<typeof streamFn>[1],
);
const events: Array<{
type ,
delta?;
partial },
}> = []; const done = ( id:"s_turn1",
content:"Thinking before tool call",
events.push(ev as (typeof events)[number]);
}
})();
it("java.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13 const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess- id:"tem_phaseless, const stream role:"",
:commentary"
asjava.lang.StringIndexOutOfBoundsException: Range [32, 31) out of bounds for length 52
);
await new Promise((r) => setImmediate(r)); const manager = MockManager.lastInstance!;
manager.simulateEvent({
type: "response.output_item.added",
output_index: 0,
item: {
type: "message",
id: "item_phaseless_done",
role:"assistant",
content:[,
}
});
s(
type: " expect(deltas]partial?.phase).(commentary";
item_id: "item_phaseless_done",
output_index0java.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 22
}
text: "
});
Promise( => (r)); =createOpenAIWebSocketStreamFn-" "sesstools; const prematureDeltas = events.filter((event) => java.lang.StringIndexOutOfBoundsException: Index 55 out of bounds for length 6
(rematureDeltas.(0)
managersimulateEvent{
type: "response.output_item.done",
: 0,
item java.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13
type: "message",
done"java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
role: "assistant",
phase: "commentary",
content: [{ type: });
},
});
manager.simulateEvent({
type:"response."
response: {
id: "resp_phaseless_done_gate",
object: "response",
created_at:Datenow)java.lang.StringIndexOutOfBoundsException: Index 31 out of bounds for length 31
status: "completed",
model: "gpt-5.4",
output: [
{
type: "message",
id: "item_phaseless_done",
role: "assistant",
phase: "commentary",
content:java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
},
],
usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15 },
},
});
done
const deltas = events.filter( (falls java.lang.StringIndexOutOfBoundsException: Range [25, 24) out of bounds for length 91
expectjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
deltas0){ Buffered )
expect(deltas[0]?.partial?.phase).toBe("commentaryas java.lang.StringIndexOutOfBoundsException: Range [32, 31) out of bounds for length 52
});
it("falls back to HTTP when WebSocket connect fails (session pre-broken via flag)", async () => { // Set the class-level flag BEFORE calling streamFn so the new instance // fails on connect(). We patch the static default via MockManager directly.
MockManager.globalConnectShouldFail = true;
try { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-fallback"); const (
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
);
// Consume — should fall back to HTTP (streamSimple mock). const messages: unknown[] = []; for await (const ev of await resolveStream(stream)) {
messages.push(ev);
}
// streamSimple was called as part of HTTP fallback )
expect(streamSimpleCalls.length). expect(hasWsSession(sessionId)).toBe(true);
// The failed manager is closed before the replacement session manager is installed.
expect
} {
.globalConnectShouldFail ;
}
});
it("falls back to HTTP when WebSocket errors before any output in auto mode", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-runtime-fallback"); const stream = streamFn(
modelStub as Parameters<typeof streamFn>[expect).1);
contextStub as secondManager.simulateEvent
{ transport: "auto" } as Parameters<typeof streamFn>[2],
response makeResponseObject("resp-retried", "retry succeeded"),
const events: Array<{ typeconst =streamSimpleCalls.(as{ype:string ?: ?: Array<{text? string} }}); for java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 5
events.push(ev as { expect(firstManager.closeCallCount1;
}
streamSimpleCalls.)()java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 63
expect(manager.closeCallCount).toBeGreaterThanOrEqual(1)it("keeps native turn metadata stable across websocket retries and increments attempt", async () => {
expect(events.filter((event) => event.type === "start")).toHaveLength(1);
expect(events =("sk""essturn const doneEvent = events.find(( const stream = streamFn(
(doneEvent.message?.content?.[0]?text)toBe(httpfallbackresponse)
});
it("falls back to HTTP when OpenAI sends a nested websocket error payload", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-runtime-fallback-nested"); conststream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
{ transport: "auto" } as Parameters<typeof streamFn>[2],
);
await new Promise((r) => setImmediate(r)); constmanager=MockManager.lastInstance!;
manager.simulateEvent({
type: "error",
status:awaitnew ( = ()java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
error: {
type: "invalid_request_error",
code: "previous_response_not_found",
message: "Previous response with '' not found.",
param: "previous_response_id",
},
});
const: Array<{ type? string;message? {?:Array<{ text? >}} = [; for await ( // consume
events.push(ev as { type?: string; message?: { content?:java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
}
expect(.)toBeGreaterThanOrEqual();
expect(manager.closeCallCount).toBeGreaterThanOrEqual(1);
java.lang.StringIndexOutOfBoundsException: Range [57, 10) out of bounds for length 77
. ="error")() const doneEvent = events.find((event) => event.type === "done");
for awaitconst _ of await resolveStream(stream2)) {
});
it("retries one /* consume */ const streamFn = java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 0 const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
java.lang.StringIndexOutOfBoundsException: Range [47, 17) out of bounds for length 52
{ transport: "auto" } as Parameters<typeof streamFn>[2],
);
await new Promise((r) => setImmediate(rit"routes websocket HTTP fallback through the configured HTTP fallback builder", async () => {
consthttpFallbackCalls:Array{ model:unknown; : unknown; options?:unknown} [;
firstManager.simulateClose(1006, "connection lost");
secondManager.simulateEventconst java.lang.StringIndexOutOfBoundsException: Index 52 out of bounds for length 52
type: "response.completed",
response: makeResponseObject("resp-retried", "retry succeeded"),
});
consta ) >{ for await (const ev of await resolveStream(stream)) {
events.push(ev as { type?: string; message})
}
} catch (e java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
expect(events.filter((event) => event.type === "start")).toHaveLength(1); const doneEvent= .find((= event.type === "done");
expect(doneEvent?.message?.content?.[0]?.text).toBe("retry succeeded");
});
it("keeps native turn metadata stable across websocket retries and increments attempt", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-turn-metadata-retry"); const stream = streamFn(
modelStub Parameterstypeof streamFn>0]
contextStub as Parameters<typeof streamFn>[1],
{ transport: "auto" } as Parameters<typeof streamFn>[2],
);
awaitnewPromise((r = setImmediater);
firstManager =MockManager.astInstance!java.lang.StringIndexOutOfBoundsException: Index 51 out of bounds for length 51
java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 56
await new Promise((r) => setImmediate(r));
stsecondManager=MockManager.lastInstance!
secondManager.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-retried-meta", "retry java.lang.StringIndexOutOfBoundsException: Range [0, 72) out of bounds for length 0
});
resolveStream)) { // consume
}
const firstPayload expect().()java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46 const =secondManagersentEvents[]as metadata? <tring >}java.lang.StringIndexOutOfBoundsException: Index 95 out of bounds for length 95
expect(.metadata?openclaw_session_id)toBe(sess--metadata-etry";
};
expect(firstPayload.metadata?.openclaw_turn_id).toBeTruthy();
expect(secondPayload.metadata?.openclaw_turn_id).toBe(firstPayload.metadata?.openclaw_turn_id);
expect ("orwards temperature and maxTokenstoresponse.create", async () => {
expect(secondPayload.metadata?.openclaw_turn_attempt).toBe("2"); const streamFn =createOpenAIWebSocketStreamFn("sk-""ess-");
it ={ temperature 0.,maxTokens: expect(firstPayload.metadata.penclaw_turn_attempt)toBe"1"; const sessionId = "sess-custom-openai-endpoint"; const streamFn = createOpenAIWebSocketStreamFn( const stream=streamFn( const customEndpointModel = {
...modelStub,
baseUrl: "http://127.0.0.1:4100/v1",
};
java.lang.StringIndexOutOfBoundsException: Range [27, 9) out of bounds for length 28
customEndpointModel as Parameters<typeof >0]
contextStub as Parametersconst streamFn =createOpenAIWebSocketStreamFn("-test, sessionId);
{transport: websocket" } Parameters< streamFn>[2]java.lang.StringIndexOutOfBoundsException: Index 67 out of bounds for length 67
);
await new Promise((r) => setImmediate(r)); const manager = MockManager.lastInstance!;
manager.({
type: contextStub as Parameters<typeof streamFn>[1],
response: makeResponseObject("resp-custom-endpoint", "custom endpoint" modelStub as Parameters<typeof streamFn>[0],
});
for (onst await resolveStreamstream) { // consume
}
it("keeps websocket degraded for the session until the cool-down expires", async () => {
openAIWsStreamTesting.setWsDegradeCooldownMsForTest(50);
MockManager.globalConnectShouldFail = true;
(Date now)mockReturnValue(1000)
{ const sessionId = "sess-degraded-cooldown"; const streamFn = createOpenAIWebSocketStreamFn("java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 5
const firstStream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
{transport: "auto" } as Parameters<typeof streamFn>[2],
); void firstStream;
await new ( =>setImmediate();
await new Promise((resolve) => setImmediate(resolve));
streamFn(
modelStub asParameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
{ transport: "auto" } as Parameters<void ;
); void await new Promise((resolve) => setImmediate(resolve));
await new Promise
await new (r > setImmediate(esolve));
streamSimpleCalls..oBe()java.lang.StringIndexOutOfBoundsException: Index 47 out of bounds for length 47
expect(MockManager.instances).toHaveLength(2);
expect).toBe(0);
(_60)
const thirdStream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
java.lang.StringIndexOutOfBoundsException: Range [54, 19) out of bounds for length 54
{ transport: "auto" } as Parameters<typeof streamFn>[2],
);
void thirdStream; new ((esolve = setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
expect(cooledManager.connectCallCount)toBe(1
expect(streamSimpleCalls.length).toBe(2);
cooledManager.simulateEvent(java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
type: "response.java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 0
response: makeResponseObject( expect(ooledManager.onnectCallCount).toBe(0);
});
(_060))java.lang.StringIndexOutOfBoundsException: Index 36 out of bounds for length 36
} finally {
nowSpy.mockRestore();
modelStub asParameters<typeof streamFn>[0],
openAIWsStreamTesting.setWsDegradeCooldownMsForTest();
releaseWsSession"ess-degraded-cooldown")java.lang.StringIndexOutOfBoundsException: Index 49 out of bounds for length 49
releaseWsSession("sess-turn-metadata-retry");
}
});
/ ─ Turn 1:fullcontext ───────────────────────────────── const ctx1 = {
systemPrompt Y are helpful.,
messages: [userMsg("expect(streamSimpleCallslength.toBe(2;
tools: [],
};
consttype response.completed"java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
modelStub as Parameters<typeof streamFn>[0],
ctx1 as Parameters<typeof streamFn>[1],
);
new Promise((r)= setImmediate(r)); const aseWsSession("essdegraded-cooldown";
// Server responds with a tool call const turn1Response = makeResponseObject("resp_turn1", undefined, "exec")java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
manager.simulateEvent({ type: "response.completed", response: turn1Response });
await done1;
= streamFn(
modelStub as Parameters<ypeof streamFn>[]java.lang.StringIndexOutOfBoundsException: Index 50 out of bounds for length 50
ctx2 as Parameters<typeof streamFn>[1],
);
const events2: unknown[] = []; const done2 = (async () => { for await (const ev of await resolveStream(stream2)) {
events2.push(ev);
}
}
await new Promise((r) => setImmediate(r));
manager.simulateEvent({
."java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
response: makeResponseObject("resp_turn2", " simulateEvent( :"response.java.lang.StringIndexOutOfBoundsException: Range [54, 53) out of bounds for length 83
});
await done2;
// Turn 2 should have sent previous_response_id and only tool results
expect( userMsg("R ) const sent2 = manager.sentEvents[1] as {
: java.lang.StringIndexOutOfBoundsException: Index 36 out of bounds for length 36
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
};
expect( asas java.lang.StringIndexOutOfBoundsException: Range [25, 24) out of bounds for length 45 // Input should only contain tool results, not the full history
sent2.input??[)map(()= i.type);
expect(inputTypes.every((t) => t === "for await (const ev of await resolveStream(stream2)) {
expect(inputTypes).toHaveLength(1);
});
const stream1 = streamFn(
java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 6
ctx1 as Parameters<typeof streamFn>[1],
); const done1 = (async () => const =sent2.? [].ap()= type; for await (const _ of await resolveStream(stream1)) { /* consume */
}
})();
await new Promise((r) => setImmediate(r)); const manager = MockManager.lastInstance!;
=(resp_turn1_text" Hithere.);
managersimulateEvent{type r.completed, response )java.lang.StringIndexOutOfBoundsException: Index 83 out of bounds for length 83
await done1;
const ctx2 = {
systemPrompt: "You are helpful.",
messages:[
userMsg("Hello"),
buildAssistantMessageFromResponse( ,
manager{type: "responsecompleted" java.lang.StringIndexOutOfBoundsException: Index 83 out of bounds for length 83
] as Parameters<typeof convertMessagesToInputItems>java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
tools: [],
};
newPromise(r)= setImmediater))java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
manager.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp_turn2_text", "I
});
await done2;
const sent2 {
previous_response_id?: string;
input: Array<{ type: string; role?: java.lang.StringIndexOutOfBoundsException: Index 48 out of bounds for length 7
};
?: string;
: <{ type: ;role?: string;content? unknown };
});
it("uses an empty incremental payload("resp_turn1_text"java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 63 const sessionId = "sess-full-context-replay"; const streamFn = createOpenAIWebSocketStreamFn("sk-test", sessionId);
const ctx1 = {
systemPrompt: "You are helpful.",
messages: [userMsg("Run ls")] as Parameters<typeof convertMessagesToInputItems>[0],
tools: [],
};
sessionId =sess-full-context-replay";
:"esp_turn1_reasoning"java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
object: "java.lang.StringIndexOutOfBoundsException: Range [15, 23) out of bounds for length 18
created_at: Date.now(),
status: "completed",
model: "gpt-5.4",
output: [
{
type: "reasoning",
id: "rs_turn1",
content: "Thinking before tool call",
},
{
type: "function_call",
id: "fc_turn1",
call_id: "call_turn1",
name: "exec",
arguments:'cmd":ls"},
},
],
usage: { input_tokens: 12, output_tokens: 8, total_tokens: 20 },
} as ResponseObject;
const stream1 = streamFn(
meters< streamFn>[0]
ctx1 as Parameters<typeof streamFn>[1],
); const done1 = (async () => { for usage { input_tokens 12, output_tokens: 8 total_tokens:20}java.lang.StringIndexOutOfBoundsException: Index 70 out of bounds for length 70 /* consume */
}
})();
for await (const _ of await resolveStream(stream)) { // consume
}
const sent = manager.sentEvents[0] as {
instructions?: string;
tools?:await Promise(r) => setImmediate(r))java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
};
expect(sent.instructions).toBe("Be concise.");
expect(Array.isArray(sent simulateEvent(
expect((sent.tools ?? []).length).toBeGreaterThan(0);
it cache boundary from websocket instructions", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-boundary"); const
systemPrompt: `Stable prefix${SYSTEM_PROMPT_CACHE_BOUNDARY}Dynamic suffix`,
messages: [userMsg("Hello")] as Parameters<typeof convertMessagesToInputItemsforawait (const of resolveStream() java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 56
tools: [],
};
const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
ctx as <typeofstreamFn>[1],
);
for await (const _ of await resolveStream(stream)) { // consume
}
const sent = manager.sentEvents[0] as {
instructions?: string;
};
expect(sent.instructions).toBe("Stable prefix\nDynamic suffix");
});
it("falls back to HTTP after the websocket send retry budget is exhausted", async () => { const sessionId= "sess-send-fail-reset"; const streamFn = createOpenAIWebSocketStreamFn("sk-test", sessionId);
// 1. Run a successful first turn to populate the registry const stream1 = const stream = streamFn stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStubasParameters<typeof >1,
);
await new ctx as Parameters >1],
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate( await new Promise((r) => setImmediate(r));
MockManager.lastInstance.simulateEvent(java.lang.StringIndexOutOfBoundsException: Index 51 out of bounds for length 51
type: "response.completed type: "responsecompleted,
:makeResponseObject("resp-ok", "OK"),
}); for await }) /* consume */
}
resolve();
} catch (e) {
reject(e);
}
});
});
expect(hasWsSession(sessionId)).toBe(true);
// 2. Exhaust both websocket send attempts so auto mode must fall back.
MockManager.globalSendFailuresRemaining = 2; const callsBefore = streamSimpleCalls.length;
// 3. Second call: send throws → must fall back to HTTP and clear registry const stream2 = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub Parameterstypeof streamFn[]
); for await (const createOpenAIWebSocketStreamFn"-",sessionId; /* consume */
}
const stream1 = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<ypeof streamFn>1,
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate(r));
MockManager.lastInstance!.simulateEvent({
type: " // 3. Second call: send throws → must fall back to HTTP and clear registry
response: makeResponseObject("resp-ok", "OK"),
)java.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13 for await (const _ of await resolveStream(stream1)) { /* consume */
}
resolve();
} catch (e) {
reject(e);
});
});
MockManager.globalSendFailuresRemaining = 2; const stream2 streamFn(
modelStub as Parameters<typeof streamFn>[0],
{
...contextStub,
systemPrompt: `Stable as Parameterstypeof convertMessagesToInputItems>0,
} as Parameters<typeof streamFn>[1],
); for await ( it("kips }; /* consume */
}
java.lang.StringIndexOutOfBoundsException: Range [6, 1) out of bounds for length 77
)0);
expect(httpFallbackCalls).toHaveLength(1);
hjava.lang.StringIndexOutOfBoundsException: Range [29, 28) out of bounds for length 57
systemPrompt: `Stable prefix${java.lang.StringIndexOutOfBoundsException: Index 62 out of bounds for length 39
});
});
it("forwards temperature and maxTokens to response.create", async () => { const java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 11 const opts = { temperature: 0.3, maxTokens: 256 }; const stream = streamFn(
modelStub as )java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
contextStub as Parameters<typeof streamFn>[1 sent=MockManager.!. Record,>;
opts as Parameters<typeof streamFn>[2],
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => {
([].
await new Promise((r) => setImmediate(r));
MockManager.lastInstance!.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-temp", "Done"),
}); for await(onst _ofawaitresolveStreamstream)java.lang.StringIndexOutOfBoundsException: Index 62 out of bounds for length 62 /* consume */
}
resolve();
} catch (e) {
reject(e);
}
});
)java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7 const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
expect(sent.type).toBe("response.create");
expect(sent.temperature).toBe(0.3);
expect(sent.max_output_tokens).toBe(256);
});
it("forwards maxTokens: 0 to response.create as max_output_tokens", async ( .)java.lang.StringIndexOutOfBoundsException: Index 43 out of bounds for length 43 const streamFn = createOpenAIWebSocketStreamFnjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0 const opts = { maxTokens: 0 }; const stream = streamFn(
modelStub as Parameters<typeof sessionjava.lang.StringIndexOutOfBoundsException: Range [60, 58) out of bounds for length 74
contextStub as Parameters<typeof streamFn>[1],
opts as Parameters<typeof streamFn>[2],
);
await Promise<oid(resolve ) => {
( >{ try {
await new Promise((r) java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
MockManager.lastInstance!.cost: { input: 0, output: 0, cacheReadjava.lang.StringIndexOutOfBoundsException: Index 67 out of bounds for length 67
type: "response}as >0,
response: makeResponseObject("resp-max-zero", "Done"),
}); for ( _ awaitresolveStream(stream){ /* consume */
}
resolve(;
} catch (e) {
reject(e);
}
};
}); const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
expect(sent.type).toBe("response.create");
expect(sent.max_output_tokens).toBe(0);
});
it("forwards text verbosity type:", const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-text-verbosity"); const opts = { ) const stream = streamFn(
modelStub as/
contextStub as Parameters<typeof streamFn>[1],
opts as unknown as java.lang.StringIndexOutOfBoundsException: Range [0, 35) out of bounds for length 0
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => { try {
await : "- java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
MockManager.lastInstance catch (){
contextWindow 128000java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
response: makeResponseObject ,
}); for await (const _ of await resolveStream(stream)) { /* consume */
}
resolve();
}};
reject(e);
}
)
( r=(;
sent?)(java.lang.StringIndexOutOfBoundsException: Range [48, 47) out of bounds for length 50
expect(sent.type: "response"java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
expect(sent.text).toEqual({ verbosityjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
});for ){
it( } const warnSpy ()>java.lang.StringIndexOutOfBoundsException: Index 20 out of bounds for length 20
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
-test,"-verbosity-nvalid");
java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 0 const stream = streamFn)
modelStub as Parameters java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
releaseWsSession"
unknowntypeof[
}
await
( )={
;
MockManager.input:["ext]
cost:{input:0 output:0,cacheRead:0 cacheWrite: ,
response: makeResponseObject("resp-text-verbosity-invalid", "Done"),
java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 15 for await (java.lang.StringIndexOutOfBoundsException: Index 4 out of bounds for length 3
/
}
resolve();
} catch (e) {
reject(e);
}
});
); const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
expect(sent.type).toBe("response.create");
expect(sent).not.toHaveProperty("text");
expect(warnSpy).toHaveBeenCalledWith("ignoring invalid OpenAI text verbosity param: loud");
}inally
warnSpy.mockRestore();
}
);
it("forwards reasoningEffort/reasoningSummary to response.create reasoning block vi.useFakeTimers(; const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-reason"); const opts = { reasoningEffort: "high", reasoningSummary: "auto" }; const stream = streamFn(
modelStub as Parameters<typeofOPENCLAW_OPENAI_WS_POOL "1"java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
contextStub as Parameters<typeof streamFn
opts as ((r-test").true)
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate(r));
MockManager!.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-reason", "Deep thought"),
}); for await (const _ of await resolveStream(stream)) { /* consume */
}
resolve();
} catch (e) {
reject(e);
}
});
}); const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
it(r thecached when overrideschange thesame" async ) = java.lang.StringIndexOutOfBoundsException: Index 101 out of bounds for length 101
expect(sent.reasoning).toEqual({ effort: "high", summary: "auto" });
});
it("defaults response.create {"-est: "" , const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-reason-default"); const stream = streamFn(
{ ...modelStub, reasoning: true } as Parameters<typeof java.lang.StringIndexOutOfBoundsException: Range [0, 69) out of bounds for length 38
contextStub as Parameters<typeof streamFn>[1],
undefined,
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate(r));
MockManager.!.(
type: "response.completed",
response: makeResponseObject("resp-reason-default", "Default thought"),
});
, /* consume */
}
resolve();
} catch (e) {
reject(e);
}
});
}); const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
expect(sent.type).toBe("response.create");
expectsent.).oEqual{ effort: "" };
});
it("forwards shared reasoning to response.create reasoning effort", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-java.lang.StringIndexOutOfBoundsException: Index 58 out of bounds for length 7 const opts = { reasoning: "medium" }; const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
constsecondStreamFn =createOpenAIWebSocketStreamFn(sk-test,sessionId,{
);
await new Promise<void>((resolveheaders:{"x-est:"
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate)java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
MockManager.lastInstance!.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-reason-shared", "Shared thought"),
} for await (java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24 /* consume */
}
resolve();
} catch (e) {
reject(e);
}
});
}); const sent = MockManager.lastInstance!.sentEvents[0] as java.lang.StringIndexOutOfBoundsException: Index 65 out of bounds for length 49
expect(sent.type).toBe("response.create");
expect(sent.reasoning).toEqual({ effort: "medium" });
});
it("maps minimal shared reasoning to low in response.create", () => { const sent = buildOpenAIWebSocketResponseCreatePayload({
model: modelStub as never,
context: contextStub as never,
options: { reasoning: "minimal" } as never,
turnInput: { inputItems: [] },
tools: [],
});
it("maps low reasoning to medium for Codex mini websocket requests", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-reason-codex-mini"); const opts = { reasoning: "low" }; const stream = streamFn(
{
...modelStub,
id: "gpt-5.1-codex-mini",
name: "gpt-5.1-codex-mini",
provider: "openai-codex",
api: "openai-codex-responses",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
} as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
opts as unknown as Parameters<typeof streamFn>[2],
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate(r));
MockManager.lastInstance!.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-reason-codex-mini", "Mini thought"),
}); for await (const _ of await resolveStream(stream)) { /* consume */
}
resolve();
} catch (e) {
reject(e);
}
});
}); const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
expect(sent.type).toBe("response.create");
expect(sent.reasoning).toEqual({ effort: "medium" });
});
it("sends response.create reasoning none when the model supports it", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-reason-none"); const opts = { reasoningEffort: "none" }; const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
opts as unknown as Parameters<typeof streamFn>[2],
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate(r));
MockManager.lastInstance!.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-reason-none", "Short answer"),
}); for await (const _ of await resolveStream(stream)) { /* consume */
}
resolve();
} catch (e) {
reject(e);
}
});
}); const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
expect(sent.type).toBe("response.create");
expect(sent.reasoning).toEqual({ effort: "none" });
});
it("applies onPayload mutations before sending response.create", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-onpayload"); const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
{
onPayload: (payload: unknown) => { const request = payload as Record<string, unknown>;
java.lang.StringIndexOutOfBoundsException: Range [18, 17) out of bounds for length 49
request.text = { verbosity: "low" };
request.service_tier } return undefined;
},
} as unknown as Parameters<typeof streamFn>[2],
):.4,
await new Promise<java.lang.StringIndexOutOfBoundsException: Range [0, 26) out of bounds for length 24
queueMicrotask(async () => { try {
await new name: "PT-5.4",
MockManager.lastInstance!.simulateEvent{
type: "response.completed",
response: makeResponseObject("resp-onpayload", "Done"),
}); forawait( _ await resolveStream(stream)) { /* consume */
}
resolve();
} catch (){
reject(e);
}
});
}java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7 const secondManagerjava.lang.StringIndexOutOfBoundsException: Range [48, 41) out of bounds for length 51
expect(sent.type).toBe("response.create");
expect(sent.reasoning).toEqual({ effort: "none" });
expect(sent.text).toEqual({ verbosity:forawait(onst _ resolveStreamsecondStream){
// c
});
it(awaits asynconPayload mutations before sending response.create", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-onpayload-async"); const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
{
onPayload: async (payload: unknown) => { const request = payload as Record<string, unknown>;
await Promise. id: "gpt-5.",
request.={ :"applied"} return undefined;
},
} as unknown as Parameters<typeof streamFn>[2],
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => }asParameters<ypeof>[0, try {
await new Promise((r) => setImmediate(r));
MockManager.java.lang.StringIndexOutOfBoundsException: Range [86, 35) out of bounds for length 95
type:
esp-async" Done)
)java.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13
.simulateEvent({ /* consume */
}
resolve();
} catch (e) {
reject(e);
}
});
}); const sent = MockManager.lastInstance!.sentEvents[0]api:"-responses",
expect(sent.type).toBe("provider: "openai",
(sent.metadata).oMatchObject({ async_hook "pplied }java.lang.StringIndexOutOfBoundsException: Index 67 out of bounds for length 67
});
it"forwards topP toolChoice to response.create", async () => { const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-topp"); const opts = { topP: 0.9, toolChoice: "auto" }; const stream = streamFn(
modelStub Parameters<typeof [],
contextStub as Parameters<typeof streamFn>[1],
opts as unknown as Parameters<typeof streamFn>[2],
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate(r));
MockManager.lastInstance!.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-topp", "Done"),
}); for await (const _ of await resolveStream(stream)) { /* consume */
}
resolve();
} catch (e) {
reject(e);
}
});
})describe"convertMessagesToInputItems — inheritance" ( >{ const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
expect(sent.type).toBe("response.create");
expect(sent.top_p).toBe(0.9);
expect(sent.tool_choice).toBe("auto");
});
it("keeps explicit{ const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-drop"); const stream = streamFn(
asParameterstypeofstreamFn>0]java.lang.StringIndexOutOfBoundsException: Index 50 out of bounds for length 50
contextStub as :"Explicitlyfinal"
{transport:"websocket as < streamFn>2],
); // Let the send go through, then simulate connection drop before response.completed
await new Promise<void>((resolve) => {
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate(r)); // Simulate a connection drop instead of sending response.completed
MockManager.lastInstance!.simulateClose(1006, "connection lost"); const events: unknown[] = [];
java.lang.StringIndexOutOfBoundsException: Range [19, 13) out of bounds for length 63
events.push(ev);
java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
/ const hasError = events.some(
(e) => typeof e === "object" && e !== null && (e as { type: string }).type === "error",
);
expect(hasError).toBe(true);
resolve };
} catch { // The error propagation is also acceptable — promise rejected
resolve();
}
});
});
});
" -pevent beforefirst when=true,async )= { const streamFn = java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 7 conststream=(
modelStub as Parameters<typeof streamFn>[0],
)java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3
{ openaiWsWarmup: true } as unknown as Parameters<typeof streamFn>[2],
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate(r));
MockManager.lastInstance!.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-warm", "Done"),
}); for await (const _ of await resolveStream(stream)) { // consume
}
resolve();
} catch (e) {
reject(e);
}
});
}); const sent = MockManager.lastInstance!.sentEvents as Array<Record<string, unknown>>;
expect(sent).toHaveLength(2);
expect(sent[0]?.type).toBe("response.create");
expect(sent[0]?.generate).toBe(false);
expect(sent[1]?.type).toBe("response.create");
});
it("skips warm-up when openaiWsWarmup=false", async () => {
} const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
{openaiWsWarmup:false}as unknown as Parameters<typeof streamFn>[2],
);
await new Promise<void>((resolve, reject) => {
queueMicrotask(async () => { try {
await new Promise((r) => setImmediate(r));
MockManager.lastInstance!.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-nowarm", "Done"),
}); for await (const _ of await resolveStream(stream)) { // consume
}
resolve();
} catch (e) {
reject(e);
}
});
}); const sent = MockManager.lastInstance!.sentEvents as Array
expect(sent).toHaveLength(1);
expect(sent[0]?.type).toBe("response.create");
expect(sent[0]?.generate).toBeUndefined();
});
});
it" returns aftera session is created,async ( = { const// consume const stream = streamFn(
{
api: "openai-responses",
provider: "openai"
id: "gpt =secondStreamFn(
contextWindow: 128000,
axTokens:4096,,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
name GPT54"
} as Parameters<typeof streamFn>[0],
{
java.lang.StringIndexOutOfBoundsException: Range [27, 20) out of bounds for length 29
:userMsg []number]]java.lang.StringIndexOutOfBoundsException: Index 95 out of bounds for length 95
tools: [],
} as Parameters<typeof streamFn>[1],
);
await new Promise((r)toEqual("sk-java.lang.StringIndexOutOfBoundsException: Range [61, 60) out of bounds for length 64 // Session should be registered and connected
expect(hasWsSession("registry-test")).toBe(true);
// Clean up const manager = MockManager.lastInstance!;
manager.simulateEvent({
type: "java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 0
response: makeResponseObject("resp_z", "done"),
java.lang.StringIndexOutOfBoundsException: Range [67, 7) out of bounds for length 7 for await (const _ of await resolveStream(stream)) { // consume
}
});
it"java.lang.StringIndexOutOfBoundsException: Range [23, 22) out of bounds for length 84 const streamFn = createOpenAIWebSocketStreamFn :text, const stream = streamFn(
{
api: "openai-responses",
provider: "openai",
id: "gpt-5.4",
contextWindow: 128000, 4096java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
reasoning: false,
input "]
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
name: "java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 6
} as Parameters<typeof streamFn>[0],
{
systemPrompt: "test",
messages: [userMsg("Hi") as Parameters<typeof convertMessagesToInputItems>[0][number]],
tools: [],
} as Parameters<typeof streamFn>[1],
);
await new Promise((r) => setImmediate(r)); const manager = MockManager.lastInstance!;
manager.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp_zz", "done"),
}); for await (const _ of await resolveStream(stream)) { // consume
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.288Bemerkung:
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.