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