pi-openai-codex-compat 0.0.3 → 0.0.4
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 +36 -0
- package/README.md +71 -37
- package/extensions/openai-codex-compat/apply-patch-diff-render.ts +6 -2
- package/extensions/openai-codex-compat/apply-patch-engine.ts +89 -5
- package/extensions/openai-codex-compat/codex-cache-diagnostics.ts +97 -0
- package/extensions/openai-codex-compat/codex-installation.ts +51 -0
- package/extensions/openai-codex-compat/codex-metadata.ts +139 -0
- package/extensions/openai-codex-compat/codex-protocol.ts +1 -0
- package/extensions/openai-codex-compat/codex-provider.ts +524 -58
- package/extensions/openai-codex-compat/codex-stream.ts +52 -26
- package/extensions/openai-codex-compat/codex-thread-lineage.ts +156 -0
- package/extensions/openai-codex-compat/codex-transport.ts +1129 -86
- package/extensions/openai-codex-compat/compaction-checkpoint.ts +2 -2
- package/extensions/openai-codex-compat/config.ts +13 -0
- package/extensions/openai-codex-compat/index.ts +6 -0
- package/extensions/openai-codex-compat/namespaced-tools.ts +2 -0
- package/extensions/openai-codex-compat/output-limit-continuation.ts +151 -0
- package/extensions/openai-codex-compat/remote-compaction.ts +13 -0
- package/extensions/openai-codex-compat/request-options.ts +2 -2
- package/extensions/openai-codex-compat/responses-lite.ts +147 -0
- package/extensions/openai-codex-compat/settings-pane.ts +11 -0
- package/package.json +2 -2
|
@@ -17,7 +17,12 @@ import {
|
|
|
17
17
|
type SimpleStreamOptions,
|
|
18
18
|
type Tool,
|
|
19
19
|
type Usage,
|
|
20
|
+
uuidv7,
|
|
20
21
|
} from "@earendil-works/pi-ai";
|
|
22
|
+
import {
|
|
23
|
+
codexCacheDiagnosticContext,
|
|
24
|
+
type CodexCacheDiagnosticContext,
|
|
25
|
+
} from "./codex-cache-diagnostics.ts";
|
|
21
26
|
import {
|
|
22
27
|
checkpointData,
|
|
23
28
|
providerHistory,
|
|
@@ -35,15 +40,25 @@ import {
|
|
|
35
40
|
type ResponsesItem,
|
|
36
41
|
} from "./codex-protocol.ts";
|
|
37
42
|
import { codexCacheKey } from "./codex-cache-key.ts";
|
|
38
|
-
import {
|
|
43
|
+
import { resolveCodexInstallationId } from "./codex-installation.ts";
|
|
44
|
+
import {
|
|
45
|
+
type CodexCompactionMetadata,
|
|
46
|
+
type CodexMetadataIdentity,
|
|
47
|
+
responsesCompactionV2Metadata,
|
|
48
|
+
withCodexRequestMetadata,
|
|
49
|
+
} from "./codex-metadata.ts";
|
|
50
|
+
import { resolveCodexThreadIdentity, type CodexThreadIdentity } from "./codex-thread-lineage.ts";
|
|
51
|
+
import { applyResponsesLite } from "./responses-lite.ts";
|
|
52
|
+
import { processCodexStream, type CodexStreamAttemptState } from "./codex-stream.ts";
|
|
39
53
|
import {
|
|
40
54
|
CodexTransport,
|
|
55
|
+
CodexTurnState,
|
|
41
56
|
validateCodexAuthentication,
|
|
42
57
|
type CodexContinuationHandle,
|
|
43
58
|
type CodexTransportDiagnostic,
|
|
44
59
|
type CodexWebSocketResponseHandle,
|
|
45
60
|
} from "./codex-transport.ts";
|
|
46
|
-
import type
|
|
61
|
+
import { DEFAULT_CONFIG, type CodexCompatConfig, type ImageDetail } from "./config.ts";
|
|
47
62
|
import { nativeResponseData, NATIVE_RESPONSE_ENTRY_TYPE } from "./native-history.ts";
|
|
48
63
|
import {
|
|
49
64
|
CODEX_NAMESPACED_TOOL_NAMES,
|
|
@@ -94,12 +109,33 @@ type RequestTemplate = {
|
|
|
94
109
|
requestOptions: OpenAICodexResponsesOptions;
|
|
95
110
|
};
|
|
96
111
|
|
|
112
|
+
type ActiveAgentTurn = {
|
|
113
|
+
turnId: string;
|
|
114
|
+
startedAtUnixMs: number;
|
|
115
|
+
turnState: CodexTurnState;
|
|
116
|
+
};
|
|
117
|
+
|
|
97
118
|
type CodexCompat = {
|
|
98
119
|
supportsToolSearch?: boolean;
|
|
99
120
|
supportsStrictMode?: boolean;
|
|
100
121
|
supportsOpenAIGrammarTools?: boolean;
|
|
101
122
|
};
|
|
102
123
|
|
|
124
|
+
type CodexTerminalState = {
|
|
125
|
+
type?: "response.completed" | "response.incomplete" | "response.failed";
|
|
126
|
+
response?: JsonRecord;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type CodexResponseRetryPolicy = {
|
|
130
|
+
maxRetries: number;
|
|
131
|
+
baseDelayMs: number;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const DEFAULT_RESPONSE_RETRY_POLICY: CodexResponseRetryPolicy = {
|
|
135
|
+
maxRetries: 5,
|
|
136
|
+
baseDelayMs: 200,
|
|
137
|
+
};
|
|
138
|
+
|
|
103
139
|
function markerSummary(): string {
|
|
104
140
|
return `OpenAI Codex remote compaction checkpoint (${randomUUID()}).`;
|
|
105
141
|
}
|
|
@@ -154,6 +190,7 @@ function nativeOverrideRequired(
|
|
|
154
190
|
function captureRawEvents(
|
|
155
191
|
events: AsyncIterable<JsonRecord>,
|
|
156
192
|
items: ResponsesItem[],
|
|
193
|
+
terminalState?: CodexTerminalState,
|
|
157
194
|
): AsyncIterable<JsonRecord> {
|
|
158
195
|
return {
|
|
159
196
|
async *[Symbol.asyncIterator]() {
|
|
@@ -162,7 +199,9 @@ function captureRawEvents(
|
|
|
162
199
|
items.push(structuredClone(event.item));
|
|
163
200
|
}
|
|
164
201
|
if (
|
|
165
|
-
(event.type === "response.completed" ||
|
|
202
|
+
(event.type === "response.completed" ||
|
|
203
|
+
event.type === "response.incomplete" ||
|
|
204
|
+
event.type === "response.failed") &&
|
|
166
205
|
isObject(event.response) &&
|
|
167
206
|
Array.isArray(event.response["output"])
|
|
168
207
|
) {
|
|
@@ -171,6 +210,19 @@ function captureRawEvents(
|
|
|
171
210
|
items.splice(0, items.length, ...terminalItems.map((item) => structuredClone(item)));
|
|
172
211
|
}
|
|
173
212
|
}
|
|
213
|
+
if (
|
|
214
|
+
terminalState &&
|
|
215
|
+
(event.type === "response.completed" ||
|
|
216
|
+
event.type === "response.incomplete" ||
|
|
217
|
+
event.type === "response.failed")
|
|
218
|
+
) {
|
|
219
|
+
terminalState.type = event.type;
|
|
220
|
+
if (isObject(event.response)) {
|
|
221
|
+
terminalState.response = structuredClone(event.response);
|
|
222
|
+
} else {
|
|
223
|
+
delete terminalState.response;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
174
226
|
yield event;
|
|
175
227
|
}
|
|
176
228
|
},
|
|
@@ -202,6 +254,92 @@ function clearStreamingScratchState(message: AssistantMessage): void {
|
|
|
202
254
|
}
|
|
203
255
|
}
|
|
204
256
|
|
|
257
|
+
function emptyUsage(): Usage {
|
|
258
|
+
return {
|
|
259
|
+
input: 0,
|
|
260
|
+
output: 0,
|
|
261
|
+
cacheRead: 0,
|
|
262
|
+
cacheWrite: 0,
|
|
263
|
+
totalTokens: 0,
|
|
264
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function discardIncompleteAttemptContent(
|
|
269
|
+
message: AssistantMessage,
|
|
270
|
+
attempt: CodexStreamAttemptState,
|
|
271
|
+
): void {
|
|
272
|
+
const incomplete = [...attempt.startedContentIndexes]
|
|
273
|
+
.filter((index) => !attempt.completedContentIndexes.has(index))
|
|
274
|
+
.sort((left, right) => right - left);
|
|
275
|
+
for (const index of incomplete) message.content.splice(index, 1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function accumulateUsage(previous: Usage, current: Usage): Usage {
|
|
279
|
+
const previousReasoning = previous.reasoning;
|
|
280
|
+
const currentReasoning = current.reasoning;
|
|
281
|
+
return {
|
|
282
|
+
input: previous.input + current.input,
|
|
283
|
+
output: previous.output + current.output,
|
|
284
|
+
cacheRead: previous.cacheRead + current.cacheRead,
|
|
285
|
+
cacheWrite: previous.cacheWrite + current.cacheWrite,
|
|
286
|
+
...(previousReasoning === undefined && currentReasoning === undefined
|
|
287
|
+
? {}
|
|
288
|
+
: { reasoning: (previousReasoning ?? 0) + (currentReasoning ?? 0) }),
|
|
289
|
+
totalTokens: previous.totalTokens + current.totalTokens,
|
|
290
|
+
cost: {
|
|
291
|
+
input: previous.cost.input + current.cost.input,
|
|
292
|
+
output: previous.cost.output + current.cost.output,
|
|
293
|
+
cacheRead: previous.cost.cacheRead + current.cost.cacheRead,
|
|
294
|
+
cacheWrite: previous.cost.cacheWrite + current.cost.cacheWrite,
|
|
295
|
+
total: previous.cost.total + current.cost.total,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function retryableResponseFailure(response: JsonRecord | undefined): boolean {
|
|
301
|
+
const error = isObject(response?.["error"]) ? response["error"] : undefined;
|
|
302
|
+
const code = typeof error?.["code"] === "string" ? error["code"].toLowerCase() : "";
|
|
303
|
+
return !(
|
|
304
|
+
code === "context_length_exceeded" ||
|
|
305
|
+
code === "insufficient_quota" ||
|
|
306
|
+
code === "usage_not_included" ||
|
|
307
|
+
code === "cyber_policy" ||
|
|
308
|
+
code === "invalid_prompt" ||
|
|
309
|
+
code === "bio_policy"
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function responseRetryDelayMs(baseDelayMs: number, attempt: number): number {
|
|
314
|
+
if (baseDelayMs <= 0) return 0;
|
|
315
|
+
const exponential = baseDelayMs * 2 ** Math.max(0, attempt - 1);
|
|
316
|
+
return Math.floor(exponential * (0.9 + Math.random() * 0.2));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function waitForResponseRetry(milliseconds: number, signal?: AbortSignal): Promise<void> {
|
|
320
|
+
if (signal?.aborted) return Promise.reject(new Error("Request was aborted"));
|
|
321
|
+
if (milliseconds <= 0) return Promise.resolve();
|
|
322
|
+
return new Promise((resolve, reject) => {
|
|
323
|
+
const onAbort = () => {
|
|
324
|
+
clearTimeout(timer);
|
|
325
|
+
reject(new Error("Request was aborted"));
|
|
326
|
+
};
|
|
327
|
+
const timer = setTimeout(() => {
|
|
328
|
+
signal?.removeEventListener("abort", onAbort);
|
|
329
|
+
resolve();
|
|
330
|
+
}, milliseconds);
|
|
331
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function continueResponseBody(
|
|
336
|
+
body: JsonRecord,
|
|
337
|
+
responseItems: readonly ResponsesItem[],
|
|
338
|
+
): JsonRecord | undefined {
|
|
339
|
+
if (!Array.isArray(body.input) || !body.input.every(isResponsesItem)) return undefined;
|
|
340
|
+
return updateInput(body, [...body.input, ...responseItems]);
|
|
341
|
+
}
|
|
342
|
+
|
|
205
343
|
function updateInput(payload: JsonRecord, input: readonly ResponsesItem[]): JsonRecord {
|
|
206
344
|
const result: JsonRecord = {
|
|
207
345
|
...payload,
|
|
@@ -305,13 +443,38 @@ export class CodexProviderRuntime {
|
|
|
305
443
|
readonly transport = new CodexTransport();
|
|
306
444
|
private readonly scopes = new Map<string, RuntimeScope>();
|
|
307
445
|
private readonly templates = new Map<string, RequestTemplate>();
|
|
446
|
+
private readonly prewarmedTemplates = new Set<string>();
|
|
308
447
|
private readonly requestTails = new Map<string, Promise<void>>();
|
|
448
|
+
private readonly activeAgentTurns = new Map<string, ActiveAgentTurn>();
|
|
449
|
+
private readonly windowNumbers = new Map<string, number>();
|
|
450
|
+
private readonly activeThreadIds = new Map<string, string>();
|
|
309
451
|
private readonly pi: ExtensionAPI;
|
|
310
452
|
private readonly resolveConfig: ConfigResolver;
|
|
453
|
+
private readonly installationId: string;
|
|
454
|
+
private readonly responseRetryPolicy: CodexResponseRetryPolicy;
|
|
311
455
|
|
|
312
|
-
constructor(
|
|
456
|
+
constructor(
|
|
457
|
+
pi: ExtensionAPI,
|
|
458
|
+
resolveConfig: ConfigResolver,
|
|
459
|
+
installationId: string = randomUUID(),
|
|
460
|
+
responseRetryPolicy: Partial<CodexResponseRetryPolicy> = {},
|
|
461
|
+
) {
|
|
313
462
|
this.pi = pi;
|
|
314
463
|
this.resolveConfig = resolveConfig;
|
|
464
|
+
this.installationId = installationId;
|
|
465
|
+
const maxRetries = responseRetryPolicy.maxRetries ?? DEFAULT_RESPONSE_RETRY_POLICY.maxRetries;
|
|
466
|
+
const baseDelayMs =
|
|
467
|
+
responseRetryPolicy.baseDelayMs ?? DEFAULT_RESPONSE_RETRY_POLICY.baseDelayMs;
|
|
468
|
+
if (!Number.isFinite(maxRetries) || maxRetries < 0) {
|
|
469
|
+
throw new Error(`Invalid Codex response maxRetries: ${String(maxRetries)}`);
|
|
470
|
+
}
|
|
471
|
+
if (!Number.isFinite(baseDelayMs) || baseDelayMs < 0) {
|
|
472
|
+
throw new Error(`Invalid Codex response baseDelayMs: ${String(baseDelayMs)}`);
|
|
473
|
+
}
|
|
474
|
+
this.responseRetryPolicy = {
|
|
475
|
+
maxRetries: Math.floor(maxRetries),
|
|
476
|
+
baseDelayMs: Math.floor(baseDelayMs),
|
|
477
|
+
};
|
|
315
478
|
}
|
|
316
479
|
|
|
317
480
|
captureScope(ctx: ExtensionContext): void {
|
|
@@ -330,13 +493,154 @@ export class CodexProviderRuntime {
|
|
|
330
493
|
});
|
|
331
494
|
}
|
|
332
495
|
|
|
496
|
+
beginAgentTurn(ctx: ExtensionContext): void {
|
|
497
|
+
this.activeAgentTurns.set(ctx.sessionManager.getSessionId(), {
|
|
498
|
+
turnId: uuidv7(),
|
|
499
|
+
startedAtUnixMs: Date.now(),
|
|
500
|
+
turnState: new CodexTurnState(),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
endAgentTurn(ctx: ExtensionContext): void {
|
|
505
|
+
this.activeAgentTurns.delete(ctx.sessionManager.getSessionId());
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
updateSessionConfig(sessionId: string, config: CodexCompatConfig): void {
|
|
509
|
+
const scope = this.scopes.get(sessionId);
|
|
510
|
+
if (scope) scope.config = config;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private responsesLiteEnabled(sessionId: string | undefined): boolean {
|
|
514
|
+
return (
|
|
515
|
+
(sessionId ? this.scopes.get(sessionId)?.config.responsesLite : undefined) ??
|
|
516
|
+
DEFAULT_CONFIG.responsesLite
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private agentTurn(sessionId: string | undefined): ActiveAgentTurn {
|
|
521
|
+
return (
|
|
522
|
+
(sessionId ? this.activeAgentTurns.get(sessionId) : undefined) ?? {
|
|
523
|
+
turnId: uuidv7(),
|
|
524
|
+
startedAtUnixMs: Date.now(),
|
|
525
|
+
turnState: new CodexTurnState(),
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private metadataIdentity(
|
|
531
|
+
metadataSessionId: string | undefined,
|
|
532
|
+
turn?: ActiveAgentTurn,
|
|
533
|
+
runtimeSessionId = metadataSessionId,
|
|
534
|
+
): CodexMetadataIdentity {
|
|
535
|
+
const thread: Partial<CodexThreadIdentity> = runtimeSessionId
|
|
536
|
+
? this.threadIdentity(runtimeSessionId)
|
|
537
|
+
: metadataSessionId
|
|
538
|
+
? { threadId: metadataSessionId }
|
|
539
|
+
: {};
|
|
540
|
+
const windowKey =
|
|
541
|
+
runtimeSessionId && thread.threadId ? `${runtimeSessionId}\0${thread.threadId}` : undefined;
|
|
542
|
+
return {
|
|
543
|
+
installationId: this.installationId,
|
|
544
|
+
...(thread.threadId ? { threadId: thread.threadId } : {}),
|
|
545
|
+
...(thread.forkedFromThreadId ? { forkedFromThreadId: thread.forkedFromThreadId } : {}),
|
|
546
|
+
windowNumber: windowKey ? (this.windowNumbers.get(windowKey) ?? 0) : 0,
|
|
547
|
+
...(turn ? { turnStartedAtUnixMs: turn.startedAtUnixMs } : {}),
|
|
548
|
+
threadSource: "user",
|
|
549
|
+
// Pi extensions execute without Codex's platform sandbox.
|
|
550
|
+
sandbox: "none",
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private threadIdentity(sessionId: string): CodexThreadIdentity {
|
|
555
|
+
const branch = this.scopes.get(sessionId)?.manager.getBranch() as SessionEntry[] | undefined;
|
|
556
|
+
return resolveCodexThreadIdentity(sessionId, branch ?? []);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private clearPrewarmState(sessionId: string): void {
|
|
560
|
+
for (const key of this.prewarmedTemplates) {
|
|
561
|
+
if (key.startsWith(`${sessionId}\0`)) this.prewarmedTemplates.delete(key);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private activateThread(sessionId: string | undefined): void {
|
|
566
|
+
if (!sessionId) return;
|
|
567
|
+
const threadId = this.threadIdentity(sessionId).threadId;
|
|
568
|
+
const previous = this.activeThreadIds.get(sessionId);
|
|
569
|
+
if (previous && previous !== threadId) {
|
|
570
|
+
this.transport.close(sessionId);
|
|
571
|
+
this.clearPrewarmState(sessionId);
|
|
572
|
+
}
|
|
573
|
+
this.activeThreadIds.set(sessionId, threadId);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private advanceWindow(sessionId: string): void {
|
|
577
|
+
const threadId = this.threadIdentity(sessionId).threadId;
|
|
578
|
+
const key = `${sessionId}\0${threadId}`;
|
|
579
|
+
this.windowNumbers.set(key, (this.windowNumbers.get(key) ?? 0) + 1);
|
|
580
|
+
}
|
|
581
|
+
|
|
333
582
|
clearSession(sessionId: string): void {
|
|
334
583
|
this.scopes.delete(sessionId);
|
|
335
584
|
this.templates.delete(sessionId);
|
|
585
|
+
this.clearPrewarmState(sessionId);
|
|
336
586
|
this.requestTails.delete(sessionId);
|
|
587
|
+
this.activeAgentTurns.delete(sessionId);
|
|
588
|
+
for (const key of this.windowNumbers.keys()) {
|
|
589
|
+
if (key.startsWith(`${sessionId}\0`)) this.windowNumbers.delete(key);
|
|
590
|
+
}
|
|
591
|
+
this.activeThreadIds.delete(sessionId);
|
|
337
592
|
this.transport.close(sessionId);
|
|
338
593
|
}
|
|
339
594
|
|
|
595
|
+
private async maybePrewarm(options: {
|
|
596
|
+
model: Model<any>;
|
|
597
|
+
body: JsonRecord;
|
|
598
|
+
fullBody: JsonRecord;
|
|
599
|
+
requestOptions: OpenAICodexResponsesOptions;
|
|
600
|
+
accountId: string;
|
|
601
|
+
diagnostics: CodexTransportDiagnostic[];
|
|
602
|
+
turnState: CodexTurnState;
|
|
603
|
+
cacheDiagnostics: CodexCacheDiagnosticContext;
|
|
604
|
+
}): Promise<void> {
|
|
605
|
+
const sessionId = options.requestOptions.sessionId;
|
|
606
|
+
if (
|
|
607
|
+
!sessionId ||
|
|
608
|
+
options.requestOptions.cacheRetention === "none" ||
|
|
609
|
+
options.requestOptions.transport === "sse" ||
|
|
610
|
+
!Array.isArray(options.body.input) ||
|
|
611
|
+
!Array.isArray(options.fullBody.input)
|
|
612
|
+
) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const key = `${sessionId}\0${options.model.id}\0${options.cacheDiagnostics.envelope}`;
|
|
616
|
+
if (this.prewarmedTemplates.has(key)) return;
|
|
617
|
+
this.prewarmedTemplates.add(key);
|
|
618
|
+
|
|
619
|
+
const cacheSessionId = codexCacheKey(sessionId);
|
|
620
|
+
const prewarmBody = withCodexRequestMetadata(
|
|
621
|
+
options.body,
|
|
622
|
+
cacheSessionId,
|
|
623
|
+
{ kind: "prewarm" },
|
|
624
|
+
"",
|
|
625
|
+
this.metadataIdentity(cacheSessionId, undefined, sessionId),
|
|
626
|
+
);
|
|
627
|
+
try {
|
|
628
|
+
await this.transport.prewarm(options.model, prewarmBody, {
|
|
629
|
+
...options.requestOptions,
|
|
630
|
+
accountId: options.accountId,
|
|
631
|
+
turnState: options.turnState,
|
|
632
|
+
cacheDiagnostics: options.cacheDiagnostics,
|
|
633
|
+
requestKind: "prewarm",
|
|
634
|
+
onTransportDiagnostic(diagnostic) {
|
|
635
|
+
options.diagnostics.push(diagnostic);
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
} catch {
|
|
639
|
+
// Warmup is best-effort. The transport has already activated sticky SSE
|
|
640
|
+
// after exhausting its WebSocket retry budget.
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
340
644
|
private async acquireRequest(
|
|
341
645
|
sessionId: string | undefined,
|
|
342
646
|
signal?: AbortSignal,
|
|
@@ -412,7 +716,7 @@ export class CodexProviderRuntime {
|
|
|
412
716
|
grammarToolInputProperties,
|
|
413
717
|
deferredTools: splitDeferredTools(context, Boolean(compat?.supportsToolSearch)).deferred,
|
|
414
718
|
toolOptions: {
|
|
415
|
-
strict:
|
|
719
|
+
strict: false,
|
|
416
720
|
supportsStrictMode: compat?.supportsStrictMode ?? true,
|
|
417
721
|
supportsOpenAIGrammarTools: compat?.supportsOpenAIGrammarTools ?? false,
|
|
418
722
|
},
|
|
@@ -438,10 +742,11 @@ export class CodexProviderRuntime {
|
|
|
438
742
|
runtimeSessionId: string | undefined,
|
|
439
743
|
cacheSessionId: string | undefined,
|
|
440
744
|
grammarToolInputProperties: GrammarToolInputProperties,
|
|
745
|
+
turnId: string,
|
|
441
746
|
): JsonRecord {
|
|
442
747
|
const compat = model.compat as CodexCompat | undefined;
|
|
443
748
|
const toolPlacement = splitDeferredTools(context, Boolean(compat?.supportsToolSearch));
|
|
444
|
-
|
|
749
|
+
let body: JsonRecord = {
|
|
445
750
|
model: model.id,
|
|
446
751
|
store: false,
|
|
447
752
|
stream: true,
|
|
@@ -456,12 +761,23 @@ export class CodexProviderRuntime {
|
|
|
456
761
|
prompt_cache_key: cacheSessionId,
|
|
457
762
|
tool_choice: options.toolChoice ?? "auto",
|
|
458
763
|
parallel_tool_calls: true,
|
|
764
|
+
tools: [],
|
|
459
765
|
};
|
|
460
|
-
|
|
766
|
+
body = withCodexRequestMetadata(
|
|
767
|
+
body,
|
|
768
|
+
cacheSessionId,
|
|
769
|
+
{ kind: "turn" },
|
|
770
|
+
turnId,
|
|
771
|
+
this.metadataIdentity(
|
|
772
|
+
cacheSessionId,
|
|
773
|
+
this.activeAgentTurns.get(runtimeSessionId ?? ""),
|
|
774
|
+
runtimeSessionId,
|
|
775
|
+
),
|
|
776
|
+
);
|
|
461
777
|
if (options.serviceTier !== undefined) body.service_tier = options.serviceTier;
|
|
462
778
|
if (toolPlacement.immediate.length > 0) {
|
|
463
779
|
body.tools = convertResponsesTools(toolPlacement.immediate, {
|
|
464
|
-
strict:
|
|
780
|
+
strict: false,
|
|
465
781
|
supportsStrictMode: compat?.supportsStrictMode ?? true,
|
|
466
782
|
supportsOpenAIGrammarTools: compat?.supportsOpenAIGrammarTools ?? false,
|
|
467
783
|
namespacedToolNames: CODEX_NAMESPACED_TOOL_NAMES,
|
|
@@ -491,21 +807,51 @@ export class CodexProviderRuntime {
|
|
|
491
807
|
instructions: string;
|
|
492
808
|
grammarToolInputProperties: GrammarToolInputProperties;
|
|
493
809
|
priority: boolean;
|
|
810
|
+
compactionMetadata: CodexCompactionMetadata;
|
|
811
|
+
agentTurn?: ActiveAgentTurn;
|
|
812
|
+
responsesLiteEnabled?: boolean;
|
|
494
813
|
}): Promise<{ checkpoint: CheckpointData; usage?: Usage }> {
|
|
495
814
|
const sessionId = options.requestOptions.sessionId;
|
|
496
815
|
if (!sessionId) throw new Error("Codex compaction requires a Pi session id.");
|
|
816
|
+
this.activateThread(sessionId);
|
|
817
|
+
const agentTurn = options.agentTurn ?? this.agentTurn(sessionId);
|
|
818
|
+
const responsesLiteEnabled =
|
|
819
|
+
options.responsesLiteEnabled ?? this.responsesLiteEnabled(sessionId);
|
|
497
820
|
const accountId = validateCodexAuthentication(options.model, options.requestOptions.apiKey);
|
|
498
|
-
const payload =
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
821
|
+
const payload = withCodexRequestMetadata(
|
|
822
|
+
remoteCompactionPayload({
|
|
823
|
+
template: options.template,
|
|
824
|
+
modelId: options.model.id,
|
|
825
|
+
history: options.history,
|
|
826
|
+
instructions: options.instructions,
|
|
827
|
+
sessionId:
|
|
828
|
+
options.requestOptions.cacheRetention === "none" ? undefined : codexCacheKey(sessionId),
|
|
829
|
+
priority: options.priority,
|
|
830
|
+
}),
|
|
831
|
+
options.requestOptions.cacheRetention === "none" ? undefined : codexCacheKey(sessionId),
|
|
832
|
+
{ kind: "compaction", compaction: options.compactionMetadata },
|
|
833
|
+
agentTurn.turnId,
|
|
834
|
+
this.metadataIdentity(
|
|
504
835
|
options.requestOptions.cacheRetention === "none" ? undefined : codexCacheKey(sessionId),
|
|
505
|
-
|
|
506
|
-
|
|
836
|
+
agentTurn,
|
|
837
|
+
sessionId,
|
|
838
|
+
),
|
|
839
|
+
);
|
|
507
840
|
const transformed = await options.requestOptions.onPayload?.(payload, options.model);
|
|
508
|
-
const
|
|
841
|
+
const ordinaryRequest = transformed === undefined ? payload : (transformed as JsonRecord);
|
|
842
|
+
const request = applyResponsesLite(ordinaryRequest, options.model.id, responsesLiteEnabled);
|
|
843
|
+
const staticRequest = applyResponsesLite(
|
|
844
|
+
updateInput(ordinaryRequest, []),
|
|
845
|
+
options.model.id,
|
|
846
|
+
responsesLiteEnabled,
|
|
847
|
+
);
|
|
848
|
+
const cacheDiagnostics = codexCacheDiagnosticContext(
|
|
849
|
+
ordinaryRequest,
|
|
850
|
+
request,
|
|
851
|
+
staticRequest,
|
|
852
|
+
options.model.id,
|
|
853
|
+
responsesLiteEnabled,
|
|
854
|
+
);
|
|
509
855
|
let webSocketResponseHandle: CodexWebSocketResponseHandle | undefined;
|
|
510
856
|
let compacted: Awaited<ReturnType<typeof collectRemoteCompaction>>;
|
|
511
857
|
try {
|
|
@@ -513,6 +859,9 @@ export class CodexProviderRuntime {
|
|
|
513
859
|
this.transport.request(options.model, request, {
|
|
514
860
|
...options.requestOptions,
|
|
515
861
|
accountId,
|
|
862
|
+
requestKind: "compaction",
|
|
863
|
+
turnState: agentTurn.turnState,
|
|
864
|
+
cacheDiagnostics,
|
|
516
865
|
onWebSocketResponseHandle(handle) {
|
|
517
866
|
webSocketResponseHandle = handle;
|
|
518
867
|
},
|
|
@@ -543,12 +892,16 @@ export class CodexProviderRuntime {
|
|
|
543
892
|
options: OpenAICodexResponsesOptions,
|
|
544
893
|
body: JsonRecord,
|
|
545
894
|
grammarToolInputProperties: GrammarToolInputProperties,
|
|
895
|
+
agentTurn: ActiveAgentTurn,
|
|
896
|
+
responsesLiteEnabled: boolean,
|
|
546
897
|
): Promise<JsonRecord> {
|
|
547
898
|
const sessionId = options.sessionId;
|
|
548
899
|
const scope = sessionId ? this.scopes.get(sessionId) : undefined;
|
|
549
900
|
const threshold = scope?.config.autoCompactAtPercent;
|
|
901
|
+
if (!sessionId || !scope) {
|
|
902
|
+
return body;
|
|
903
|
+
}
|
|
550
904
|
if (
|
|
551
|
-
!scope ||
|
|
552
905
|
threshold === undefined ||
|
|
553
906
|
scope.contextPercent === null ||
|
|
554
907
|
scope.contextPercent < threshold ||
|
|
@@ -599,6 +952,9 @@ export class CodexProviderRuntime {
|
|
|
599
952
|
: context.systemPrompt || "You are a helpful assistant.",
|
|
600
953
|
grammarToolInputProperties,
|
|
601
954
|
priority: scope.config.fastMode,
|
|
955
|
+
compactionMetadata: responsesCompactionV2Metadata("auto", "context_limit", "pre_turn"),
|
|
956
|
+
agentTurn,
|
|
957
|
+
responsesLiteEnabled,
|
|
602
958
|
});
|
|
603
959
|
const firstKeptEntryId = userEntryAfterLastSampled(branch)?.id ?? scope.manager.getLeafId();
|
|
604
960
|
if (!firstKeptEntryId || typeof scope.manager.appendCompaction !== "function") {
|
|
@@ -619,7 +975,15 @@ export class CodexProviderRuntime {
|
|
|
619
975
|
`OpenAI Codex context compacted at ${scope.contextPercent.toFixed(1)}% and will continue.`,
|
|
620
976
|
"info",
|
|
621
977
|
);
|
|
622
|
-
|
|
978
|
+
this.advanceWindow(sessionId);
|
|
979
|
+
const cacheSessionId = options.cacheRetention === "none" ? undefined : codexCacheKey(sessionId);
|
|
980
|
+
return withCodexRequestMetadata(
|
|
981
|
+
updateInput(body, compacted.checkpoint.history),
|
|
982
|
+
cacheSessionId,
|
|
983
|
+
{ kind: "turn" },
|
|
984
|
+
agentTurn.turnId,
|
|
985
|
+
this.metadataIdentity(cacheSessionId, agentTurn, sessionId),
|
|
986
|
+
);
|
|
623
987
|
}
|
|
624
988
|
|
|
625
989
|
stream(
|
|
@@ -636,14 +1000,7 @@ export class CodexProviderRuntime {
|
|
|
636
1000
|
api: CODEX_API,
|
|
637
1001
|
provider: model.provider,
|
|
638
1002
|
model: model.id,
|
|
639
|
-
usage:
|
|
640
|
-
input: 0,
|
|
641
|
-
output: 0,
|
|
642
|
-
cacheRead: 0,
|
|
643
|
-
cacheWrite: 0,
|
|
644
|
-
totalTokens: 0,
|
|
645
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
646
|
-
},
|
|
1003
|
+
usage: emptyUsage(),
|
|
647
1004
|
stopReason: "pending",
|
|
648
1005
|
timestamp: Date.now(),
|
|
649
1006
|
};
|
|
@@ -652,8 +1009,11 @@ export class CodexProviderRuntime {
|
|
|
652
1009
|
try {
|
|
653
1010
|
const accountId = validateCodexAuthentication(model, requestOptions.apiKey);
|
|
654
1011
|
releaseRequest = await this.acquireRequest(runtimeSessionId, requestOptions.signal);
|
|
1012
|
+
this.activateThread(runtimeSessionId);
|
|
655
1013
|
const cacheSessionId =
|
|
656
1014
|
requestOptions.cacheRetention === "none" ? undefined : codexCacheKey(runtimeSessionId);
|
|
1015
|
+
const agentTurn = this.agentTurn(runtimeSessionId);
|
|
1016
|
+
const responsesLiteEnabled = this.responsesLiteEnabled(runtimeSessionId);
|
|
657
1017
|
const grammarToolInputProperties = createGrammarToolInputProperties(
|
|
658
1018
|
context.tools,
|
|
659
1019
|
(model.compat as CodexCompat | undefined)?.supportsOpenAIGrammarTools ?? false,
|
|
@@ -665,6 +1025,7 @@ export class CodexProviderRuntime {
|
|
|
665
1025
|
runtimeSessionId,
|
|
666
1026
|
cacheSessionId,
|
|
667
1027
|
grammarToolInputProperties,
|
|
1028
|
+
agentTurn.turnId,
|
|
668
1029
|
);
|
|
669
1030
|
const transformed = await requestOptions.onPayload?.(body, model);
|
|
670
1031
|
if (transformed !== undefined) body = transformed as JsonRecord;
|
|
@@ -682,9 +1043,37 @@ export class CodexProviderRuntime {
|
|
|
682
1043
|
requestOptions,
|
|
683
1044
|
body,
|
|
684
1045
|
grammarToolInputProperties,
|
|
1046
|
+
agentTurn,
|
|
1047
|
+
responsesLiteEnabled,
|
|
1048
|
+
);
|
|
1049
|
+
const ordinaryBody = body;
|
|
1050
|
+
const staticBody = applyResponsesLite(
|
|
1051
|
+
updateInput(ordinaryBody, []),
|
|
1052
|
+
model.id,
|
|
1053
|
+
responsesLiteEnabled,
|
|
1054
|
+
);
|
|
1055
|
+
body = applyResponsesLite(ordinaryBody, model.id, responsesLiteEnabled);
|
|
1056
|
+
const cacheDiagnostics = codexCacheDiagnosticContext(
|
|
1057
|
+
ordinaryBody,
|
|
1058
|
+
body,
|
|
1059
|
+
staticBody,
|
|
1060
|
+
model.id,
|
|
1061
|
+
responsesLiteEnabled,
|
|
685
1062
|
);
|
|
686
1063
|
|
|
687
1064
|
const rawItems: ResponsesItem[] = [];
|
|
1065
|
+
const prewarmDiagnostics: CodexTransportDiagnostic[] = [];
|
|
1066
|
+
await this.maybePrewarm({
|
|
1067
|
+
model,
|
|
1068
|
+
body: staticBody,
|
|
1069
|
+
fullBody: body,
|
|
1070
|
+
requestOptions,
|
|
1071
|
+
accountId,
|
|
1072
|
+
diagnostics: prewarmDiagnostics,
|
|
1073
|
+
turnState: agentTurn.turnState,
|
|
1074
|
+
cacheDiagnostics,
|
|
1075
|
+
});
|
|
1076
|
+
if (prewarmDiagnostics.length > 0) output.diagnostics = prewarmDiagnostics;
|
|
688
1077
|
let continuationHandle: CodexContinuationHandle | undefined;
|
|
689
1078
|
let webSocketResponseHandle: CodexWebSocketResponseHandle | undefined;
|
|
690
1079
|
let startEmitted = false;
|
|
@@ -696,6 +1085,9 @@ export class CodexProviderRuntime {
|
|
|
696
1085
|
const transportRequestOptions = {
|
|
697
1086
|
...requestOptions,
|
|
698
1087
|
accountId,
|
|
1088
|
+
requestKind: "turn" as const,
|
|
1089
|
+
turnState: agentTurn.turnState,
|
|
1090
|
+
cacheDiagnostics,
|
|
699
1091
|
onContinuationReady(handle: CodexContinuationHandle) {
|
|
700
1092
|
continuationHandle = handle;
|
|
701
1093
|
},
|
|
@@ -707,38 +1099,95 @@ export class CodexProviderRuntime {
|
|
|
707
1099
|
output.diagnostics = [...(output.diagnostics ?? []), diagnostic];
|
|
708
1100
|
},
|
|
709
1101
|
};
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1102
|
+
let responseRequests = 0;
|
|
1103
|
+
let responseRetries = 0;
|
|
1104
|
+
while (true) {
|
|
1105
|
+
responseRequests += 1;
|
|
1106
|
+
const attemptItems: ResponsesItem[] = [];
|
|
1107
|
+
const terminalState: CodexTerminalState = {};
|
|
1108
|
+
const attemptState: CodexStreamAttemptState = {
|
|
1109
|
+
startedContentIndexes: new Set(),
|
|
1110
|
+
completedContentIndexes: new Set(),
|
|
1111
|
+
};
|
|
1112
|
+
const usageBeforeAttempt = structuredClone(output.usage);
|
|
1113
|
+
output.usage = emptyUsage();
|
|
1114
|
+
try {
|
|
1115
|
+
await processCodexStream(
|
|
1116
|
+
startOnFirstEvent(
|
|
1117
|
+
captureRawEvents(
|
|
1118
|
+
this.transport.request(model, body, transportRequestOptions),
|
|
1119
|
+
attemptItems,
|
|
1120
|
+
terminalState,
|
|
1121
|
+
),
|
|
1122
|
+
emitStart,
|
|
716
1123
|
),
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1124
|
+
output,
|
|
1125
|
+
stream,
|
|
1126
|
+
model,
|
|
1127
|
+
grammarToolInputProperties,
|
|
1128
|
+
{
|
|
1129
|
+
attemptState,
|
|
1130
|
+
applyServiceTierPricing(usage, responseServiceTier) {
|
|
1131
|
+
applyServiceTierPricing(usage, model, body.service_tier, responseServiceTier);
|
|
1132
|
+
},
|
|
726
1133
|
},
|
|
727
|
-
|
|
1134
|
+
);
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
output.usage = usageBeforeAttempt;
|
|
1137
|
+
if (!requestOptions.signal?.aborted) {
|
|
1138
|
+
webSocketResponseHandle?.failParsing(error);
|
|
1139
|
+
}
|
|
1140
|
+
throw error;
|
|
1141
|
+
}
|
|
1142
|
+
output.usage = accumulateUsage(usageBeforeAttempt, output.usage);
|
|
1143
|
+
rawItems.push(...attemptItems.map((item) => structuredClone(item)));
|
|
1144
|
+
|
|
1145
|
+
const nextBody = continueResponseBody(body, attemptItems);
|
|
1146
|
+
const attemptHasToolCall = [...attemptState.completedContentIndexes].some(
|
|
1147
|
+
(index) => output.content[index]?.type === "toolCall",
|
|
728
1148
|
);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1149
|
+
discardIncompleteAttemptContent(output, attemptState);
|
|
1150
|
+
const retryableTerminal =
|
|
1151
|
+
terminalState.type === "response.incomplete" ||
|
|
1152
|
+
(terminalState.type === "response.failed" &&
|
|
1153
|
+
retryableResponseFailure(terminalState.response));
|
|
1154
|
+
if (
|
|
1155
|
+
retryableTerminal &&
|
|
1156
|
+
nextBody &&
|
|
1157
|
+
responseRetries < this.responseRetryPolicy.maxRetries
|
|
1158
|
+
) {
|
|
1159
|
+
responseRetries += 1;
|
|
1160
|
+
body = nextBody;
|
|
1161
|
+
output.stopReason = "pending";
|
|
1162
|
+
delete output.errorMessage;
|
|
1163
|
+
delete output.rawStopReason;
|
|
1164
|
+
await waitForResponseRetry(
|
|
1165
|
+
responseRetryDelayMs(this.responseRetryPolicy.baseDelayMs, responseRetries),
|
|
1166
|
+
requestOptions.signal,
|
|
1167
|
+
);
|
|
1168
|
+
continue;
|
|
732
1169
|
}
|
|
733
|
-
|
|
1170
|
+
|
|
1171
|
+
if (
|
|
1172
|
+
terminalState.type === "response.completed" &&
|
|
1173
|
+
terminalState.response?.["end_turn"] === false &&
|
|
1174
|
+
!attemptHasToolCall
|
|
1175
|
+
) {
|
|
1176
|
+
if (!nextBody) {
|
|
1177
|
+
throw new Error(
|
|
1178
|
+
"Codex requested a follow-up response, but its completed output could not be appended to request history.",
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
responseRetries = 0;
|
|
1182
|
+
body = nextBody;
|
|
1183
|
+
output.stopReason = "pending";
|
|
1184
|
+
delete output.errorMessage;
|
|
1185
|
+
delete output.rawStopReason;
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
break;
|
|
734
1189
|
}
|
|
735
1190
|
if (requestOptions.signal?.aborted) throw new Error("Request was aborted");
|
|
736
|
-
try {
|
|
737
|
-
assertSuccessfulOutput(output);
|
|
738
|
-
} catch (error) {
|
|
739
|
-
webSocketResponseHandle?.discard();
|
|
740
|
-
throw error;
|
|
741
|
-
}
|
|
742
1191
|
|
|
743
1192
|
const compat = model.compat as CodexCompat | undefined;
|
|
744
1193
|
const canonicalContext: Context = {
|
|
@@ -755,7 +1204,7 @@ export class CodexProviderRuntime {
|
|
|
755
1204
|
deferredTools: splitDeferredTools(context, Boolean(compat?.supportsToolSearch))
|
|
756
1205
|
.deferred,
|
|
757
1206
|
toolOptions: {
|
|
758
|
-
strict:
|
|
1207
|
+
strict: false,
|
|
759
1208
|
supportsStrictMode: compat?.supportsStrictMode ?? true,
|
|
760
1209
|
supportsOpenAIGrammarTools: compat?.supportsOpenAIGrammarTools ?? false,
|
|
761
1210
|
},
|
|
@@ -771,7 +1220,8 @@ export class CodexProviderRuntime {
|
|
|
771
1220
|
item["type"] !== "function_call_output" && item["type"] !== "custom_tool_call_output",
|
|
772
1221
|
);
|
|
773
1222
|
const persistNativeItems =
|
|
774
|
-
rawItems.length > 0 &&
|
|
1223
|
+
rawItems.length > 0 &&
|
|
1224
|
+
(responseRequests > 1 || nativeOverrideRequired(rawItems, canonicalItems));
|
|
775
1225
|
if (persistNativeItems) {
|
|
776
1226
|
if (!output.responseId) throw new Error("Codex response is missing a response id.");
|
|
777
1227
|
this.pi.appendEntry(
|
|
@@ -779,8 +1229,18 @@ export class CodexProviderRuntime {
|
|
|
779
1229
|
nativeResponseData(model.id, output.responseId, rawItems),
|
|
780
1230
|
);
|
|
781
1231
|
}
|
|
1232
|
+
try {
|
|
1233
|
+
assertSuccessfulOutput(output);
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
webSocketResponseHandle?.discard();
|
|
1236
|
+
throw error;
|
|
1237
|
+
}
|
|
782
1238
|
const readyContinuation = continuationHandle;
|
|
783
|
-
if (
|
|
1239
|
+
if (
|
|
1240
|
+
responseRequests === 1 &&
|
|
1241
|
+
readyContinuation &&
|
|
1242
|
+
readyContinuation.responseId === output.responseId
|
|
1243
|
+
) {
|
|
784
1244
|
readyContinuation.replaceResponseItems(persistNativeItems ? rawItems : canonicalItems);
|
|
785
1245
|
}
|
|
786
1246
|
|
|
@@ -824,6 +1284,7 @@ export class CodexProviderRuntime {
|
|
|
824
1284
|
grammarToolInputProperties: GrammarToolInputProperties;
|
|
825
1285
|
template: JsonRecord;
|
|
826
1286
|
priority: boolean;
|
|
1287
|
+
compactionMetadata: CodexCompactionMetadata;
|
|
827
1288
|
}): Promise<{ checkpoint: CheckpointData; usage?: Usage }> {
|
|
828
1289
|
validateCodexAuthentication(options.model, options.requestOptions.apiKey);
|
|
829
1290
|
const release = await this.acquireRequest(
|
|
@@ -831,7 +1292,10 @@ export class CodexProviderRuntime {
|
|
|
831
1292
|
options.requestOptions.signal,
|
|
832
1293
|
);
|
|
833
1294
|
try {
|
|
834
|
-
|
|
1295
|
+
const compacted = await this.performCompaction(options);
|
|
1296
|
+
const sessionId = options.requestOptions.sessionId;
|
|
1297
|
+
if (sessionId) this.advanceWindow(sessionId);
|
|
1298
|
+
return compacted;
|
|
835
1299
|
} finally {
|
|
836
1300
|
release();
|
|
837
1301
|
}
|
|
@@ -842,7 +1306,7 @@ export function registerCodexProvider(
|
|
|
842
1306
|
pi: ExtensionAPI,
|
|
843
1307
|
resolveConfig: ConfigResolver,
|
|
844
1308
|
): CodexProviderRuntime {
|
|
845
|
-
const runtime = new CodexProviderRuntime(pi, resolveConfig);
|
|
1309
|
+
const runtime = new CodexProviderRuntime(pi, resolveConfig, resolveCodexInstallationId());
|
|
846
1310
|
pi.on("session_start", (_event, ctx) => {
|
|
847
1311
|
const base =
|
|
848
1312
|
ctx.modelRegistry.getRegisteredNativeProvider(CODEX_PROVIDER) ??
|
|
@@ -850,5 +1314,7 @@ export function registerCodexProvider(
|
|
|
850
1314
|
if (!base) throw new Error("Pi's built-in OpenAI Codex provider is unavailable.");
|
|
851
1315
|
pi.registerProvider(runtime.createProvider(base));
|
|
852
1316
|
});
|
|
1317
|
+
pi.on("agent_start", (_event, ctx) => runtime.beginAgentTurn(ctx));
|
|
1318
|
+
pi.on("agent_end", (_event, ctx) => runtime.endAgentTurn(ctx));
|
|
853
1319
|
return runtime;
|
|
854
1320
|
}
|