praxis-agent 0.53.1 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,8 @@
1
- import { ModelProviderError, malformedModelToolCall, } from '../core/runtime.js';
1
+ import { ModelProviderError, } from '../core/runtime.js';
2
2
  import { CodexOAuthError, } from './codex-oauth.js';
3
3
  import { reportProviderTransportActivity } from './provider-transport-activity.js';
4
+ import { ResponsesCodec } from './responses-codec.js';
4
5
  export const CODEX_RESPONSES_ENDPOINT = 'https://chatgpt.com/backend-api/codex/responses';
5
- const DEFAULT_MAX_STREAM = 1024 * 1024;
6
- const DEFAULT_MAX_ARGUMENTS = 1024 * 1024;
7
- const DEFAULT_MAX_CALLS = 32;
8
- const DEFAULT_MAX_METADATA = 1024 * 1024;
9
- const DEFAULT_MAX_REASONING = 1024 * 1024;
10
6
  const DEFAULT_MAX_ERROR = 64 * 1024;
11
7
  function isRecord(value) {
12
8
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -15,29 +11,9 @@ function positiveInteger(value, fallback) {
15
11
  if (value === undefined)
16
12
  return fallback;
17
13
  if (!Number.isSafeInteger(value) || value <= 0)
18
- throw new Error('Codex parser limits must be positive integers');
14
+ throw new Error('Codex transport limits must be positive integers');
19
15
  return value;
20
16
  }
21
- function bytes(value) {
22
- return Buffer.byteLength(value, 'utf8');
23
- }
24
- function invalid(message) {
25
- return new ModelProviderError(message, {
26
- kind: 'invalid_request',
27
- retryable: false,
28
- });
29
- }
30
- function validateThinking(thinking) {
31
- if (thinking === undefined)
32
- return;
33
- if (thinking.mode !== 'disabled' &&
34
- thinking.mode !== 'enabled' &&
35
- thinking.mode !== 'adaptive') {
36
- throw new Error('Codex subscription provider received an invalid thinking mode');
37
- }
38
- if (thinking.maxTokens !== undefined)
39
- throw new Error('Codex subscription provider does not support thinking token budgets');
40
- }
41
17
  function providerError(kind, retryable, status) {
42
18
  return new ModelProviderError(status === undefined
43
19
  ? 'Codex subscription provider request failed'
@@ -58,9 +34,8 @@ function transportError(error, signal) {
58
34
  if (signal?.aborted)
59
35
  return providerError('cancelled', false);
60
36
  if (isRecord(error) &&
61
- (error.name === 'TimeoutError' || error.code === 'ETIMEDOUT')) {
37
+ (error.name === 'TimeoutError' || error.code === 'ETIMEDOUT'))
62
38
  return providerError('timeout', true);
63
- }
64
39
  return providerError('transport_error', true);
65
40
  }
66
41
  function accessError(error, signal) {
@@ -74,590 +49,6 @@ function accessError(error, signal) {
74
49
  return error;
75
50
  return transportError(error, signal);
76
51
  }
77
- function dataUrl(image) {
78
- return `data:${image.mediaType};base64,${image.data}`;
79
- }
80
- function appendMedia(blocks, legacy, role) {
81
- const output = [];
82
- const seenImages = new Set();
83
- const addImage = (image) => {
84
- const url = dataUrl(image);
85
- if (seenImages.has(url))
86
- return;
87
- seenImages.add(url);
88
- output.push({ type: 'input_image', image_url: url });
89
- };
90
- let hasDocument = false;
91
- for (const block of blocks ?? []) {
92
- if (block.type === 'text') {
93
- if (block.text.length > 0)
94
- output.push({ type: 'input_text', text: block.text });
95
- }
96
- else if (block.type === 'image') {
97
- addImage(block);
98
- }
99
- else {
100
- hasDocument = true;
101
- }
102
- }
103
- if (hasDocument)
104
- throw invalid(`Codex subscription provider does not support ${role} documents`);
105
- for (const image of legacy ?? [])
106
- addImage(image);
107
- return output;
108
- }
109
- function userItem(message) {
110
- const content = appendMedia(message.contentBlocks, message.images, 'user');
111
- if (content.length === 0 && message.content.length > 0)
112
- content.push({ type: 'input_text', text: message.content });
113
- return { role: 'user', content };
114
- }
115
- function serializeMessages(messages) {
116
- const instructions = [];
117
- const input = [];
118
- for (const message of messages) {
119
- if (message.role === 'system') {
120
- instructions.push(message.content);
121
- continue;
122
- }
123
- if (message.role === 'user') {
124
- input.push(userItem(message));
125
- continue;
126
- }
127
- if (message.role === 'assistant') {
128
- for (const block of message.thinkingBlocks ?? []) {
129
- if (block.type !== 'thinking' || block.signature.length === 0)
130
- throw invalid('Codex subscription provider cannot replay unsigned thinking');
131
- input.push({
132
- type: 'reasoning',
133
- summary: [{ type: 'summary_text', text: block.thinking }],
134
- encrypted_content: block.signature,
135
- });
136
- }
137
- if (message.content.length > 0)
138
- input.push({
139
- role: 'assistant',
140
- content: [{ type: 'output_text', text: message.content }],
141
- });
142
- for (const call of message.toolCalls ?? []) {
143
- input.push({
144
- type: 'function_call',
145
- call_id: call.id,
146
- name: call.name,
147
- arguments: JSON.stringify(call.input),
148
- });
149
- }
150
- continue;
151
- }
152
- if (message.documents?.length)
153
- throw invalid('Codex subscription provider does not support tool documents');
154
- const content = appendMedia(message.contentBlocks, message.images, 'tool');
155
- const output = message.contentBlocks
156
- ? message.contentBlocks
157
- .filter((block) => block.type === 'text')
158
- .map((block) => block.text)
159
- .join('')
160
- : message.content;
161
- input.push({
162
- type: 'function_call_output',
163
- call_id: message.toolCallId,
164
- output,
165
- });
166
- const images = content.filter((block) => block.type === 'input_image');
167
- if (images.length > 0)
168
- input.push({ role: 'user', content: images });
169
- }
170
- return { instructions: instructions.join('\n\n'), input };
171
- }
172
- export function serializeCodexRequest(request, model, configuredThinking) {
173
- if (request.webSearch !== undefined)
174
- throw invalid('Codex subscription provider does not support web search');
175
- if (request.betas !== undefined)
176
- throw invalid('Codex subscription provider does not support beta features');
177
- const thinking = request.thinking ?? configuredThinking;
178
- if (thinking !== undefined &&
179
- thinking.mode !== 'disabled' &&
180
- thinking.mode !== 'enabled' &&
181
- thinking.mode !== 'adaptive')
182
- throw invalid('Codex subscription provider received an invalid thinking mode');
183
- if (thinking?.maxTokens !== undefined)
184
- throw invalid('Codex subscription provider does not support thinking token budgets');
185
- if (request.messages.some((message) => message.role === 'user' && message.documents?.length))
186
- throw invalid('Codex subscription provider does not support user documents');
187
- if (request.messages.some((message) => message.role === 'tool' && message.documents?.length))
188
- throw invalid('Codex subscription provider does not support tool documents');
189
- const { instructions, input } = serializeMessages(request.messages);
190
- const body = {
191
- model,
192
- store: false,
193
- stream: true,
194
- include: ['reasoning.encrypted_content'],
195
- instructions,
196
- input,
197
- };
198
- if (request.tools?.length) {
199
- body.tools = request.tools.map((tool) => ({
200
- type: 'function',
201
- name: tool.name,
202
- description: tool.description,
203
- parameters: tool.inputSchema,
204
- strict: false,
205
- }));
206
- }
207
- if (thinking && thinking.mode !== 'disabled') {
208
- body.reasoning = {
209
- ...(request.effort ? { effort: request.effort } : {}),
210
- summary: 'auto',
211
- };
212
- }
213
- return body;
214
- }
215
- function usageFrom(value) {
216
- if (!isRecord(value))
217
- return undefined;
218
- const input = value.input_tokens;
219
- const output = value.output_tokens;
220
- if (input === undefined && output === undefined) {
221
- throw invalid('Codex provider returned invalid usage');
222
- }
223
- if (typeof input !== 'number' ||
224
- typeof output !== 'number' ||
225
- !Number.isSafeInteger(input) ||
226
- input < 0 ||
227
- !Number.isSafeInteger(output) ||
228
- output < 0)
229
- throw invalid('Codex provider returned invalid usage');
230
- const details = isRecord(value.input_tokens_details)
231
- ? value.input_tokens_details
232
- : undefined;
233
- const cached = details?.cached_tokens;
234
- if (cached !== undefined &&
235
- (typeof cached !== 'number' || !Number.isSafeInteger(cached) || cached < 0))
236
- throw invalid('Codex provider returned invalid usage');
237
- return {
238
- type: 'usage',
239
- usage: {
240
- inputTokens: input,
241
- outputTokens: output,
242
- ...(typeof cached === 'number' && cached > 0
243
- ? { cacheReadInputTokens: cached }
244
- : {}),
245
- },
246
- };
247
- }
248
- function responsePayload(value) {
249
- if (isRecord(value.response))
250
- return value.response;
251
- return value;
252
- }
253
- function callKey(value) {
254
- if (value.item_id !== undefined && typeof value.item_id !== 'string')
255
- throw invalid('Codex provider returned an invalid function item identity');
256
- const id = typeof value.item_id === 'string' ? value.item_id : undefined;
257
- const outputIndex = typeof value.output_index === 'number' ? value.output_index : undefined;
258
- if (value.output_index !== undefined &&
259
- typeof value.output_index !== 'number')
260
- throw invalid('Codex provider returned an invalid output index');
261
- if (outputIndex !== undefined &&
262
- (!Number.isSafeInteger(outputIndex) || outputIndex < 0))
263
- throw invalid('Codex provider returned an invalid output index');
264
- if (outputIndex !== undefined)
265
- return { key: `index:${outputIndex}`, index: outputIndex };
266
- if (id)
267
- return { key: `id:${id}`, index: Number.MAX_SAFE_INTEGER };
268
- throw invalid('Codex provider returned a function call without an identity');
269
- }
270
- function finishReason(reason) {
271
- if (reason === 'max_output_tokens')
272
- return 'max_tokens';
273
- throw invalid('Codex provider returned an unsupported incomplete response');
274
- }
275
- function finishReasonFor(value) {
276
- const response = responsePayload(value);
277
- if (value.type === 'response.incomplete' ||
278
- response.status === 'incomplete') {
279
- const details = isRecord(response.incomplete_details)
280
- ? response.incomplete_details
281
- : undefined;
282
- return finishReason(details?.reason ?? response.reason);
283
- }
284
- return 'tool_use';
285
- }
286
- function reasoningBytes(reasoning) {
287
- return (bytes(reasoning.summary) +
288
- (reasoning.signature === undefined ? 0 : bytes(reasoning.signature)));
289
- }
290
- function completedReasoning(state) {
291
- const reasoning = state.reasoning;
292
- if (!reasoning ||
293
- reasoning.activePart ||
294
- !reasoning.itemDone ||
295
- !reasoning.signature)
296
- throw invalid('Codex provider returned an incomplete reasoning block');
297
- const events = [];
298
- if (reasoning.signature.length > 0)
299
- events.push({
300
- type: 'thinking-signature-delta',
301
- delta: reasoning.signature,
302
- });
303
- events.push({
304
- type: 'thinking-stop',
305
- block: {
306
- type: 'thinking',
307
- thinking: reasoning.summary,
308
- signature: reasoning.signature,
309
- },
310
- });
311
- state.reasoning = undefined;
312
- return events;
313
- }
314
- function parseEvent(value, state, limits) {
315
- if (state.terminal)
316
- throw invalid('Codex provider emitted data after terminal response');
317
- const type = typeof value.type === 'string' ? value.type : '';
318
- const events = [];
319
- if (type === 'response.output_text.delta') {
320
- const delta = value.delta;
321
- if (typeof delta !== 'string')
322
- throw invalid('Codex provider returned invalid text delta');
323
- if (delta.length)
324
- events.push({ type: 'text-delta', delta });
325
- return events;
326
- }
327
- if (type === 'response.reasoning_summary_part.added') {
328
- const itemId = value.item_id;
329
- if (typeof itemId !== 'string' || itemId.length === 0)
330
- throw invalid('Codex provider returned reasoning without an item identity');
331
- const part = isRecord(value.part) ? value.part : value;
332
- if (state.reasoning) {
333
- if (state.reasoning.itemId !== itemId || state.reasoning.itemDone)
334
- throw invalid('Codex provider interleaved reasoning blocks');
335
- if (state.reasoning.activePart)
336
- throw invalid('Codex provider interleaved reasoning summary parts');
337
- if (state.reasoning.summary.length > 0) {
338
- state.reasoning.summary += '\n';
339
- if (reasoningBytes(state.reasoning) > limits.reasoning)
340
- throw invalid('Codex provider exceeded the reasoning limit');
341
- events.push({ type: 'thinking-delta', delta: '\n' });
342
- }
343
- state.reasoning.activePart = true;
344
- state.reasoning.partSeen = true;
345
- }
346
- else {
347
- state.reasoning = {
348
- itemId,
349
- summary: '',
350
- activePart: true,
351
- partSeen: true,
352
- itemDone: false,
353
- };
354
- events.push({
355
- type: 'thinking-start',
356
- block: { type: 'thinking', thinking: '' },
357
- });
358
- }
359
- if (isRecord(part) && typeof part.text === 'string' && part.text.length) {
360
- state.reasoning.summary += part.text;
361
- if (reasoningBytes(state.reasoning) > limits.reasoning)
362
- throw invalid('Codex provider exceeded the reasoning limit');
363
- events.push({ type: 'thinking-delta', delta: part.text });
364
- }
365
- return events;
366
- }
367
- if (type === 'response.reasoning_summary_text.delta') {
368
- if (!state.reasoning)
369
- throw invalid('Codex provider returned reasoning text without a block');
370
- if (!state.reasoning.activePart)
371
- throw invalid('Codex provider returned reasoning text without an active part');
372
- if (value.item_id !== state.reasoning.itemId)
373
- throw invalid('Codex provider reasoning item identity mismatch');
374
- const delta = value.delta;
375
- if (typeof delta !== 'string')
376
- throw invalid('Codex provider returned invalid reasoning delta');
377
- state.reasoning.summary += delta;
378
- if (reasoningBytes(state.reasoning) > limits.reasoning)
379
- throw invalid('Codex provider exceeded the reasoning limit');
380
- if (delta.length)
381
- events.push({ type: 'thinking-delta', delta });
382
- return events;
383
- }
384
- if (type === 'response.reasoning_summary_part.done') {
385
- if (!state.reasoning)
386
- throw invalid('Codex provider completed unknown reasoning block');
387
- if (value.item_id !== state.reasoning.itemId)
388
- throw invalid('Codex provider reasoning item identity mismatch');
389
- if (!state.reasoning.activePart)
390
- throw invalid('Codex provider duplicated a reasoning summary part');
391
- state.reasoning.activePart = false;
392
- if (state.reasoning.itemDone)
393
- events.push(...completedReasoning(state));
394
- return events;
395
- }
396
- if (type === 'response.function_call_arguments.delta') {
397
- const { key, index } = callKey(value);
398
- let call = value.item_id === undefined
399
- ? undefined
400
- : state.calls.get(`id:${String(value.item_id)}`);
401
- if (!call)
402
- call = state.calls.get(key);
403
- if (!call) {
404
- if (state.toolCallCount >= limits.calls)
405
- throw invalid('Codex provider exceeded the tool call limit');
406
- call = {
407
- key,
408
- index,
409
- id: '',
410
- name: '',
411
- arguments: '',
412
- done: false,
413
- emitted: false,
414
- hadArgumentDelta: false,
415
- };
416
- state.calls.set(key, call);
417
- state.toolCallCount++;
418
- }
419
- if (call.done)
420
- throw invalid('Codex provider duplicated a function call');
421
- const delta = value.delta;
422
- if (typeof delta !== 'string')
423
- throw invalid('Codex provider returned invalid function arguments');
424
- call.arguments += delta;
425
- call.hadArgumentDelta = true;
426
- state.argumentBytes += bytes(delta);
427
- if (state.argumentBytes > limits.arguments)
428
- throw invalid('Codex provider exceeded the function argument limit');
429
- if (typeof value.item_id === 'string')
430
- state.metadataBytes += bytes(value.item_id);
431
- if (state.metadataBytes > limits.metadata)
432
- throw invalid('Codex provider exceeded function metadata limits');
433
- return events;
434
- }
435
- if (type === 'response.output_item.done') {
436
- const item = isRecord(value.item) ? value.item : undefined;
437
- if (!item || typeof item.type !== 'string')
438
- throw invalid('Codex provider returned an invalid output item');
439
- if (item.type === 'reasoning') {
440
- const itemId = typeof item.id === 'string' ? item.id : undefined;
441
- if (!itemId)
442
- throw invalid('Codex provider returned reasoning without an item identity');
443
- const signature = item.encrypted_content;
444
- if (typeof signature !== 'string' || signature.length === 0)
445
- throw invalid('Codex provider returned unsigned reasoning');
446
- if (!state.reasoning) {
447
- const summary = item.summary;
448
- if (!Array.isArray(summary) || summary.length === 0)
449
- throw invalid('Codex provider returned an invalid reasoning summary');
450
- const texts = [];
451
- for (const entry of summary) {
452
- if (!isRecord(entry) ||
453
- entry.type !== 'summary_text' ||
454
- typeof entry.text !== 'string' ||
455
- entry.text.length === 0)
456
- throw invalid('Codex provider returned an invalid reasoning summary');
457
- texts.push(entry.text);
458
- }
459
- state.reasoning = {
460
- itemId,
461
- summary: texts.join('\n'),
462
- activePart: false,
463
- partSeen: true,
464
- itemDone: true,
465
- signature,
466
- };
467
- if (reasoningBytes(state.reasoning) > limits.reasoning)
468
- throw invalid('Codex provider exceeded the reasoning limit');
469
- events.push({
470
- type: 'thinking-start',
471
- block: { type: 'thinking', thinking: '' },
472
- });
473
- events.push({ type: 'thinking-delta', delta: state.reasoning.summary });
474
- events.push(...completedReasoning(state));
475
- return events;
476
- }
477
- if (state.reasoning.itemId !== itemId)
478
- throw invalid('Codex provider reasoning item identity mismatch');
479
- if (state.reasoning.itemDone)
480
- throw invalid('Codex provider duplicated a reasoning item');
481
- if (item.summary !== undefined) {
482
- if (!Array.isArray(item.summary) || item.summary.length === 0)
483
- throw invalid('Codex provider returned an invalid reasoning summary');
484
- for (const entry of item.summary) {
485
- if (!isRecord(entry) ||
486
- entry.type !== 'summary_text' ||
487
- typeof entry.text !== 'string' ||
488
- entry.text.length === 0)
489
- throw invalid('Codex provider returned an invalid reasoning summary');
490
- }
491
- }
492
- state.reasoning.signature = signature;
493
- state.reasoning.itemDone = true;
494
- if (reasoningBytes(state.reasoning) > limits.reasoning)
495
- throw invalid('Codex provider exceeded the reasoning limit');
496
- if (!state.reasoning.activePart)
497
- events.push(...completedReasoning(state));
498
- return events;
499
- }
500
- if (item.type !== 'function_call')
501
- return events;
502
- const id = typeof item.call_id === 'string' ? item.call_id : '';
503
- const name = typeof item.name === 'string' ? item.name : '';
504
- if (!id || !name)
505
- throw invalid('Codex provider returned an invalid function call');
506
- const outputIndex = typeof value.output_index === 'number'
507
- ? value.output_index
508
- : typeof item.output_index === 'number'
509
- ? item.output_index
510
- : undefined;
511
- if ((value.output_index !== undefined &&
512
- typeof value.output_index !== 'number') ||
513
- (item.output_index !== undefined && typeof item.output_index !== 'number'))
514
- throw invalid('Codex provider returned an invalid output index');
515
- const itemId = typeof item.id === 'string' ? item.id : undefined;
516
- if (outputIndex !== undefined &&
517
- (!Number.isSafeInteger(outputIndex) || outputIndex < 0))
518
- throw invalid('Codex provider returned an invalid output index');
519
- const key = outputIndex !== undefined
520
- ? `index:${outputIndex}`
521
- : itemId
522
- ? `id:${itemId}`
523
- : undefined;
524
- if (!key)
525
- throw invalid('Codex provider returned a function call without an identity');
526
- let call = itemId === undefined ? undefined : state.calls.get(`id:${itemId}`);
527
- if (!call)
528
- call = state.calls.get(key);
529
- if (!call && outputIndex !== undefined) {
530
- call = [...state.calls.values()].find((candidate) => candidate.index === outputIndex);
531
- }
532
- if (!call) {
533
- if (state.toolCallCount >= limits.calls)
534
- throw invalid('Codex provider exceeded the tool call limit');
535
- call = {
536
- key,
537
- index: outputIndex ?? state.callOrder++,
538
- id: '',
539
- name: '',
540
- arguments: '',
541
- done: false,
542
- emitted: false,
543
- hadArgumentDelta: false,
544
- };
545
- state.calls.set(key, call);
546
- state.toolCallCount++;
547
- }
548
- if (call.done || call.emitted)
549
- throw invalid('Codex provider duplicated a function call');
550
- call.id = id;
551
- call.name = name;
552
- const fullArguments = item.arguments;
553
- if (call.arguments.length === 0) {
554
- if (typeof fullArguments !== 'string' || fullArguments.length === 0)
555
- throw invalid('Codex provider returned a function call without arguments');
556
- call.arguments = fullArguments;
557
- state.argumentBytes += bytes(fullArguments);
558
- if (state.argumentBytes > limits.arguments)
559
- throw invalid('Codex provider exceeded the function argument limit');
560
- }
561
- else if (typeof fullArguments === 'string' &&
562
- call.hadArgumentDelta &&
563
- fullArguments !== call.arguments) {
564
- throw invalid('Codex provider returned mismatched function arguments');
565
- }
566
- state.metadataBytes +=
567
- bytes(id) + bytes(name) + (itemId === undefined ? 0 : bytes(itemId));
568
- if (state.metadataBytes > limits.metadata)
569
- throw invalid('Codex provider exceeded function metadata limits');
570
- call.done = true;
571
- let parsed;
572
- try {
573
- parsed = JSON.parse(call.arguments || '{}');
574
- }
575
- catch {
576
- if (!id || !name)
577
- throw invalid('Codex provider returned an invalid tool call');
578
- call.emitted = true;
579
- state.toolCallEmitted = true;
580
- events.push({ type: 'tool-call', call: malformedModelToolCall(id, name) });
581
- return events;
582
- }
583
- if (!isRecord(parsed))
584
- throw invalid('Codex provider returned non-object function arguments');
585
- call.emitted = true;
586
- state.toolCallEmitted = true;
587
- const toolCall = { id, name, input: parsed };
588
- events.push({ type: 'tool-call', call: toolCall });
589
- return events;
590
- }
591
- if (type === 'response.completed' || type === 'response.incomplete') {
592
- if (state.reasoning)
593
- throw invalid('Codex provider ended with incomplete reasoning');
594
- for (const call of state.calls.values()) {
595
- if (!call.done || !call.emitted)
596
- throw invalid('Codex provider ended with an incomplete function call');
597
- }
598
- const response = responsePayload(value);
599
- const usage = usageFrom(isRecord(response.usage)
600
- ? response.usage
601
- : isRecord(value.usage)
602
- ? value.usage
603
- : undefined);
604
- if (usage && !state.usageEmitted) {
605
- state.usageEmitted = true;
606
- events.push(usage);
607
- }
608
- state.terminal = true;
609
- events.push({
610
- type: 'terminal',
611
- reason: type === 'response.incomplete'
612
- ? finishReasonFor(value)
613
- : state.toolCallEmitted
614
- ? 'tool_use'
615
- : 'end_turn',
616
- });
617
- return events;
618
- }
619
- if (type === 'response.failed' || type === 'error' || isRecord(value.error))
620
- throw providerError('api_error', true);
621
- return events;
622
- }
623
- function parseFrame(frame, state, limits) {
624
- const data = frame
625
- .split('\n')
626
- .filter((line) => line.startsWith('data:'))
627
- .map((line) => line.slice(5).trimStart())
628
- .join('\n');
629
- if (!data)
630
- return [];
631
- let parsed;
632
- try {
633
- parsed = JSON.parse(data);
634
- }
635
- catch {
636
- throw invalid('Codex provider returned malformed SSE JSON');
637
- }
638
- if (!isRecord(parsed))
639
- throw invalid('Codex provider returned malformed SSE event');
640
- return parseEvent(parsed, state, limits);
641
- }
642
- export function parseCodexSseFrame(data) {
643
- const state = {
644
- reasoning: undefined,
645
- calls: new Map(),
646
- callOrder: 0,
647
- toolCallCount: 0,
648
- argumentBytes: 0,
649
- metadataBytes: 0,
650
- terminal: false,
651
- usageEmitted: false,
652
- toolCallEmitted: false,
653
- };
654
- return parseFrame(data, state, {
655
- arguments: DEFAULT_MAX_ARGUMENTS,
656
- calls: DEFAULT_MAX_CALLS,
657
- metadata: DEFAULT_MAX_METADATA,
658
- reasoning: DEFAULT_MAX_REASONING,
659
- });
660
- }
661
52
  async function drainBody(response, maxBytes, request) {
662
53
  const reader = response.body?.getReader();
663
54
  if (!reader)
@@ -684,7 +75,7 @@ async function drainBody(response, maxBytes, request) {
684
75
  await reader.cancel();
685
76
  }
686
77
  catch {
687
- /* preserve primary error */
78
+ /* preserve the primary error */
688
79
  }
689
80
  }
690
81
  reader.releaseLock();
@@ -704,26 +95,35 @@ export class CodexSubscriptionProvider {
704
95
  terminalReasons: true,
705
96
  };
706
97
  fetchImplementation;
707
- maxStreamBufferBytes;
708
- maxToolArgumentsBytes;
709
- maxToolCallsPerResponse;
710
- maxToolMetadataBytes;
711
- maxReasoningBytes;
712
98
  maxErrorBodyBytes;
99
+ responsesCodec;
713
100
  constructor(options) {
714
101
  this.options = options;
715
- validateThinking(options.thinking);
716
102
  this.model = options.model;
717
103
  this.fetchImplementation = options.fetchImplementation ?? fetch;
718
- this.maxStreamBufferBytes = positiveInteger(options.maxStreamBufferBytes, DEFAULT_MAX_STREAM);
719
- this.maxToolArgumentsBytes = positiveInteger(options.maxToolArgumentsBytes, DEFAULT_MAX_ARGUMENTS);
720
- this.maxToolCallsPerResponse = positiveInteger(options.maxToolCallsPerResponse, DEFAULT_MAX_CALLS);
721
- this.maxToolMetadataBytes = positiveInteger(options.maxToolMetadataBytes, DEFAULT_MAX_METADATA);
722
- this.maxReasoningBytes = positiveInteger(options.maxReasoningBytes, DEFAULT_MAX_REASONING);
723
104
  this.maxErrorBodyBytes = positiveInteger(options.maxErrorBodyBytes, DEFAULT_MAX_ERROR);
105
+ this.responsesCodec = new ResponsesCodec({
106
+ providerLabel: 'Codex subscription provider',
107
+ ...(options.thinking === undefined ? {} : { thinking: options.thinking }),
108
+ ...(options.maxStreamBufferBytes === undefined
109
+ ? {}
110
+ : { maxStreamBufferBytes: options.maxStreamBufferBytes }),
111
+ ...(options.maxToolArgumentsBytes === undefined
112
+ ? {}
113
+ : { maxToolArgumentsBytes: options.maxToolArgumentsBytes }),
114
+ ...(options.maxToolCallsPerResponse === undefined
115
+ ? {}
116
+ : { maxToolCallsPerResponse: options.maxToolCallsPerResponse }),
117
+ ...(options.maxToolMetadataBytes === undefined
118
+ ? {}
119
+ : { maxToolMetadataBytes: options.maxToolMetadataBytes }),
120
+ ...(options.maxReasoningBytes === undefined
121
+ ? {}
122
+ : { maxReasoningBytes: options.maxReasoningBytes }),
123
+ });
724
124
  }
725
125
  async *complete(request) {
726
- const body = serializeCodexRequest(request, this.model, this.options.thinking);
126
+ const body = this.responsesCodec.serialize(request, this.model);
727
127
  const signal = request.signal;
728
128
  let access;
729
129
  try {
@@ -762,7 +162,7 @@ export class CodexSubscriptionProvider {
762
162
  await drainBody(response, this.maxErrorBodyBytes, request);
763
163
  }
764
164
  catch {
765
- /* ignore unread body failures */
165
+ /* ignore unread body failures while refreshing credentials */
766
166
  }
767
167
  try {
768
168
  reportProviderTransportActivity(request, 'request-started');
@@ -781,7 +181,7 @@ export class CodexSubscriptionProvider {
781
181
  await drainBody(response, this.maxErrorBodyBytes, request);
782
182
  }
783
183
  catch {
784
- /* preserve generic error */
184
+ /* preserve the redacted transport error */
785
185
  }
786
186
  const status = response.status;
787
187
  const classified = statusKind(status);
@@ -791,72 +191,10 @@ export class CodexSubscriptionProvider {
791
191
  }
792
192
  if (!response.body)
793
193
  throw providerError('transport_error', true);
794
- const reader = response.body.getReader();
795
- const decoder = new TextDecoder();
796
- let buffer = '';
797
- let streamEnded = false;
798
- const state = {
799
- reasoning: undefined,
800
- calls: new Map(),
801
- callOrder: 0,
802
- toolCallCount: 0,
803
- argumentBytes: 0,
804
- metadataBytes: 0,
805
- terminal: false,
806
- usageEmitted: false,
807
- toolCallEmitted: false,
808
- };
809
- const limits = {
810
- arguments: this.maxToolArgumentsBytes,
811
- calls: this.maxToolCallsPerResponse,
812
- metadata: this.maxToolMetadataBytes,
813
- reasoning: this.maxReasoningBytes,
814
- };
815
- try {
816
- while (true) {
817
- const next = await reader.read();
818
- if (!next.done && next.value.byteLength > 0)
819
- reportProviderTransportActivity(request, 'response-chunk');
820
- buffer += decoder.decode(next.value, { stream: !next.done });
821
- buffer = buffer.replaceAll('\r\n', '\n');
822
- if (bytes(buffer) > this.maxStreamBufferBytes)
823
- throw invalid('Codex provider exceeded the SSE buffer limit');
824
- let boundary = buffer.indexOf('\n\n');
825
- while (boundary >= 0) {
826
- const frame = buffer.slice(0, boundary);
827
- buffer = buffer.slice(boundary + 2);
828
- for (const event of parseFrame(frame, state, limits))
829
- yield event;
830
- if (state.terminal)
831
- break;
832
- boundary = buffer.indexOf('\n\n');
833
- }
834
- if (state.terminal)
835
- break;
836
- if (next.done) {
837
- streamEnded = true;
838
- if (buffer.trim().length > 0)
839
- throw providerError('transport_error', true);
840
- throw providerError('transport_error', true);
841
- }
842
- }
843
- }
844
- catch (error) {
845
- if (error instanceof ModelProviderError)
846
- throw error;
847
- throw transportError(error, signal);
848
- }
849
- finally {
850
- if (!streamEnded) {
851
- try {
852
- await reader.cancel();
853
- }
854
- catch {
855
- /* preserve primary error or consumer return */
856
- }
857
- }
858
- reader.releaseLock();
859
- }
194
+ yield* this.responsesCodec.stream(response.body, {
195
+ ...(signal === undefined ? {} : { signal }),
196
+ onChunk: () => reportProviderTransportActivity(request, 'response-chunk'),
197
+ });
860
198
  }
861
199
  }
862
200
  //# sourceMappingURL=codex-subscription.js.map