Quelle openai-ws-stream.test.ts
Sprache: JAVA
|
|
java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3
*java.lang.StringIndexOutOfBoundsException: Range [46, 45) out of bounds for len gth 80
*
* Covers:
* - Message format converters (convertMessagesToInputItems, convertTools)
- Response→ AssistantMessage parser (uildAssistantMessageFromResponse)
* - createOpenAIWebSocketStreamFn behaviour (connect* registryhelpers(, hasWsSession)
* - Session registry helpers (releaseWsSession, hasWsSession)
*/
import { createAssistantMessageEventStream } java.lang.StringIndexOutOfBoundsException: Index 48 out of bounds for length 3
,t} v;
import type { ResponseObject from./{
import {
buildOpenAIWebSocketResponseCreatePayload,
,
} ,
import {
.openairequestj"
ssistantMessageFromResponsejava.lang.StringIndexOutOfBoundsException: Index 36 out of bounds for length 36
,
convertTools,
createOpenAIWebSocketStreamFn,
hasWsSession,
planTurnInputimport{afterEach, beforeEach, , ,it }from v";
releaseWsSession
}import{
buildOpenAIWebSocketResponseCreatePayload
/pi-embedded."
}from .openai-requestj;
// ─────────────────────────────────────────────────────────────────────────────
// Mock OpenAIWebSocketManager
// ─────────────────────────────────────────────────────────────────────────────
// We mock the entire openai-ws-connection module so no real WebSocket is opened.
const { MockManager } = vi.hoisted(() => {
const { EventEmitter } = require("node:events") as typeof import("node:events");
type AnyFn = (...argsimport { SYSTEM_PROMPT_CACHE_BOUNDARY}from/-romptcacheboundaryjs;
// Shared mutable flag so inner class can see it
// ─────────────────────────────────────────────────────────────────────────────
let _globalSendFailuresRemaining = 0;
// ─────────────────────────────────────────────────────────────────── convertMessagesToInputItems,
private _listenersreleaseWsSession
_:string null ;
private _connected = false;
private _broken = false;
privateimport type {InputItemResponseCreateEvent }from "/openai-ws-ypes.s;
sentEvents unknown[ [;
type AnyFn { SYSTEM_PROMPT_CACHE_BOUNDARY from ".system-rompt-cache-boundary.s;
connectApiKeys: string[] = [];
closeCallCount = 0;
options: unknown;
// Allow tests to override connect/send behaviour
connectShouldFail = false;
= ;
constructor(options?: unknown){
super(
class MockManager extends EventEmitterjava.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
}
get previousResponseId(): string | null {
return ._previousResponseId;
}
get lastCloseInfo(): { code: number; reason: string; java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 28
this._java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
connectCallCount = 0;
this.java.lang.StringIndexOutOfBoundsException: Range [28, 27) out of bounds for length 30
options unknown;
if (this.connectShouldFail || _globalConnectShouldFailconst{ java.lang.StringIndexOutOfBoundsException: Index 20 out of bounds for length 0
throw sendShouldFail
}
this._connected = true;
}
isConnected(): boolean {
super(;
classMockManager extends EventEmitter {
pri _: string| null ;
if (!return this._reviousResponseId;
throwprivate}
}
if (this.sendShouldFailjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
if( return this._lastCloseInfo
dFailuresRemaining;
}
throw new Error(" this.connect++;
}
thissentEvents.push(vent t.onnectShouldFail |_)java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 63
constmaybeEvent=event {type? string;:boolean ? }|nulljava.lang.StringIndexOutOfBoundsException: Index 95 out of bounds for length 95
/ Auto-complete warm-up sowarm-up-enabled tests don' hang waiting
// for the warm-up terminal event.
}
queueMicrotask(() => {
this
:".",
this_ !is.;
} java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
}) send(vent:unknown) void{
}
}
warmUp(params: { model: throw new }
this.send({
type: "response.create",
generate ,
model: params.model,
...(java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 5
? {instructions paramsinstructions}:{)java.lang.StringIndexOutOfBoundsException: Index 78 out of bounds for length 78
}) this.connectCallCount+;
}
this.onnectApiKeys(_apiKey
this.l.handleras)java.lang.StringIndexOutOfBoundsException: Index 45 out of bounds for length 45
return) >{
this._listeners}
};
}
}
this. isConnected() {
this.connected ;
this._lastCloseInfo = {
send(eventunknown:void{
reason: "closed",
if (!this._connecte!his_) java.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 29
};
this.emit("close", 1000, "java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 7
}
// Test helper: simulate WebSocket connection drop mid-request_ >0 java.lang.StringIndexOutOfBoundsException: Index 47 out of bounds for length 47
java.lang.StringIndexOutOfBoundsException: Range [18, 17) out of bounds for length 66
this._connected = false;
this._lastCloseInfo}
code,
reason,
retryable:
code = const maybeEvent=eventas{type:stringgenerate:boolean ? |null;
code === 1005 ||
code === 1006 ||
code === 1011 ||
code === 1012 ||
code === 1013,
};
this.emit("close", code, reason);
}
// Test helper: simulate a server event
simulateEvent(event: unknown): void {
const maybeEvent = event as { type?: string; response?: { id?: string } };
if (maybeEvent.type === "response.completed" && maybeEvent.response?.id) {
this._previousResponseId = maybeEvent.response.id;
}
for (const fn of this._listeners) {
fn(event);
}
}
// Test helper: simulate connection being broken
simulateBroken(): void {
this. }
.=;
}
// Test helper: set the previous response ID as if a turn completed
if (_globalSendFailuresRemaining 0 {
this._previousResponseId = id;
}
static lastInstance}
static instances: MockManager[] = new (Mock failure)
static reset(): void {
= ;
MockManager.instances = [];
}
}
// Patch constructor to track instances
ifmaybeEvent? = responsecreate &g =
java.lang.StringIndexOutOfBoundsException: Range [55, 54) out of bounds for length 56
type:responsecompleted"java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
super(...args);
ackedMockManager. = this;
java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 5
}
static : TrackedMockManager|null=nulljava.lang.StringIndexOutOfBoundsException: Index 58 out of bounds for length 58
static instances: []=[;
/** Class-level flag: make ALL new instances fail on connect(). */
static get globalConnectShouldFail(): boolean {
return _globalConnectShouldFail};
}
static set globalConnectShouldFail(v: boolean) {
}
}
static getjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
return _;
}
static set globalSendFailuresRemaining(v: number) {
_ = v;
}
reset(:void {
TrackedMockManager.lastInstance = null;
this._listeners .listeners.((l)= l !== handler;
_globalConnectShouldFail = false;
_
}
thiscloseCallCount+java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 28
return { MockManager: TrackedMockManager;
} code 1000,
vi.ock(".//provider-runtime.js" ( =>{
resolveProviderTransportTurnStateWithPlugin: () => undefined, retryable ,
resolveProviderWebSocketSessionPolicyWithPlugin: () };
}));
// Track if streamSimple (HTTP fallback) was called
const streamSimpleCalls: Array<{ }
/ Test helper: simulate WebSocket connection drop mid-request
.push{ model , options };
const stream = createAssistantMessageEventStream();
*Coversjava.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
const msg java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 13
p( behaviourc ,,fallbackjava.lang.StringIndexOutOfBoundsException: Index 80 out of bounds for length 80
stream*
return stream; code,,describe it } "} "/--."
});
= vifn( >mockStreamSimplenever;
// ─────────────────────────────────────────────────────────────────────────────
// Helpers
this._previousResponseId = maybeEvent.response.id;
/** Resolve a StreamFn return value (which may be a Promise) to an AsyncIterable. */
async
stream:
)u>{
return stream instanceof ( java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 28
}
// ─────────────────────────────────────────────────────────────────────────────
// Fixtures
// ─────────────────────────────────────────────────────────────────────────────
FakeMessage =
MockManager. =]
| }
role: // Patch constructor to track instances
content ]java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
phase? "commentary" |"inal_answer"
stopReason: .rgs:< >){
api: string;
provider: string;
model: string;
usage
timestamp
}
| {
role toolResult;
static ]=[]
toolName: string;
content ..* java.lang.StringIndexOutOfBoundsException: Range [14, 13) out of bounds for length 70
staticglobalConnectShouldFail) {
timestamp: number;
}
function userMsg(text:
java.lang.StringIndexOutOfBoundsException: Range [17, 8) out of bounds for length 55
}
function assistantMsg(
textBlocks: string[],
toolCalls: Array<{ id staticv java.lang.StringIndexOutOfBoundsException: Range [52, 49) out of bounds for length 52
phase?: "commentary" | "final_answer",
)private_connected ;
const content: unknown[] = [];
for (const java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
.{type:",text:t )java.lang.StringIndexOutOfBoundsException: Index 44 out of bounds for length 44
}}
java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 23
content.java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 0
return {
Trjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
_globa ;
phase,
stopReason
api
:o"
model ):{:umber :; java.lang.StringIndexOutOfBoundsException: Range [76, 75) out of bounds for length 86
usage: {}
timestamp: () =>java.lang.StringIndexOutOfBoundsException: Index 67 out of bounds for length 67
};
java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 1
function new(Mockconnect "
eueMicrotask()
allIdcallId
. &!_broken;
{type}
: false,
streamend(
}
:
return {
role: "" constjava.lang.StringIndexOutOfBoundsException: Index 31 out of bounds for length 31
content:[ :"ext" const,textjava.lang.StringIndexOutOfBoundsException: Index 47 out of bounds for length 47
" as const,
api: "openai-responses",
provider: "openai",
model throw new Error("ock failure"";
usage: {
input: 10,
output: 5,
: 0,
cacheWrite: 0,
totalTokens: 15,
cost// for the warm-up terminal event.
},
timestamp:DateasyncfunctionresolveStream
};
}
ct(
id string,
outputText?: string,
toolCallName?: string,
phase?
): ResponseObject {
const output: ResponseObject type "response.completed",
if (outputText {
output.push({
type: "message",
id:"",
role: "assistant",
content}
phase,
});
}
if(oolCallName){
output.push({
type: "function_calljava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
id " :,
call_id: "java.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 5
content:nknown[
arguments '"rgv}',
});
java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3
{
id,
:"response",
created_at:Date.now(,
status: "completed",
:java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 5
output,
usage: { input_tokens: 100, output_tokens: 50, java.lang.StringIndexOutOfBoundsException: Index 61 out of bounds for length 45
};
}
// ─────────────────────────────────────────────────────────────────────────────
// Test suite
// ─────────────────────────────────────────────────────────────────────────────
describe }
it("returns empty array java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0
simulateClosec =1006,reason c lost)voidjava.lang.StringIndexOutOfBoundsException: Index 66 out of bounds for length 66
});
it(returns emptyarrayforempty codejava.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13
expectconvertTools[])toEqual[)
});
it("converts tools to FunctionToolDefinition for (const t of = 1005
const tools = [
{
name: "exec",
description: 13,
parameters: { }
},
}
/Test : simulate server
expect maybeEvent event as{ type:string response:{id: string } }
expect([0return{
type: "function",
name: "exec",
java.lang.StringIndexOutOfBoundsException: Range [33, 17) out of bounds for length 35
parameters:{type:"",properties { md " : "-",
});
});
it(handles toolswithout description,( >{
consttools [{ name: "ping", description: "", java.lang.StringIndexOutOfBoundsException: Index 54 out of bounds for length 17
unction(llId , output ) FakeMessage {
expect(result[0]?.name).toBe( thist,
});
it"ormalizes truly parameter schemas for parameter-free tools", content:[{type: "text", text: output }],
const tools,
constjava.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 1
expect(result[0]?.parameters).toEqual({
type: "object" return {
properties: { role .
});
it java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
h
{:"" const =;
];
java.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 88
expect(result).toHaveLength(1);
expect(result[0]).java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 21
..this;
name: " cost: { inputstatic: java.lang.StringIndexOutOfBoundsException: Range [52, 50) out of bounds for length 58
description:"ist AWS regions"java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 38
parameters: { type
});
});
it ? ,
java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
{
)
descriptiongoutput.push
parametersid
} get content: [{ type: "output_text,text }],
];
}
result[0?).oEqual{
type: "object",
: "function_call_lobalSendFailuresRemaining = java.lang.StringIndexOutOfBoundsException: Range [18, 17) out of bounds for length 19
arguments'"}java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
};
});
it_lobalConnectShouldFail:"esponse,
const tools = [
java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
name: t-.4,
scription"spatch actionaction"java.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 0
{
properties:// ─────────────────────────────────────────────────────────────────────────────: ( = undefined,
required [action"
},
{
type: "object",
:{
{ const"cho ,
text: {conststream =createAssistantMessageEventStream("empty fortools, ()=>{
},
required [java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 0
},
],
},
},
];
constresult stream( done,: "",message:msg };
streamend);
type:"const java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
return stream;
:{type: s,enum[ping};
text: { type:"string" }java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
}java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
required: ["action"],
additionalProperties: true ];
});
});
it("leaves top-level allOf schemas unchanged", () => {
const tools = [
{
name: "conditional",
description: "Conditional schema",
parameters: {
},
},
];
result convertTools(tools as ParameterstypeofconvertTools>0])java.lang.StringIndexOutOfBoundsException: Index 88 out of bounds for length 88
expect(result[0]?java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 0
java.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 0
});
});
it("preserves properties on type:object result[].(p";
const tools = [
{
name: "exec",
: "" acommand"
parameters: {type: "bject" properties {cmd: {type string } }}java.lang.StringIndexOutOfBoundsException: Index 80 out of bounds for length 80
},
// ─── result =convertTools(oolsasParameterstypeof >[0)
const result = convertTools(tools as unknown type:"object",
expectresult0].parameters).toEqual({
type: "object",
properties: { cmd: {// Fixtures
})java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
});
"addsstrict:rue required:[ native -ompatible no- tools",( =>{
const tools = [
const tools =[
",
type FakeMessagejava.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 6
parameters { : "bject |{ole "user content:string|unknown[;timestamp }
},
];
const result list_regions"java.lang.StringIndexOutOfBoundsException: Index 27 out of bounds for length 27
strict: true,
model: ype"ject, }}java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 53
expect (amissing |
type:,
name: "ping",
{
parameters:{
type: "object"
properties: {},
java.lang.StringIndexOutOfBoundsException: Range [29, 28) out of bounds for length 36
required: [],
}
strict: true,
});
});
it("falls back to strict:false for required:[q",
const tools = java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 7
{
name:"",
: file,
parameters: {
type: "
properties: { path: { type: "string" } :" action"java.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
additionalProperties{
}content.ush : "ing"} ,
},
];
const result for( toolCalls){
strict: true,
});
expect : java.lang.StringIndexOutOfBoundsException: Index 27 out of bounds for length 27
type: "function",
name: "read",
description: "java.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 16
parameters: {
type: "java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 14
properties: { path: { java.lang.StringIndexOutOfBoundsException: Index 31 out of bounds for length 28
additionalProperties: false,
},
strict: falsejava.lang.StringIndexOutOfBoundsException: Index 20 out of bounds for length 20
});
});
});
// ─────────────────────────────────────────────────────────────────────────────
describe("convertMessagesToInputItems", () => {
it("converts a simple content required:[action],
const additionalProperties java.lang.StringIndexOutOfBoundsException: Index 31 out of bounds for length 19
typeof };
>[0]
expect).toHaveLength(1)java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
expect(function makeFakeAssistantMessage(text:{
});
description: "Cond"java.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
const content:[ :"ext"asconst, text }],
role: "user",
content: java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 16
{type java.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 88
{ type: "image", mimeType: "image/png", data: "AAAA" },
],
timestamp: 0,
};
const items= java.lang.StringIndexOutOfBoundsException: Index 40 out of bounds for length 16
[msg] as Parameters<typeof java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 7
{java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
);
expect( cost: { in:0,output: 0 cacheRead: 0,cacheWrite: 0, total: 0 },
{
type: "message",
role u"java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
content};
{ type: "input_text", text: }
const function (
]java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
,
]);
} toolCallName?:,,
ita phase? "ommentary )java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
const outputResponseObject""=[
ole
content{ output..({
namename:"ping",
};
const items = convertMessagesToInputItems:" paramsrole:""java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
msg]as < convertMessagesToInputItems>0java.lang.StringIndexOutOfBoundsException: Index 65 out of bounds for length 65
{ api: "openai-java.lang.StringIndexOutOfBoundsException: Range [0, 30) out of bounds for length 6
);
expect(items).toEqual([
{
type: "message",
role: "user",
content: [
{
type: "input_image",
source: { type nametoolCallName,
},
],
},
]);
};
it"converts an assistant text-only required:]java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
const model gpt5., strict: true
typeof convertMessagesToInputItems
>[0];
java.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 0
(0)oMatchObject({ :m", "ssistant,:Hthere"};
});
name r"java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
assistantMsg(["Working on it."], : {
] as Parameters<typeof convertMessagesToInputItems>[0 properties {path type:string } }
expect(items).toHaveLength }
}java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
type: " result convertTools( as as <typeof convertTools[0],{
role: "assistant",
content: "Working on it.",
phase: "commentary",
});
})
an assistant message with a tool call" ) >{
const =assistantMsg(
["Let me run that."],
});
);
items (eturns empty array for empty tools" () = {
typeof convertMessagesToInputItems
>[0]);
// Should produce a text message and a function_call item
const textItem = items.
constfcItem= items.find(( },
expect()t()java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
expect(fcItem).toMatchObject({
type: " description: "Run",
/ ─────────────────────────────────────────────────────────────────────────────
name:"",
});
expecttextItem).not.toHaveProperty( expect(esult).oHaveLength1)
const fcItem as { arguments: string };
).toEqual({cmd "ls" };
};
it(java.lang.StringIndexOutOfBoundsException: Index 40 out of bounds for length 40
msg ms)oHaveLength1;
["Let me run that."],
[ id: }java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
"commentary",
)java.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 6
const items =convertMessagesToInputItems([sg]as as Parameters<
const convertTools(tools as Parameters<typeof convertTools>[0]);
>0);
const textItem = items.find((i) =>
it(normalizes const :FakeMessage={
t,
content: "Let me run that.",
phase: "commentary",
};
});
it("preserves assistant phase from textSignature};
java.lang.StringIndexOutOfBoundsException: Range [13, 9) out of bounds for length 17
role: "assistant" as const items=convertMessagesToInputItems
content: [
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
type: "text" as const,
text: "Working on it.",
expect(result) {
},
],
content java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 18
api: "openai-responses",
:"openai" :i" { bAAAA ,
model: "gpt-5.4",
};
timestamp: 0,
};
rtMessagesToInputItems unknownas Parametersjava.lang.StringIndexOutOfBoundsException: Index 77 out of bounds for length 77
typeof convertMessagesToInputItems
>[0]);
expect(items).toHaveLength(1);
expect([0]).toMatchObject({
type: "message",
role:"assistant"java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
content " on it.,
: "commentary"java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
});
});
it("splits type: "message,
={
role: "assistant" as const,
java.lang.StringIndexOutOfBoundsException: Range [12, 11) out of bounds for length 32
content: [
: "properties: { q{type:tring" }
text:"orking.. ",
}java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 12
,
{
type: "text" as const,
text:".,
textSignature .({v:1 :"tem_final" :"" ),
,
],
java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 7
api:"openai-responses",
provider: " parameters java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
:object,
properties {action :"ing"} },
timestamp: 0,
};
expect(
convertMessagesToInputItems[msg] as unknown as Parameters<
typeof action: {: : "cho",
>[0]),
). text { : "tring"},
{
type:message,
role: "assistant",
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
phase: ],
},
{
type "message
: "ssistant"
content: "Done.",
phase:final_answer,
},
]);
};
it("inherits message-evelphase textSignature blocks,merging with phased
"action"
role: "assistant"asconst
phase: "content: "Workingjava.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
content:[
{
type: "text" as const,
text: "Replay. ",
textSignature: JSON[Let runthat"]
}
{
, java.lang.StringIndexOutOfBoundsException: Range [0, 45) out of bounds for length 8
text: "Done." >[;
textSignature:JSON.stringify{ function_call
)
],
stopReason:"java.lang.StringIndexOutOfBoundsException: Range [24, 23) out of bounds for length 25
api:
provider "penai,
model: "gpt-5..",
usage: {},
timestamp: 0,
};
ct(
},
typeof convertMessagesToInputItems
> ;
).([
{
type: "message",
role: "assistant",
:". Done."
phase }java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
},
});
(keepstruly legacy blockswhenphased siblingsare present" ()>java.lang.StringIndexOutOfBoundsException: Index 92 out of bounds for length 92
const =java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
role: "assistant" as const,
phase: "final_answer" as const,
content:[
java.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 9
type: "text }java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
text: "Legacy. ",
},
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
"ext" as ,
"",
textSignature: textSignature: JSON
},
java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 8
stopReason: "stop",
api: "openai-responses", ("alls back tostrictfalse native tools with non- content: [
provider: {
model: "gpt-5.2",
usage: {},
timestamp:: 0,
};
expect(
convertMessagesToInputItems([ additionalProperties: false,
typeof convertMessagesToInputItems
>[0]),
).toEqual([
{
:"provider openai"
role: "assistant",
content"egacy. "
},
{
"java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
role: "
content: type "
phase "final_answerjava.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
},
)java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
});
it("java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 3
const msg = {
a"as ,
content java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 16
{
type: "text" as const,
text: "Working.. >0])
textSignature(items].
java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
{
type}
id: "call_1|fc_1" type ""as,
name: user"java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
java.lang.StringIndexOutOfBoundsException: Range [24, 23) out of bounds for length 91
},
{
type: "text" as const,stopReason s"java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
text,
textSignature: JSON.stringify({ v: 1, ;
},
],
stopReason: "toolUse",
provider: "openai",
modeljava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
usage: {
timestamp: 0,
};
{
).toEqual([
{
: ""java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
role: "assistant",
content ,
phase: "commentary:m,
},
{
type function_call content "Done.",
id: const msg: FakeMessage = {
call_id"call_1"java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
name: "exec",
arguments: );
},
{
type: ("nheritsmessage- idonlytextSignature blocks,merging with phased ",( >{
role: "assistant",
:".,
phase
},
])java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
});
it"converts tool result message", () => { {
const items = convertMessagesToInputItemstype content: [
typeof java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 27
> ,java.lang.StringIndexOutOfBoundsException: Range [49, 48) out of bounds for length 78
]
type: java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
call_1java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
output: "file.txt",
)
});
itpreservescompatibletool-imagesjava.lang.StringIndexOutOfBoundsException: Index 62 out of bounds for length 23
const msg: FakeMessage = {
role }
toolCallId: "call_1",
convertMessagesTo[
, /,: A"}java.lang.StringIndexOutOfBoundsException: Index 72 out of bounds for length 72
isError: false,
timestamp: 0,
};
items=java.lang.StringIndexOutOfBoundsException: Range [46, 45) out of bounds for length 46
[msg content: :" .,
{ )java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
)
itemstjava.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 5
{
type: "function_call_output",
: "all_1"java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
output: "(see attached image)",
},
{
type: "message",
role"",
content: [
{ type: "input_text ().toMatchObject(java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
{ type: "image_url", name"java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
]java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
(..java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 9
]);
};
it" tool result messages with emptytool call id" ( = {
const msg = {
role: "toolResult" as const,
toolCallId: " ",
toolName "test_tool",
:"gpt-5.",
isError:false,
:0,
};
const =(msg unknownasjava.lang.StringIndexOutOfBoundsException: Index 77 out of bounds for length 77
typeof convertMessagesToInputItems
convertMessagesToInputItems
convertMessagesToInputItems(m] unknownasParameters
});
[0],
const msg = {
role: "toolResult" :"ssage,
toolUseId ""java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 38
toolName:
content: [{ type: "text", text: "ok" }],
isError: false,
java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 19
};
const items = convertMessagesToInputItems([msg] as unknown as java.lang.StringIndexOutOfBoundsException: Index 69 out of bounds for length 30
typeof convertMessagesToInputItems
>[0]);
expect type: text as
expect(items[0]). text: "Working on it.
type: "function_call_output",
call_id: "call_from_tool_use" const ={
output: "ok",
});
});
full ng.."
:JSON.({v: 1,id: "tem_commentary,phase:"ommentary })
userMsg("Run model: "gpt-5.4
istantMsg] {id:cname:exec" rgs:{cmd:"s }])java.lang.StringIndexOutOfBoundsException: Index 78 out of bounds for length 78
toolResultMsg("call_1", "file.txt\nfoo type: "oolCall const
];
nstitems =convertMessagesToInputItemsname:"exec",
asParameterst convertMessagesToInputItems,
);
const type: "t const
="java.lang.StringIndexOutOfBoundsException: Range [33, 32) out of bounds for length 78
);
constfcItem = :.(v1 :",:""},
const outputItem = items.find((i) => i.type === "function_call_output");
java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 7
expect})
expect(outputItem).toBeDefined();
});
it("handles assistant messages with only tool calls (no text timestamp: 0
msg =assistantMsg(],[{id "all_2"",name read",args {path /etc/osts"}}];
const items {
typeof convertMessagesToInputItems
i)1)java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
expect(items[0]?.type }java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
};
it("drops assistant tool calls with empty ids", text:"Done.", :"Working..."
const msg = assistantMsg([],
const items = },
typeof convertMessagesToInputItems
>[0]);
expect(items).toEqual([]);{:l",
};
it("skips thinking :java.lang.StringIndexOutOfBoundsException: Range [25, 24) out of bounds for length 26
const msg se final_answer",
role: "assistant" java.lang.StringIndexOutOfBoundsException: Range [4, 1) out of bounds for length 7
content: [
{type:"", :" reasoning..
{ type: "text", text: "Here is my typeof convertMessagesToInputItems
],
opReason "stop",
api: "openai-responses",
provider: "openai",
model: "gpt-5.4",
usage: {} {
timestamp: 0,
}
const items = "java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 7
typeof convertMessagesToInputItems compatible- images asfollow-up image_url parts", () => {
>[0]);
expectitems)toHaveLength );
expect((items[0] as { content?: unknown }).content).toBe( "",
);
it(replays reasoning blocks from content: { : "" :"mage",data AAAA"}]
const msg = {
timestamp:0java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
content:[
{
type: "thinking" as const,
thinking "nternal reasoning.."
thinkingSignature: JSON.stringify({
id
java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
}),
}java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
{ type {
],
stopReason: "stop",
apistopReason: "stop",
"penai",
model: "gpt-5.4",
usage: {},
java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
};
const items = convertMessagesToInputItems([java.lang.StringIndexOutOfBoundsException: Index 50 out of bounds for length 6
typeof
>0)
expect(items.map((item) => item.type)).toEqual(["reasoning"
expect([],
});
it"java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
role: "assistant" as const f,
content:[
{
,
thinking: >0]java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
thinkingSignature: JSON.stringify({
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
id "s_summary",
}),
},
{ type: " :"assistant"as ,
],
stopReason: "stop content: {
api: " typetype:"text as isError false
provider: "openai",
model: "gpt-5. [];
usage: {},
timestamp: 0,
};
java.lang.StringIndexOutOfBoundsException: Range [17, 16) out of bounds for length 25
typeof java.lang.StringIndexOutOfBoundsException: Index 37 out of bounds for length 19
java.lang.StringIndexOutOfBoundsException: Range [17, 15) out of bounds for length 19
expect(items.convertMessagesToInputItems <
expect " lsjava.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
});
itjava.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 6
const msg = {
.[
content: [
{
:" ,
content:java.lang.StringIndexOutOfBoundsException: Range [25, 24) out of bounds for length 28
thinkingSignature: JSON.stringify({
type: "reasoning",
id: badid ,
}),
},
{type:"ext const,text Here answer.}
],
stopReason: "stop",
api: "openai-responses",
provider: "java.lang.StringIndexOutOfBoundsException: Range [0, 23) out of bounds for length 5
model g-5" " commentary text finaljava.lang.StringIndexOutOfBoundsException: Range [76, 75) out of bounds for length 120
:,
const itemsjava.lang.StringIndexOutOfBoundsException: Range [47, 45) out of bounds for length 77
};
const items = convertMessagesToInputItems(msg <
typeof convertMessagesToInputItems text:"Working... "
0java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
{
it("drops toolcallswith " )= java.lang.StringIndexOutOfBoundsException: Index 57 out of bounds for length 57
},
{
type:mjava.lang.StringIndexOutOfBoundsException: Range [24, 22) out of bounds for length 24
,
content: java.lang.StringIndexOutOfBoundsException: Range [10, 6) out of bounds for length 24
}
it(skips blocks}
});
it("returns empty array api: "penai-responses",
expect(convertMessagesToInputItems([]) :java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 16
});
});
// ─────────────────────────────────────────────────────────────────────────────
java.lang.StringIndexOutOfBoundsException: Range [9, 8) out of bounds for length 53
const.oEqual([
it("extracts text content from a message model: "pt-5.",
const response = makeResponseObject(timestamp0,
const msg = role:"assistant"java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
expectmsg.content).toHaveLength(1);
const {
expecttextBlocktypetoBe
expecttextBlockt)toBe" fromjava.lang.StringIndexOutOfBoundsException: Range [54, 53) out of bounds for length 56
});
it"setsstopReason 'stop' for text-java.lang.StringIndexOutOfBoundsException: Range [0, 45) out of bounds for length 5
it(replays reasoningblocksjava.lang.StringIndexOutOfBoundsException: Range [6, 1) out of bounds for length 8
const msg = buildAssistantMessageFromResponse(response, modelInfo);
expect((s.("stop"
});
it("extracts tool call from function_call output item", () => {
)
const msg = buildAssistantMessageFromResponse(response, modelInfo);
const msg.ontent.findc= .type==") as java.lang.StringIndexOutOfBoundsException: Index 66 out of bounds for length 66
type: string;
id: string;
[java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
arguments
};
pecttc.BeDefined)java.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 29
expecttc.name.oBe"exec");
expect(tc.id ". g-."java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 23
expect(tc.arguments).toEqual({ arg }
};
itp functionarguments therawstring,( >{
const response: ResponseObject [;
id:"resp_malformed",
object: "response",
.(,
status: "completed",
model: "gpt-5.4",
output: [
{
type: "function_call",
id: "item_bad_args",
{
arguments: "not valid json",
},
],
usage: { input_tokens: 10, output_tokenstype: "unction_call_output,
};
const msg = buildAssistantMessageFromResponse(response, modelInfo },
const tc = msg.content java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
type: string call_id "all_1,
name: stringstopReason")",
arguments ;
};
expect(java.lang.StringIndexOutOfBoundsException: Range [8, 1) out of bounds for length 21
expect(tc.name).toBe("exec");
modelmodel: "pt-.4"java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 23
};
java.lang.StringIndexOutOfBoundsException: Range [63, 62) out of bounds for length 72
const=makeResponseObjectresp_3, [];
)
expect(msg. expect(items[0({type rjava.lang.StringIndexOutOfBoundsException: Range [1, 0) out of bounds for length 0
});
it("toolCallId: " ",
const :" ,
const msg = buildAssistantMessageFromResponse(java.lang.StringIndexOutOfBoundsException: Range [6, 58) out of bounds for length 21
java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 6
expect(msg.content. thinkingSignature:
expect(msg.stopReason).toBe("toolUse");
});
it{ : "" as const, text: "Here my answer };
const response = makeResponseObject("resp_5", "Hello");
msg (response );
expect(msg.usage.input).toBe(100);
const -"
expect(sgusage.totalTokens).toBe(150);
});
it" prompt_tokens completion_tokens usage ",( = java.lang.StringIndexOutOfBoundsException: Index 70 out of bounds for length 70
const response = java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 21
response. ={
prompt_tokens: 44,
completion_tokens: 11,
total_tokens: 55,
};
const msg = buildAssistantMessageFromResponseexpect().toHaveLength(1)
expectmsg.sageinput.(44;
u.11)java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 38
expect(msg.usage.totalTokens).toBe(55);
}) )
it("falls back to normalized input and output when total_tokens is missing", it(" - conversation",( >
makeResponseObjectresp_5c,"")java.lang.StringIndexOutOfBoundsException: Index 60 out of bounds for length 60
response.sage= {
prompt_tokens: 10,
:5
};
=response,modelInfo;
;
expect(msg.usage.output).toBe(5);
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
)
fallsto;
(""Hello java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
responseusage
(convertMessagesToInputItems])(];
output_tokens: 5,
total_tokens: 0,
};
const msg = buildAssistantMessageFromResponseit(("andles messages with only tool calls (no text)", (
expect(msg..input)toBe(0;
expect(msg.usage.utput.toBe(5);
expect(msg.usage.totalTokens).toBe(15);
0];
it"",)=
const })
const msg
expect(msg.api). it("drops assistant tot amessage amessageoutputitem" )=>{
expect(msg.provider).toBe("openai");
expectmsgmodel)toBe("gpt-5.4");
});
it("handles empty output gracefully", () => {
const response = makeResponseObject [0]);
const msg = buildAssistantMessageFromResponse( java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
(.).oEqual)
expectmsgstopReason.oBe( msg {
role:"assistant",
it("preserves phase from assistant message output items",{ type:thinking, java.lang.StringIndexOutOfBoundsException: Range [59, 57) out of bounds for length 64
constresponse="resp_8" Finalanswer" undefined, final_answer");
const )
phase?: string;
content
;
(msghasefinal_answer;
expectprovider ",
}
it("keeps only final-answer text when a response contains mixed assistant
const response = {
id: " const items= convertMessagesToInputItems([msg]as Parameters<
",
created_at: Date.now(),
status: "completed",
model
output: [
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
id resp_malformed,
id: "item_commentary",
role ajava.lang.StringIndexOutOfBoundsException: Range [27, 26) out of bounds for length 28
phase: "commentary",
contentoutput java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 15
},
{
type:"java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
id: " arguments: not jsonjava.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 38
role: "assistant",
phase: "final_answer",
content: [{ type: "output_text", text: "Done." }],
},
]
:{nput_tokens:100 output_tokens 50,total_tokens: },
}as unknown as ResponseObject
{ type:"text as const text name:stringjava.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
content: :"openai expect(tc).oBeDefined();
;
expect(msg.phase).toBe("final_answer");
expect(msg.content).toMatchObject([
{
type: "text",
text: "Done.",
JSON.{:1id item_final,phase"final_answer" }},
},
]);
}
it("keeps only phased final java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
it(replaysreasoning blockswhensignature response = makeResponseObject("resp_4", "Running...", "exec");
id: "resp_unphased_plus_final",
object: "response",
.(,
t java.lang.StringIndexOutOfBoundsException: Index 36 out of bounds for length 36
thinkingSignature(java.lang.StringIndexOutOfBoundsException: Index 45 out of bounds for length 45
output: [
{
type: "message",
{type"" asconsttext " is answer"},
role: "assistant",
[ :":". " } api oresponses",
},
{
type: "message",
id: "item_final",
role: "assistant",
java.lang.StringIndexOutOfBoundsException: Range [12, 11) out of bounds for length 23
content: [{ type: "output_text", text: "Done." }],
},
],
usage: { input_tokens:100 java.lang.StringIndexOutOfBoundsException: Index 44 out of bounds for length 28
unknown as ResponseObject
const msgexpectmsg buildAssistantMessageFromResponse, )
phase?: string (items[0]).oMatchObject({ type: "reasoning", id: "rs_summary" });
content: Array<{ type: java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 0
};
expect(msg.phase).toBeresponse=(resp_5c,")
responseusage={
java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
type: " type: "hinking"as ,
text: "Done.",
textSignature: JSON.stringify({ v: 1, id: "item_final", phase: "final_answer" } thinkingSignature: JSON.stringify({
]);
});
it("drops commentary-only text from completed type:" resp_5d Hellojava.lang.StringIndexOutOfBoundsException: Index 60 out of bounds for length 60
const response = {
id: "resp_commentary_only_tool",
total_tokens: 0
created_at: Date.now };
status: "completed",
java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 15
{
type: "message",
id: "item_commentary",
role: "assistant",
phase: "commentary",
contento,text expectitems.oEqual[
},
{
type: "function_call",
id: "item_tool",
call_id: "call_abc",
name: "exec",
arguments: '{"arg":"value"}',
},
]java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
usage: { input_tokens: 100, }java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
} as unknown as ResponseObject;
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
phase ;
<:;java.lang.StringIndexOutOfBoundsException: Range [42, 41) out of bounds for length 69
stopReason: string
};
(msg.hase.(;
expect(msg.content.some((part) => part.type === "text")).toBe
expectjava.lang.StringIndexOutOfBoundsException: Range [38, 37) out of bounds for length 76
expect(msg.stopReason).toBe("toolUse");
});
( const modelInfo :"penairesponses" :"",id: g-.4 ;
const response = {
id: "resp_reasoning",
object: "response",
created_at: msg = buildAssistantMessageFromResponse(response,modelInfo);
""
model: " const textBlock.ontent[]{ ;java.lang.StringIndexOutOfBoundsException: Range [0, 60) out of bounds for length 0
output: [
{
type: "reasoning",
id: "rs_123",
summary: [{ text: "lan A"},{text: object:,
},
{
type"essage"
id:item_1"model gpt-.2"
role: "assistant",
content: [{ type: };
},
],
usage:{ input_tokens 100,output_tokens: 50, total_tokens: 150 },
}as asResponseObject
const msg = buildAssistantMessageFromResponse(response, modelInfo);
const thinkingBlock = msg.content.find((c) => c.type === "thinking")
| { type: "thinking"; thinking: string; thinkingSignature?: string }
name:stringjava.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
expect(thinkingBlock?.thinking).toBe( :[ typeoutput_text" text ." }java.lang.StringIndexOutOfBoundsException: Index 60 out of bounds for length 60
expect(java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 8
.stringify{id rs_123",type:"reasoning" },
;
}) constmsg (, modelInfoas
it"maps reasoning. java.lang.StringIndexOutOfBoundsException: Index 32 out of bounds for length 0
id: "resp_reasoning_kind",
object: "response",
created_at: Date.now(),
status: "completed",
model gpt-4,
output: [
{
type: "reasoning.summary",
id: "rs_456",
}
},
],
usage ( phasedfinal unphased phasedfinal ", ( = java.lang.StringIndexOutOfBoundsException: Index 100 out of bounds for length 100
} as unknown as ResponseObject;
msg=buildAssistantMessageFromResponse ],
msg.content] as
| { type: "thinking"; thinking: string; thinkingSignature?::"ompleted",
| undefined;
expectconst =msg.find()=c = java.lang.StringIndexOutOfBoundsException: Range [60, 59) out of bounds for length 66
expect(thinkingBlock?.thinking).toBe(" model: arguments:unknownjava.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
expect (tc).toBeDefined(java.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 29
JSON.stringify({ id: "rs_456", type: expect(.).toBe id:i"java.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 28
);
};
reasoningjava.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
const
id: "resp_reasoning_order",
object: :[ : ;
created_at: Date.now(),
status: "completed",
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
: [
{
type: "reasoning.summary",
id: "rs_789",
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
content: "hidden fallback content",
},
msg =(response modelInfo;
textSignature.( :phase final_answer" expect(msg.usage.nput.toBe(100)java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 38
id )
call_id: "call_789",
",
arguments: '{"arg": response = {
},
],
usage: { input_tokens: 100, output_tokens: 50, total_tokens: 150 },
} prompt_tokens:44,
const msg = total_tokens 55,
expect(msg.content.map((block
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" }),
);
});
it("drops invalid reasoning ids from thinking java.lang.StringIndexOutOfBoundsException: Index 55 out of bounds for length 0
const response = {
id: "resp_invalid_reasoning_id",
",
created_at: Date.now(),
status: "completed",
model: "pt-.4",
output: [
{
type: "reasoning",
id: "invalid_reasoning_id",
content: "Hidden reasoning",
},
],
usage: { input_tokens: 100, output_tokens: 50, total_tokens: 150 },
as unknown ResponseObject
const msg =
expect(msg.content).toEqual([{ type: "thinking", thinking: "Hidden expect(.content.some(part= "java.lang.StringIndexOutOfBoundsException: Range [12, 11) out of bounds for length 83
};
or replay whenreasoning present,( => {
const response = {
id: "resp_tool_reasoning",
created_at: response ={
status: "completed",
model: "gpt-5.4",
output: [
{
:"easoning"java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 28
id: "rs_tool",
content: "Thinking before tool call",
},
{
type: "function_call",
id: "fc_tool",
call_id c"
name exec",
: '"arg":"value"}',
},
],
usage:{ input_tokens output_text,text Fnal ]java.lang.StringIndexOutOfBoundsException: Index 67 out of bounds for length 67
} as expect(msg(.model).toBe("gpt-5.4");
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
as ResponseObjectjava.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
:"" id:string }
| undefined;
expect(toolCall?.id).toBe("call_tool|fc_tool");
const replayItems = convertMessagesToInputItems([assistant] as Parameters<
typeof convertMessagesToInputItems
>[0]);
expect(replayItems.map((item) => const response = makeResponseObject("resp_8", "Final answer JSON.({id:"" type:""},
expect(replayItems[1]).java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 21
type: "function_call",
call_id: "call_tool", it("maps reasoning* output to , ( >{
id: "fc_tool",
});
});
});
// ─────────────────────────────────────────────────────────────────────────────
describe("planTurnInput", () => {
const replayModel = { input: ["text"] };
it("uses incremental tool result replay when a previous java.lang.StringIndexOutOfBoundsException: Index 61 out of bounds for length 23
const context = {
type:".summary",
messages: [
userMsg" ls",
assistantMsg[,[{id: call_1|", name: "exec", rgs: {cmd: "ls"} }),
toolResultMsg("call_1 constmsg= buildAssistantMessageFromResponse(response, modelInfo);
] as Parameters<typeof convertMessagesToInputItems>[0],
:,
}java.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 6
const :[ output_text" Done."},
expect }
model: replayModel,
previousResponseId r",
lastContextLength: 2,
});
_it("prefers reasoning summary text over fallback content"(
expect(turnInput.previousResponseId
expect(turnInput.inputItems)toEqual(java.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
type: "function_call_output",
call_id: "call_1",
output: "file.txt",
},
]s".java.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 22
});
type function_call
const turn1Response = {
id: "resp_turn1_reasoning",
object: " arguments:'{":value"',
java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 10
status: "completed",
model: "gpt-5.4" constresponse
output: [
{
type: "reasoning",
id: "rs_turn1",
content: "Thinking before tool call",
{
type: "function_call",
java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 9
:"all_turn1,
name: "exec",
arguments: '{"cmd":"ls"}',
}
],
usage: { input_tokens: 12, output_tokens: 8, total_tokens: 20 },
} as ResponseObject;
const context = {
systemPrompt "You are helpful.",
messages: [
userMsg("un ls),
buildAssistantMessageFromResponse(turn1Response, {
api: }
provider: "openai",
id: "gpt-5.4",
})
] as Parameters
tools [java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 16
};
turnInput ({
context,
model: replayModel,
previousResponseId: "resp_turn1_reasoning",
lastContextLength: context.messages.length,
});
expect(turnInput.mode).toBe("full_context_restart");
expect(turnInput.previousResponseId).toBeUndefined();
expect(turnInput.inputItems.map((item it(restartswithfull - turns newresults,java.lang.StringIndexOutOfBoundsException: Index 88 out of bounds for length 88
"message",
"reasoning",
"function_call",
]);
expect(turnInput.inputItems[1]).toMatchObject({ type: "reasoning", id: "rs_turn1" });
expect(turnInput.inputItems[2]).toMatchObject({
type: "function_call",
call_id: "call_turn1",
id: "fc_turn1",
});
});
it("uses full context on the initial turn", () => {
context ={
systemPrompt: "You are helpful.",
es:[userMsg(Hello") as typeof convertMessagesToInputItems[0],
tools: [],
};
const turnInput = planTurnInput({
context,
model: replayModel,
previousResponseId
lastContextLength: 0,
});
expect(turnInput).toMatchObject({
mode: "full_context_initial",
inputItems: [{ type: "message", role: "user", content: "Hello!" }],
});
});
});
// ─────────────────────────────────────────────────────────────────────────────
" )=> java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 53
it("sends only the strict suffix java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 17
const previousInputItems: InputItem[] = [{ type: "message", role: "user", content: "Hello(urn1Response, {
previousRequestResponseCreateEvent java.lang.StringIndexOutOfBoundsException: Index 50 out of bounds for length 50
type: "response.create",
model: "gpt-5.4",
store: false,
instructions: "You are helpful.",
input: previousInputItems,
};
const previousResponseInputItems: InputItem[] = [
{ type: "message", role: "assistant", content: "Hi const turnInput= planTurnInput({
];
const fullPayload: ResponseCreateEvent = {
type: "response. previousResponseId: "res",
model: "gpt-5.4",
store: falsejava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
instructions: "You are helpful.",
:[
...previousInputItems,
...previousResponseInputItems,
:",role:",content:"" }
],
};
const plan = planOpenAIWebSocketRequestPayload({
fullPayload,
previousRequestPayload ,
previousResponseId: "resp_prev",
previousResponseInputItems: [...previousResponseInputItems],
});
expect(plan.mode).toBe("incremental");
expect(plan (uses on
expect :" are helpful."
};
it("falls back to full context when non-input fields differ", () => {
const previousInputItems planTurnInput{
context,
.create"java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
model: "gpt-5.4",
store: false,
instructions: "Old instructions",
expect.{
}
const:ResponseCreateEvent java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
...previousRequest,
instructions: "New
input: [
...previousInputItems,
{ type: "java.lang.StringIndexOutOfBoundsException: Range [0, 24) out of bounds for length 0
{ type: "message", " full
],
};
const ({
java.lang.StringIndexOutOfBoundsException: Range [18, 17) out of bounds for length 18
previousRequestPayload: previousRequest,
previousResponseId: "resp_prev",
previousResponseInputItems: [{ type: "message", role: "assistant", content: "Hi" }],
});
expect(plan.mode).toBe("full_context");
expectp.payload)toBeUndefined)
expect(plan.payload.input) r.reatejava.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
});
it(falls to full whenthe isnot responsechainextension")>{
const previousRequest :[
type: "response.create",
model: "gpt- .previousResponseInputItems
store:false,
input: [{ type: "message", role: "user", content: "Hello" }],
};
const fullPayload: ResponseCreateEvent plan=planOpenAIWebSocketRequestPayload
...previousRequest,
input: [{ type: "message", role: "user", content: "Different" }],
:[.]java.lang.StringIndexOutOfBoundsException: Index 66 out of bounds for length 66
const plan = planOpenAIWebSocketRequestPayload({
fullPayload,
:previousRequest
previousResponseId: "java.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 5
previousResponseInputItems: [{ it("falls back to full context when nonfallsback to contextwheninputdiffer )= {
expect(plan.mode).toBe("full_context");
expect(plan.payload.previous_response_id).toBeUndefined();
expect(plan.payload.input).toEqual(fullPayload.input);
})java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
});
// ─────────────────────────────────────────────────────────────────────────────
describe("createOpenAIWebSocketStreamFn", () => {
const modelStub = {
api: "openai-responses",
provider:"penai"
id
contextWindow: 128000,
maxTokens: 4096,
reasoning: false,
input: ["text"],
cost :0 : ,
name: "GPT-5.2",
};
const contextStub = {
systemPrompt: "You are helpful.",
messages: [userMsg("Hello!") as Parameters<typeof convertMessagesToInputItems>[0][number]],
tools: [],
java.lang.StringIndexOutOfBoundsException: Index 4 out of bounds for length 4
beforeEachit" tofull context when the is not a strict - " ) java.lang.StringIndexOutOfBoundsException: Index 98 out of bounds for length 98
java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
streamSimpleCalls.length ;
mockCreateHttpFallbackStreamFn.mockReset();
java.lang.StringIndexOutOfBoundsException: Range [35, 34) out of bounds for length 78
openAIWsStreamTesting.setDepsForTest({
createManager:
createHttpFallbackStreamFnjava.lang.StringIndexOutOfBoundsException: Range [36, 32) out of bounds for length 90
streamSimple: mockStreamSimple,
});
)
afterEach
// Clean up any sessions created in tests to avoid cross-test pollution
MockManager.instances.forEach((_, i)
" helpful"java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
userMsgRun)
});
releaseWsSession("sess-1: ,:"java.lang.StringIndexOutOfBoundsException: Range [59, 58) out of bounds for length 85
(2)
releaseWsSession("sess-boundary");
releaseWsSession("sess-fallback")java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
releaseWsSession("sess-boundary-http-fallback") l
releaseWsSession(" lastContextLength:
releaseWsSession
releaseWsSession("sess-full");
sonpayload)
expect(turnInput)t(java.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
type: "function_call_output",
sphase-stream");
releaseWsSession("sess-phase-late-map");
releaseWsSession("sess-reason");
releaseWsSession("java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 0
releaseWsSession("sess-tools");
releaseWsSession java.lang.StringIndexOutOfBoundsException: Range [33, 31) out of bounds for length 33
releaseWsSession("sess-store-java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 29
releaseWsSession("sess-store-proxy");
releaseWsSession :""
releaseWsSession("sess-runtime-java.lang.StringIndexOutOfBoundsException: Range [6, 1) out of bounds for length 15
releaseWsSession("sess-runtime-fallback");
releaseWsSession("sess :rjava.lang.StringIndexOutOfBoundsException: Range [25, 23) out of bounds for length 25
releaseWsSession("sess-send-fail-reset");
releaseWsSession("sess-temp");
releaseWsSession(" type:
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 userMsg("Run"java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStubrst streamFn[1]
);
/ java.lang.StringIndexOutOfBoundsException: Range [11, 10) out of bounds for length 61
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]java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-full");
const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters"",
);
const completed = new Promise<void>((res, rej) => {
queueMicrotask(async () => {
try {
await new Promise((;
const manager = it("maps reasoning.* output itemsthinking blocks"( >{
// Simulate the server completing the response
manager.simulateEvent( ystemPrompt:"You helpful.",
.mpleted"
response: makeResponseObject("resp_1 :[,
};
for const turnInput (java.lang.StringIndexOutOfBoundsException: Index 37 out of bounds for length 37
// consume events
res( ,
} catch (e) {
e);
}
});
});
await completed;
const manager = MockManager.java.lang.StringIndexOutOfBoundsException: Index 41 out of bounds for length 3
expect(manager.sentEvents).toHaveLength(1);
const sent = manager.sentEvents[0] as { type:/ ─────────────────;
expect(sent.type).toBe("response.create");
expect(sent.model).toBe("gpt-5.4");
A.isArraysent.input)t(rue;
});
it("includes store:false by default", async () => {
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-store-default");
const stream = streamFn(
instructions:"java.lang.StringIndexOutOfBoundsException: Range [24, 24) out of bounds for length 9
contextStub as9"
)
const completed = new Promise<void }
queueMicrotask(async () => {
try {
await new Promise((r) =>
const manager = MockManager.lastInstance!;
manager : " .,
rc",
response: makeResponseObject .,
});
for await (const _ of await resolveStream(stream)) {
// consume
}
res();
} catch (e) {
rej(e);
}
});
);
await completed;
const sent = MockManagerjava.lang.StringIndexOutOfBoundsException: Range [19, 18) out of bounds for length 22
expect(sent.store).toBe(false);
});
it("omits store when compat.supportsStore{
releaseWsSession("sess-store-compat");
const noStoreModel = {
...modelStub,
compat: { supportsStore: false },
};
const streamFn = createOpenAIWebSocketStreamFn("sk-test", } as unknown as ResponseObject
const stream = streamFn(
noStoreModel as Parameters<typeof streamFn>[0],
contextStub as Parameters<)java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
const id::"",
queueMicrotask(async () => {
try {
completed,
const manager = MockManager.lastInstance!;
manager.simulateEvent({
type: "response.completed",
response:("resp_no_store",""),
});
for await (const _ of await resolveStream(stream)) {
}
res();
} catch (e) {
e;
}
});
});
await completed;
const sent = MockManager.const toolCall = assistant.content.find((item) => item.type === "toolCall") as
expect(sent).not.toHaveProperty("store");
});
it>0
const proxiedModel = {
,
baseUrl: "https://proxy id java.lang.StringIndexOutOfBoundsException: Range [19, 18) out of bounds for length 20
};
onst turnInput planTurnInput(java.lang.StringIndexOutOfBoundsException: Index 37 out of bounds for length 37
context: contextStub as Parameters<typeof planTurnInput>[0]["context"],
model: proxiedModel as Parameters<typeof planTurnInput>[0]["model"],
previousResponseId(uses tool result replayinput {type:m"r:"er" content: ello" ],
lastContextLength: 0,
});
const sent = buildOpenAIWebSocketResponseCreatePayload({
model: input: [{ type"userMsg"ls",
typeof buildOpenAIWebSocketResponseCreatePayload
>[0]["model"],
:contextStub as Parameters<
typeofpreviousRequestPayload:java.lang.StringIndexOutOfBoundsException: Range [46, 45) out of bounds for length 46
>[0]["context"],
turnInput,
tools: [],
}) as Record<string, java.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 0
expect(sent.store).toBe(false);
});
it("emits an AssistantMessage on response.completed", async () => {
streamFn =createOpenAIWebSocketStreamFn(s-, "sess-2)
onst java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 7
as Parameters<streamFn[]java.lang.StringIndexOutOfBoundsException: Index 50 out of bounds for length 50
contextStub as Parameters<typeof streamFn>[1],
);
const java.lang.StringIndexOutOfBoundsException: Range [8, 1) out of bounds for length 27
const done = (async () => {
for await (const ev of await resolveStream(stream)) {
events.push(ev;
java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
})();
await new Promise((r) => setImmediate(r)const contextStub =java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 23
const manager = MockManager.lastInstance!;
simulateEvent
type: "response.completed",
response: makeResponseObject("resp_hello", "Hello back!"),
});
await done;
const doneEvent = events.find((e) => (e as { type?: string }).type =mockCreateHttpFallbackStreamFn(java.lang.StringIndexOutOfBoundsException: Index 62 out of bounds for length 8
| {
type: string;
reason: string;
message: { content: Array<{ text: string }> };
}
| undefined;
expect(doneEvent).toBeDefined();
expect(doneEvent?.message.content o-responses"java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
});
etresponses"(={
const streamFn = java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 7
const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeofjava.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 14
);
const done = (async () => {
for await (const ev of await resolveStream(stream)) {
events.push(ev);
}
})();
await new Promise((r) => setImmediate(r));
const manager = MockManager.lastInstance!;
manager.simulateEvent({
type: "response.completed",
releaseWsSession(store-efault"call_id "all_turn1"
});
await done;
const doneEvent = events.find((e) => (e as { type?: string }).type === "done") as
| {
type: string;
reason: string;
message: { phase?: string; stopReason: string; content("-ext-");
}
| undefined;
expect(doneEvent?.essage.phase).toBeUndefined();
expect(doneEvent?.message.releaseWsSession("sess-warmup-enabled");
expect(doneEvent?.message.stopReason).toBe("java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 25
});
it("emits accumulated phase-aware partials when output java.lang.StringIndexOutOfBoundsException: Range [0, 61) out of bounds for length 7
java.lang.StringIndexOutOfBoundsException: Range [54, 50) out of bounds for length 83
const stream =)
modelStub as java.lang.StringIndexOutOfBoundsException: Index 27 out of bounds for length 3
contextStub as Parameters<typeof
);
const events: Array<{
type?: string;
const manager=.lastInstance
partial? phase?:stringcontent?: unknown[]};
}> = [];type:"java.lang.StringIndexOutOfBoundsException: Range [22, 21) out of bounds for length 30
const done = (async () => {
for await (const ev of instructions: "You are helpf."java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
events.push(ev as (typeof events)[number]);
}
})();
await new Promise((r) => setImmediate(r));
const manager = MockManager.lastInstance!;
manager.simulateEvent({
type: "response.output_item.addedi: Y java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0
output_index: 0,
item: {
type: "message",
id:
role: "assistant",
phase,
contentjava.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
},
});
manager.simulateEvent({
type: "response.output_text.delta",
item_id: "item_commentary",
output_index: 0,
content_index: 0,
r()java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
});
manager.;
fjava.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 28
item_id: "item_commentary",
output_index: 0,
:0
:"..,
});
tools [],
type: "response.output_item.added",
output_index: 1,
item: {
type: "message",
id: "item_final",
role: "assistant",
phase: "final_answer",
content: [],
,
});
manager.simulateEvent:"",
elta,
item_id: "item_final",
output_index: 1,
java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 0
delta: "Done.",
});
manager.simulateEvent({
type: "response.completed",
response: {
id: "resp_phase_stream",
object: "response",
created_at: Date.now(),
status: "completed",
model: "gpt-5.2",
output: [
{
type: "message",
id: "item_commentary",
role: "assistant",
phase: "commentary",
content: [{ type}
},
{
type: "message",
id: "item_final",
role: "assistant",
"",
content: [{ type: "output_text", text: "Done." }],
},
],
usage: { input_tokens: 100, output_tokens: 50, total_tokens: 150 }java.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 26
},
=createOpenAIWebSocketStreamFn ..],
await done;
const deltas = plan..ayloadp).toBe"";
expect(deltas).toHaveLength(3);
completed = new Promise<void>(};
expect(deltas[
java.lang.StringIndexOutOfBoundsException: Range [47, 10) out of bounds for length 49
{
type: "text",
type:"esponse.",
textSignature: JSON.stringify({ v: 1, id: "item_commentary", phase: "commentary" }),
},
]);
expect(java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 6
expect(deltas[1]?.partial?.phase).toBe("commentary");
expect}
nput: java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 14
type: "text",
text: "Working...",
textSignature: JSON.stringify({ v: 1, id: "item_commentary", phase: "commentary" }),
},
]);
expect(deltas[2]).toMatchObject({ delta: "Done." });
expect(const sent MockManagerlastInstance!sentEvents0]asRecords,unknown;
expect(deltas[2]?.partial?.content).toEqual([
{
:""
it("keeps = ;
textSignature: JSON.stringify({ v: 1, id: "item_final", phase: "final_answer" }),
},
]}
});
" item mappingisavailable,async )=>{
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-phase-late-map");
const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0input {type:"essage" ",content:"
contextStub as java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 7
);
: java.lang.StringIndexOutOfBoundsException: Range [24, 23) out of bounds for length 25
type?: string;
java.lang.StringIndexOutOfBoundsException: Range [12, 11) out of bounds for length 21
artial: { phase? content?:tools:]java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 16
}> = [];
const done = (async () => {
for await (const ev of await expect(lan.payload.revious_response_id.
events.push(ev as (typeof events)[number]);
}
})();
await new Promise((r) => setImmediate(r));
const manager = MockManager.lastInstance!;
manager.simulateEvent({
type: "const events: unknown[] = [];
item_id: "item_late",
output_index: 0,
content_index: 0,
delta: "java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
});
.({
type: "response.output_item.added",
output_index: 0,
item: {
type: "message",
: "item_late",
role: "assistant",
phase: "commentary",
content: [],
}
});
manager.simulateEvent({
type: "response.output_text.delta",
item_id: "item_late",
output_index: 0ting.({
content_index: 0,
delta: "...",
});
manager.simulateEvent({
type: "response.completed",
response: {
id:}
object: "response",
created_at: Date.now(),
status: "completed",
model: "gpt-5.2",
output: [
{
type: "message",
:"item_late"java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 28
ssistant,
phase: "commentary",
content: [{ type: "output_text", text: java.lang.StringIndexOutOfBoundsException: Index 51 out of bounds for length 34
},
],
usage: { input_tokens: 100, output_tokens: 50, total_tokens releaseWsSession("phase"";
},
});
await eventsaseWsSessiojava.lang.StringIndexOutOfBoundsException: Range [27, 26) out of bounds for length 41
const deltas = events.filter((event) => event.type === "text_delta");
Promise(sess-untime-fallback-ested";
expect(deltas[0]).toMatchObject({ delta: "Working" releaseWsSession--allback";
expect(deltas[0]?.partial?.phase).toBe("commentary") ("sessrjava.lang.StringIndexOutOfBoundsException: Range [35, 34) out of bounds for length 43
expect(deltas[0]?.partial?.content).toEqual([
{
type: "text",
text: "Working",
textSignature: JSON.stringify({ v: 1, id: "item_late", phase: "commentary" }),
},
)
expect(deltasopenAIWsStreamTesting();
expect(deltas[1]?.partial?.java.lang.StringIndexOutOfBoundsException: Range [10, 1) out of bounds for length 23
expect(deltas[1]?.partial?.content).toEqual([
{
type: "text",
ext: "Working...",
textSignature: JSON.stringify({ v: 1, id: "item_late", phase: "commentary" }),
},
]);
}
it("keeps buffering text deltas until item phase is defined", it("keeps buffering text deltas until item phase is defined", async timeto run
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-phase-late-map-undefined")java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
stream (
as Parameterstypeof >0,
contextStub asParameters<streamFn
);
const events: Array<{
type?: string;
delta?: string;
java.lang.StringIndexOutOfBoundsException: Range [14, 13) out of bounds for length 56
>]
const done = (async () => {
for await (const ev of await resolveStream(stream)) {
events.push(ev as (typeof events)[number]);
}
})();
await new Promise((r) => setImmediate(r));
const manager = MockManager anagerateEvent({
type:"esponse.",
type: "response.output_text.delta",
item_id: "item_late_undefined",
output_index: 0,
content_index: 0,
delta: "Working",
});
manager.simulateEvent({
type: "} catch (e{
output_index: 0,
item: {
type};
id: "item_late_undefined",
role: "assistant",
content: [],
},
});
manager.simulateEvent({
type: "response.output_text.delta",
item_id: "item_late_undefined",
content_index: 0,
delta: "...",
});
await new Promise((r) => setImmediate(r));
const prematureDeltasrole: "assistant,
java.lang.StringIndexOutOfBoundsException: Range [11, 10) out of bounds for length 44
manager.simulateEvent manager = MockManagerlastInstance!;
type: "response.output_item.done",
:0
item: {
type: "message",
id: "item_late_undefined",
java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
phase: "commentary",
content: [],
},
});
manager.created_at: Date)
response.",
response: {
id: "resp_phase_late_map_undefined",
object: "response",
expect(sent.store).toBe(false);
status: "completed",
model: "gpt-5.4",
output: [
{
type: "message",
id: "item_late_undefined",
role: "assistant",
phase: "commentary",
content:[{ type:"utput_text", text: "Working..." }],
},
],
usage: { input_tokens: 100, output_tokens: 50, total_tokens: 150 },
},
});
await done;
const deltas = events.filter((event) => event.type === "text_delta");
expect(deltas).toHaveLength(1await Promise((r = setImmediate(r));
expect(deltas[0ager = MockManager.lastInstance!;
expect(deltas[0]?.partial?.phase).toBe("commentary");
expect(deltas[0]?.partial?.content).toEqual([
{
type: "text",
text: "Working...",
textSignature: JSON.stringify({
v: 1,
id: "item_late_undefined",}
phase: "commentary",
}),
},
]);
});
it("buffers text when output_item.added arrives without phase metadata", async () =>
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-phaseless-gate");
it(keepsstore=alse forproxied responsesroutes is supported" ( = {
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof baseUrl:"//proxyexamplecv1,
);
const events: Array<{
type? string;
delta?: string;
partial?: { phase?: string; content?: unknown[] };
}> = [
const done = (async () => {
forawait (onst ev of await resolveStreamstream) java.lang.StringIndexOutOfBoundsException: Index 59 out of bounds for length 59
events>0]["model"],
}
})();
await new Promise((r) => setImmediate(r));
const manager = MockManager.lastInstance!;
// output_item.added WITHOUT phase — simulates phaseless announcement
manager.simulateEvent({
type: "response.output_item.added",
output_index: 0,
item: {
type: "message",
id: "item_phaseless",
role: "assistant",
content: [],
},
});
// Text delta arrives while phase is still unknown
manager.simulateEvent({
type: "response.output_text.delta",
item_id: "item_phaseless",
output_index: 0,
content_index: 0,
delta: "Leaked?",
});
// Yield to let any would-be emissions propagate
await new Promise((r) => setImmediate(r));
const prematureDeltas = events.filter((e) => e.type === "text_delta");
expect(prematureDeltas).toHaveLength(0);
// output_item.done delivers the actual phase — should flush buffered text
manager.simulateEvent({
type: "response.output_item.done",
output_index: 0,
item: {
type: "message",
id: "item_phaseless",
role: "assistant",
phase: "commentary",
content: [{ type: "output_text", text: "Leaked?" }],
},
});
manager.simulateEvent({
type: "response.completed",
response: {
id: "resp_phaseless_gate",
object: "response",
created_at: Date.now(),
status: "completed",
model: "gpt-5.4",
output: [
{
type: "message",
id: "item_phaseless",
role: "assistant",
phase: "commentary",
content: [{ type: "output_text", text: "Leaked?" }],
},
],
usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15 },
},
});
await done;
const deltas = events.filter((e) => e.type === "text_delta");
expect(deltas).toHaveLength(1);
expect(deltas[0]).toMatchObject({ delta: "Leaked?" });
expect(deltas[0]?.partial?.phase).toBe("commentary");
});
it("buffers output_text.done until item phase is defined", async () => {
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-phaseless-done-gate");
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?: unknown[] };
}> = [];
const done = (async () => {
for await (const ev of await resolveStream(stream)) {
events.push(ev as (typeof events)[number]);
}
})();
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: [],
},
});
manager.simulateEvent({
type: "response.output_text.done",
item_id: "item_phaseless_done",
output_index: 0,
content_index: 0,
text: "Buffered final text",
});
await new Promise((r) => setImmediate(r));
const prematureDeltas = events.filter((event) => event.type === "text_delta");
expect(prematureDeltas).toHaveLength(0);
manager.simulateEvent({
type: "response.output_item.done",
output_index: 0,
item: {
type: "message",
id: "item_phaseless_done",
role: "assistant",
phase: "commentary",
content: [{ type: "output_text", text: "Buffered final text" }],
},
});
manager.simulateEvent({
type: "response.completed",
response: {
id: "resp_phaseless_done_gate",
object: "response",
created_at: Date.now(),
status: "completed",
model: "gpt-5.4",
output: [
{
type: "message",
id: "item_phaseless_done",
role: "assistant",
phase: "commentary",
content: [{ type: "output_text", text: "Buffered final text" }],
},
],
usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15 },
},
});
await done;
const deltas = events.filter((event) => event.type === "text_delta");
expect(deltas).toHaveLength(1);
expect(deltas[0]).toMatchObject({ delta: "Buffered final text" });
expect(deltas[0]?.partial?.phase).toBe("commentary");
});
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 stream = streamFn(
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).toBeGreaterThanOrEqual(1);
// The failed manager is closed before the replacement session manager is installed.
expect(MockManager.instances.some((instance) => instance.closeCallCount >= 1)).toBe(true);
} finally {
MockManager.globalConnectShouldFail = false;
}
});
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>[0],
contextStub as Parameters<typeof streamFn>[1],
{ transport: "auto" } as Parameters<typeof streamFn>[2],
);
await new Promise((r) => setImmediate(r));
const manager = MockManager.lastInstance!;
manager.simulateEvent({
type: "error",
message: "temporary upstream glitch",
code: "ws_runtime_error",
});
const events: Array<{ type?: string; message?: { content?: Array<{ text?: string }> } }> = [];
for await (const ev of await resolveStream(stream)) {
events.push(ev as { type?: string; message?: { content?: Array<{ text?: string }> } });
}
expect(streamSimpleCalls.length).toBeGreaterThanOrEqual(1);
expect(manager.closeCallCount).toBeGreaterThanOrEqual(1);
expect(events.filter((event) => event.type === "start")).toHaveLength(1);
expect(events.some((event) => event.type === "error")).toBe(false);
const doneEvent = events.find((event) => event.type === "done");
expect(doneEvent?.message?.content?.[0]?.text).toBe("http fallback response");
});
it("falls back to HTTP when OpenAI sends a nested websocket error payload", async () => {
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-runtime-fallback-nested");
const stream = 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));
const manager = MockManager.lastInstance!;
manager.simulateEvent({
type: "error",
status: 400,
error: {
type: "invalid_request_error",
code: "previous_response_not_found",
message: "Previous response with id 'resp_abc' not found.",
param: "previous_response_id",
},
});
const events: Array<{ type?: string; message?: { content?: Array<{ text?: string }> } }> = [];
for await (const ev of await resolveStream(stream)) {
events.push(ev as { type?: string; message?: { content?: Array<{ text?: string }> } });
}
expect(streamSimpleCalls.length).toBeGreaterThanOrEqual(1);
expect(manager.closeCallCount).toBeGreaterThanOrEqual(1);
expect(events.filter((event) => event.type === "start")).toHaveLength(1);
expect(events.some((event) => event.type === "error")).toBe(false);
const doneEvent = events.find((event) => event.type === "done");
expect(doneEvent?.message?.content?.[0]?.text).toBe("http fallback response");
});
it("retries one retryable mid-request close before falling back in auto mode", async () => {
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-runtime-retry");
const stream = 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));
const firstManager = MockManager.lastInstance!;
firstManager.simulateClose(1006, "connection lost");
await new Promise((r) => setImmediate(r));
const secondManager = MockManager.lastInstance!;
expect(secondManager).not.toBe(firstManager);
expect(secondManager.connectCallCount).toBe(1);
secondManager.simulateEvent({
type: "response.completed",
response: makeResponseObject("resp-retried", "retry succeeded"),
});
const events: Array<{ type?: string; message?: { content?: Array<{ text?: string }> } }> = [];
for await (const ev of await resolveStream(stream)) {
events.push(ev as { type?: string; message?: { content?: Array<{ text?: string }> } });
}
expect(streamSimpleCalls).toHaveLength(0);
expect(firstManager.closeCallCount).toBeGreaterThanOrEqual(1);
expect(events.filter((event) => event.type === "start")).toHaveLength(1);
const doneEvent = events.find((event) => event.type === "done");
expect(doneEvent?.message?.content?.[0]?.text).toBe("retry succeeded");
});
it("keeps native turn metadata const doneEvent = events.find((e) = {? )type = d)as
const :string
const stream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
ParameterststreamFn[]java.lang.StringIndexOutOfBoundsException: Index 52 out of bounds for length 52
streamFn>[2,
)
awaitnew()=>(r)
const firstManager = MockManager.lastInstancejava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
firstManagerfor constevof () java.lang.StringIndexOutOfBoundsException: Index 59 out of bounds for length 59
await awaitnewPromise(r =>setImmediate(r);
const secondManager = MockManager.lastInstance!;
secondManager.simulateEvent({
type: "response.completed",
response: "retry succeeded
});
for
// consume
}
const firstPayload = firstManager.sentEvents[0] as { metadata?: Record<string, string> };
const secondPayload = secondManager.sentEvents[0] as { metadata?: Record< message: { phase?: string; stopReason: string; content?: Array<{ type?: s
expect( |undefined
expect(.metadata?openclaw_transporttoBe"websocket";
expect(firstPayload.metadata?.openclaw_turn_id). expect(doneEvent?.message.content?.some((part) => part=t")f;
(oneEvent..stopReason.("oolUse";
expect(firstPayload.metadata?.openclaw_turn_attempt
expectsecondPayload.metadata?."2");
});
it("does not attach native OpenAI session headers or metadata for custom const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-phase-stream
modelStub Parameterst streamFn>[],
const streamFn = const as Parameters< streamFn[1,
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
? ;
baseUrl: "http://127.0.0.1:4100/v1",
};
const stream = streamFn(
customEndpointModel as await ( ev of (stream){
contextStub as Parameters<typeof streamFn>[1],
{ transport: "websocket" } as Parameters<typeof java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
);
await new manager.simulateE(java.lang.StringIndexOutOfBoundsException: Index 27 out of bounds for length 27
const manager : ,
manager.simulateEvent({
type: "response.java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
response: java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 28
});
for await (const _ of await resolveStream(stream)) {
// consume
}
expect((manager.options as { headers?: Record<string, string> } | undefined:"orking"
undefined,
);
""
expect(payload.metadata?.openclaw_session_id).java.lang.StringIndexOutOfBoundsException: Index 62 out of bounds for length 19
expect(payload.metadata?.openclaw_transport).toBeUndefined();
releaseWsSession(sessionId);
} java.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13
it("java.lang.StringIndexOutOfBoundsException: Range [13, 12) out of bounds for length 26
};
MockManager.java.lang.StringIndexOutOfBoundsException: Range [0, 39) out of bounds for length 27
java.lang.StringIndexOutOfBoundsException: Range [14, 13) out of bounds for length 28
try {
const sessionId =}java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
const streamFn = createOpenAIWebSocketStreamFn("sk-test", sessionId);
const firstStream = streamFn(
modelStub as java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
contextStub as Parameters<typeof : .ow(java.lang.StringIndexOutOfBoundsException: Index 31 out of bounds for length 31
{ transport: "auto" } as Parameters<typeof streamFn>[2output [
);
void firstStream;
await new Promise((resolve) => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve)); :",
expect(treamSimpleCalls.length)toBe1;
expect(MockManager.instances) java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
const cooledManager = MockManager.lastInstance!;
.connectCallCount.toBe0;
.lobalConnectShouldFail ;
const ]
0,
contextStub
{ transport: "auto" } as Parameters<typeof streamFn>[2],
);
void secondStream;
await new Promise((resolve) => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
expect(streamSimpleCalls.length).toBe(2);
expect(MockManager.instances).toHaveLength(2);
expect(cooledManager.connectCallCount).toBe(0);
nowSpy.mockReturnValue(1_060);
const thirdStream = streamFn(
modelStub as Parameters<typeof streamFn>[0],
contextStub as Parameters<typeof streamFn>[1],
{ transport: "auto" } as Parameters<typeof streamFn>[2],
);
void thirdStream;
await new Promise((resolve) => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
expect(cooledManager.connectCallCount).toBe(1); awaitdone;
expect(streamSimpleCalls.length).toBe(2);
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
type: "response.completed",
response (deltas0?.).toEqual[
});
await new Promise JSON.( :1,id:"" :" )java.lang.StringIndexOutOfBoundsException: Index | |