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.
- package/README.md +29 -5
- package/dist/application/native-sidechain-transcript.d.ts +2 -0
- package/dist/application/native-sidechain-transcript.js +3 -0
- package/dist/application/session-service.d.ts +2 -0
- package/dist/application/session-service.js +22 -1
- package/dist/application/subagent-service.d.ts +2 -0
- package/dist/application/subagent-service.js +28 -12
- package/dist/application/team-agent-runtime.d.ts +1 -0
- package/dist/application/team-agent-runtime.js +3 -0
- package/dist/cli-runtime.js +19 -6
- package/dist/providers/codex-subscription.d.ts +1 -7
- package/dist/providers/codex-subscription.js +32 -694
- package/dist/providers/fallback-provider.d.ts +5 -1
- package/dist/providers/fallback-provider.js +62 -4
- package/dist/providers/openai-responses.d.ts +26 -0
- package/dist/providers/openai-responses.js +162 -0
- package/dist/providers/provider-registry.js +22 -0
- package/dist/providers/provider-settings.d.ts +1 -1
- package/dist/providers/provider-settings.js +13 -2
- package/dist/providers/responses-codec.d.ts +31 -0
- package/dist/providers/responses-codec.js +742 -0
- package/package.json +1 -1
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import { ModelProviderError,
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|