pi-openai-codex-compat 0.0.7-alpha.0 → 0.0.8
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/APPLY_PATCH_INSTRUCTION_FEEDBACK.md +617 -0
- package/CHANGELOG.md +46 -0
- package/LICENSES/tree-sitter-wasms-MIT.txt +21 -0
- package/LICENSES/web-tree-sitter-MIT.txt +21 -0
- package/README.md +31 -9
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/extensions/openai-codex-compat/apply-patch-diff-render.ts +499 -20
- package/extensions/openai-codex-compat/apply-patch-engine.ts +4085 -491
- package/extensions/openai-codex-compat/apply-patch-matcher.ts +1535 -0
- package/extensions/openai-codex-compat/apply-patch-render.ts +85 -19
- package/extensions/openai-codex-compat/apply-patch.ts +41 -5
- package/extensions/openai-codex-compat/codex-provider.ts +256 -21
- package/extensions/openai-codex-compat/codex-stream.ts +83 -79
- package/extensions/openai-codex-compat/codex-transport.ts +0 -10
- package/extensions/openai-codex-compat/compaction-checkpoint.ts +28 -0
- package/extensions/openai-codex-compat/config.ts +18 -0
- package/extensions/openai-codex-compat/footer.ts +4 -8
- package/extensions/openai-codex-compat/index.ts +4 -1
- package/extensions/openai-codex-compat/remote-compaction.ts +4 -0
- package/extensions/openai-codex-compat/settings-pane.ts +11 -0
- package/extensions/openai-codex-compat/tools.ts +2 -1
- package/package.json +9 -4
|
@@ -40,14 +40,79 @@ type ApplyPatchResult = {
|
|
|
40
40
|
details?: unknown;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
export type ApplyPatchDebugResolver = () => boolean;
|
|
44
|
+
|
|
45
|
+
class ApplyPatchTitleComponent implements Component {
|
|
46
|
+
private readonly text = new Text("", 0, 0);
|
|
47
|
+
private readonly theme: Theme;
|
|
48
|
+
private readonly resolveDebug: ApplyPatchDebugResolver;
|
|
49
|
+
|
|
50
|
+
constructor(theme: Theme, resolveDebug: ApplyPatchDebugResolver) {
|
|
51
|
+
this.theme = theme;
|
|
52
|
+
this.resolveDebug = resolveDebug;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
render(width: number): string[] {
|
|
56
|
+
const title = this.resolveDebug() ? "apply_patch (debug)" : "apply_patch";
|
|
57
|
+
this.text.setText(this.theme.fg("toolTitle", this.theme.bold(title)));
|
|
58
|
+
return this.text.render(width);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
invalidate(): void {
|
|
62
|
+
this.text.invalidate();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function modelFeedback(result: ApplyPatchResult): string | undefined {
|
|
67
|
+
const text = result.content.flatMap((item) =>
|
|
68
|
+
item.type === "text" && typeof item.text === "string" ? [item.text] : [],
|
|
69
|
+
);
|
|
70
|
+
return text.length > 0 ? text.join("\n") : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class ApplyPatchResultComponent implements Component {
|
|
74
|
+
private readonly ordinary: Component;
|
|
75
|
+
private readonly feedback: Container | undefined;
|
|
76
|
+
private readonly expanded: boolean;
|
|
77
|
+
private readonly resolveDebug: ApplyPatchDebugResolver;
|
|
78
|
+
|
|
79
|
+
constructor(
|
|
80
|
+
ordinary: Component,
|
|
81
|
+
result: ApplyPatchResult,
|
|
82
|
+
expanded: boolean,
|
|
83
|
+
resolveDebug: ApplyPatchDebugResolver,
|
|
84
|
+
) {
|
|
85
|
+
this.ordinary = ordinary;
|
|
86
|
+
this.expanded = expanded;
|
|
87
|
+
this.resolveDebug = resolveDebug;
|
|
88
|
+
const feedback = modelFeedback(result);
|
|
89
|
+
if (feedback !== undefined) {
|
|
90
|
+
this.feedback = new Container();
|
|
91
|
+
this.feedback.addChild(new Text(feedback, 0, 0));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
render(width: number): string[] {
|
|
96
|
+
return !this.expanded && this.resolveDebug() && this.feedback
|
|
97
|
+
? this.feedback.render(width)
|
|
98
|
+
: this.ordinary.render(width);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
invalidate(): void {
|
|
102
|
+
this.ordinary.invalidate();
|
|
103
|
+
this.feedback?.invalidate();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
43
107
|
export function renderApplyPatchCall(
|
|
44
108
|
args: ApplyPatchArgs,
|
|
45
109
|
theme: Theme,
|
|
46
110
|
context: ApplyPatchRenderContext,
|
|
47
111
|
resolveBackground: CodexToolBackgroundResolver = () => DEFAULT_CONFIG.toolBackground,
|
|
112
|
+
resolveDebug: ApplyPatchDebugResolver = () => DEFAULT_CONFIG.applyPatchDebug,
|
|
48
113
|
): Component {
|
|
49
114
|
const container = new Container();
|
|
50
|
-
container.addChild(new
|
|
115
|
+
container.addChild(new ApplyPatchTitleComponent(theme, resolveDebug));
|
|
51
116
|
|
|
52
117
|
if (context.isPartial) {
|
|
53
118
|
const state = context.state;
|
|
@@ -96,23 +161,20 @@ export function renderApplyPatchResult(
|
|
|
96
161
|
theme: Theme,
|
|
97
162
|
context: ApplyPatchRenderContext,
|
|
98
163
|
resolveBackground: CodexToolBackgroundResolver = () => DEFAULT_CONFIG.toolBackground,
|
|
164
|
+
resolveDebug: ApplyPatchDebugResolver = () => DEFAULT_CONFIG.applyPatchDebug,
|
|
99
165
|
): Component {
|
|
100
166
|
if (options.isPartial) return new Container();
|
|
101
167
|
|
|
102
168
|
const details = isApplyPatchDetails(result.details) ? result.details : undefined;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
details?.status === "failed" && preview
|
|
106
|
-
? {
|
|
107
|
-
...preview,
|
|
108
|
-
status: "failed" as const,
|
|
109
|
-
...(details.error !== undefined ? { error: details.error } : {}),
|
|
110
|
-
}
|
|
111
|
-
: details;
|
|
112
|
-
|
|
113
|
-
if (renderDetails) {
|
|
169
|
+
|
|
170
|
+
if (details) {
|
|
114
171
|
return new CodexToolSurfaceComponent(
|
|
115
|
-
new
|
|
172
|
+
new ApplyPatchResultComponent(
|
|
173
|
+
new ApplyPatchDiffComponent(details, theme, context.cwd, context.expanded),
|
|
174
|
+
result,
|
|
175
|
+
context.expanded,
|
|
176
|
+
resolveDebug,
|
|
177
|
+
),
|
|
116
178
|
theme,
|
|
117
179
|
{
|
|
118
180
|
background: resolveBackground,
|
|
@@ -124,10 +186,14 @@ export function renderApplyPatchResult(
|
|
|
124
186
|
}
|
|
125
187
|
const text = context.isError ? theme.bold(theme.fg("error", "✘ Failed to apply patch")) : "";
|
|
126
188
|
if (!text) return new Container();
|
|
127
|
-
return new CodexToolSurfaceComponent(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
189
|
+
return new CodexToolSurfaceComponent(
|
|
190
|
+
new ApplyPatchResultComponent(new Text(text, 0, 0), result, context.expanded, resolveDebug),
|
|
191
|
+
theme,
|
|
192
|
+
{
|
|
193
|
+
background: resolveBackground,
|
|
194
|
+
status: "error",
|
|
195
|
+
top: false,
|
|
196
|
+
bottom: true,
|
|
197
|
+
},
|
|
198
|
+
);
|
|
133
199
|
}
|
|
@@ -7,14 +7,30 @@ import {
|
|
|
7
7
|
applyPatch,
|
|
8
8
|
type ApplyPatchDetails,
|
|
9
9
|
ApplyPatchExecutionError,
|
|
10
|
+
ApplyPatchInputError,
|
|
11
|
+
ApplyPatchVerificationError,
|
|
12
|
+
formatApplyPatchFailureSummary,
|
|
10
13
|
formatApplyPatchModelOutput,
|
|
11
14
|
formatApplyPatchSummary,
|
|
12
15
|
} from "./apply-patch-engine.ts";
|
|
13
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
type ApplyPatchDebugResolver,
|
|
18
|
+
renderApplyPatchCall,
|
|
19
|
+
renderApplyPatchResult,
|
|
20
|
+
} from "./apply-patch-render.ts";
|
|
14
21
|
|
|
15
22
|
export {
|
|
16
23
|
applyPatch,
|
|
17
24
|
type ApplyPatchDetails,
|
|
25
|
+
type ApplyPatchExecutionFilesystem,
|
|
26
|
+
type ApplyPatchExecutionHooks,
|
|
27
|
+
type ApplyPatchFailureDetails,
|
|
28
|
+
type ApplyPatchInstructionDetails,
|
|
29
|
+
type ApplyPatchInstructionEffect,
|
|
30
|
+
type ApplyPatchInstructionReason,
|
|
31
|
+
type ApplyPatchInstructionReasonCode,
|
|
32
|
+
type ApplyPatchInstructionStatus,
|
|
33
|
+
type ApplyPatchFinalPathState,
|
|
18
34
|
type AppliedPatchChange,
|
|
19
35
|
ApplyPatchExecutionError,
|
|
20
36
|
ApplyPatchInputError,
|
|
@@ -27,6 +43,7 @@ export {
|
|
|
27
43
|
parsePatchDocument,
|
|
28
44
|
previewPatch,
|
|
29
45
|
} from "./apply-patch-engine.ts";
|
|
46
|
+
export type { FormatterMatchFailureDetails } from "./apply-patch-matcher.ts";
|
|
30
47
|
|
|
31
48
|
export const APPLY_PATCH_TOOL_NAME = "apply_patch";
|
|
32
49
|
export const APPLY_PATCH_INPUT_PROPERTY = "patch";
|
|
@@ -57,6 +74,7 @@ eof_line: "*** End of File" LF
|
|
|
57
74
|
export default function registerApplyPatch(
|
|
58
75
|
pi: ExtensionAPI,
|
|
59
76
|
resolveToolBackground: CodexToolBackgroundResolver = () => DEFAULT_CONFIG.toolBackground,
|
|
77
|
+
resolveDebug: ApplyPatchDebugResolver = () => DEFAULT_CONFIG.applyPatchDebug,
|
|
60
78
|
): void {
|
|
61
79
|
const failedDetails = new Map<string, ApplyPatchDetails>();
|
|
62
80
|
|
|
@@ -115,7 +133,7 @@ export default function registerApplyPatch(
|
|
|
115
133
|
text: formatApplyPatchModelOutput(
|
|
116
134
|
0,
|
|
117
135
|
executionDurationMs(),
|
|
118
|
-
formatApplyPatchSummary(details),
|
|
136
|
+
formatApplyPatchSummary(details, ctx.cwd),
|
|
119
137
|
),
|
|
120
138
|
},
|
|
121
139
|
],
|
|
@@ -125,17 +143,35 @@ export default function registerApplyPatch(
|
|
|
125
143
|
if (error instanceof ApplyPatchExecutionError) {
|
|
126
144
|
failedDetails.set(toolCallId, error.details);
|
|
127
145
|
throw new Error(
|
|
128
|
-
formatApplyPatchModelOutput(
|
|
146
|
+
formatApplyPatchModelOutput(
|
|
147
|
+
1,
|
|
148
|
+
executionDurationMs(),
|
|
149
|
+
formatApplyPatchFailureSummary(error.details, ctx.cwd),
|
|
150
|
+
),
|
|
129
151
|
);
|
|
130
152
|
}
|
|
153
|
+
if (error instanceof ApplyPatchVerificationError) {
|
|
154
|
+
failedDetails.set(toolCallId, error.details);
|
|
155
|
+
throw new Error(formatApplyPatchFailureSummary(error.details, ctx.cwd));
|
|
156
|
+
} else if (error instanceof ApplyPatchInputError && error.details) {
|
|
157
|
+
failedDetails.set(toolCallId, error.details);
|
|
158
|
+
throw new Error(formatApplyPatchFailureSummary(error.details, ctx.cwd));
|
|
159
|
+
}
|
|
131
160
|
throw error;
|
|
132
161
|
}
|
|
133
162
|
},
|
|
134
163
|
renderCall(args, theme, context) {
|
|
135
|
-
return renderApplyPatchCall(args, theme, context, resolveToolBackground);
|
|
164
|
+
return renderApplyPatchCall(args, theme, context, resolveToolBackground, resolveDebug);
|
|
136
165
|
},
|
|
137
166
|
renderResult(result, options, theme, context) {
|
|
138
|
-
return renderApplyPatchResult(
|
|
167
|
+
return renderApplyPatchResult(
|
|
168
|
+
result,
|
|
169
|
+
options,
|
|
170
|
+
theme,
|
|
171
|
+
context,
|
|
172
|
+
resolveToolBackground,
|
|
173
|
+
resolveDebug,
|
|
174
|
+
);
|
|
139
175
|
},
|
|
140
176
|
});
|
|
141
177
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
clampThinkingLevel,
|
|
10
10
|
createAssistantMessageEventStream,
|
|
11
11
|
type AssistantMessage,
|
|
12
|
+
type AssistantMessageDiagnostic,
|
|
12
13
|
type AssistantMessageEventStream,
|
|
13
14
|
type Context,
|
|
14
15
|
type Model,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
providerHistory,
|
|
29
30
|
searchCheckpoint,
|
|
30
31
|
type CheckpointData,
|
|
32
|
+
type CompactionDecision,
|
|
31
33
|
type GrammarToolInputProperties,
|
|
32
34
|
} from "./compaction-checkpoint.ts";
|
|
33
35
|
import {
|
|
@@ -113,6 +115,7 @@ type ActiveAgentTurn = {
|
|
|
113
115
|
turnId: string;
|
|
114
116
|
startedAtUnixMs: number;
|
|
115
117
|
turnState: CodexTurnState;
|
|
118
|
+
pendingPostToolDisposition?: CodexPostToolDisposition;
|
|
116
119
|
};
|
|
117
120
|
|
|
118
121
|
type CodexCompat = {
|
|
@@ -126,6 +129,32 @@ type CodexTerminalState = {
|
|
|
126
129
|
response?: JsonRecord;
|
|
127
130
|
};
|
|
128
131
|
|
|
132
|
+
type CodexAttemptCapture = {
|
|
133
|
+
streamedItems: ResponsesItem[];
|
|
134
|
+
streamedToolCallIndexes: Set<number>;
|
|
135
|
+
streamedCompletedToolCallIndexes: Set<number>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type CodexToolCallAssessment = {
|
|
139
|
+
completedCount: number;
|
|
140
|
+
discardedPartialCount: number;
|
|
141
|
+
hasCompletedCalls: boolean;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
type CodexPostToolDisposition = {
|
|
145
|
+
callIds: string[];
|
|
146
|
+
response?: JsonRecord;
|
|
147
|
+
retryAttempt: number;
|
|
148
|
+
terminalType: "response.incomplete" | "response.failed";
|
|
149
|
+
type: "error" | "retry";
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
type CodexResponseDecision =
|
|
153
|
+
| "continue_no_tools"
|
|
154
|
+
| "retry_original_input"
|
|
155
|
+
| "return_terminal"
|
|
156
|
+
| "return_tool_use";
|
|
157
|
+
|
|
129
158
|
export type CodexResponseRetryPolicy = {
|
|
130
159
|
maxRetries: number;
|
|
131
160
|
baseDelayMs: number;
|
|
@@ -187,27 +216,100 @@ function nativeOverrideRequired(
|
|
|
187
216
|
);
|
|
188
217
|
}
|
|
189
218
|
|
|
219
|
+
function isToolCallItem(item: ResponsesItem): boolean {
|
|
220
|
+
return item.type === "function_call" || item.type === "custom_tool_call";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function eventOutputIndex(event: JsonRecord): number {
|
|
224
|
+
return typeof event["output_index"] === "number" ? event["output_index"] : 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function assessAttemptToolCalls(
|
|
228
|
+
items: readonly ResponsesItem[],
|
|
229
|
+
capture: CodexAttemptCapture,
|
|
230
|
+
): CodexToolCallAssessment {
|
|
231
|
+
const completedCount = items.filter(isToolCallItem).length;
|
|
232
|
+
const discardedPartialCount = [...capture.streamedToolCallIndexes].filter(
|
|
233
|
+
(index) => !capture.streamedCompletedToolCallIndexes.has(index),
|
|
234
|
+
).length;
|
|
235
|
+
return {
|
|
236
|
+
completedCount,
|
|
237
|
+
discardedPartialCount,
|
|
238
|
+
hasCompletedCalls: completedCount > 0,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function outputItemTypeCounts(items: readonly ResponsesItem[]): Record<string, number> {
|
|
243
|
+
const counts: Record<string, number> = {};
|
|
244
|
+
for (const item of items) {
|
|
245
|
+
const type = item.type ?? "message";
|
|
246
|
+
counts[type] = (counts[type] ?? 0) + 1;
|
|
247
|
+
}
|
|
248
|
+
return counts;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function responseDecisionDiagnostic(options: {
|
|
252
|
+
attempt: number;
|
|
253
|
+
attemptItems: readonly ResponsesItem[];
|
|
254
|
+
capture: CodexAttemptCapture;
|
|
255
|
+
decision: CodexResponseDecision;
|
|
256
|
+
incompleteReason?: string;
|
|
257
|
+
postToolDisposition?: "continue" | "error" | "retry";
|
|
258
|
+
terminalState: CodexTerminalState;
|
|
259
|
+
toolCalls: CodexToolCallAssessment;
|
|
260
|
+
}): AssistantMessageDiagnostic | undefined {
|
|
261
|
+
const endTurn =
|
|
262
|
+
typeof options.terminalState.response?.["end_turn"] === "boolean"
|
|
263
|
+
? options.terminalState.response["end_turn"]
|
|
264
|
+
: undefined;
|
|
265
|
+
const nontrivial =
|
|
266
|
+
options.attempt > 1 ||
|
|
267
|
+
options.terminalState.type !== "response.completed" ||
|
|
268
|
+
endTurn === false ||
|
|
269
|
+
options.capture.streamedToolCallIndexes.size > 0;
|
|
270
|
+
if (!nontrivial) return undefined;
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
type: "codex_response_decision",
|
|
274
|
+
timestamp: Date.now(),
|
|
275
|
+
details: {
|
|
276
|
+
attempt: options.attempt,
|
|
277
|
+
terminalType: options.terminalState.type ?? "missing",
|
|
278
|
+
...(options.incompleteReason ? { incompleteReason: options.incompleteReason } : {}),
|
|
279
|
+
...(endTurn === undefined ? {} : { endTurn }),
|
|
280
|
+
outputItemTypes: outputItemTypeCounts(options.attemptItems),
|
|
281
|
+
streamedCallsStarted: options.capture.streamedToolCallIndexes.size,
|
|
282
|
+
streamedCallsDone: options.capture.streamedCompletedToolCallIndexes.size,
|
|
283
|
+
returnedCalls: options.toolCalls.completedCount,
|
|
284
|
+
discardedPartialCalls: options.toolCalls.discardedPartialCount,
|
|
285
|
+
...(options.postToolDisposition ? { postToolDisposition: options.postToolDisposition } : {}),
|
|
286
|
+
decision: options.decision,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
190
291
|
function captureRawEvents(
|
|
191
292
|
events: AsyncIterable<JsonRecord>,
|
|
192
|
-
|
|
293
|
+
capture: CodexAttemptCapture,
|
|
193
294
|
terminalState?: CodexTerminalState,
|
|
194
295
|
): AsyncIterable<JsonRecord> {
|
|
195
296
|
return {
|
|
196
297
|
async *[Symbol.asyncIterator]() {
|
|
197
298
|
for await (const event of events) {
|
|
198
|
-
if (event.type === "response.output_item.done" && isResponsesItem(event.item)) {
|
|
199
|
-
items.push(structuredClone(event.item));
|
|
200
|
-
}
|
|
201
299
|
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
isObject(event.response) &&
|
|
206
|
-
Array.isArray(event.response["output"])
|
|
300
|
+
event.type === "response.output_item.added" &&
|
|
301
|
+
isResponsesItem(event.item) &&
|
|
302
|
+
isToolCallItem(event.item)
|
|
207
303
|
) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
304
|
+
capture.streamedToolCallIndexes.add(eventOutputIndex(event));
|
|
305
|
+
}
|
|
306
|
+
if (event.type === "response.output_item.done" && isResponsesItem(event.item)) {
|
|
307
|
+
const item = structuredClone(event.item);
|
|
308
|
+
capture.streamedItems.push(item);
|
|
309
|
+
if (isToolCallItem(item)) {
|
|
310
|
+
const index = eventOutputIndex(event);
|
|
311
|
+
capture.streamedToolCallIndexes.add(index);
|
|
312
|
+
capture.streamedCompletedToolCallIndexes.add(index);
|
|
211
313
|
}
|
|
212
314
|
}
|
|
213
315
|
if (
|
|
@@ -310,6 +412,48 @@ function retryableResponseFailure(response: JsonRecord | undefined): boolean {
|
|
|
310
412
|
);
|
|
311
413
|
}
|
|
312
414
|
|
|
415
|
+
function terminalErrorMessage(disposition: CodexPostToolDisposition): string {
|
|
416
|
+
if (disposition.terminalType === "response.failed") {
|
|
417
|
+
const error = isObject(disposition.response?.["error"])
|
|
418
|
+
? disposition.response["error"]
|
|
419
|
+
: undefined;
|
|
420
|
+
return typeof error?.["message"] === "string" ? error["message"] : "Codex response failed";
|
|
421
|
+
}
|
|
422
|
+
const details = isObject(disposition.response?.["incomplete_details"])
|
|
423
|
+
? disposition.response["incomplete_details"]
|
|
424
|
+
: undefined;
|
|
425
|
+
const reason = typeof details?.["reason"] === "string" ? details["reason"] : undefined;
|
|
426
|
+
return reason
|
|
427
|
+
? `Response incomplete: ${reason}`
|
|
428
|
+
: "Response incomplete without a provider reason";
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function completedAttemptToolCallIds(
|
|
432
|
+
message: AssistantMessage,
|
|
433
|
+
attempt: CodexStreamAttemptState,
|
|
434
|
+
): string[] {
|
|
435
|
+
return [...attempt.completedContentIndexes]
|
|
436
|
+
.sort((left, right) => left - right)
|
|
437
|
+
.flatMap((index) => {
|
|
438
|
+
const block = message.content[index];
|
|
439
|
+
return block?.type === "toolCall" ? [block.id] : [];
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function assertLinkedToolOutputs(context: Context, disposition: CodexPostToolDisposition): void {
|
|
444
|
+
const outputIds = new Set(
|
|
445
|
+
context.messages.flatMap((message) =>
|
|
446
|
+
message.role === "toolResult" ? [message.toolCallId] : [],
|
|
447
|
+
),
|
|
448
|
+
);
|
|
449
|
+
const missing = disposition.callIds.filter((callId) => !outputIds.has(callId));
|
|
450
|
+
if (missing.length > 0) {
|
|
451
|
+
throw new Error(
|
|
452
|
+
`Codex cannot process the ${disposition.terminalType} response until Pi records tool output for: ${missing.join(", ")}`,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
313
457
|
function responseRetryDelayMs(baseDelayMs: number, attempt: number): number {
|
|
314
458
|
if (baseDelayMs <= 0) return 0;
|
|
315
459
|
const exponential = baseDelayMs * 2 ** Math.max(0, attempt - 1);
|
|
@@ -808,6 +952,7 @@ export class CodexProviderRuntime {
|
|
|
808
952
|
grammarToolInputProperties: GrammarToolInputProperties;
|
|
809
953
|
priority: boolean;
|
|
810
954
|
compactionMetadata: CodexCompactionMetadata;
|
|
955
|
+
compactionDecision: CompactionDecision;
|
|
811
956
|
agentTurn?: ActiveAgentTurn;
|
|
812
957
|
responsesLiteEnabled?: boolean;
|
|
813
958
|
}): Promise<{ checkpoint: CheckpointData; usage?: Usage }> {
|
|
@@ -881,6 +1026,7 @@ export class CodexProviderRuntime {
|
|
|
881
1026
|
options.history,
|
|
882
1027
|
compacted.item,
|
|
883
1028
|
options.postCompactionTail,
|
|
1029
|
+
options.compactionDecision,
|
|
884
1030
|
),
|
|
885
1031
|
...(compacted.usage ? { usage: compacted.usage } : {}),
|
|
886
1032
|
};
|
|
@@ -953,6 +1099,7 @@ export class CodexProviderRuntime {
|
|
|
953
1099
|
grammarToolInputProperties,
|
|
954
1100
|
priority: scope.config.fastMode,
|
|
955
1101
|
compactionMetadata: responsesCompactionV2Metadata("auto", "context_limit", "pre_turn"),
|
|
1102
|
+
compactionDecision: { reason: "provider-boundary", willRetry: true },
|
|
956
1103
|
agentTurn,
|
|
957
1104
|
responsesLiteEnabled,
|
|
958
1105
|
});
|
|
@@ -1014,6 +1161,26 @@ export class CodexProviderRuntime {
|
|
|
1014
1161
|
requestOptions.cacheRetention === "none" ? undefined : codexCacheKey(runtimeSessionId);
|
|
1015
1162
|
const agentTurn = this.agentTurn(runtimeSessionId);
|
|
1016
1163
|
const responsesLiteEnabled = this.responsesLiteEnabled(runtimeSessionId);
|
|
1164
|
+
let carriedResponseRetries = 0;
|
|
1165
|
+
const pendingPostToolDisposition = agentTurn.pendingPostToolDisposition;
|
|
1166
|
+
if (pendingPostToolDisposition) {
|
|
1167
|
+
assertLinkedToolOutputs(context, pendingPostToolDisposition);
|
|
1168
|
+
delete agentTurn.pendingPostToolDisposition;
|
|
1169
|
+
if (
|
|
1170
|
+
pendingPostToolDisposition.type === "error" ||
|
|
1171
|
+
pendingPostToolDisposition.retryAttempt > this.responseRetryPolicy.maxRetries
|
|
1172
|
+
) {
|
|
1173
|
+
throw new Error(terminalErrorMessage(pendingPostToolDisposition));
|
|
1174
|
+
}
|
|
1175
|
+
carriedResponseRetries = pendingPostToolDisposition.retryAttempt;
|
|
1176
|
+
await waitForResponseRetry(
|
|
1177
|
+
responseRetryDelayMs(
|
|
1178
|
+
this.responseRetryPolicy.baseDelayMs,
|
|
1179
|
+
pendingPostToolDisposition.retryAttempt,
|
|
1180
|
+
),
|
|
1181
|
+
requestOptions.signal,
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1017
1184
|
const grammarToolInputProperties = createGrammarToolInputProperties(
|
|
1018
1185
|
context.tools,
|
|
1019
1186
|
(model.compat as CodexCompat | undefined)?.supportsOpenAIGrammarTools ?? false,
|
|
@@ -1100,10 +1267,14 @@ export class CodexProviderRuntime {
|
|
|
1100
1267
|
},
|
|
1101
1268
|
};
|
|
1102
1269
|
let responseRequests = 0;
|
|
1103
|
-
let responseRetries =
|
|
1270
|
+
let responseRetries = carriedResponseRetries;
|
|
1104
1271
|
while (true) {
|
|
1105
1272
|
responseRequests += 1;
|
|
1106
|
-
const
|
|
1273
|
+
const attemptCapture: CodexAttemptCapture = {
|
|
1274
|
+
streamedItems: [],
|
|
1275
|
+
streamedToolCallIndexes: new Set(),
|
|
1276
|
+
streamedCompletedToolCallIndexes: new Set(),
|
|
1277
|
+
};
|
|
1107
1278
|
const terminalState: CodexTerminalState = {};
|
|
1108
1279
|
const attemptState: CodexStreamAttemptState = {
|
|
1109
1280
|
startedContentIndexes: new Set(),
|
|
@@ -1116,7 +1287,7 @@ export class CodexProviderRuntime {
|
|
|
1116
1287
|
startOnFirstEvent(
|
|
1117
1288
|
captureRawEvents(
|
|
1118
1289
|
this.transport.request(model, body, transportRequestOptions),
|
|
1119
|
-
|
|
1290
|
+
attemptCapture,
|
|
1120
1291
|
terminalState,
|
|
1121
1292
|
),
|
|
1122
1293
|
emitStart,
|
|
@@ -1140,12 +1311,71 @@ export class CodexProviderRuntime {
|
|
|
1140
1311
|
throw error;
|
|
1141
1312
|
}
|
|
1142
1313
|
output.usage = accumulateUsage(usageBeforeAttempt, output.usage);
|
|
1143
|
-
|
|
1314
|
+
// `response.output_item.done` is Codex's item-level commit point. Terminal
|
|
1315
|
+
// response.output snapshots are deliberately ignored.
|
|
1316
|
+
const attemptItems = attemptCapture.streamedItems;
|
|
1317
|
+
const toolCalls = assessAttemptToolCalls(attemptItems, attemptCapture);
|
|
1318
|
+
const incompleteDetails = isObject(terminalState.response?.["incomplete_details"])
|
|
1319
|
+
? terminalState.response["incomplete_details"]
|
|
1320
|
+
: undefined;
|
|
1321
|
+
const incompleteReason =
|
|
1322
|
+
typeof incompleteDetails?.["reason"] === "string"
|
|
1323
|
+
? incompleteDetails["reason"]
|
|
1324
|
+
: undefined;
|
|
1325
|
+
const recordDecision = (
|
|
1326
|
+
decision: CodexResponseDecision,
|
|
1327
|
+
postToolDisposition?: "continue" | "error" | "retry",
|
|
1328
|
+
): void => {
|
|
1329
|
+
const diagnostic = responseDecisionDiagnostic({
|
|
1330
|
+
attempt: responseRequests,
|
|
1331
|
+
attemptItems,
|
|
1332
|
+
capture: attemptCapture,
|
|
1333
|
+
decision,
|
|
1334
|
+
...(incompleteReason ? { incompleteReason } : {}),
|
|
1335
|
+
...(postToolDisposition ? { postToolDisposition } : {}),
|
|
1336
|
+
terminalState,
|
|
1337
|
+
toolCalls,
|
|
1338
|
+
});
|
|
1339
|
+
if (diagnostic) output.diagnostics = [...(output.diagnostics ?? []), diagnostic];
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1342
|
+
// Return each done tool call before processing an unsuccessful response
|
|
1343
|
+
// terminal. Pi will execute the completed subset; started-only siblings
|
|
1344
|
+
// are discarded and never enter provider history.
|
|
1345
|
+
if (toolCalls.hasCompletedCalls) {
|
|
1346
|
+
const callIds = completedAttemptToolCallIds(output, attemptState);
|
|
1347
|
+
if (callIds.length !== toolCalls.completedCount) {
|
|
1348
|
+
throw new Error("Codex completed tool-call items could not be mapped to Pi calls.");
|
|
1349
|
+
}
|
|
1350
|
+
let postToolDisposition: "continue" | "error" | "retry" = "continue";
|
|
1351
|
+
if (
|
|
1352
|
+
terminalState.type === "response.incomplete" ||
|
|
1353
|
+
terminalState.type === "response.failed"
|
|
1354
|
+
) {
|
|
1355
|
+
const retryable =
|
|
1356
|
+
terminalState.type === "response.incomplete" ||
|
|
1357
|
+
retryableResponseFailure(terminalState.response);
|
|
1358
|
+
postToolDisposition = retryable ? "retry" : "error";
|
|
1359
|
+
agentTurn.pendingPostToolDisposition = {
|
|
1360
|
+
callIds,
|
|
1361
|
+
...(terminalState.response
|
|
1362
|
+
? { response: structuredClone(terminalState.response) }
|
|
1363
|
+
: {}),
|
|
1364
|
+
retryAttempt: retryable ? responseRetries + 1 : responseRetries,
|
|
1365
|
+
terminalType: terminalState.type,
|
|
1366
|
+
type: postToolDisposition,
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
discardIncompleteAttemptContent(output, attemptState);
|
|
1370
|
+
rawItems.push(...attemptItems.map((item) => structuredClone(item)));
|
|
1371
|
+
output.stopReason = "toolUse";
|
|
1372
|
+
delete output.errorMessage;
|
|
1373
|
+
recordDecision("return_tool_use", postToolDisposition);
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1144
1376
|
|
|
1145
1377
|
const nextBody = continueResponseBody(body, attemptItems);
|
|
1146
|
-
|
|
1147
|
-
(index) => output.content[index]?.type === "toolCall",
|
|
1148
|
-
);
|
|
1378
|
+
rawItems.push(...attemptItems.map((item) => structuredClone(item)));
|
|
1149
1379
|
discardIncompleteAttemptContent(output, attemptState);
|
|
1150
1380
|
const retryableTerminal =
|
|
1151
1381
|
terminalState.type === "response.incomplete" ||
|
|
@@ -1161,6 +1391,9 @@ export class CodexProviderRuntime {
|
|
|
1161
1391
|
output.stopReason = "pending";
|
|
1162
1392
|
delete output.errorMessage;
|
|
1163
1393
|
delete output.rawStopReason;
|
|
1394
|
+
recordDecision(
|
|
1395
|
+
attemptItems.length === 0 ? "retry_original_input" : "continue_no_tools",
|
|
1396
|
+
);
|
|
1164
1397
|
await waitForResponseRetry(
|
|
1165
1398
|
responseRetryDelayMs(this.responseRetryPolicy.baseDelayMs, responseRetries),
|
|
1166
1399
|
requestOptions.signal,
|
|
@@ -1170,8 +1403,7 @@ export class CodexProviderRuntime {
|
|
|
1170
1403
|
|
|
1171
1404
|
if (
|
|
1172
1405
|
terminalState.type === "response.completed" &&
|
|
1173
|
-
terminalState.response?.["end_turn"] === false
|
|
1174
|
-
!attemptHasToolCall
|
|
1406
|
+
terminalState.response?.["end_turn"] === false
|
|
1175
1407
|
) {
|
|
1176
1408
|
if (!nextBody) {
|
|
1177
1409
|
throw new Error(
|
|
@@ -1183,8 +1415,10 @@ export class CodexProviderRuntime {
|
|
|
1183
1415
|
output.stopReason = "pending";
|
|
1184
1416
|
delete output.errorMessage;
|
|
1185
1417
|
delete output.rawStopReason;
|
|
1418
|
+
recordDecision("continue_no_tools");
|
|
1186
1419
|
continue;
|
|
1187
1420
|
}
|
|
1421
|
+
recordDecision("return_terminal");
|
|
1188
1422
|
break;
|
|
1189
1423
|
}
|
|
1190
1424
|
if (requestOptions.signal?.aborted) throw new Error("Request was aborted");
|
|
@@ -1285,6 +1519,7 @@ export class CodexProviderRuntime {
|
|
|
1285
1519
|
template: JsonRecord;
|
|
1286
1520
|
priority: boolean;
|
|
1287
1521
|
compactionMetadata: CodexCompactionMetadata;
|
|
1522
|
+
compactionDecision: CompactionDecision;
|
|
1288
1523
|
}): Promise<{ checkpoint: CheckpointData; usage?: Usage }> {
|
|
1289
1524
|
validateCodexAuthentication(options.model, options.requestOptions.apiKey);
|
|
1290
1525
|
const release = await this.acquireRequest(
|