@dsh-enhanced/coding-subscription-provider 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -30
- package/lib/adapter.d.ts +34 -3
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +288 -71
- package/lib/adapter.js.map +1 -1
- package/lib/codex-direct-auth.d.ts +34 -0
- package/lib/codex-direct-auth.d.ts.map +1 -0
- package/lib/codex-direct-auth.js +604 -0
- package/lib/codex-direct-auth.js.map +1 -0
- package/lib/codex-direct-responses.d.ts +50 -0
- package/lib/codex-direct-responses.d.ts.map +1 -0
- package/lib/codex-direct-responses.js +1585 -0
- package/lib/codex-direct-responses.js.map +1 -0
- package/lib/config.d.ts +16 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +34 -1
- package/lib/config.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +32 -4
- package/lib/index.js.map +1 -1
- package/lib/process.d.ts +6 -0
- package/lib/process.d.ts.map +1 -1
- package/lib/process.js +26 -4
- package/lib/process.js.map +1 -1
- package/lib/prompt.d.ts.map +1 -1
- package/lib/prompt.js +33 -2
- package/lib/prompt.js.map +1 -1
- package/lib/session-cwd.d.ts +24 -0
- package/lib/session-cwd.d.ts.map +1 -0
- package/lib/session-cwd.js +39 -0
- package/lib/session-cwd.js.map +1 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +14 -4
|
@@ -0,0 +1,1585 @@
|
|
|
1
|
+
import { CallId, offloadRequestImages, } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
const DEFAULT_MAX_REQUEST_BYTES = 32 * 1024 * 1024;
|
|
3
|
+
const DEFAULT_MAX_REQUEST_IMAGE_BYTES = 24 * 1024 * 1024;
|
|
4
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
5
|
+
const DEFAULT_MAX_SSE_EVENT_BYTES = 1024 * 1024;
|
|
6
|
+
const MAX_PROVIDER_ID_BYTES = 4 * 1024;
|
|
7
|
+
const MAX_CALL_ID_BYTES = 4 * 1024;
|
|
8
|
+
const MAX_TOOL_NAME_BYTES = 1024;
|
|
9
|
+
const MAX_JSON_DEPTH = 64;
|
|
10
|
+
export class CodexDirectResponsesError extends Error {
|
|
11
|
+
status;
|
|
12
|
+
constructor(message, cause, status) {
|
|
13
|
+
super(message, { cause });
|
|
14
|
+
this.name = 'CodexDirectResponsesError';
|
|
15
|
+
this.status = status;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Translate one DSH call to Codex's private Responses dialect and stream only
|
|
20
|
+
* provider-neutral chunks. It never reads credentials and never executes tools.
|
|
21
|
+
*/
|
|
22
|
+
export async function* runCodexDirectResponses(options, dependencies) {
|
|
23
|
+
const signal = combineSignals(options.signal, dependencies.signal);
|
|
24
|
+
throwIfAborted(signal);
|
|
25
|
+
const limits = resolveLimits(dependencies);
|
|
26
|
+
if (options.stop !== undefined && options.stop.length > 0) {
|
|
27
|
+
throw protocolError('Codex private Responses does not support stop sequences');
|
|
28
|
+
}
|
|
29
|
+
if (options.temperature !== undefined) {
|
|
30
|
+
throw protocolError('Codex private Responses does not support temperature');
|
|
31
|
+
}
|
|
32
|
+
if (options.maxTokens !== undefined) {
|
|
33
|
+
throw protocolError('Codex private Responses does not support max tokens');
|
|
34
|
+
}
|
|
35
|
+
const toolNames = validateToolSchemas(options);
|
|
36
|
+
const request = await buildRequest(options, dependencies, limits.maxRequestImageBytes, signal);
|
|
37
|
+
let serialized;
|
|
38
|
+
try {
|
|
39
|
+
serialized = JSON.stringify(request);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw protocolError('Codex private Responses request is not serializable');
|
|
43
|
+
}
|
|
44
|
+
if (Buffer.byteLength(serialized, 'utf8') > limits.maxRequestBytes) {
|
|
45
|
+
throw new CodexDirectResponsesError('Codex private Responses request exceeds the configured request limit', 'prompt-limit');
|
|
46
|
+
}
|
|
47
|
+
const response = await dependencies.request(serialized, signal);
|
|
48
|
+
if (signal.aborted) {
|
|
49
|
+
cancelBody(response.body);
|
|
50
|
+
throwIfAborted(signal);
|
|
51
|
+
}
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
cancelBody(response.body);
|
|
54
|
+
if (response.status === 401) {
|
|
55
|
+
throw new CodexDirectResponsesError('Codex private Responses rejected the ChatGPT subscription session', 'subscription-auth', 401);
|
|
56
|
+
}
|
|
57
|
+
throw new CodexDirectResponsesError(`Codex private Responses returned HTTP ${response.status}`, 'provider-http', response.status);
|
|
58
|
+
}
|
|
59
|
+
const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
|
|
60
|
+
if (!contentType.startsWith('text/event-stream')) {
|
|
61
|
+
cancelBody(response.body);
|
|
62
|
+
throw protocolError('Codex private Responses returned a non-SSE response');
|
|
63
|
+
}
|
|
64
|
+
const processor = new ResponseStreamProcessor(options, toolNames);
|
|
65
|
+
let preliminaryTerminal;
|
|
66
|
+
for await (const payload of parseSse(response, signal, limits.maxResponseBytes, limits.maxSseEventBytes)) {
|
|
67
|
+
if (payload === '[DONE]') {
|
|
68
|
+
if (preliminaryTerminal === undefined) {
|
|
69
|
+
throw protocolError('Codex private Responses ended before a terminal event');
|
|
70
|
+
}
|
|
71
|
+
for (const chunk of processor.accept(preliminaryTerminal.type, preliminaryTerminal.event))
|
|
72
|
+
yield chunk;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const event = requireRecord(payload, 'SSE event');
|
|
76
|
+
const type = requireBoundedString(event.type, 'event type', MAX_PROVIDER_ID_BYTES);
|
|
77
|
+
if (type === 'response.completed') {
|
|
78
|
+
for (const chunk of processor.accept(type, event))
|
|
79
|
+
yield chunk;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (type === 'response.incomplete' || type === 'response.failed') {
|
|
83
|
+
if (preliminaryTerminal !== undefined) {
|
|
84
|
+
throw protocolError('Codex private Responses repeated a preliminary terminal event');
|
|
85
|
+
}
|
|
86
|
+
preliminaryTerminal = { type, event };
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (preliminaryTerminal !== undefined && isOfficialPrivateNoopEvent(type))
|
|
90
|
+
continue;
|
|
91
|
+
if (preliminaryTerminal !== undefined) {
|
|
92
|
+
throw protocolError('Codex private Responses emitted semantic data after a preliminary terminal event');
|
|
93
|
+
}
|
|
94
|
+
for (const chunk of processor.accept(type, event))
|
|
95
|
+
yield chunk;
|
|
96
|
+
}
|
|
97
|
+
if (preliminaryTerminal !== undefined) {
|
|
98
|
+
for (const chunk of processor.accept(preliminaryTerminal.type, preliminaryTerminal.event))
|
|
99
|
+
yield chunk;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
throw protocolError('Codex private Responses stream ended without a terminal event');
|
|
103
|
+
}
|
|
104
|
+
async function buildRequest(options, dependencies, maxRequestImageBytes, signal) {
|
|
105
|
+
const input = await buildInput(options, dependencies.attachments, maxRequestImageBytes, signal);
|
|
106
|
+
const tools = options.tools?.map(tool => ({
|
|
107
|
+
type: 'function',
|
|
108
|
+
name: tool.name,
|
|
109
|
+
description: tool.description,
|
|
110
|
+
parameters: tool.parameters,
|
|
111
|
+
strict: false,
|
|
112
|
+
}));
|
|
113
|
+
return {
|
|
114
|
+
model: options.model,
|
|
115
|
+
instructions: options.system ?? '',
|
|
116
|
+
input,
|
|
117
|
+
...(tools === undefined || tools.length === 0 ? {} : { tools }),
|
|
118
|
+
tool_choice: 'auto',
|
|
119
|
+
parallel_tool_calls: true,
|
|
120
|
+
stream: true,
|
|
121
|
+
store: false,
|
|
122
|
+
include: ['reasoning.encrypted_content'],
|
|
123
|
+
reasoning: options.reasoningEffort === undefined
|
|
124
|
+
? null
|
|
125
|
+
: {
|
|
126
|
+
effort: String(options.reasoningEffort) === 'ultra' ? 'max' : String(options.reasoningEffort),
|
|
127
|
+
summary: 'auto',
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function buildInput(options, attachments, maxRequestImageBytes, signal) {
|
|
132
|
+
const messages = offloadRequestImages(options.messages, maxRequestImageBytes);
|
|
133
|
+
const input = [];
|
|
134
|
+
for (const message of messages) {
|
|
135
|
+
const replay = matchingReplay(message, options.provider, options.model);
|
|
136
|
+
if (replay !== undefined) {
|
|
137
|
+
input.push(...replay.output);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
await appendGenericMessage(input, message, attachments, signal);
|
|
141
|
+
}
|
|
142
|
+
return input;
|
|
143
|
+
}
|
|
144
|
+
function matchingReplay(message, provider, model) {
|
|
145
|
+
if (message.role !== 'assistant' || message.source.kind !== 'model')
|
|
146
|
+
return undefined;
|
|
147
|
+
// The durable source retains the user-facing model alias (`default`) while
|
|
148
|
+
// replay records the concrete model sent on the wire. The replay's own model
|
|
149
|
+
// identity plus exact durable-content projection is the safe comparison.
|
|
150
|
+
if (message.source.provider !== provider)
|
|
151
|
+
return undefined;
|
|
152
|
+
const envelope = message.source.replayState;
|
|
153
|
+
if (!isRecord(envelope))
|
|
154
|
+
return undefined;
|
|
155
|
+
const response = envelope.response;
|
|
156
|
+
if (!isRecord(response)
|
|
157
|
+
|| response.kind !== 'codex-private-responses'
|
|
158
|
+
|| (response.version !== 2 && response.version !== 3)
|
|
159
|
+
|| response.provider !== provider
|
|
160
|
+
|| response.model !== model
|
|
161
|
+
|| !isBoundedString(response.responseId, MAX_PROVIDER_ID_BYTES)
|
|
162
|
+
|| !Array.isArray(response.output)
|
|
163
|
+
|| !isLosslessJson(response.output)
|
|
164
|
+
|| !Array.isArray(response.blockOrder)
|
|
165
|
+
|| !isLosslessJson(response.blockOrder))
|
|
166
|
+
return undefined;
|
|
167
|
+
let projected;
|
|
168
|
+
try {
|
|
169
|
+
projected = projectNativeOutput(response.output, response.blockOrder);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
if (!contentEquals(projected.blocks, message.content))
|
|
175
|
+
return undefined;
|
|
176
|
+
return response;
|
|
177
|
+
}
|
|
178
|
+
async function appendGenericMessage(input, message, attachments, signal) {
|
|
179
|
+
if (message.role === 'assistant') {
|
|
180
|
+
let content = [];
|
|
181
|
+
const flush = () => {
|
|
182
|
+
if (content.length === 0)
|
|
183
|
+
return;
|
|
184
|
+
input.push({ role: 'assistant', content });
|
|
185
|
+
content = [];
|
|
186
|
+
};
|
|
187
|
+
for (const block of message.content) {
|
|
188
|
+
switch (block.type) {
|
|
189
|
+
case 'text':
|
|
190
|
+
case 'reasoning':
|
|
191
|
+
content.push({ type: 'input_text', text: block.text });
|
|
192
|
+
break;
|
|
193
|
+
case 'tool-call':
|
|
194
|
+
flush();
|
|
195
|
+
input.push({
|
|
196
|
+
type: 'function_call',
|
|
197
|
+
call_id: String(block.id),
|
|
198
|
+
name: block.name,
|
|
199
|
+
arguments: block.arguments,
|
|
200
|
+
});
|
|
201
|
+
break;
|
|
202
|
+
case 'image':
|
|
203
|
+
case 'tool-result':
|
|
204
|
+
throw protocolError(`Codex private Responses cannot serialize ${block.type} in an assistant message`);
|
|
205
|
+
default:
|
|
206
|
+
throw protocolError('Codex private Responses encountered an unknown assistant content block');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
flush();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const role = message.role === 'system' ? 'system' : 'user';
|
|
213
|
+
let content = [];
|
|
214
|
+
const flush = () => {
|
|
215
|
+
if (content.length === 0)
|
|
216
|
+
return;
|
|
217
|
+
input.push({ role, content });
|
|
218
|
+
content = [];
|
|
219
|
+
};
|
|
220
|
+
for (const block of message.content) {
|
|
221
|
+
switch (block.type) {
|
|
222
|
+
case 'text':
|
|
223
|
+
case 'reasoning':
|
|
224
|
+
content.push({ type: 'input_text', text: block.text });
|
|
225
|
+
break;
|
|
226
|
+
case 'image':
|
|
227
|
+
if (message.role === 'system') {
|
|
228
|
+
throw protocolError('Codex private Responses cannot serialize an image in a system message');
|
|
229
|
+
}
|
|
230
|
+
content.push(await inputImage(block.attachment, attachments, signal));
|
|
231
|
+
break;
|
|
232
|
+
case 'tool-result':
|
|
233
|
+
flush();
|
|
234
|
+
input.push({
|
|
235
|
+
type: 'function_call_output',
|
|
236
|
+
call_id: String(block.toolCallId),
|
|
237
|
+
output: await functionOutput(block.content, block.isError === true, attachments, signal),
|
|
238
|
+
});
|
|
239
|
+
break;
|
|
240
|
+
case 'tool-call':
|
|
241
|
+
throw protocolError(`Codex private Responses cannot serialize ${block.type} in a ${message.role} message`);
|
|
242
|
+
default:
|
|
243
|
+
throw protocolError('Codex private Responses encountered an unknown input content block');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
flush();
|
|
247
|
+
}
|
|
248
|
+
async function functionOutput(content, isError, attachments, signal) {
|
|
249
|
+
const parts = isError ? [{ type: 'input_text', text: '[tool_error]' }] : [];
|
|
250
|
+
for (const block of content) {
|
|
251
|
+
switch (block.type) {
|
|
252
|
+
case 'text':
|
|
253
|
+
case 'reasoning':
|
|
254
|
+
parts.push({ type: 'input_text', text: block.text });
|
|
255
|
+
break;
|
|
256
|
+
case 'image':
|
|
257
|
+
parts.push(await inputImage(block.attachment, attachments, signal));
|
|
258
|
+
break;
|
|
259
|
+
case 'tool-call':
|
|
260
|
+
case 'tool-result':
|
|
261
|
+
throw protocolError('Codex private Responses cannot nest a tool block inside a tool result');
|
|
262
|
+
default:
|
|
263
|
+
throw protocolError('Codex private Responses encountered an unknown tool-result content block');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (parts.length === 0)
|
|
267
|
+
return '';
|
|
268
|
+
if (parts.length === 1 && isRecord(parts[0]) && parts[0].type === 'input_text')
|
|
269
|
+
return String(parts[0].text);
|
|
270
|
+
return parts;
|
|
271
|
+
}
|
|
272
|
+
async function inputImage(ref, attachments, signal) {
|
|
273
|
+
if (attachments === undefined)
|
|
274
|
+
throw protocolError('Codex private Responses requires attachment storage for image input');
|
|
275
|
+
throwIfAborted(signal);
|
|
276
|
+
const stored = await attachments.readImage(ref, signal);
|
|
277
|
+
throwIfAborted(signal);
|
|
278
|
+
if (!(stored.data instanceof Uint8Array)
|
|
279
|
+
|| stored.data.byteLength !== ref.bytes
|
|
280
|
+
|| stored.ref === undefined
|
|
281
|
+
|| !sameImageRef(ref, stored.ref)) {
|
|
282
|
+
throw protocolError('Codex private Responses received an invalid stored image');
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
type: 'input_image',
|
|
286
|
+
detail: 'auto',
|
|
287
|
+
image_url: `data:${ref.mediaType};base64,${Buffer.from(stored.data).toString('base64')}`,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function sameImageRef(left, right) {
|
|
291
|
+
return left.attachmentId === right.attachmentId
|
|
292
|
+
&& left.mediaType === right.mediaType
|
|
293
|
+
&& left.bytes === right.bytes
|
|
294
|
+
&& left.width === right.width
|
|
295
|
+
&& left.height === right.height;
|
|
296
|
+
}
|
|
297
|
+
class ResponseStreamProcessor {
|
|
298
|
+
#items = new Map();
|
|
299
|
+
#providerItems = new Map();
|
|
300
|
+
#outputIndexes = new Map();
|
|
301
|
+
#doneItems = new Map();
|
|
302
|
+
#blocks = new Map();
|
|
303
|
+
#callIds = new Map();
|
|
304
|
+
#toolNames;
|
|
305
|
+
#options;
|
|
306
|
+
#nextIndex = 0;
|
|
307
|
+
#hasVisibleOutput = false;
|
|
308
|
+
#activeItemId;
|
|
309
|
+
#createdSeen = false;
|
|
310
|
+
#createdResponseId;
|
|
311
|
+
constructor(options, toolNames) {
|
|
312
|
+
this.#options = options;
|
|
313
|
+
this.#toolNames = toolNames;
|
|
314
|
+
}
|
|
315
|
+
accept(type, event) {
|
|
316
|
+
switch (type) {
|
|
317
|
+
case 'response.created':
|
|
318
|
+
return this.#created(event);
|
|
319
|
+
case 'response.queued':
|
|
320
|
+
case 'response.in_progress':
|
|
321
|
+
case 'response.output_text.annotation.added':
|
|
322
|
+
case 'codex.response.metadata':
|
|
323
|
+
case 'response.metadata':
|
|
324
|
+
case 'responsesapi.websocket_timing':
|
|
325
|
+
case 'response.custom_tool_call_input.done':
|
|
326
|
+
return [];
|
|
327
|
+
case 'response.output_item.added':
|
|
328
|
+
return this.#outputItemAdded(event);
|
|
329
|
+
case 'response.output_item.done':
|
|
330
|
+
return this.#outputItemDone(event);
|
|
331
|
+
case 'response.content_part.added':
|
|
332
|
+
if (!hasRichPartIdentity(event))
|
|
333
|
+
return [];
|
|
334
|
+
return this.#contentPart(event, false);
|
|
335
|
+
case 'response.content_part.done':
|
|
336
|
+
if (!hasRichPartIdentity(event))
|
|
337
|
+
return [];
|
|
338
|
+
return this.#contentPart(event, true);
|
|
339
|
+
case 'response.output_text.delta':
|
|
340
|
+
return this.#textDelta(event, 'output_text');
|
|
341
|
+
case 'response.output_text.done':
|
|
342
|
+
if (!hasRichPartIdentity(event))
|
|
343
|
+
return [];
|
|
344
|
+
return this.#textDone(event, 'output_text');
|
|
345
|
+
case 'response.refusal.delta':
|
|
346
|
+
return this.#textDelta(event, 'refusal');
|
|
347
|
+
case 'response.refusal.done':
|
|
348
|
+
return this.#textDone(event, 'refusal');
|
|
349
|
+
case 'response.reasoning_summary_part.added':
|
|
350
|
+
return this.#reasoningPart(event, false);
|
|
351
|
+
case 'response.reasoning_summary_part.done':
|
|
352
|
+
if (event.item_id === undefined && event.output_index === undefined)
|
|
353
|
+
return [];
|
|
354
|
+
return this.#reasoningPart(event, true);
|
|
355
|
+
case 'response.reasoning_summary_text.delta':
|
|
356
|
+
return this.#textDelta(event, 'summary_text');
|
|
357
|
+
case 'response.reasoning_summary_text.done':
|
|
358
|
+
return this.#textDone(event, 'summary_text');
|
|
359
|
+
case 'response.function_call_arguments.delta':
|
|
360
|
+
if (event.item_id === undefined && event.output_index === undefined)
|
|
361
|
+
return [];
|
|
362
|
+
return this.#functionDelta(event);
|
|
363
|
+
case 'response.function_call_arguments.done':
|
|
364
|
+
if (event.item_id === undefined && event.output_index === undefined)
|
|
365
|
+
return [];
|
|
366
|
+
return this.#functionDone(event);
|
|
367
|
+
case 'response.reasoning_text.delta':
|
|
368
|
+
return this.#rawReasoning(event, false);
|
|
369
|
+
case 'response.reasoning_text.done':
|
|
370
|
+
return this.#rawReasoning(event, true);
|
|
371
|
+
case 'response.completed':
|
|
372
|
+
case 'response.incomplete':
|
|
373
|
+
case 'response.failed':
|
|
374
|
+
return this.#terminal(type, event);
|
|
375
|
+
case 'error':
|
|
376
|
+
throw new CodexDirectResponsesError('Codex private Responses request failed', 'provider-failure');
|
|
377
|
+
default:
|
|
378
|
+
throw protocolError('Codex private Responses emitted an unsupported event type');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
#created(event) {
|
|
382
|
+
const response = requireRecord(event.response, 'created response');
|
|
383
|
+
const responseId = optionalBoundedString(response.id, 'created response id', MAX_PROVIDER_ID_BYTES);
|
|
384
|
+
if (this.#createdSeen) {
|
|
385
|
+
throw protocolError('Codex private Responses repeated its created event');
|
|
386
|
+
}
|
|
387
|
+
this.#createdSeen = true;
|
|
388
|
+
this.#createdResponseId = responseId;
|
|
389
|
+
return [];
|
|
390
|
+
}
|
|
391
|
+
#outputItemAdded(event) {
|
|
392
|
+
const item = requireRecord(event.item, 'output item');
|
|
393
|
+
if (!isLosslessJson(item))
|
|
394
|
+
throw protocolError('Codex private Responses output item is invalid');
|
|
395
|
+
const explicitIndex = optionalIndex(event.output_index, 'output index');
|
|
396
|
+
const itemType = requireBoundedString(item.type, 'output item type', MAX_PROVIDER_ID_BYTES);
|
|
397
|
+
validateOutputItem(item, 'streamed output item', true);
|
|
398
|
+
const active = this.#activeItemId === undefined ? undefined : this.#items.get(this.#activeItemId);
|
|
399
|
+
if (explicitIndex === undefined && active !== undefined && !(active.provisional && itemType === 'message')) {
|
|
400
|
+
throw protocolError('Codex private Responses overlapped active output items');
|
|
401
|
+
}
|
|
402
|
+
const outputIndex = explicitIndex ?? active?.outputIndex ?? this.#outputIndexes.size;
|
|
403
|
+
const itemState = this.#registerItem(item, outputIndex, itemType);
|
|
404
|
+
itemState.provisional = false;
|
|
405
|
+
if (this.#activeItemId === undefined)
|
|
406
|
+
this.#activeItemId = itemState.id;
|
|
407
|
+
const chunks = [];
|
|
408
|
+
this.#seedAddedItem(item, itemState, chunks);
|
|
409
|
+
return chunks;
|
|
410
|
+
}
|
|
411
|
+
#outputItemDone(event) {
|
|
412
|
+
const item = requireRecord(event.item, 'completed output item');
|
|
413
|
+
if (!isLosslessJson(item))
|
|
414
|
+
throw protocolError('Codex private Responses completed output item is invalid');
|
|
415
|
+
const explicitIndex = optionalIndex(event.output_index, 'output index');
|
|
416
|
+
const itemType = requireBoundedString(item.type, 'output item type', MAX_PROVIDER_ID_BYTES);
|
|
417
|
+
const providerId = optionalBoundedString(item.id, 'output item id', MAX_PROVIDER_ID_BYTES);
|
|
418
|
+
const providerKey = providerId === undefined ? undefined : this.#providerItems.get(providerId);
|
|
419
|
+
let outputIndex;
|
|
420
|
+
if (explicitIndex !== undefined) {
|
|
421
|
+
outputIndex = explicitIndex;
|
|
422
|
+
}
|
|
423
|
+
else if (providerKey !== undefined) {
|
|
424
|
+
const known = this.#items.get(providerKey);
|
|
425
|
+
if (known === undefined)
|
|
426
|
+
throw protocolError('Codex private Responses lost an output item');
|
|
427
|
+
outputIndex = known.outputIndex;
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
const candidates = this.#unfinishedItems(itemType);
|
|
431
|
+
if (candidates.length > 1) {
|
|
432
|
+
throw protocolError('Codex private Responses emitted an ambiguous identity-less completed item');
|
|
433
|
+
}
|
|
434
|
+
outputIndex = candidates[0]?.outputIndex ?? this.#outputIndexes.size;
|
|
435
|
+
}
|
|
436
|
+
if (outputIndex !== this.#doneItems.size) {
|
|
437
|
+
throw protocolError('Codex private Responses completed output items out of order');
|
|
438
|
+
}
|
|
439
|
+
const chunks = [];
|
|
440
|
+
const state = this.#finalizeItem(item, outputIndex, chunks, 'stream-item');
|
|
441
|
+
if (this.#activeItemId !== undefined) {
|
|
442
|
+
if (state.id !== this.#activeItemId) {
|
|
443
|
+
throw protocolError('Codex private Responses completed a different active output item');
|
|
444
|
+
}
|
|
445
|
+
this.#activeItemId = undefined;
|
|
446
|
+
}
|
|
447
|
+
if (state.doneItem !== undefined && !jsonEquals(state.doneItem, item)) {
|
|
448
|
+
throw protocolError('Codex private Responses changed an already completed output item');
|
|
449
|
+
}
|
|
450
|
+
state.doneItem = item;
|
|
451
|
+
this.#doneItems.set(outputIndex, item);
|
|
452
|
+
return chunks;
|
|
453
|
+
}
|
|
454
|
+
#seedAddedItem(item, state, chunks) {
|
|
455
|
+
switch (state.type) {
|
|
456
|
+
case 'message': {
|
|
457
|
+
if (item.content === undefined)
|
|
458
|
+
return;
|
|
459
|
+
if (!Array.isArray(item.content))
|
|
460
|
+
throw protocolError('Codex private Responses message content is invalid');
|
|
461
|
+
item.content.forEach((value, contentIndex) => {
|
|
462
|
+
const part = requireRecord(value, 'message content part');
|
|
463
|
+
if (part.type !== 'output_text' && part.type !== 'refusal') {
|
|
464
|
+
throw protocolError('Codex private Responses emitted an unsupported message content part');
|
|
465
|
+
}
|
|
466
|
+
const text = requireString(part.type === 'refusal' ? part.refusal : part.text, 'message content prefix');
|
|
467
|
+
const block = this.#ensureText(state.id, state.outputIndex, contentIndex, part.type, chunks, true, true);
|
|
468
|
+
this.#appendText(block, text, chunks);
|
|
469
|
+
});
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
case 'reasoning': {
|
|
473
|
+
if (item.summary === undefined)
|
|
474
|
+
return;
|
|
475
|
+
if (!Array.isArray(item.summary))
|
|
476
|
+
throw protocolError('Codex private Responses reasoning summary is invalid');
|
|
477
|
+
item.summary.forEach((value, summaryIndex) => {
|
|
478
|
+
const part = requireRecord(value, 'reasoning summary part');
|
|
479
|
+
if (part.type !== 'summary_text')
|
|
480
|
+
throw protocolError('Codex private Responses emitted an unsupported reasoning summary');
|
|
481
|
+
const block = this.#ensureText(state.id, state.outputIndex, summaryIndex, 'summary_text', chunks, true, true);
|
|
482
|
+
this.#appendText(block, requireString(part.text, 'reasoning summary prefix'), chunks);
|
|
483
|
+
});
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
case 'function_call':
|
|
487
|
+
this.#ensureTool(item, state, chunks);
|
|
488
|
+
return;
|
|
489
|
+
default:
|
|
490
|
+
throw protocolError('Codex private Responses emitted an unsupported output item');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
#resolveItem(event, expectedType, label, allowProvisionalMessage = false) {
|
|
494
|
+
const providerId = optionalBoundedString(event.item_id, `${label} item id`, MAX_PROVIDER_ID_BYTES);
|
|
495
|
+
const outputIndex = optionalIndex(event.output_index, `${label} output index`);
|
|
496
|
+
const providerKey = providerId === undefined ? undefined : this.#providerItems.get(providerId);
|
|
497
|
+
const indexedKey = outputIndex === undefined ? undefined : this.#outputIndexes.get(outputIndex);
|
|
498
|
+
if (providerKey !== undefined && indexedKey !== undefined && providerKey !== indexedKey) {
|
|
499
|
+
throw protocolError('Codex private Responses changed an output item identity');
|
|
500
|
+
}
|
|
501
|
+
let key = providerKey ?? indexedKey;
|
|
502
|
+
if (key === undefined && providerId === undefined && outputIndex === undefined) {
|
|
503
|
+
const candidates = this.#unfinishedItems(expectedType);
|
|
504
|
+
if (candidates.length > 1) {
|
|
505
|
+
throw protocolError('Codex private Responses emitted ambiguous identity-less item data');
|
|
506
|
+
}
|
|
507
|
+
key = candidates[0]?.id;
|
|
508
|
+
}
|
|
509
|
+
if (key === undefined && allowProvisionalMessage && expectedType === 'message'
|
|
510
|
+
&& providerId === undefined && outputIndex === undefined && this.#activeItemId === undefined) {
|
|
511
|
+
const provisional = this.#registerItem({ type: 'message', role: 'assistant' }, this.#outputIndexes.size, 'message');
|
|
512
|
+
provisional.provisional = true;
|
|
513
|
+
this.#activeItemId = provisional.id;
|
|
514
|
+
key = provisional.id;
|
|
515
|
+
}
|
|
516
|
+
const state = key === undefined ? undefined : this.#items.get(key);
|
|
517
|
+
if (state === undefined || state.type !== expectedType) {
|
|
518
|
+
throw protocolError(`Codex private Responses emitted ${label} for an unknown or incompatible output item`);
|
|
519
|
+
}
|
|
520
|
+
if (providerId !== undefined && state.providerId !== providerId) {
|
|
521
|
+
throw protocolError('Codex private Responses changed an output item identity');
|
|
522
|
+
}
|
|
523
|
+
if (outputIndex !== undefined && state.outputIndex !== outputIndex) {
|
|
524
|
+
throw protocolError('Codex private Responses changed an output item index');
|
|
525
|
+
}
|
|
526
|
+
return state;
|
|
527
|
+
}
|
|
528
|
+
#unfinishedItems(expectedType) {
|
|
529
|
+
return [...this.#items.values()].filter(state => state.type === expectedType && state.doneItem === undefined);
|
|
530
|
+
}
|
|
531
|
+
#contentPart(event, done) {
|
|
532
|
+
const part = requireRecord(event.part, 'content part');
|
|
533
|
+
const nativeType = part.type;
|
|
534
|
+
if (nativeType !== 'output_text' && nativeType !== 'refusal') {
|
|
535
|
+
throw protocolError('Codex private Responses emitted an unsupported message content part');
|
|
536
|
+
}
|
|
537
|
+
const item = this.#resolveItem(event, 'message', 'message content');
|
|
538
|
+
const contentIndex = requireIndex(event.content_index, 'content index');
|
|
539
|
+
const chunks = [];
|
|
540
|
+
const state = this.#ensureText(item.id, item.outputIndex, contentIndex, nativeType, chunks, true, !done);
|
|
541
|
+
const value = requireString(nativeType === 'refusal' ? part.refusal : part.text, 'content part text');
|
|
542
|
+
if (done)
|
|
543
|
+
this.#finishText(state, value, chunks);
|
|
544
|
+
else
|
|
545
|
+
this.#appendText(state, value, chunks);
|
|
546
|
+
return chunks;
|
|
547
|
+
}
|
|
548
|
+
#reasoningPart(event, done) {
|
|
549
|
+
const part = event.part === undefined
|
|
550
|
+
? { type: 'summary_text', text: '' }
|
|
551
|
+
: requireRecord(event.part, 'reasoning summary part');
|
|
552
|
+
if (part.type !== 'summary_text') {
|
|
553
|
+
throw protocolError('Codex private Responses emitted an unsupported reasoning summary part');
|
|
554
|
+
}
|
|
555
|
+
const item = this.#resolveItem(event, 'reasoning', 'reasoning summary');
|
|
556
|
+
const summaryIndex = requireIndex(event.summary_index, 'summary index');
|
|
557
|
+
const chunks = [];
|
|
558
|
+
const state = this.#ensureText(item.id, item.outputIndex, summaryIndex, 'summary_text', chunks, true, !done);
|
|
559
|
+
const value = requireString(part.text, 'reasoning summary part text');
|
|
560
|
+
if (done)
|
|
561
|
+
this.#finishText(state, value, chunks);
|
|
562
|
+
else
|
|
563
|
+
this.#appendText(state, value, chunks);
|
|
564
|
+
return chunks;
|
|
565
|
+
}
|
|
566
|
+
#textDelta(event, nativeType) {
|
|
567
|
+
const expectedType = nativeType === 'summary_text' ? 'reasoning' : 'message';
|
|
568
|
+
const item = this.#resolveItem(event, expectedType, nativeType === 'summary_text' ? 'reasoning summary' : 'text', nativeType === 'output_text');
|
|
569
|
+
const rawPartIndex = nativeType === 'summary_text' ? event.summary_index : event.content_index;
|
|
570
|
+
const partIndex = rawPartIndex === undefined && nativeType !== 'summary_text'
|
|
571
|
+
? 0
|
|
572
|
+
: requireIndex(rawPartIndex, nativeType === 'summary_text' ? 'summary index' : 'content index');
|
|
573
|
+
const delta = requireString(event.delta, 'text delta');
|
|
574
|
+
const chunks = [];
|
|
575
|
+
const state = this.#ensureText(item.id, item.outputIndex, partIndex, nativeType, chunks, true);
|
|
576
|
+
if (state.closed)
|
|
577
|
+
throw protocolError('Codex private Responses emitted text after closing its content part');
|
|
578
|
+
state.text += delta;
|
|
579
|
+
if (delta.length > 0) {
|
|
580
|
+
chunks.push(state.kind === 'text'
|
|
581
|
+
? { type: 'text-delta', index: state.index, text: delta }
|
|
582
|
+
: { type: 'reasoning-delta', index: state.index, text: delta });
|
|
583
|
+
}
|
|
584
|
+
return chunks;
|
|
585
|
+
}
|
|
586
|
+
#textDone(event, nativeType) {
|
|
587
|
+
const expectedType = nativeType === 'summary_text' ? 'reasoning' : 'message';
|
|
588
|
+
const item = this.#resolveItem(event, expectedType, nativeType === 'summary_text' ? 'reasoning summary' : 'text');
|
|
589
|
+
const rawPartIndex = nativeType === 'summary_text' ? event.summary_index : event.content_index;
|
|
590
|
+
const partIndex = rawPartIndex === undefined && nativeType !== 'summary_text'
|
|
591
|
+
? 0
|
|
592
|
+
: requireIndex(rawPartIndex, nativeType === 'summary_text' ? 'summary index' : 'content index');
|
|
593
|
+
const value = nativeType === 'refusal' ? event.refusal : event.text;
|
|
594
|
+
const text = requireString(value, 'completed text');
|
|
595
|
+
const chunks = [];
|
|
596
|
+
const state = this.#ensureText(item.id, item.outputIndex, partIndex, nativeType, chunks, true);
|
|
597
|
+
this.#finishText(state, text, chunks);
|
|
598
|
+
return chunks;
|
|
599
|
+
}
|
|
600
|
+
#functionDelta(event) {
|
|
601
|
+
const item = this.#resolveItem(event, 'function_call', 'function arguments');
|
|
602
|
+
const delta = requireString(event.delta, 'function arguments delta');
|
|
603
|
+
const state = this.#toolForItem(item.id, item.outputIndex);
|
|
604
|
+
if (state.closed)
|
|
605
|
+
throw protocolError('Codex private Responses emitted arguments after closing a function call');
|
|
606
|
+
state.arguments += delta;
|
|
607
|
+
return delta.length === 0
|
|
608
|
+
? []
|
|
609
|
+
: [{ type: 'tool-call-delta', index: state.index, id: CallId(state.callId), argumentsDelta: delta }];
|
|
610
|
+
}
|
|
611
|
+
#functionDone(event) {
|
|
612
|
+
const item = this.#resolveItem(event, 'function_call', 'function arguments');
|
|
613
|
+
const state = this.#toolForItem(item.id, item.outputIndex);
|
|
614
|
+
const finalArguments = requireString(event.arguments, 'function arguments');
|
|
615
|
+
if (state.argumentsDone || state.closed) {
|
|
616
|
+
throw protocolError('Codex private Responses repeated completed function arguments');
|
|
617
|
+
}
|
|
618
|
+
const chunks = [];
|
|
619
|
+
this.#reconcileToolArguments(state, finalArguments, chunks);
|
|
620
|
+
state.argumentsDone = true;
|
|
621
|
+
return chunks;
|
|
622
|
+
}
|
|
623
|
+
#rawReasoning(event, done) {
|
|
624
|
+
this.#resolveItem(event, 'reasoning', 'raw reasoning');
|
|
625
|
+
requireIndex(event.content_index, 'reasoning content index');
|
|
626
|
+
requireString(done ? event.text : event.delta, done ? 'completed raw reasoning' : 'raw reasoning delta');
|
|
627
|
+
return [];
|
|
628
|
+
}
|
|
629
|
+
#terminal(type, event) {
|
|
630
|
+
const response = requireRecord(event.response, 'terminal response');
|
|
631
|
+
const expectedStatus = type.slice('response.'.length);
|
|
632
|
+
if (response.status !== undefined && response.status !== expectedStatus) {
|
|
633
|
+
throw protocolError('Codex private Responses terminal status does not match its event');
|
|
634
|
+
}
|
|
635
|
+
if (type === 'response.failed') {
|
|
636
|
+
const code = isRecord(response.error) && typeof response.error.code === 'string'
|
|
637
|
+
&& Buffer.byteLength(response.error.code, 'utf8') <= MAX_PROVIDER_ID_BYTES
|
|
638
|
+
? response.error.code
|
|
639
|
+
: undefined;
|
|
640
|
+
if (code === 'context_length_exceeded') {
|
|
641
|
+
throw new CodexDirectResponsesError('Codex private Responses exceeded the model context window', 'context-window');
|
|
642
|
+
}
|
|
643
|
+
if (code === 'insufficient_quota') {
|
|
644
|
+
throw new CodexDirectResponsesError('Codex private Responses subscription quota is unavailable', 'quota');
|
|
645
|
+
}
|
|
646
|
+
throw new CodexDirectResponsesError('Codex private Responses request failed', 'provider-failure');
|
|
647
|
+
}
|
|
648
|
+
const responseId = requireBoundedString(response.id, 'response id', MAX_PROVIDER_ID_BYTES);
|
|
649
|
+
if (this.#createdResponseId !== undefined && this.#createdResponseId !== responseId) {
|
|
650
|
+
throw protocolError('Codex private Responses changed its response id');
|
|
651
|
+
}
|
|
652
|
+
if (response.end_turn !== undefined
|
|
653
|
+
&& response.end_turn !== null
|
|
654
|
+
&& typeof response.end_turn !== 'boolean') {
|
|
655
|
+
throw protocolError('Codex private Responses end-turn flag is invalid');
|
|
656
|
+
}
|
|
657
|
+
if (response.end_turn === false) {
|
|
658
|
+
throw protocolError('Codex private Responses requested an unsupported follow-up turn');
|
|
659
|
+
}
|
|
660
|
+
let incomplete = false;
|
|
661
|
+
if (type === 'response.incomplete') {
|
|
662
|
+
const details = requireRecord(response.incomplete_details, 'incomplete response details');
|
|
663
|
+
if (details.reason === 'content_filter') {
|
|
664
|
+
throw new CodexDirectResponsesError('Codex private Responses output was filtered', 'content-filter');
|
|
665
|
+
}
|
|
666
|
+
if (details.reason !== 'max_output_tokens') {
|
|
667
|
+
throw protocolError('Codex private Responses ended in an unsupported incomplete state');
|
|
668
|
+
}
|
|
669
|
+
incomplete = true;
|
|
670
|
+
}
|
|
671
|
+
let terminalOutput;
|
|
672
|
+
if (response.output !== undefined && response.output !== null) {
|
|
673
|
+
if (!Array.isArray(response.output) || !isLosslessJson(response.output)) {
|
|
674
|
+
throw protocolError('Codex private Responses terminal output is invalid');
|
|
675
|
+
}
|
|
676
|
+
terminalOutput = response.output;
|
|
677
|
+
}
|
|
678
|
+
const chunks = [];
|
|
679
|
+
let output;
|
|
680
|
+
const observed = [...this.#doneItems.entries()]
|
|
681
|
+
.sort(([left], [right]) => left - right)
|
|
682
|
+
.map(([, item]) => item);
|
|
683
|
+
if (terminalOutput?.length === 0 && observed.length > 0)
|
|
684
|
+
terminalOutput = undefined;
|
|
685
|
+
const active = this.#activeItemId === undefined ? undefined : this.#items.get(this.#activeItemId);
|
|
686
|
+
if (terminalOutput !== undefined && observed.length === 0 && active?.provisional === true) {
|
|
687
|
+
if (terminalOutput.length !== 1) {
|
|
688
|
+
throw protocolError('Codex private Responses terminal output conflicts with provisional text');
|
|
689
|
+
}
|
|
690
|
+
const placeholder = requireRecord(terminalOutput[0], 'terminal provisional message');
|
|
691
|
+
if (placeholder.type !== 'message' || placeholder.role !== 'assistant'
|
|
692
|
+
|| !Array.isArray(placeholder.content) || placeholder.content.length !== 0) {
|
|
693
|
+
throw protocolError('Codex private Responses terminal output conflicts with provisional text');
|
|
694
|
+
}
|
|
695
|
+
const text = this.#blocks.get(`text:${active.id}:0`);
|
|
696
|
+
if (active.type !== 'message' || text?.kind !== 'text' || text.nativeType !== 'output_text') {
|
|
697
|
+
throw protocolError('Codex private Responses completed an invalid provisional message');
|
|
698
|
+
}
|
|
699
|
+
terminalOutput = [{
|
|
700
|
+
...placeholder,
|
|
701
|
+
content: [{ type: 'output_text', text: text.text }],
|
|
702
|
+
}];
|
|
703
|
+
}
|
|
704
|
+
if (terminalOutput !== undefined && observed.length > 0) {
|
|
705
|
+
if (terminalOutput.length !== observed.length
|
|
706
|
+
|| terminalOutput.some((item, index) => !jsonEquals(item, observed[index]))) {
|
|
707
|
+
throw protocolError('Codex private Responses terminal output disagrees with completed items');
|
|
708
|
+
}
|
|
709
|
+
output = terminalOutput;
|
|
710
|
+
}
|
|
711
|
+
else if (terminalOutput !== undefined) {
|
|
712
|
+
output = terminalOutput;
|
|
713
|
+
}
|
|
714
|
+
else if (observed.length > 0) {
|
|
715
|
+
output = observed;
|
|
716
|
+
}
|
|
717
|
+
else if (this.#activeItemId !== undefined && incomplete) {
|
|
718
|
+
output = [];
|
|
719
|
+
}
|
|
720
|
+
else if (this.#activeItemId !== undefined) {
|
|
721
|
+
const active = this.#items.get(this.#activeItemId);
|
|
722
|
+
if (active === undefined || !active.provisional || active.type !== 'message') {
|
|
723
|
+
throw protocolError('Codex private Responses completed with an active output item');
|
|
724
|
+
}
|
|
725
|
+
const state = this.#blocks.get(`text:${active.id}:0`);
|
|
726
|
+
if (state?.kind !== 'text' || state.nativeType !== 'output_text') {
|
|
727
|
+
throw protocolError('Codex private Responses completed an invalid provisional message');
|
|
728
|
+
}
|
|
729
|
+
output = [{
|
|
730
|
+
type: 'message', role: 'assistant',
|
|
731
|
+
content: [{ type: 'output_text', text: state.text }],
|
|
732
|
+
}];
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
output = [];
|
|
736
|
+
}
|
|
737
|
+
if (!isLosslessJson(output))
|
|
738
|
+
throw protocolError('Codex private Responses terminal output is invalid');
|
|
739
|
+
if (this.#activeItemId !== undefined && !incomplete) {
|
|
740
|
+
const active = this.#items.get(this.#activeItemId);
|
|
741
|
+
if (active === undefined || (!active.provisional && observed.length === 0)) {
|
|
742
|
+
throw protocolError('Codex private Responses completed with an active output item');
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
output.forEach((value, outputIndex) => {
|
|
746
|
+
const item = requireRecord(value, 'terminal output item');
|
|
747
|
+
const state = this.#finalizeItem(item, outputIndex, chunks, incomplete ? 'max-tokens-terminal' : 'completed-terminal');
|
|
748
|
+
if (state.doneItem !== undefined && !jsonEquals(state.doneItem, item)) {
|
|
749
|
+
throw protocolError('Codex private Responses terminal output disagrees with its completed item');
|
|
750
|
+
}
|
|
751
|
+
state.doneItem = item;
|
|
752
|
+
this.#doneItems.set(outputIndex, item);
|
|
753
|
+
});
|
|
754
|
+
if (incomplete) {
|
|
755
|
+
for (const state of this.#blocks.values()) {
|
|
756
|
+
if (state.closed)
|
|
757
|
+
continue;
|
|
758
|
+
if (state.kind === 'tool-call')
|
|
759
|
+
this.#finishTool(state, state.arguments, chunks);
|
|
760
|
+
else
|
|
761
|
+
this.#finishText(state, state.text, chunks);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
if (this.#items.size !== output.length) {
|
|
766
|
+
throw protocolError('Codex private Responses terminal output omitted a streamed item');
|
|
767
|
+
}
|
|
768
|
+
for (const state of this.#items.values()) {
|
|
769
|
+
if (state.doneItem === undefined)
|
|
770
|
+
throw protocolError('Codex private Responses terminal output omitted a streamed item');
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
this.#activeItemId = undefined;
|
|
774
|
+
const blockOrder = [...this.#blocks.values()]
|
|
775
|
+
.sort(compareBlockOrder)
|
|
776
|
+
.map(state => state.key);
|
|
777
|
+
if (!incomplete)
|
|
778
|
+
projectNativeOutput(output, blockOrder);
|
|
779
|
+
const usage = parseUsage(response.usage);
|
|
780
|
+
if (!incomplete && !this.#hasVisibleOutput) {
|
|
781
|
+
throw new CodexDirectResponsesError('Codex private Responses returned no visible output', 'empty-response');
|
|
782
|
+
}
|
|
783
|
+
if (usage !== undefined)
|
|
784
|
+
chunks.push({ type: 'usage', usage });
|
|
785
|
+
if (incomplete) {
|
|
786
|
+
chunks.push({ type: 'finish', reason: { kind: 'max-tokens' } });
|
|
787
|
+
return chunks;
|
|
788
|
+
}
|
|
789
|
+
const replay = {
|
|
790
|
+
kind: 'codex-private-responses',
|
|
791
|
+
version: 3,
|
|
792
|
+
provider: this.#options.provider,
|
|
793
|
+
model: this.#options.model,
|
|
794
|
+
responseId,
|
|
795
|
+
output,
|
|
796
|
+
blockOrder,
|
|
797
|
+
};
|
|
798
|
+
chunks.push({
|
|
799
|
+
type: 'finish',
|
|
800
|
+
reason: this.#callIds.size > 0 ? { kind: 'tool-calls' } : { kind: 'stop' },
|
|
801
|
+
replayState: { response: replay },
|
|
802
|
+
});
|
|
803
|
+
return chunks;
|
|
804
|
+
}
|
|
805
|
+
#finalizeItem(item, outputIndex, chunks, context) {
|
|
806
|
+
const type = requireBoundedString(item.type, 'output item type', MAX_PROVIDER_ID_BYTES);
|
|
807
|
+
validateOutputItem(item, 'completed output item');
|
|
808
|
+
if (context === 'completed-terminal' && item.status !== undefined && item.status !== 'completed') {
|
|
809
|
+
throw protocolError('Codex private Responses completed with an incomplete output item');
|
|
810
|
+
}
|
|
811
|
+
const itemState = this.#registerItem(item, outputIndex, type);
|
|
812
|
+
itemState.provisional = false;
|
|
813
|
+
const itemId = itemState.id;
|
|
814
|
+
switch (type) {
|
|
815
|
+
case 'message': {
|
|
816
|
+
if (!Array.isArray(item.content))
|
|
817
|
+
throw protocolError('Codex private Responses message content is invalid');
|
|
818
|
+
this.#validateTerminalParts(itemState, item.content, 'message');
|
|
819
|
+
item.content.forEach((value, contentIndex) => {
|
|
820
|
+
const part = requireRecord(value, 'message content part');
|
|
821
|
+
if (part.type !== 'output_text' && part.type !== 'refusal') {
|
|
822
|
+
throw protocolError('Codex private Responses returned an unsupported message content part');
|
|
823
|
+
}
|
|
824
|
+
const state = this.#ensureText(itemId, outputIndex, contentIndex, part.type, chunks, false);
|
|
825
|
+
const text = requireString(part.type === 'refusal' ? part.refusal : part.text, 'message text');
|
|
826
|
+
this.#finishText(state, text, chunks);
|
|
827
|
+
});
|
|
828
|
+
return itemState;
|
|
829
|
+
}
|
|
830
|
+
case 'reasoning': {
|
|
831
|
+
if (item.encrypted_content !== undefined && item.encrypted_content !== null) {
|
|
832
|
+
requireString(item.encrypted_content, 'encrypted reasoning');
|
|
833
|
+
}
|
|
834
|
+
if (item.summary === undefined) {
|
|
835
|
+
if (itemState.streamedParts) {
|
|
836
|
+
throw protocolError('Codex private Responses terminal reasoning omitted streamed summary parts');
|
|
837
|
+
}
|
|
838
|
+
return itemState;
|
|
839
|
+
}
|
|
840
|
+
if (!Array.isArray(item.summary))
|
|
841
|
+
throw protocolError('Codex private Responses reasoning summary is invalid');
|
|
842
|
+
this.#validateTerminalParts(itemState, item.summary, 'reasoning');
|
|
843
|
+
item.summary.forEach((value, summaryIndex) => {
|
|
844
|
+
const part = requireRecord(value, 'reasoning summary');
|
|
845
|
+
if (part.type !== 'summary_text')
|
|
846
|
+
throw protocolError('Codex private Responses returned an unsupported reasoning summary');
|
|
847
|
+
const state = this.#ensureText(itemId, outputIndex, summaryIndex, 'summary_text', chunks, false);
|
|
848
|
+
this.#finishText(state, requireString(part.text, 'reasoning summary text'), chunks);
|
|
849
|
+
});
|
|
850
|
+
return itemState;
|
|
851
|
+
}
|
|
852
|
+
case 'function_call': {
|
|
853
|
+
const argumentsValue = requireString(item.arguments, 'function arguments');
|
|
854
|
+
const state = this.#ensureTool(item, itemState, chunks);
|
|
855
|
+
if (context === 'max-tokens-terminal') {
|
|
856
|
+
this.#finishTool(state, argumentsValue, chunks);
|
|
857
|
+
return itemState;
|
|
858
|
+
}
|
|
859
|
+
if (item.status !== undefined && item.status !== 'completed') {
|
|
860
|
+
throw protocolError('Codex private Responses returned an incomplete function call');
|
|
861
|
+
}
|
|
862
|
+
if (context === 'stream-item') {
|
|
863
|
+
this.#reconcileToolArguments(state, argumentsValue, chunks);
|
|
864
|
+
state.argumentsDone = true;
|
|
865
|
+
return itemState;
|
|
866
|
+
}
|
|
867
|
+
this.#finishTool(state, argumentsValue, chunks);
|
|
868
|
+
return itemState;
|
|
869
|
+
}
|
|
870
|
+
default:
|
|
871
|
+
throw protocolError('Codex private Responses returned an unsupported output item');
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
#registerItem(item, outputIndex, type) {
|
|
875
|
+
const providerId = optionalBoundedString(item.id, 'output item id', MAX_PROVIDER_ID_BYTES);
|
|
876
|
+
const indexedKey = this.#outputIndexes.get(outputIndex);
|
|
877
|
+
const providerKey = providerId === undefined ? undefined : this.#providerItems.get(providerId);
|
|
878
|
+
if (indexedKey !== undefined && providerKey !== undefined && indexedKey !== providerKey) {
|
|
879
|
+
throw protocolError('Codex private Responses changed an output item identity');
|
|
880
|
+
}
|
|
881
|
+
const existingKey = indexedKey ?? providerKey;
|
|
882
|
+
const existing = existingKey === undefined ? undefined : this.#items.get(existingKey);
|
|
883
|
+
if (existing !== undefined) {
|
|
884
|
+
if (existing.outputIndex !== outputIndex || existing.type !== type) {
|
|
885
|
+
throw protocolError('Codex private Responses changed an output item identity');
|
|
886
|
+
}
|
|
887
|
+
if (providerId !== undefined) {
|
|
888
|
+
if (existing.providerId !== undefined && existing.providerId !== providerId) {
|
|
889
|
+
throw protocolError('Codex private Responses changed an output item identity');
|
|
890
|
+
}
|
|
891
|
+
existing.providerId = providerId;
|
|
892
|
+
this.#providerItems.set(providerId, existing.id);
|
|
893
|
+
}
|
|
894
|
+
return existing;
|
|
895
|
+
}
|
|
896
|
+
if (outputIndex !== this.#outputIndexes.size) {
|
|
897
|
+
throw protocolError('Codex private Responses registered output items out of order');
|
|
898
|
+
}
|
|
899
|
+
const itemId = `@output:${outputIndex}`;
|
|
900
|
+
if (this.#items.has(itemId))
|
|
901
|
+
throw protocolError('Codex private Responses repeated an output item id');
|
|
902
|
+
const state = {
|
|
903
|
+
id: itemId,
|
|
904
|
+
...(providerId === undefined ? {} : { providerId }),
|
|
905
|
+
outputIndex,
|
|
906
|
+
type,
|
|
907
|
+
parts: new Map(),
|
|
908
|
+
addedParts: new Set(),
|
|
909
|
+
streamedParts: false,
|
|
910
|
+
provisional: false,
|
|
911
|
+
};
|
|
912
|
+
this.#items.set(itemId, state);
|
|
913
|
+
if (providerId !== undefined)
|
|
914
|
+
this.#providerItems.set(providerId, itemId);
|
|
915
|
+
this.#outputIndexes.set(outputIndex, itemId);
|
|
916
|
+
return state;
|
|
917
|
+
}
|
|
918
|
+
#ensureText(itemId, outputIndex, partIndex, nativeType, chunks, fromStream, added = false) {
|
|
919
|
+
const kind = nativeType === 'summary_text' ? 'reasoning' : 'text';
|
|
920
|
+
const item = this.#items.get(itemId);
|
|
921
|
+
const expectedItemType = kind === 'reasoning' ? 'reasoning' : 'message';
|
|
922
|
+
if (item === undefined || item.type !== expectedItemType || item.outputIndex !== outputIndex) {
|
|
923
|
+
throw protocolError('Codex private Responses emitted content for an unknown or incompatible output item');
|
|
924
|
+
}
|
|
925
|
+
if (fromStream) {
|
|
926
|
+
const registeredType = item.parts.get(partIndex);
|
|
927
|
+
if (registeredType !== undefined && registeredType !== nativeType) {
|
|
928
|
+
throw protocolError('Codex private Responses changed a content part type');
|
|
929
|
+
}
|
|
930
|
+
if (added && item.addedParts.has(partIndex)) {
|
|
931
|
+
throw protocolError('Codex private Responses repeated a content part index');
|
|
932
|
+
}
|
|
933
|
+
if (registeredType === undefined && partIndex !== item.parts.size) {
|
|
934
|
+
throw protocolError('Codex private Responses registered content parts out of order');
|
|
935
|
+
}
|
|
936
|
+
item.parts.set(partIndex, nativeType);
|
|
937
|
+
if (added)
|
|
938
|
+
item.addedParts.add(partIndex);
|
|
939
|
+
item.streamedParts = true;
|
|
940
|
+
}
|
|
941
|
+
const key = `${kind}:${itemId}:${partIndex}`;
|
|
942
|
+
const existing = this.#blocks.get(key);
|
|
943
|
+
if (existing !== undefined) {
|
|
944
|
+
if (existing.kind === 'tool-call' || existing.kind !== kind || existing.nativeType !== nativeType) {
|
|
945
|
+
throw protocolError('Codex private Responses changed a content part type');
|
|
946
|
+
}
|
|
947
|
+
return existing;
|
|
948
|
+
}
|
|
949
|
+
if (fromStream && outputIndex !== this.#outputIndexes.size - 1) {
|
|
950
|
+
throw protocolError('Codex private Responses registered visible content out of order');
|
|
951
|
+
}
|
|
952
|
+
if (!fromStream && [...this.#blocks.values()].some(block => block.outputIndex > outputIndex)) {
|
|
953
|
+
throw protocolError('Codex private Responses synthesized visible content out of order');
|
|
954
|
+
}
|
|
955
|
+
const state = {
|
|
956
|
+
key,
|
|
957
|
+
kind,
|
|
958
|
+
index: this.#nextIndex,
|
|
959
|
+
itemId,
|
|
960
|
+
outputIndex,
|
|
961
|
+
partIndex,
|
|
962
|
+
nativeType,
|
|
963
|
+
text: '',
|
|
964
|
+
closed: false,
|
|
965
|
+
};
|
|
966
|
+
this.#nextIndex += 1;
|
|
967
|
+
this.#blocks.set(key, state);
|
|
968
|
+
chunks.push({ type: 'block-start', index: state.index, blockType: kind });
|
|
969
|
+
return state;
|
|
970
|
+
}
|
|
971
|
+
#validateTerminalParts(item, values, kind) {
|
|
972
|
+
if (!item.streamedParts)
|
|
973
|
+
return;
|
|
974
|
+
if (values.length !== item.parts.size) {
|
|
975
|
+
throw protocolError(`Codex private Responses terminal ${kind} parts do not cover streamed part indexes`);
|
|
976
|
+
}
|
|
977
|
+
values.forEach((value, partIndex) => {
|
|
978
|
+
const part = requireRecord(value, `${kind} content part`);
|
|
979
|
+
const nativeType = kind === 'reasoning' ? 'summary_text' : part.type;
|
|
980
|
+
if (item.parts.get(partIndex) !== nativeType) {
|
|
981
|
+
throw protocolError(`Codex private Responses terminal ${kind} changed a streamed part type`);
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
#finishText(state, finalText, chunks) {
|
|
986
|
+
if (state.closed) {
|
|
987
|
+
if (state.text !== finalText)
|
|
988
|
+
throw protocolError('Codex private Responses changed completed text');
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
if (state.text.length === 0 && finalText.length > 0) {
|
|
992
|
+
chunks.push(state.kind === 'text'
|
|
993
|
+
? { type: 'text-delta', index: state.index, text: finalText }
|
|
994
|
+
: { type: 'reasoning-delta', index: state.index, text: finalText });
|
|
995
|
+
state.text = finalText;
|
|
996
|
+
}
|
|
997
|
+
else if (state.text !== finalText) {
|
|
998
|
+
throw protocolError('Codex private Responses text deltas disagree with completed text');
|
|
999
|
+
}
|
|
1000
|
+
state.closed = true;
|
|
1001
|
+
if (state.text.length > 0)
|
|
1002
|
+
this.#hasVisibleOutput = true;
|
|
1003
|
+
chunks.push({
|
|
1004
|
+
type: 'block-end',
|
|
1005
|
+
index: state.index,
|
|
1006
|
+
block: state.kind === 'text'
|
|
1007
|
+
? { type: 'text', text: state.text }
|
|
1008
|
+
: { type: 'reasoning', text: state.text },
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
#appendText(state, value, chunks) {
|
|
1012
|
+
if (state.closed)
|
|
1013
|
+
throw protocolError('Codex private Responses emitted text after closing its content part');
|
|
1014
|
+
state.text += value;
|
|
1015
|
+
if (value.length === 0)
|
|
1016
|
+
return;
|
|
1017
|
+
chunks.push(state.kind === 'text'
|
|
1018
|
+
? { type: 'text-delta', index: state.index, text: value }
|
|
1019
|
+
: { type: 'reasoning-delta', index: state.index, text: value });
|
|
1020
|
+
}
|
|
1021
|
+
#ensureTool(item, itemState, chunks) {
|
|
1022
|
+
const itemId = itemState.id;
|
|
1023
|
+
const outputIndex = itemState.outputIndex;
|
|
1024
|
+
const callId = requireBoundedString(item.call_id, 'function call id', MAX_CALL_ID_BYTES);
|
|
1025
|
+
const name = requireBoundedString(item.name, 'function name', MAX_TOOL_NAME_BYTES);
|
|
1026
|
+
if (!this.#toolNames.has(name))
|
|
1027
|
+
throw protocolError('Codex private Responses requested an unknown tool');
|
|
1028
|
+
const key = `tool:${itemId}`;
|
|
1029
|
+
const existing = this.#blocks.get(key);
|
|
1030
|
+
if (existing !== undefined) {
|
|
1031
|
+
if (existing.kind !== 'tool-call' || existing.callId !== callId || existing.name !== name) {
|
|
1032
|
+
throw protocolError('Codex private Responses changed a function call identity');
|
|
1033
|
+
}
|
|
1034
|
+
return existing;
|
|
1035
|
+
}
|
|
1036
|
+
const duplicate = this.#callIds.get(callId);
|
|
1037
|
+
if (duplicate !== undefined && duplicate !== itemId) {
|
|
1038
|
+
throw protocolError('Codex private Responses repeated a function call id');
|
|
1039
|
+
}
|
|
1040
|
+
const initialArguments = item.arguments === undefined ? '' : requireString(item.arguments, 'function arguments');
|
|
1041
|
+
const state = {
|
|
1042
|
+
key,
|
|
1043
|
+
kind: 'tool-call',
|
|
1044
|
+
index: this.#nextIndex,
|
|
1045
|
+
itemId,
|
|
1046
|
+
outputIndex,
|
|
1047
|
+
callId,
|
|
1048
|
+
name,
|
|
1049
|
+
arguments: initialArguments,
|
|
1050
|
+
argumentsDone: false,
|
|
1051
|
+
closed: false,
|
|
1052
|
+
};
|
|
1053
|
+
this.#nextIndex += 1;
|
|
1054
|
+
this.#hasVisibleOutput = true;
|
|
1055
|
+
this.#blocks.set(key, state);
|
|
1056
|
+
this.#callIds.set(callId, itemState.id);
|
|
1057
|
+
chunks.push({ type: 'block-start', index: state.index, blockType: 'tool-call' }, {
|
|
1058
|
+
type: 'tool-call-delta',
|
|
1059
|
+
index: state.index,
|
|
1060
|
+
id: CallId(callId),
|
|
1061
|
+
name,
|
|
1062
|
+
argumentsDelta: initialArguments,
|
|
1063
|
+
});
|
|
1064
|
+
return state;
|
|
1065
|
+
}
|
|
1066
|
+
#toolForItem(itemId, outputIndex) {
|
|
1067
|
+
const state = this.#blocks.get(`tool:${itemId}`);
|
|
1068
|
+
if (state?.kind !== 'tool-call' || state.outputIndex !== outputIndex) {
|
|
1069
|
+
throw protocolError('Codex private Responses emitted function arguments before the function item');
|
|
1070
|
+
}
|
|
1071
|
+
return state;
|
|
1072
|
+
}
|
|
1073
|
+
#finishTool(state, finalArguments, chunks) {
|
|
1074
|
+
if (state.closed) {
|
|
1075
|
+
if (state.arguments !== finalArguments)
|
|
1076
|
+
throw protocolError('Codex private Responses changed completed function arguments');
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
this.#reconcileToolArguments(state, finalArguments, chunks);
|
|
1080
|
+
state.argumentsDone = true;
|
|
1081
|
+
state.closed = true;
|
|
1082
|
+
chunks.push({
|
|
1083
|
+
type: 'block-end',
|
|
1084
|
+
index: state.index,
|
|
1085
|
+
block: {
|
|
1086
|
+
type: 'tool-call',
|
|
1087
|
+
id: CallId(state.callId),
|
|
1088
|
+
name: state.name,
|
|
1089
|
+
arguments: state.arguments,
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
#reconcileToolArguments(state, finalArguments, chunks) {
|
|
1094
|
+
if (state.arguments.length === 0 && finalArguments.length > 0) {
|
|
1095
|
+
chunks.push({
|
|
1096
|
+
type: 'tool-call-delta',
|
|
1097
|
+
index: state.index,
|
|
1098
|
+
id: CallId(state.callId),
|
|
1099
|
+
argumentsDelta: finalArguments,
|
|
1100
|
+
});
|
|
1101
|
+
state.arguments = finalArguments;
|
|
1102
|
+
}
|
|
1103
|
+
else if (state.arguments !== finalArguments) {
|
|
1104
|
+
throw protocolError('Codex private Responses argument deltas disagree with completed arguments');
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function compareBlockOrder(left, right) {
|
|
1109
|
+
return left.outputIndex - right.outputIndex || blockPartIndex(left) - blockPartIndex(right);
|
|
1110
|
+
}
|
|
1111
|
+
function blockPartIndex(state) {
|
|
1112
|
+
return state.kind === 'tool-call' ? 0 : state.partIndex;
|
|
1113
|
+
}
|
|
1114
|
+
function validateOutputItem(item, label, allowInProgress = false) {
|
|
1115
|
+
const type = requireBoundedString(item.type, `${label} type`, MAX_PROVIDER_ID_BYTES);
|
|
1116
|
+
if (type !== 'message' && type !== 'reasoning' && type !== 'function_call') {
|
|
1117
|
+
throw protocolError(`Codex private Responses ${label} type is unsupported`);
|
|
1118
|
+
}
|
|
1119
|
+
if (item.status !== undefined
|
|
1120
|
+
&& item.status !== 'completed'
|
|
1121
|
+
&& item.status !== 'incomplete'
|
|
1122
|
+
&& !(allowInProgress && item.status === 'in_progress')) {
|
|
1123
|
+
throw protocolError(`Codex private Responses ${label} status is unsupported`);
|
|
1124
|
+
}
|
|
1125
|
+
if (type === 'message' && item.role !== 'assistant') {
|
|
1126
|
+
throw protocolError(`Codex private Responses ${label} role is unsupported`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
function projectNativeOutput(output, blockOrder) {
|
|
1130
|
+
const blocksByKey = new Map();
|
|
1131
|
+
const legacyAliases = new Map();
|
|
1132
|
+
let hiddenReasoning = false;
|
|
1133
|
+
const callIds = new Set();
|
|
1134
|
+
for (const [outputIndex, value] of output.entries()) {
|
|
1135
|
+
const item = requireRecord(value, 'replay output item');
|
|
1136
|
+
validateOutputItem(item, 'replay output item');
|
|
1137
|
+
const providerId = optionalBoundedString(item.id, 'replay output item id', MAX_PROVIDER_ID_BYTES);
|
|
1138
|
+
const itemId = `@output:${outputIndex}`;
|
|
1139
|
+
switch (item.type) {
|
|
1140
|
+
case 'message': {
|
|
1141
|
+
if (!Array.isArray(item.content))
|
|
1142
|
+
throw protocolError('Replay message content is invalid');
|
|
1143
|
+
for (const [contentIndex, value] of item.content.entries()) {
|
|
1144
|
+
const part = requireRecord(value, 'replay message content');
|
|
1145
|
+
if (part.type === 'output_text') {
|
|
1146
|
+
blocksByKey.set(`text:${itemId}:${contentIndex}`, {
|
|
1147
|
+
type: 'text', text: requireString(part.text, 'replay output text'),
|
|
1148
|
+
});
|
|
1149
|
+
if (providerId !== undefined) {
|
|
1150
|
+
legacyAliases.set(`text:${providerId}:${contentIndex}`, `text:${itemId}:${contentIndex}`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
else if (part.type === 'refusal') {
|
|
1154
|
+
blocksByKey.set(`text:${itemId}:${contentIndex}`, {
|
|
1155
|
+
type: 'text', text: requireString(part.refusal, 'replay refusal'),
|
|
1156
|
+
});
|
|
1157
|
+
if (providerId !== undefined) {
|
|
1158
|
+
legacyAliases.set(`text:${providerId}:${contentIndex}`, `text:${itemId}:${contentIndex}`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
throw protocolError('Replay message content type is unsupported');
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
break;
|
|
1166
|
+
}
|
|
1167
|
+
case 'reasoning': {
|
|
1168
|
+
if (item.encrypted_content !== undefined && item.encrypted_content !== null) {
|
|
1169
|
+
requireString(item.encrypted_content, 'replay encrypted reasoning');
|
|
1170
|
+
hiddenReasoning = true;
|
|
1171
|
+
}
|
|
1172
|
+
if (item.summary === undefined)
|
|
1173
|
+
break;
|
|
1174
|
+
if (!Array.isArray(item.summary))
|
|
1175
|
+
throw protocolError('Replay reasoning summary is invalid');
|
|
1176
|
+
for (const [summaryIndex, value] of item.summary.entries()) {
|
|
1177
|
+
const part = requireRecord(value, 'replay reasoning summary');
|
|
1178
|
+
if (part.type !== 'summary_text')
|
|
1179
|
+
throw protocolError('Replay reasoning summary type is unsupported');
|
|
1180
|
+
blocksByKey.set(`reasoning:${itemId}:${summaryIndex}`, {
|
|
1181
|
+
type: 'reasoning', text: requireString(part.text, 'replay reasoning summary text'),
|
|
1182
|
+
});
|
|
1183
|
+
if (providerId !== undefined) {
|
|
1184
|
+
legacyAliases.set(`reasoning:${providerId}:${summaryIndex}`, `reasoning:${itemId}:${summaryIndex}`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
break;
|
|
1188
|
+
}
|
|
1189
|
+
case 'function_call': {
|
|
1190
|
+
const callId = requireBoundedString(item.call_id, 'replay function call id', MAX_CALL_ID_BYTES);
|
|
1191
|
+
if (callIds.has(callId))
|
|
1192
|
+
throw protocolError('Replay repeated a function call id');
|
|
1193
|
+
callIds.add(callId);
|
|
1194
|
+
blocksByKey.set(`tool:${itemId}`, {
|
|
1195
|
+
type: 'tool-call',
|
|
1196
|
+
id: CallId(callId),
|
|
1197
|
+
name: requireBoundedString(item.name, 'replay function name', MAX_TOOL_NAME_BYTES),
|
|
1198
|
+
arguments: requireString(item.arguments, 'replay function arguments'),
|
|
1199
|
+
});
|
|
1200
|
+
if (providerId !== undefined)
|
|
1201
|
+
legacyAliases.set(`tool:${providerId}`, `tool:${itemId}`);
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
default:
|
|
1205
|
+
throw protocolError('Replay output item type is unsupported');
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if (blockOrder.length !== blocksByKey.size)
|
|
1209
|
+
throw protocolError('Replay block order is incomplete');
|
|
1210
|
+
const seen = new Set();
|
|
1211
|
+
const blocks = blockOrder.map(value => {
|
|
1212
|
+
const suppliedKey = requireBoundedString(value, 'replay block key', MAX_PROVIDER_ID_BYTES * 2);
|
|
1213
|
+
const key = legacyAliases.get(suppliedKey) ?? suppliedKey;
|
|
1214
|
+
if (seen.has(key))
|
|
1215
|
+
throw protocolError('Replay block order contains a duplicate key');
|
|
1216
|
+
seen.add(key);
|
|
1217
|
+
const block = blocksByKey.get(key);
|
|
1218
|
+
if (block === undefined)
|
|
1219
|
+
throw protocolError('Replay block order references an unknown key');
|
|
1220
|
+
return block;
|
|
1221
|
+
});
|
|
1222
|
+
return { blocks, hiddenReasoning };
|
|
1223
|
+
}
|
|
1224
|
+
function contentEquals(left, right) {
|
|
1225
|
+
if (left.length !== right.length)
|
|
1226
|
+
return false;
|
|
1227
|
+
return left.every((block, index) => {
|
|
1228
|
+
const candidate = right[index];
|
|
1229
|
+
if (candidate === undefined || candidate.type !== block.type)
|
|
1230
|
+
return false;
|
|
1231
|
+
switch (block.type) {
|
|
1232
|
+
case 'text':
|
|
1233
|
+
case 'reasoning':
|
|
1234
|
+
return candidate.type === block.type && candidate.text === block.text;
|
|
1235
|
+
case 'tool-call':
|
|
1236
|
+
return candidate.type === 'tool-call'
|
|
1237
|
+
&& candidate.id === block.id
|
|
1238
|
+
&& candidate.name === block.name
|
|
1239
|
+
&& candidate.arguments === block.arguments;
|
|
1240
|
+
case 'image':
|
|
1241
|
+
return candidate.type === 'image' && sameImageRef(candidate.attachment, block.attachment);
|
|
1242
|
+
case 'tool-result':
|
|
1243
|
+
return candidate.type === 'tool-result'
|
|
1244
|
+
&& candidate.toolCallId === block.toolCallId
|
|
1245
|
+
&& candidate.isError === block.isError
|
|
1246
|
+
&& contentEquals(candidate.content, block.content);
|
|
1247
|
+
default:
|
|
1248
|
+
return false;
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
async function* parseSse(response, signal, maxResponseBytes, maxEventBytes) {
|
|
1253
|
+
const reader = response.body?.getReader();
|
|
1254
|
+
if (reader === undefined)
|
|
1255
|
+
throw protocolError('Codex private Responses returned an empty SSE body');
|
|
1256
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
1257
|
+
let buffer = '';
|
|
1258
|
+
let dataLines = [];
|
|
1259
|
+
let eventBytes = 0;
|
|
1260
|
+
let totalBytes = 0;
|
|
1261
|
+
let naturalEof = false;
|
|
1262
|
+
const dispatch = () => {
|
|
1263
|
+
if (dataLines.length === 0)
|
|
1264
|
+
return undefined;
|
|
1265
|
+
const data = dataLines.join('\n');
|
|
1266
|
+
dataLines = [];
|
|
1267
|
+
eventBytes = 0;
|
|
1268
|
+
if (data === '[DONE]')
|
|
1269
|
+
return '[DONE]';
|
|
1270
|
+
try {
|
|
1271
|
+
return JSON.parse(data);
|
|
1272
|
+
}
|
|
1273
|
+
catch {
|
|
1274
|
+
throw protocolError('Codex private Responses emitted invalid SSE JSON');
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
const consumeLine = (rawLine) => {
|
|
1278
|
+
const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
|
|
1279
|
+
if (line === '')
|
|
1280
|
+
return dispatch();
|
|
1281
|
+
if (line.startsWith(':'))
|
|
1282
|
+
return undefined;
|
|
1283
|
+
const separator = line.indexOf(':');
|
|
1284
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
1285
|
+
let value = separator === -1 ? '' : line.slice(separator + 1);
|
|
1286
|
+
if (value.startsWith(' '))
|
|
1287
|
+
value = value.slice(1);
|
|
1288
|
+
if (field !== 'data')
|
|
1289
|
+
return undefined;
|
|
1290
|
+
eventBytes += Buffer.byteLength(value, 'utf8') + (dataLines.length === 0 ? 0 : 1);
|
|
1291
|
+
if (eventBytes > maxEventBytes)
|
|
1292
|
+
throw protocolError('Codex private Responses SSE event exceeded its safety limit');
|
|
1293
|
+
dataLines.push(value);
|
|
1294
|
+
return undefined;
|
|
1295
|
+
};
|
|
1296
|
+
try {
|
|
1297
|
+
while (true) {
|
|
1298
|
+
throwIfAborted(signal);
|
|
1299
|
+
const part = await waitForAbort(reader.read(), signal);
|
|
1300
|
+
if (part.done)
|
|
1301
|
+
break;
|
|
1302
|
+
totalBytes += part.value.byteLength;
|
|
1303
|
+
if (totalBytes > maxResponseBytes)
|
|
1304
|
+
throw protocolError('Codex private Responses stream exceeded its safety limit');
|
|
1305
|
+
try {
|
|
1306
|
+
buffer += decoder.decode(part.value, { stream: true });
|
|
1307
|
+
}
|
|
1308
|
+
catch {
|
|
1309
|
+
throw protocolError('Codex private Responses SSE was not valid UTF-8');
|
|
1310
|
+
}
|
|
1311
|
+
const payloads = [];
|
|
1312
|
+
let doneParsed = false;
|
|
1313
|
+
let newline = buffer.indexOf('\n');
|
|
1314
|
+
while (newline !== -1) {
|
|
1315
|
+
const rawLine = buffer.slice(0, newline);
|
|
1316
|
+
const normalizedLine = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
|
|
1317
|
+
if (doneParsed && normalizedLine !== '') {
|
|
1318
|
+
throw protocolError('Codex private Responses emitted data after DONE');
|
|
1319
|
+
}
|
|
1320
|
+
const payload = consumeLine(rawLine);
|
|
1321
|
+
buffer = buffer.slice(newline + 1);
|
|
1322
|
+
if (payload !== undefined)
|
|
1323
|
+
payloads.push(payload);
|
|
1324
|
+
if (payload === '[DONE]')
|
|
1325
|
+
doneParsed = true;
|
|
1326
|
+
newline = buffer.indexOf('\n');
|
|
1327
|
+
}
|
|
1328
|
+
const doneIndex = payloads.indexOf('[DONE]');
|
|
1329
|
+
if (doneIndex !== -1) {
|
|
1330
|
+
if (payloads.lastIndexOf('[DONE]') !== doneIndex) {
|
|
1331
|
+
throw protocolError('Codex private Responses emitted a duplicate DONE marker');
|
|
1332
|
+
}
|
|
1333
|
+
if (doneIndex !== payloads.length - 1) {
|
|
1334
|
+
throw protocolError('Codex private Responses emitted data after DONE');
|
|
1335
|
+
}
|
|
1336
|
+
if (buffer.length > 0 || dataLines.length > 0) {
|
|
1337
|
+
throw protocolError('Codex private Responses emitted data after DONE');
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
const terminalIndexes = payloads.flatMap((payload, index) => {
|
|
1341
|
+
if (!isRecord(payload) || !isTerminalEvent(payload.type))
|
|
1342
|
+
return [];
|
|
1343
|
+
return [index];
|
|
1344
|
+
});
|
|
1345
|
+
if (terminalIndexes.length > 1) {
|
|
1346
|
+
throw protocolError('Codex private Responses emitted a duplicate terminal event');
|
|
1347
|
+
}
|
|
1348
|
+
const terminalIndex = terminalIndexes[0];
|
|
1349
|
+
if (terminalIndex !== undefined) {
|
|
1350
|
+
const trailing = payloads.slice(terminalIndex + 1);
|
|
1351
|
+
if (trailing.length > 1 || (trailing.length === 1 && trailing[0] !== '[DONE]')) {
|
|
1352
|
+
throw protocolError('Codex private Responses emitted data after its terminal event');
|
|
1353
|
+
}
|
|
1354
|
+
if (buffer.length > 0 || dataLines.length > 0) {
|
|
1355
|
+
throw protocolError('Codex private Responses emitted data after its terminal event');
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
for (const payload of payloads)
|
|
1359
|
+
yield payload;
|
|
1360
|
+
if (Buffer.byteLength(buffer, 'utf8') + eventBytes > maxEventBytes) {
|
|
1361
|
+
throw protocolError('Codex private Responses SSE line exceeded its safety limit');
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
try {
|
|
1365
|
+
buffer += decoder.decode();
|
|
1366
|
+
}
|
|
1367
|
+
catch {
|
|
1368
|
+
throw protocolError('Codex private Responses SSE ended with invalid UTF-8');
|
|
1369
|
+
}
|
|
1370
|
+
if (buffer.length > 0 || dataLines.length > 0) {
|
|
1371
|
+
throw protocolError('Codex private Responses SSE ended with an unterminated event');
|
|
1372
|
+
}
|
|
1373
|
+
naturalEof = true;
|
|
1374
|
+
}
|
|
1375
|
+
finally {
|
|
1376
|
+
if (!naturalEof)
|
|
1377
|
+
cancelReader(reader);
|
|
1378
|
+
try {
|
|
1379
|
+
reader.releaseLock();
|
|
1380
|
+
}
|
|
1381
|
+
catch {
|
|
1382
|
+
// Cancellation is best effort; prompt completion and the original failure win.
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
function parseUsage(value) {
|
|
1387
|
+
if (value === undefined || value === null)
|
|
1388
|
+
return undefined;
|
|
1389
|
+
const usage = requireRecord(value, 'usage');
|
|
1390
|
+
const inputTokens = requireTokenCount(usage.input_tokens, 'input token count');
|
|
1391
|
+
const outputTokens = requireTokenCount(usage.output_tokens, 'output token count');
|
|
1392
|
+
const inputDetails = usage.input_tokens_details === undefined || usage.input_tokens_details === null
|
|
1393
|
+
? {}
|
|
1394
|
+
: requireRecord(usage.input_tokens_details, 'input token details');
|
|
1395
|
+
const outputDetails = usage.output_tokens_details === undefined || usage.output_tokens_details === null
|
|
1396
|
+
? {}
|
|
1397
|
+
: requireRecord(usage.output_tokens_details, 'output token details');
|
|
1398
|
+
const cacheReadTokens = optionalTokenCount(inputDetails.cached_tokens, 'cache read token count') ?? 0;
|
|
1399
|
+
const cacheWriteTokens = optionalTokenCount(inputDetails.cache_write_tokens, 'cache write token count') ?? 0;
|
|
1400
|
+
const reasoningTokens = optionalTokenCount(outputDetails.reasoning_tokens, 'reasoning token count');
|
|
1401
|
+
if (cacheReadTokens + cacheWriteTokens > inputTokens) {
|
|
1402
|
+
throw protocolError('Codex private Responses usage cache tokens exceed input tokens');
|
|
1403
|
+
}
|
|
1404
|
+
if (reasoningTokens !== undefined && reasoningTokens > outputTokens) {
|
|
1405
|
+
throw protocolError('Codex private Responses reasoning tokens exceed output tokens');
|
|
1406
|
+
}
|
|
1407
|
+
if (usage.total_tokens !== undefined
|
|
1408
|
+
&& requireTokenCount(usage.total_tokens, 'total token count') !== inputTokens + outputTokens) {
|
|
1409
|
+
throw protocolError('Codex private Responses total token count is inconsistent');
|
|
1410
|
+
}
|
|
1411
|
+
return {
|
|
1412
|
+
inputTokens: inputTokens - cacheReadTokens - cacheWriteTokens,
|
|
1413
|
+
outputTokens,
|
|
1414
|
+
...(cacheReadTokens === 0 ? {} : { cacheReadTokens }),
|
|
1415
|
+
...(cacheWriteTokens === 0 ? {} : { cacheWriteTokens }),
|
|
1416
|
+
...(reasoningTokens === undefined ? {} : { reasoningTokens }),
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function validateToolSchemas(options) {
|
|
1420
|
+
const names = new Set();
|
|
1421
|
+
for (const tool of options.tools ?? []) {
|
|
1422
|
+
const name = requireBoundedString(tool.name, 'tool name', MAX_TOOL_NAME_BYTES);
|
|
1423
|
+
if (names.has(name))
|
|
1424
|
+
throw protocolError('Codex private Responses received duplicate tool schemas');
|
|
1425
|
+
names.add(name);
|
|
1426
|
+
if (!isLosslessJson(tool.parameters))
|
|
1427
|
+
throw protocolError('Codex private Responses received invalid tool parameters');
|
|
1428
|
+
}
|
|
1429
|
+
return names;
|
|
1430
|
+
}
|
|
1431
|
+
function resolveLimits(dependencies) {
|
|
1432
|
+
return {
|
|
1433
|
+
maxRequestBytes: positiveLimit(dependencies.maxRequestBytes, DEFAULT_MAX_REQUEST_BYTES, 'request'),
|
|
1434
|
+
maxRequestImageBytes: positiveLimit(dependencies.maxRequestImageBytes, DEFAULT_MAX_REQUEST_IMAGE_BYTES, 'image request'),
|
|
1435
|
+
maxResponseBytes: positiveLimit(dependencies.maxResponseBytes, DEFAULT_MAX_RESPONSE_BYTES, 'response'),
|
|
1436
|
+
maxSseEventBytes: positiveLimit(dependencies.maxSseEventBytes, DEFAULT_MAX_SSE_EVENT_BYTES, 'SSE event'),
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function positiveLimit(value, fallback, label) {
|
|
1440
|
+
const resolved = value ?? fallback;
|
|
1441
|
+
if (!Number.isSafeInteger(resolved) || resolved <= 0)
|
|
1442
|
+
throw protocolError(`Codex private Responses ${label} limit is invalid`);
|
|
1443
|
+
return resolved;
|
|
1444
|
+
}
|
|
1445
|
+
function requireIndex(value, label) {
|
|
1446
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
1447
|
+
throw protocolError(`Codex private Responses ${label} is invalid`);
|
|
1448
|
+
return value;
|
|
1449
|
+
}
|
|
1450
|
+
function optionalIndex(value, label) {
|
|
1451
|
+
return value === undefined ? undefined : requireIndex(value, label);
|
|
1452
|
+
}
|
|
1453
|
+
function optionalBoundedString(value, label, maxBytes) {
|
|
1454
|
+
return value === undefined ? undefined : requireBoundedString(value, label, maxBytes);
|
|
1455
|
+
}
|
|
1456
|
+
function hasRichPartIdentity(event) {
|
|
1457
|
+
return event.item_id !== undefined || event.output_index !== undefined || event.content_index !== undefined;
|
|
1458
|
+
}
|
|
1459
|
+
function requireTokenCount(value, label) {
|
|
1460
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
1461
|
+
throw protocolError(`Codex private Responses ${label} is invalid`);
|
|
1462
|
+
return value;
|
|
1463
|
+
}
|
|
1464
|
+
function optionalTokenCount(value, label) {
|
|
1465
|
+
return value === undefined ? undefined : requireTokenCount(value, label);
|
|
1466
|
+
}
|
|
1467
|
+
function requireString(value, label) {
|
|
1468
|
+
if (typeof value !== 'string')
|
|
1469
|
+
throw protocolError(`Codex private Responses ${label} is invalid`);
|
|
1470
|
+
return value;
|
|
1471
|
+
}
|
|
1472
|
+
function requireBoundedString(value, label, maxBytes) {
|
|
1473
|
+
if (!isBoundedString(value, maxBytes))
|
|
1474
|
+
throw protocolError(`Codex private Responses ${label} is invalid`);
|
|
1475
|
+
return value;
|
|
1476
|
+
}
|
|
1477
|
+
function isBoundedString(value, maxBytes) {
|
|
1478
|
+
return typeof value === 'string' && value.length > 0 && Buffer.byteLength(value, 'utf8') <= maxBytes;
|
|
1479
|
+
}
|
|
1480
|
+
function requireRecord(value, label) {
|
|
1481
|
+
if (!isRecord(value))
|
|
1482
|
+
throw protocolError(`Codex private Responses ${label} is invalid`);
|
|
1483
|
+
return value;
|
|
1484
|
+
}
|
|
1485
|
+
function isRecord(value) {
|
|
1486
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1487
|
+
}
|
|
1488
|
+
function isLosslessJson(value, depth = 0, seen = new Set()) {
|
|
1489
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
1490
|
+
return true;
|
|
1491
|
+
if (typeof value === 'number')
|
|
1492
|
+
return Number.isFinite(value);
|
|
1493
|
+
if (depth >= MAX_JSON_DEPTH || typeof value !== 'object')
|
|
1494
|
+
return false;
|
|
1495
|
+
if (seen.has(value))
|
|
1496
|
+
return false;
|
|
1497
|
+
seen.add(value);
|
|
1498
|
+
const valid = Array.isArray(value)
|
|
1499
|
+
? value.every(item => isLosslessJson(item, depth + 1, seen))
|
|
1500
|
+
: Object.getPrototypeOf(value) === Object.prototype
|
|
1501
|
+
&& Object.values(value).every(item => isLosslessJson(item, depth + 1, seen));
|
|
1502
|
+
seen.delete(value);
|
|
1503
|
+
return valid;
|
|
1504
|
+
}
|
|
1505
|
+
function jsonEquals(left, right) {
|
|
1506
|
+
if (Object.is(left, right))
|
|
1507
|
+
return true;
|
|
1508
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
1509
|
+
return Array.isArray(left)
|
|
1510
|
+
&& Array.isArray(right)
|
|
1511
|
+
&& left.length === right.length
|
|
1512
|
+
&& left.every((value, index) => jsonEquals(value, right[index]));
|
|
1513
|
+
}
|
|
1514
|
+
if (!isRecord(left) || !isRecord(right))
|
|
1515
|
+
return false;
|
|
1516
|
+
const keys = Object.keys(left);
|
|
1517
|
+
return keys.length === Object.keys(right).length
|
|
1518
|
+
&& keys.every(key => Object.hasOwn(right, key) && jsonEquals(left[key], right[key]));
|
|
1519
|
+
}
|
|
1520
|
+
function isTerminalEvent(type) {
|
|
1521
|
+
return type === 'response.completed';
|
|
1522
|
+
}
|
|
1523
|
+
function isOfficialPrivateNoopEvent(type) {
|
|
1524
|
+
return type === 'response.queued'
|
|
1525
|
+
|| type === 'codex.response.metadata'
|
|
1526
|
+
|| type === 'response.content_part.added'
|
|
1527
|
+
|| type === 'response.content_part.done'
|
|
1528
|
+
|| type === 'response.custom_tool_call_input.done'
|
|
1529
|
+
|| type === 'response.function_call_arguments.delta'
|
|
1530
|
+
|| type === 'response.function_call_arguments.done'
|
|
1531
|
+
|| type === 'response.in_progress'
|
|
1532
|
+
|| type === 'response.metadata'
|
|
1533
|
+
|| type === 'response.output_text.done'
|
|
1534
|
+
|| type === 'response.output_text.annotation.added'
|
|
1535
|
+
|| type === 'response.reasoning_summary_part.done'
|
|
1536
|
+
|| type === 'responsesapi.websocket_timing';
|
|
1537
|
+
}
|
|
1538
|
+
function cancelBody(body) {
|
|
1539
|
+
if (body === null)
|
|
1540
|
+
return;
|
|
1541
|
+
try {
|
|
1542
|
+
void body.cancel().catch(() => { });
|
|
1543
|
+
}
|
|
1544
|
+
catch {
|
|
1545
|
+
// Cancellation is best effort; never delay or replace the classified outcome.
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
function cancelReader(reader) {
|
|
1549
|
+
try {
|
|
1550
|
+
void reader.cancel().catch(() => { });
|
|
1551
|
+
}
|
|
1552
|
+
catch {
|
|
1553
|
+
// Cancellation is best effort; never delay or replace the classified outcome.
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
function throwIfAborted(signal) {
|
|
1557
|
+
if (signal?.aborted !== true)
|
|
1558
|
+
return;
|
|
1559
|
+
if (signal.reason instanceof Error)
|
|
1560
|
+
throw signal.reason;
|
|
1561
|
+
throw new Error('Codex private Responses request was aborted', { cause: 'abort' });
|
|
1562
|
+
}
|
|
1563
|
+
function combineSignals(request, lifecycle) {
|
|
1564
|
+
if (request === undefined)
|
|
1565
|
+
return lifecycle ?? new AbortController().signal;
|
|
1566
|
+
if (lifecycle === undefined || lifecycle === request)
|
|
1567
|
+
return request;
|
|
1568
|
+
return AbortSignal.any([request, lifecycle]);
|
|
1569
|
+
}
|
|
1570
|
+
function waitForAbort(promise, signal) {
|
|
1571
|
+
if (signal.aborted)
|
|
1572
|
+
return Promise.reject(abortReason(signal));
|
|
1573
|
+
return new Promise((resolve, reject) => {
|
|
1574
|
+
const aborted = () => reject(abortReason(signal));
|
|
1575
|
+
signal.addEventListener('abort', aborted, { once: true });
|
|
1576
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', aborted)).catch(() => { });
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
function abortReason(signal) {
|
|
1580
|
+
return signal.reason ?? new Error('Codex private Responses request was aborted', { cause: 'abort' });
|
|
1581
|
+
}
|
|
1582
|
+
function protocolError(message) {
|
|
1583
|
+
return new CodexDirectResponsesError(message, 'protocol');
|
|
1584
|
+
}
|
|
1585
|
+
//# sourceMappingURL=codex-direct-responses.js.map
|