pi-openai-codex-compat 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +30 -16
- package/extensions/openai-codex-compat/apply-patch.ts +4 -5
- package/extensions/openai-codex-compat/codex-cache-key.ts +9 -0
- package/extensions/openai-codex-compat/codex-protocol.ts +3 -2
- package/extensions/openai-codex-compat/codex-provider.ts +221 -107
- package/extensions/openai-codex-compat/codex-stream.ts +85 -14
- package/extensions/openai-codex-compat/codex-transport.ts +714 -161
- package/extensions/openai-codex-compat/config.ts +2 -2
- package/extensions/openai-codex-compat/image-generation-schema.ts +37 -0
- package/extensions/openai-codex-compat/image-generation.ts +25 -48
- package/extensions/openai-codex-compat/index.ts +7 -0
- package/extensions/openai-codex-compat/provider-error.ts +79 -0
- package/extensions/openai-codex-compat/responses-replay.ts +0 -7
- package/extensions/openai-codex-compat/web-run.ts +7 -0
- package/package.json +2 -1
|
@@ -34,8 +34,15 @@ import {
|
|
|
34
34
|
type JsonRecord,
|
|
35
35
|
type ResponsesItem,
|
|
36
36
|
} from "./codex-protocol.ts";
|
|
37
|
+
import { codexCacheKey } from "./codex-cache-key.ts";
|
|
37
38
|
import { processCodexStream } from "./codex-stream.ts";
|
|
38
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
CodexTransport,
|
|
41
|
+
validateCodexAuthentication,
|
|
42
|
+
type CodexContinuationHandle,
|
|
43
|
+
type CodexTransportDiagnostic,
|
|
44
|
+
type CodexWebSocketResponseHandle,
|
|
45
|
+
} from "./codex-transport.ts";
|
|
39
46
|
import type { CodexCompatConfig, ImageDetail } from "./config.ts";
|
|
40
47
|
import { nativeResponseData, NATIVE_RESPONSE_ENTRY_TYPE } from "./native-history.ts";
|
|
41
48
|
import {
|
|
@@ -49,10 +56,10 @@ import {
|
|
|
49
56
|
createGrammarToolInputProperties,
|
|
50
57
|
type ResponsesItem as SerializedResponsesItem,
|
|
51
58
|
} from "./vendor/pi-ai/openai-responses-serialization.ts";
|
|
59
|
+
import { formatProviderError } from "./provider-error.ts";
|
|
52
60
|
|
|
53
61
|
const CODEX_PROVIDER = "openai-codex";
|
|
54
62
|
const CODEX_API = "openai-codex-responses";
|
|
55
|
-
const CHECKPOINT_STATUS_ID = "openai-codex-compat-compaction";
|
|
56
63
|
const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
|
|
57
64
|
|
|
58
65
|
type ConfigResolver = (ctx: ExtensionContext) => CodexCompatConfig;
|
|
@@ -78,7 +85,6 @@ type RuntimeScope = {
|
|
|
78
85
|
config: CodexCompatConfig;
|
|
79
86
|
hasUI: boolean;
|
|
80
87
|
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
81
|
-
setStatus(message: string | undefined): void;
|
|
82
88
|
};
|
|
83
89
|
|
|
84
90
|
type RequestTemplate = {
|
|
@@ -94,11 +100,6 @@ type CodexCompat = {
|
|
|
94
100
|
supportsOpenAIGrammarTools?: boolean;
|
|
95
101
|
};
|
|
96
102
|
|
|
97
|
-
function clampPromptCacheKey(key: string | undefined): string | undefined {
|
|
98
|
-
if (key === undefined) return undefined;
|
|
99
|
-
return Array.from(key).slice(0, 64).join("");
|
|
100
|
-
}
|
|
101
|
-
|
|
102
103
|
function markerSummary(): string {
|
|
103
104
|
return `OpenAI Codex remote compaction checkpoint (${randomUUID()}).`;
|
|
104
105
|
}
|
|
@@ -153,7 +154,6 @@ function nativeOverrideRequired(
|
|
|
153
154
|
function captureRawEvents(
|
|
154
155
|
events: AsyncIterable<JsonRecord>,
|
|
155
156
|
items: ResponsesItem[],
|
|
156
|
-
metadata: { serviceTier?: string },
|
|
157
157
|
): AsyncIterable<JsonRecord> {
|
|
158
158
|
return {
|
|
159
159
|
async *[Symbol.asyncIterator]() {
|
|
@@ -171,12 +171,23 @@ function captureRawEvents(
|
|
|
171
171
|
items.splice(0, items.length, ...terminalItems.map((item) => structuredClone(item)));
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
174
|
+
yield event;
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function startOnFirstEvent(
|
|
181
|
+
events: AsyncIterable<JsonRecord>,
|
|
182
|
+
onStart: () => void,
|
|
183
|
+
): AsyncIterable<JsonRecord> {
|
|
184
|
+
return {
|
|
185
|
+
async *[Symbol.asyncIterator]() {
|
|
186
|
+
let started = false;
|
|
187
|
+
for await (const event of events) {
|
|
188
|
+
if (!started) {
|
|
189
|
+
started = true;
|
|
190
|
+
onStart();
|
|
180
191
|
}
|
|
181
192
|
yield event;
|
|
182
193
|
}
|
|
@@ -184,6 +195,13 @@ function captureRawEvents(
|
|
|
184
195
|
};
|
|
185
196
|
}
|
|
186
197
|
|
|
198
|
+
function clearStreamingScratchState(message: AssistantMessage): void {
|
|
199
|
+
for (const block of message.content) {
|
|
200
|
+
delete (block as { partialJson?: string }).partialJson;
|
|
201
|
+
delete (block as { customInput?: unknown }).customInput;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
187
205
|
function updateInput(payload: JsonRecord, input: readonly ResponsesItem[]): JsonRecord {
|
|
188
206
|
const result: JsonRecord = {
|
|
189
207
|
...payload,
|
|
@@ -194,14 +212,15 @@ function updateInput(payload: JsonRecord, input: readonly ResponsesItem[]): Json
|
|
|
194
212
|
return result;
|
|
195
213
|
}
|
|
196
214
|
|
|
197
|
-
function
|
|
215
|
+
function assertSuccessfulOutput(
|
|
198
216
|
message: AssistantMessage,
|
|
199
|
-
): message is AssistantMessage & { stopReason: "stop" | "length" | "toolUse" } {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
217
|
+
): asserts message is AssistantMessage & { stopReason: "stop" | "length" | "toolUse" } {
|
|
218
|
+
if (message.stopReason === "pending") {
|
|
219
|
+
throw new Error("Codex stream ended without a stop reason");
|
|
220
|
+
}
|
|
221
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
222
|
+
throw new Error(message.errorMessage || "An unknown error occurred");
|
|
223
|
+
}
|
|
205
224
|
}
|
|
206
225
|
|
|
207
226
|
function applyServiceTierPricing(
|
|
@@ -308,7 +327,6 @@ export class CodexProviderRuntime {
|
|
|
308
327
|
config: this.resolveConfig(ctx),
|
|
309
328
|
hasUI: ctx.hasUI,
|
|
310
329
|
notify: (message, level) => ctx.ui.notify(message, level),
|
|
311
|
-
setStatus: (message) => ctx.ui.setStatus(CHECKPOINT_STATUS_ID, message),
|
|
312
330
|
});
|
|
313
331
|
}
|
|
314
332
|
|
|
@@ -319,7 +337,10 @@ export class CodexProviderRuntime {
|
|
|
319
337
|
this.transport.close(sessionId);
|
|
320
338
|
}
|
|
321
339
|
|
|
322
|
-
private async acquireRequest(
|
|
340
|
+
private async acquireRequest(
|
|
341
|
+
sessionId: string | undefined,
|
|
342
|
+
signal?: AbortSignal,
|
|
343
|
+
): Promise<() => void> {
|
|
323
344
|
if (!sessionId) return () => {};
|
|
324
345
|
const previous = this.requestTails.get(sessionId) ?? Promise.resolve();
|
|
325
346
|
let releaseCurrent!: () => void;
|
|
@@ -327,11 +348,44 @@ export class CodexProviderRuntime {
|
|
|
327
348
|
releaseCurrent = resolve;
|
|
328
349
|
});
|
|
329
350
|
this.requestTails.set(sessionId, current);
|
|
330
|
-
|
|
331
|
-
|
|
351
|
+
let released = false;
|
|
352
|
+
const release = () => {
|
|
353
|
+
if (released) return;
|
|
354
|
+
released = true;
|
|
332
355
|
releaseCurrent();
|
|
333
356
|
if (this.requestTails.get(sessionId) === current) this.requestTails.delete(sessionId);
|
|
334
357
|
};
|
|
358
|
+
|
|
359
|
+
if (signal?.aborted) {
|
|
360
|
+
void previous.then(release);
|
|
361
|
+
throw new Error("Request was aborted");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let onAbort: (() => void) | undefined;
|
|
365
|
+
try {
|
|
366
|
+
await Promise.race([
|
|
367
|
+
previous,
|
|
368
|
+
...(signal
|
|
369
|
+
? [
|
|
370
|
+
new Promise<never>((_resolve, reject) => {
|
|
371
|
+
onAbort = () => reject(new Error("Request was aborted"));
|
|
372
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
373
|
+
}),
|
|
374
|
+
]
|
|
375
|
+
: []),
|
|
376
|
+
]);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
void previous.then(release);
|
|
379
|
+
throw error;
|
|
380
|
+
} finally {
|
|
381
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (signal?.aborted) {
|
|
385
|
+
release();
|
|
386
|
+
throw new Error("Request was aborted");
|
|
387
|
+
}
|
|
388
|
+
return release;
|
|
335
389
|
}
|
|
336
390
|
|
|
337
391
|
createProvider(base: Provider): Provider {
|
|
@@ -381,7 +435,8 @@ export class CodexProviderRuntime {
|
|
|
381
435
|
model: Model<any>,
|
|
382
436
|
context: Context,
|
|
383
437
|
options: OpenAICodexResponsesOptions,
|
|
384
|
-
|
|
438
|
+
runtimeSessionId: string | undefined,
|
|
439
|
+
cacheSessionId: string | undefined,
|
|
385
440
|
grammarToolInputProperties: GrammarToolInputProperties,
|
|
386
441
|
): JsonRecord {
|
|
387
442
|
const compat = model.compat as CodexCompat | undefined;
|
|
@@ -393,12 +448,12 @@ export class CodexProviderRuntime {
|
|
|
393
448
|
instructions: context.systemPrompt || "You are a helpful assistant.",
|
|
394
449
|
input: this.wireHistory(
|
|
395
450
|
model,
|
|
396
|
-
Object.assign({}, context, { sessionId }),
|
|
451
|
+
Object.assign({}, context, { sessionId: runtimeSessionId }),
|
|
397
452
|
grammarToolInputProperties,
|
|
398
453
|
),
|
|
399
454
|
text: { verbosity: options.textVerbosity ?? "low" },
|
|
400
455
|
include: ["reasoning.encrypted_content"],
|
|
401
|
-
prompt_cache_key:
|
|
456
|
+
prompt_cache_key: cacheSessionId,
|
|
402
457
|
tool_choice: options.toolChoice ?? "auto",
|
|
403
458
|
parallel_tool_calls: true,
|
|
404
459
|
};
|
|
@@ -439,21 +494,38 @@ export class CodexProviderRuntime {
|
|
|
439
494
|
}): Promise<{ checkpoint: CheckpointData; usage?: Usage }> {
|
|
440
495
|
const sessionId = options.requestOptions.sessionId;
|
|
441
496
|
if (!sessionId) throw new Error("Codex compaction requires a Pi session id.");
|
|
497
|
+
const accountId = validateCodexAuthentication(options.model, options.requestOptions.apiKey);
|
|
442
498
|
const payload = remoteCompactionPayload({
|
|
443
499
|
template: options.template,
|
|
444
500
|
modelId: options.model.id,
|
|
445
501
|
history: options.history,
|
|
446
502
|
instructions: options.instructions,
|
|
447
|
-
sessionId
|
|
503
|
+
sessionId:
|
|
504
|
+
options.requestOptions.cacheRetention === "none" ? undefined : codexCacheKey(sessionId),
|
|
448
505
|
priority: options.priority,
|
|
449
506
|
});
|
|
450
507
|
const transformed = await options.requestOptions.onPayload?.(payload, options.model);
|
|
451
|
-
const request =
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
508
|
+
const request = transformed === undefined ? payload : (transformed as JsonRecord);
|
|
509
|
+
let webSocketResponseHandle: CodexWebSocketResponseHandle | undefined;
|
|
510
|
+
let compacted: Awaited<ReturnType<typeof collectRemoteCompaction>>;
|
|
511
|
+
try {
|
|
512
|
+
compacted = await collectRemoteCompaction(
|
|
513
|
+
this.transport.request(options.model, request, {
|
|
514
|
+
...options.requestOptions,
|
|
515
|
+
accountId,
|
|
516
|
+
onWebSocketResponseHandle(handle) {
|
|
517
|
+
webSocketResponseHandle = handle;
|
|
518
|
+
},
|
|
519
|
+
}),
|
|
520
|
+
options.model,
|
|
521
|
+
options.priority,
|
|
522
|
+
);
|
|
523
|
+
} catch (error) {
|
|
524
|
+
if (!options.requestOptions.signal?.aborted) {
|
|
525
|
+
webSocketResponseHandle?.failParsing(error);
|
|
526
|
+
}
|
|
527
|
+
throw error;
|
|
528
|
+
}
|
|
457
529
|
return {
|
|
458
530
|
checkpoint: checkpointData(
|
|
459
531
|
options.model.id,
|
|
@@ -515,44 +587,39 @@ export class CodexProviderRuntime {
|
|
|
515
587
|
return body;
|
|
516
588
|
}
|
|
517
589
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (!firstKeptEntryId || typeof scope.manager.appendCompaction !== "function") {
|
|
535
|
-
throw new Error("Pi's mutable SessionManager is unavailable for percentage compaction.");
|
|
536
|
-
}
|
|
537
|
-
if (scope.manager.getLeafId() !== scope.leafId) {
|
|
538
|
-
throw new Error("Pi's active session branch changed while Codex was compacting.");
|
|
539
|
-
}
|
|
540
|
-
scope.manager.appendCompaction(
|
|
541
|
-
markerSummary(),
|
|
542
|
-
firstKeptEntryId,
|
|
543
|
-
scope.contextTokens ?? 0,
|
|
544
|
-
compacted.checkpoint,
|
|
545
|
-
true,
|
|
546
|
-
compacted.usage,
|
|
547
|
-
);
|
|
548
|
-
scope.notify(
|
|
549
|
-
`OpenAI Codex context compacted at ${scope.contextPercent.toFixed(1)}% and will continue.`,
|
|
550
|
-
"info",
|
|
551
|
-
);
|
|
552
|
-
return updateInput(body, compacted.checkpoint.history);
|
|
553
|
-
} finally {
|
|
554
|
-
scope.setStatus(undefined);
|
|
590
|
+
const compacted = await this.performCompaction({
|
|
591
|
+
model,
|
|
592
|
+
requestOptions: options,
|
|
593
|
+
history: split.history,
|
|
594
|
+
postCompactionTail: split.tail,
|
|
595
|
+
template: withoutConversationInput(body),
|
|
596
|
+
instructions:
|
|
597
|
+
typeof body.instructions === "string"
|
|
598
|
+
? body.instructions
|
|
599
|
+
: context.systemPrompt || "You are a helpful assistant.",
|
|
600
|
+
grammarToolInputProperties,
|
|
601
|
+
priority: scope.config.fastMode,
|
|
602
|
+
});
|
|
603
|
+
const firstKeptEntryId = userEntryAfterLastSampled(branch)?.id ?? scope.manager.getLeafId();
|
|
604
|
+
if (!firstKeptEntryId || typeof scope.manager.appendCompaction !== "function") {
|
|
605
|
+
throw new Error("Pi's mutable SessionManager is unavailable for percentage compaction.");
|
|
555
606
|
}
|
|
607
|
+
if (scope.manager.getLeafId() !== scope.leafId) {
|
|
608
|
+
throw new Error("Pi's active session branch changed while Codex was compacting.");
|
|
609
|
+
}
|
|
610
|
+
scope.manager.appendCompaction(
|
|
611
|
+
markerSummary(),
|
|
612
|
+
firstKeptEntryId,
|
|
613
|
+
scope.contextTokens ?? 0,
|
|
614
|
+
compacted.checkpoint,
|
|
615
|
+
true,
|
|
616
|
+
compacted.usage,
|
|
617
|
+
);
|
|
618
|
+
scope.notify(
|
|
619
|
+
`OpenAI Codex context compacted at ${scope.contextPercent.toFixed(1)}% and will continue.`,
|
|
620
|
+
"info",
|
|
621
|
+
);
|
|
622
|
+
return updateInput(body, compacted.checkpoint.history);
|
|
556
623
|
}
|
|
557
624
|
|
|
558
625
|
stream(
|
|
@@ -581,9 +648,12 @@ export class CodexProviderRuntime {
|
|
|
581
648
|
timestamp: Date.now(),
|
|
582
649
|
};
|
|
583
650
|
const runtimeSessionId = requestOptions.sessionId;
|
|
584
|
-
|
|
651
|
+
let releaseRequest = () => {};
|
|
585
652
|
try {
|
|
586
|
-
const
|
|
653
|
+
const accountId = validateCodexAuthentication(model, requestOptions.apiKey);
|
|
654
|
+
releaseRequest = await this.acquireRequest(runtimeSessionId, requestOptions.signal);
|
|
655
|
+
const cacheSessionId =
|
|
656
|
+
requestOptions.cacheRetention === "none" ? undefined : codexCacheKey(runtimeSessionId);
|
|
587
657
|
const grammarToolInputProperties = createGrammarToolInputProperties(
|
|
588
658
|
context.tools,
|
|
589
659
|
(model.compat as CodexCompat | undefined)?.supportsOpenAIGrammarTools ?? false,
|
|
@@ -592,11 +662,12 @@ export class CodexProviderRuntime {
|
|
|
592
662
|
model,
|
|
593
663
|
context,
|
|
594
664
|
requestOptions,
|
|
665
|
+
runtimeSessionId,
|
|
595
666
|
cacheSessionId,
|
|
596
667
|
grammarToolInputProperties,
|
|
597
668
|
);
|
|
598
669
|
const transformed = await requestOptions.onPayload?.(body, model);
|
|
599
|
-
if (
|
|
670
|
+
if (transformed !== undefined) body = transformed as JsonRecord;
|
|
600
671
|
if (runtimeSessionId) {
|
|
601
672
|
this.templates.set(runtimeSessionId, {
|
|
602
673
|
modelId: model.id,
|
|
@@ -614,28 +685,60 @@ export class CodexProviderRuntime {
|
|
|
614
685
|
);
|
|
615
686
|
|
|
616
687
|
const rawItems: ResponsesItem[] = [];
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
688
|
+
let continuationHandle: CodexContinuationHandle | undefined;
|
|
689
|
+
let webSocketResponseHandle: CodexWebSocketResponseHandle | undefined;
|
|
690
|
+
let startEmitted = false;
|
|
691
|
+
const emitStart = () => {
|
|
692
|
+
if (startEmitted) return;
|
|
693
|
+
startEmitted = true;
|
|
694
|
+
stream.push({ type: "start", partial: output });
|
|
695
|
+
};
|
|
696
|
+
const transportRequestOptions = {
|
|
697
|
+
...requestOptions,
|
|
698
|
+
accountId,
|
|
699
|
+
onContinuationReady(handle: CodexContinuationHandle) {
|
|
700
|
+
continuationHandle = handle;
|
|
701
|
+
},
|
|
702
|
+
onWebSocketResponseHandle(handle: CodexWebSocketResponseHandle) {
|
|
703
|
+
webSocketResponseHandle = handle;
|
|
704
|
+
},
|
|
705
|
+
onTransportStart: emitStart,
|
|
706
|
+
onTransportDiagnostic(diagnostic: CodexTransportDiagnostic) {
|
|
707
|
+
output.diagnostics = [...(output.diagnostics ?? []), diagnostic];
|
|
708
|
+
},
|
|
709
|
+
};
|
|
710
|
+
try {
|
|
711
|
+
await processCodexStream(
|
|
712
|
+
startOnFirstEvent(
|
|
713
|
+
captureRawEvents(
|
|
714
|
+
this.transport.request(model, body, transportRequestOptions),
|
|
715
|
+
rawItems,
|
|
716
|
+
),
|
|
717
|
+
emitStart,
|
|
718
|
+
),
|
|
719
|
+
output,
|
|
720
|
+
stream,
|
|
721
|
+
model,
|
|
722
|
+
grammarToolInputProperties,
|
|
723
|
+
{
|
|
724
|
+
applyServiceTierPricing(usage, responseServiceTier) {
|
|
725
|
+
applyServiceTierPricing(usage, model, body.service_tier, responseServiceTier);
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
);
|
|
729
|
+
} catch (error) {
|
|
730
|
+
if (!requestOptions.signal?.aborted) {
|
|
731
|
+
webSocketResponseHandle?.failParsing(error);
|
|
732
|
+
}
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
if (requestOptions.signal?.aborted) throw new Error("Request was aborted");
|
|
736
|
+
try {
|
|
737
|
+
assertSuccessfulOutput(output);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
webSocketResponseHandle?.discard();
|
|
740
|
+
throw error;
|
|
632
741
|
}
|
|
633
|
-
applyServiceTierPricing(
|
|
634
|
-
output.usage,
|
|
635
|
-
model,
|
|
636
|
-
body.service_tier,
|
|
637
|
-
responseMetadata.serviceTier,
|
|
638
|
-
);
|
|
639
742
|
|
|
640
743
|
const compat = model.compat as CodexCompat | undefined;
|
|
641
744
|
const canonicalContext: Context = {
|
|
@@ -667,19 +770,26 @@ export class CodexProviderRuntime {
|
|
|
667
770
|
(item) =>
|
|
668
771
|
item["type"] !== "function_call_output" && item["type"] !== "custom_tool_call_output",
|
|
669
772
|
);
|
|
670
|
-
|
|
773
|
+
const persistNativeItems =
|
|
774
|
+
rawItems.length > 0 && nativeOverrideRequired(rawItems, canonicalItems);
|
|
775
|
+
if (persistNativeItems) {
|
|
671
776
|
if (!output.responseId) throw new Error("Codex response is missing a response id.");
|
|
672
777
|
this.pi.appendEntry(
|
|
673
778
|
NATIVE_RESPONSE_ENTRY_TYPE,
|
|
674
779
|
nativeResponseData(model.id, output.responseId, rawItems),
|
|
675
780
|
);
|
|
676
781
|
}
|
|
782
|
+
const readyContinuation = continuationHandle;
|
|
783
|
+
if (readyContinuation && readyContinuation.responseId === output.responseId) {
|
|
784
|
+
readyContinuation.replaceResponseItems(persistNativeItems ? rawItems : canonicalItems);
|
|
785
|
+
}
|
|
677
786
|
|
|
678
787
|
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
679
788
|
stream.end();
|
|
680
789
|
} catch (error) {
|
|
790
|
+
clearStreamingScratchState(output);
|
|
681
791
|
output.stopReason = requestOptions.signal?.aborted ? "aborted" : "error";
|
|
682
|
-
output.errorMessage =
|
|
792
|
+
output.errorMessage = formatProviderError(error);
|
|
683
793
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
684
794
|
stream.end();
|
|
685
795
|
} finally {
|
|
@@ -694,6 +804,7 @@ export class CodexProviderRuntime {
|
|
|
694
804
|
context: Context,
|
|
695
805
|
options?: SimpleStreamOptions,
|
|
696
806
|
): AssistantMessageEventStream {
|
|
807
|
+
if (!options?.apiKey) throw new Error(`No API key for provider: ${model.provider}`);
|
|
697
808
|
const effort = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
|
|
698
809
|
return this.stream(model, context, {
|
|
699
810
|
...options,
|
|
@@ -705,7 +816,7 @@ export class CodexProviderRuntime {
|
|
|
705
816
|
return this.templates.get(sessionId);
|
|
706
817
|
}
|
|
707
818
|
|
|
708
|
-
compact(options: {
|
|
819
|
+
async compact(options: {
|
|
709
820
|
model: Model<any>;
|
|
710
821
|
requestOptions: OpenAICodexResponsesOptions;
|
|
711
822
|
history: ResponsesItem[];
|
|
@@ -714,13 +825,16 @@ export class CodexProviderRuntime {
|
|
|
714
825
|
template: JsonRecord;
|
|
715
826
|
priority: boolean;
|
|
716
827
|
}): Promise<{ checkpoint: CheckpointData; usage?: Usage }> {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
828
|
+
validateCodexAuthentication(options.model, options.requestOptions.apiKey);
|
|
829
|
+
const release = await this.acquireRequest(
|
|
830
|
+
options.requestOptions.sessionId,
|
|
831
|
+
options.requestOptions.signal,
|
|
832
|
+
);
|
|
833
|
+
try {
|
|
834
|
+
return await this.performCompaction(options);
|
|
835
|
+
} finally {
|
|
836
|
+
release();
|
|
837
|
+
}
|
|
724
838
|
}
|
|
725
839
|
}
|
|
726
840
|
|