pi-openai-codex-compat 0.0.6 → 0.0.7
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 +44 -0
- package/LICENSES/tree-sitter-wasms-MIT.txt +21 -0
- package/LICENSES/web-tree-sitter-MIT.txt +21 -0
- package/README.md +37 -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 +252 -14
- package/extensions/openai-codex-compat/codex-stream.ts +78 -61
- 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 +10 -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 {
|
|
@@ -126,6 +128,30 @@ type CodexTerminalState = {
|
|
|
126
128
|
response?: JsonRecord;
|
|
127
129
|
};
|
|
128
130
|
|
|
131
|
+
type CodexAttemptCapture = {
|
|
132
|
+
streamedItems: ResponsesItem[];
|
|
133
|
+
streamedToolCallKeys: Set<string>;
|
|
134
|
+
streamedCompletedToolCallKeys: Set<string>;
|
|
135
|
+
terminalItems?: ResponsesItem[];
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type CodexToolCallAssessment = {
|
|
139
|
+
allComplete: boolean;
|
|
140
|
+
authoritativeCount: number;
|
|
141
|
+
hasToolCalls: boolean;
|
|
142
|
+
omittedStreamedCount: number;
|
|
143
|
+
terminalCount: number;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
type CodexResponseDecision =
|
|
147
|
+
| "continue_no_tools"
|
|
148
|
+
| "preserve_terminal_error"
|
|
149
|
+
| "reject_terminal_stream_mismatch"
|
|
150
|
+
| "retry_original_input"
|
|
151
|
+
| "return_length_incomplete_call"
|
|
152
|
+
| "return_terminal"
|
|
153
|
+
| "return_tool_use";
|
|
154
|
+
|
|
129
155
|
export type CodexResponseRetryPolicy = {
|
|
130
156
|
maxRetries: number;
|
|
131
157
|
baseDelayMs: number;
|
|
@@ -187,16 +213,143 @@ function nativeOverrideRequired(
|
|
|
187
213
|
);
|
|
188
214
|
}
|
|
189
215
|
|
|
216
|
+
function isToolCallItem(item: ResponsesItem): boolean {
|
|
217
|
+
return item.type === "function_call" || item.type === "custom_tool_call";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function toolCallKey(item: ResponsesItem): string {
|
|
221
|
+
const callId = typeof item["call_id"] === "string" ? item["call_id"] : undefined;
|
|
222
|
+
const itemId = typeof item.id === "string" ? item.id : undefined;
|
|
223
|
+
return `${String(item.type)}:${itemId ? `item:${itemId}` : `call:${callId ?? "missing"}`}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function hasValidToolCallPayload(item: ResponsesItem): boolean {
|
|
227
|
+
if (
|
|
228
|
+
typeof item.id !== "string" ||
|
|
229
|
+
typeof item["call_id"] !== "string" ||
|
|
230
|
+
typeof item.name !== "string"
|
|
231
|
+
) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
if (item.type === "custom_tool_call") return typeof item.input === "string";
|
|
235
|
+
if (item.type !== "function_call" || typeof item.arguments !== "string") return false;
|
|
236
|
+
try {
|
|
237
|
+
return isObject(JSON.parse(item.arguments));
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function toolCallIsComplete(
|
|
244
|
+
item: ResponsesItem,
|
|
245
|
+
capture: CodexAttemptCapture,
|
|
246
|
+
terminalType: CodexTerminalState["type"],
|
|
247
|
+
): boolean {
|
|
248
|
+
if (!hasValidToolCallPayload(item)) return false;
|
|
249
|
+
const status = typeof item["status"] === "string" ? item["status"] : undefined;
|
|
250
|
+
if (status !== undefined) return status === "completed";
|
|
251
|
+
if (capture.streamedCompletedToolCallKeys.has(toolCallKey(item))) return true;
|
|
252
|
+
return terminalType === "response.completed";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function assessAttemptToolCalls(
|
|
256
|
+
items: readonly ResponsesItem[],
|
|
257
|
+
capture: CodexAttemptCapture,
|
|
258
|
+
terminalType: CodexTerminalState["type"],
|
|
259
|
+
): CodexToolCallAssessment {
|
|
260
|
+
const calls = items.filter(isToolCallItem);
|
|
261
|
+
const authoritativeKeys = new Set(calls.map(toolCallKey));
|
|
262
|
+
const omittedStreamedCount = [...capture.streamedToolCallKeys].filter(
|
|
263
|
+
(key) => !authoritativeKeys.has(key),
|
|
264
|
+
).length;
|
|
265
|
+
const hasToolCalls = calls.length > 0 || capture.streamedToolCallKeys.size > 0;
|
|
266
|
+
return {
|
|
267
|
+
hasToolCalls,
|
|
268
|
+
allComplete:
|
|
269
|
+
hasToolCalls &&
|
|
270
|
+
omittedStreamedCount === 0 &&
|
|
271
|
+
calls.length > 0 &&
|
|
272
|
+
calls.every((item) => toolCallIsComplete(item, capture, terminalType)),
|
|
273
|
+
authoritativeCount: calls.length,
|
|
274
|
+
omittedStreamedCount,
|
|
275
|
+
terminalCount: capture.terminalItems?.filter(isToolCallItem).length ?? 0,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function outputItemTypeCounts(items: readonly ResponsesItem[]): Record<string, number> {
|
|
280
|
+
const counts: Record<string, number> = {};
|
|
281
|
+
for (const item of items) {
|
|
282
|
+
const type = item.type ?? "message";
|
|
283
|
+
counts[type] = (counts[type] ?? 0) + 1;
|
|
284
|
+
}
|
|
285
|
+
return counts;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function responseDecisionDiagnostic(options: {
|
|
289
|
+
attempt: number;
|
|
290
|
+
attemptItems: readonly ResponsesItem[];
|
|
291
|
+
capture: CodexAttemptCapture;
|
|
292
|
+
decision: CodexResponseDecision;
|
|
293
|
+
incompleteReason?: string;
|
|
294
|
+
terminalState: CodexTerminalState;
|
|
295
|
+
toolCalls: CodexToolCallAssessment;
|
|
296
|
+
}): AssistantMessageDiagnostic | undefined {
|
|
297
|
+
const endTurn =
|
|
298
|
+
typeof options.terminalState.response?.["end_turn"] === "boolean"
|
|
299
|
+
? options.terminalState.response["end_turn"]
|
|
300
|
+
: undefined;
|
|
301
|
+
const nontrivial =
|
|
302
|
+
options.attempt > 1 ||
|
|
303
|
+
options.terminalState.type !== "response.completed" ||
|
|
304
|
+
endTurn === false ||
|
|
305
|
+
options.toolCalls.hasToolCalls ||
|
|
306
|
+
options.toolCalls.omittedStreamedCount > 0;
|
|
307
|
+
if (!nontrivial) return undefined;
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
type: "codex_response_decision",
|
|
311
|
+
timestamp: Date.now(),
|
|
312
|
+
details: {
|
|
313
|
+
attempt: options.attempt,
|
|
314
|
+
terminalType: options.terminalState.type ?? "missing",
|
|
315
|
+
...(options.incompleteReason ? { incompleteReason: options.incompleteReason } : {}),
|
|
316
|
+
...(endTurn === undefined ? {} : { endTurn }),
|
|
317
|
+
itemSource: options.capture.terminalItems ? "terminal" : "stream-fallback",
|
|
318
|
+
outputItemTypes: outputItemTypeCounts(options.attemptItems),
|
|
319
|
+
streamedCallsStarted: options.capture.streamedToolCallKeys.size,
|
|
320
|
+
streamedCallsCompleted: options.capture.streamedCompletedToolCallKeys.size,
|
|
321
|
+
terminalCalls: options.toolCalls.terminalCount,
|
|
322
|
+
authoritativeCalls: options.toolCalls.authoritativeCount,
|
|
323
|
+
terminalOmittedStreamedCalls: options.toolCalls.omittedStreamedCount,
|
|
324
|
+
allCallsComplete: options.toolCalls.allComplete,
|
|
325
|
+
decision: options.decision,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
190
330
|
function captureRawEvents(
|
|
191
331
|
events: AsyncIterable<JsonRecord>,
|
|
192
|
-
|
|
332
|
+
capture: CodexAttemptCapture,
|
|
193
333
|
terminalState?: CodexTerminalState,
|
|
194
334
|
): AsyncIterable<JsonRecord> {
|
|
195
335
|
return {
|
|
196
336
|
async *[Symbol.asyncIterator]() {
|
|
197
337
|
for await (const event of events) {
|
|
338
|
+
if (
|
|
339
|
+
event.type === "response.output_item.added" &&
|
|
340
|
+
isResponsesItem(event.item) &&
|
|
341
|
+
isToolCallItem(event.item)
|
|
342
|
+
) {
|
|
343
|
+
capture.streamedToolCallKeys.add(toolCallKey(event.item));
|
|
344
|
+
}
|
|
198
345
|
if (event.type === "response.output_item.done" && isResponsesItem(event.item)) {
|
|
199
|
-
|
|
346
|
+
const item = structuredClone(event.item);
|
|
347
|
+
capture.streamedItems.push(item);
|
|
348
|
+
if (isToolCallItem(item)) {
|
|
349
|
+
const key = toolCallKey(item);
|
|
350
|
+
capture.streamedToolCallKeys.add(key);
|
|
351
|
+
capture.streamedCompletedToolCallKeys.add(key);
|
|
352
|
+
}
|
|
200
353
|
}
|
|
201
354
|
if (
|
|
202
355
|
(event.type === "response.completed" ||
|
|
@@ -205,10 +358,9 @@ function captureRawEvents(
|
|
|
205
358
|
isObject(event.response) &&
|
|
206
359
|
Array.isArray(event.response["output"])
|
|
207
360
|
) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
361
|
+
capture.terminalItems = event.response["output"]
|
|
362
|
+
.filter(isResponsesItem)
|
|
363
|
+
.map((item) => structuredClone(item));
|
|
212
364
|
}
|
|
213
365
|
if (
|
|
214
366
|
terminalState &&
|
|
@@ -808,6 +960,7 @@ export class CodexProviderRuntime {
|
|
|
808
960
|
grammarToolInputProperties: GrammarToolInputProperties;
|
|
809
961
|
priority: boolean;
|
|
810
962
|
compactionMetadata: CodexCompactionMetadata;
|
|
963
|
+
compactionDecision: CompactionDecision;
|
|
811
964
|
agentTurn?: ActiveAgentTurn;
|
|
812
965
|
responsesLiteEnabled?: boolean;
|
|
813
966
|
}): Promise<{ checkpoint: CheckpointData; usage?: Usage }> {
|
|
@@ -881,6 +1034,7 @@ export class CodexProviderRuntime {
|
|
|
881
1034
|
options.history,
|
|
882
1035
|
compacted.item,
|
|
883
1036
|
options.postCompactionTail,
|
|
1037
|
+
options.compactionDecision,
|
|
884
1038
|
),
|
|
885
1039
|
...(compacted.usage ? { usage: compacted.usage } : {}),
|
|
886
1040
|
};
|
|
@@ -953,6 +1107,7 @@ export class CodexProviderRuntime {
|
|
|
953
1107
|
grammarToolInputProperties,
|
|
954
1108
|
priority: scope.config.fastMode,
|
|
955
1109
|
compactionMetadata: responsesCompactionV2Metadata("auto", "context_limit", "pre_turn"),
|
|
1110
|
+
compactionDecision: { reason: "provider-boundary", willRetry: true },
|
|
956
1111
|
agentTurn,
|
|
957
1112
|
responsesLiteEnabled,
|
|
958
1113
|
});
|
|
@@ -1103,12 +1258,17 @@ export class CodexProviderRuntime {
|
|
|
1103
1258
|
let responseRetries = 0;
|
|
1104
1259
|
while (true) {
|
|
1105
1260
|
responseRequests += 1;
|
|
1106
|
-
const
|
|
1261
|
+
const attemptCapture: CodexAttemptCapture = {
|
|
1262
|
+
streamedItems: [],
|
|
1263
|
+
streamedToolCallKeys: new Set(),
|
|
1264
|
+
streamedCompletedToolCallKeys: new Set(),
|
|
1265
|
+
};
|
|
1107
1266
|
const terminalState: CodexTerminalState = {};
|
|
1108
1267
|
const attemptState: CodexStreamAttemptState = {
|
|
1109
1268
|
startedContentIndexes: new Set(),
|
|
1110
1269
|
completedContentIndexes: new Set(),
|
|
1111
1270
|
};
|
|
1271
|
+
const contentLengthBeforeAttempt = output.content.length;
|
|
1112
1272
|
const usageBeforeAttempt = structuredClone(output.usage);
|
|
1113
1273
|
output.usage = emptyUsage();
|
|
1114
1274
|
try {
|
|
@@ -1116,7 +1276,7 @@ export class CodexProviderRuntime {
|
|
|
1116
1276
|
startOnFirstEvent(
|
|
1117
1277
|
captureRawEvents(
|
|
1118
1278
|
this.transport.request(model, body, transportRequestOptions),
|
|
1119
|
-
|
|
1279
|
+
attemptCapture,
|
|
1120
1280
|
terminalState,
|
|
1121
1281
|
),
|
|
1122
1282
|
emitStart,
|
|
@@ -1140,12 +1300,87 @@ export class CodexProviderRuntime {
|
|
|
1140
1300
|
throw error;
|
|
1141
1301
|
}
|
|
1142
1302
|
output.usage = accumulateUsage(usageBeforeAttempt, output.usage);
|
|
1143
|
-
|
|
1303
|
+
// A terminal response.output is the complete provider snapshot when present.
|
|
1304
|
+
// Stream-completed items are the fallback for transports that omit that field.
|
|
1305
|
+
const attemptItems = attemptCapture.terminalItems ?? attemptCapture.streamedItems;
|
|
1306
|
+
const toolCalls = assessAttemptToolCalls(
|
|
1307
|
+
attemptItems,
|
|
1308
|
+
attemptCapture,
|
|
1309
|
+
terminalState.type,
|
|
1310
|
+
);
|
|
1311
|
+
const incompleteDetails = isObject(terminalState.response?.["incomplete_details"])
|
|
1312
|
+
? terminalState.response["incomplete_details"]
|
|
1313
|
+
: undefined;
|
|
1314
|
+
const incompleteReason =
|
|
1315
|
+
typeof incompleteDetails?.["reason"] === "string"
|
|
1316
|
+
? incompleteDetails["reason"]
|
|
1317
|
+
: undefined;
|
|
1318
|
+
const maxOutputIncomplete =
|
|
1319
|
+
terminalState.type === "response.incomplete" &&
|
|
1320
|
+
incompleteReason === "max_output_tokens";
|
|
1321
|
+
const recordDecision = (decision: CodexResponseDecision): void => {
|
|
1322
|
+
const diagnostic = responseDecisionDiagnostic({
|
|
1323
|
+
attempt: responseRequests,
|
|
1324
|
+
attemptItems,
|
|
1325
|
+
capture: attemptCapture,
|
|
1326
|
+
decision,
|
|
1327
|
+
...(incompleteReason ? { incompleteReason } : {}),
|
|
1328
|
+
terminalState,
|
|
1329
|
+
toolCalls,
|
|
1330
|
+
});
|
|
1331
|
+
if (diagnostic) output.diagnostics = [...(output.diagnostics ?? []), diagnostic];
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
// Tool calls create a client-execution boundary. Never append them to an
|
|
1335
|
+
// internal provider continuation before Pi can return the linked outputs.
|
|
1336
|
+
if (toolCalls.hasToolCalls) {
|
|
1337
|
+
if (
|
|
1338
|
+
toolCalls.allComplete &&
|
|
1339
|
+
((terminalState.type === "response.completed" && output.stopReason === "toolUse") ||
|
|
1340
|
+
maxOutputIncomplete)
|
|
1341
|
+
) {
|
|
1342
|
+
discardIncompleteAttemptContent(output, attemptState);
|
|
1343
|
+
rawItems.push(...attemptItems.map((item) => structuredClone(item)));
|
|
1344
|
+
output.stopReason = "toolUse";
|
|
1345
|
+
delete output.errorMessage;
|
|
1346
|
+
recordDecision("return_tool_use");
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
output.content.splice(contentLengthBeforeAttempt);
|
|
1351
|
+
if (
|
|
1352
|
+
terminalState.type === "response.failed" &&
|
|
1353
|
+
retryableResponseFailure(terminalState.response) &&
|
|
1354
|
+
responseRetries < this.responseRetryPolicy.maxRetries
|
|
1355
|
+
) {
|
|
1356
|
+
responseRetries += 1;
|
|
1357
|
+
output.stopReason = "pending";
|
|
1358
|
+
delete output.errorMessage;
|
|
1359
|
+
delete output.rawStopReason;
|
|
1360
|
+
recordDecision("retry_original_input");
|
|
1361
|
+
await waitForResponseRetry(
|
|
1362
|
+
responseRetryDelayMs(this.responseRetryPolicy.baseDelayMs, responseRetries),
|
|
1363
|
+
requestOptions.signal,
|
|
1364
|
+
);
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
if (terminalState.type === "response.completed" && output.stopReason !== "error") {
|
|
1368
|
+
output.stopReason = "length";
|
|
1369
|
+
output.rawStopReason = "incomplete.tool_call";
|
|
1370
|
+
delete output.errorMessage;
|
|
1371
|
+
}
|
|
1372
|
+
recordDecision(
|
|
1373
|
+
toolCalls.omittedStreamedCount > 0
|
|
1374
|
+
? "reject_terminal_stream_mismatch"
|
|
1375
|
+
: output.stopReason === "length"
|
|
1376
|
+
? "return_length_incomplete_call"
|
|
1377
|
+
: "preserve_terminal_error",
|
|
1378
|
+
);
|
|
1379
|
+
break;
|
|
1380
|
+
}
|
|
1144
1381
|
|
|
1145
1382
|
const nextBody = continueResponseBody(body, attemptItems);
|
|
1146
|
-
|
|
1147
|
-
(index) => output.content[index]?.type === "toolCall",
|
|
1148
|
-
);
|
|
1383
|
+
rawItems.push(...attemptItems.map((item) => structuredClone(item)));
|
|
1149
1384
|
discardIncompleteAttemptContent(output, attemptState);
|
|
1150
1385
|
const retryableTerminal =
|
|
1151
1386
|
terminalState.type === "response.incomplete" ||
|
|
@@ -1161,6 +1396,7 @@ export class CodexProviderRuntime {
|
|
|
1161
1396
|
output.stopReason = "pending";
|
|
1162
1397
|
delete output.errorMessage;
|
|
1163
1398
|
delete output.rawStopReason;
|
|
1399
|
+
recordDecision("continue_no_tools");
|
|
1164
1400
|
await waitForResponseRetry(
|
|
1165
1401
|
responseRetryDelayMs(this.responseRetryPolicy.baseDelayMs, responseRetries),
|
|
1166
1402
|
requestOptions.signal,
|
|
@@ -1170,8 +1406,7 @@ export class CodexProviderRuntime {
|
|
|
1170
1406
|
|
|
1171
1407
|
if (
|
|
1172
1408
|
terminalState.type === "response.completed" &&
|
|
1173
|
-
terminalState.response?.["end_turn"] === false
|
|
1174
|
-
!attemptHasToolCall
|
|
1409
|
+
terminalState.response?.["end_turn"] === false
|
|
1175
1410
|
) {
|
|
1176
1411
|
if (!nextBody) {
|
|
1177
1412
|
throw new Error(
|
|
@@ -1183,8 +1418,10 @@ export class CodexProviderRuntime {
|
|
|
1183
1418
|
output.stopReason = "pending";
|
|
1184
1419
|
delete output.errorMessage;
|
|
1185
1420
|
delete output.rawStopReason;
|
|
1421
|
+
recordDecision("continue_no_tools");
|
|
1186
1422
|
continue;
|
|
1187
1423
|
}
|
|
1424
|
+
recordDecision("return_terminal");
|
|
1188
1425
|
break;
|
|
1189
1426
|
}
|
|
1190
1427
|
if (requestOptions.signal?.aborted) throw new Error("Request was aborted");
|
|
@@ -1285,6 +1522,7 @@ export class CodexProviderRuntime {
|
|
|
1285
1522
|
template: JsonRecord;
|
|
1286
1523
|
priority: boolean;
|
|
1287
1524
|
compactionMetadata: CodexCompactionMetadata;
|
|
1525
|
+
compactionDecision: CompactionDecision;
|
|
1288
1526
|
}): Promise<{ checkpoint: CheckpointData; usage?: Usage }> {
|
|
1289
1527
|
validateCodexAuthentication(options.model, options.requestOptions.apiKey);
|
|
1290
1528
|
const release = await this.acquireRequest(
|