@oh-my-pi/pi-agent-core 17.2.3 → 17.2.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 +7 -0
- package/dist/types/agent-loop.d.ts +6 -4
- package/dist/types/compaction/compaction-v2-streaming.d.ts +1 -0
- package/dist/types/compaction/compaction.d.ts +2 -0
- package/package.json +7 -7
- package/src/agent-loop.ts +36 -12
- package/src/compaction/compaction-v2-streaming.ts +100 -39
- package/src/compaction/compaction.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.2.4] - 2026-08-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed Codex V2 remote compaction bypassing the provider's live WebSocket transport before trying SSE ([#7198](https://github.com/can1357/oh-my-pi/issues/7198)).
|
|
10
|
+
- Tool calls skipped mid-batch to service queued steering/peer input now distinguish calls that never entered `tool.execute` (`SyntheticToolResultDetails`, `executed: false`) from in-flight calls that may have performed partial work (`execution: "started"`), allowing UI/telemetry consumers to render normal steering control flow without misreporting execution state ([#7199](https://github.com/can1357/oh-my-pi/issues/7199)).
|
|
11
|
+
|
|
5
12
|
## [17.2.2] - 2026-07-31
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -101,13 +101,15 @@ export declare function abortReasonText(signal: AbortSignal | undefined): string
|
|
|
101
101
|
* (#4321): a provider-side stream error after tool-call emission (e.g. Codex
|
|
102
102
|
* websocket close) was surfaced by the CLI as if the local tool had failed.
|
|
103
103
|
*
|
|
104
|
-
* `source` names the
|
|
105
|
-
*
|
|
106
|
-
*
|
|
104
|
+
* `source` names the state that prevented execution — either an assistant-side
|
|
105
|
+
* turn termination (`assistant_stop_*`) or a mid-batch interrupt that skipped a
|
|
106
|
+
* still-pending call to service queued steering/peer input (`interrupt_skipped`).
|
|
107
|
+
* `upstreamError` is the provider-reported message when the turn ended with
|
|
108
|
+
* `stopReason === "error"`.
|
|
107
109
|
*/
|
|
108
110
|
export interface SyntheticToolResultDetails {
|
|
109
111
|
__synthetic: true;
|
|
110
|
-
source: "assistant_stop_aborted" | "assistant_stop_error" | "assistant_stop_skipped" | "assistant_stop_length";
|
|
112
|
+
source: "assistant_stop_aborted" | "assistant_stop_error" | "assistant_stop_skipped" | "assistant_stop_length" | "interrupt_skipped";
|
|
111
113
|
executed: false;
|
|
112
114
|
upstreamError?: string;
|
|
113
115
|
}
|
|
@@ -68,6 +68,7 @@ export declare function requestCompactionV2Streaming(model: Model, apiKey: strin
|
|
|
68
68
|
retryWait?: (delayMs: number, signal?: AbortSignal) => Promise<void>;
|
|
69
69
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
70
70
|
codexCompaction?: CodexCompactionContext;
|
|
71
|
+
preferWebsockets?: boolean;
|
|
71
72
|
}): Promise<CompactionV2Response>;
|
|
72
73
|
/** Build Codex-style V2 replacement history from prompt input plus compaction output. */
|
|
73
74
|
export declare function buildCompactionV2ReplacementHistory(input: unknown[], compactionItem: Record<string, unknown>, retainedMessageBudget?: number): {
|
|
@@ -200,6 +200,8 @@ export interface SummaryOptions {
|
|
|
200
200
|
promptCacheKey?: string;
|
|
201
201
|
/** Mutable provider state used to keep Codex compaction on the live session identity. */
|
|
202
202
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
203
|
+
/** Whether Codex remote compaction should prefer the provider WebSocket transport. */
|
|
204
|
+
preferWebsockets?: boolean;
|
|
203
205
|
/** Classification shared by every provider request in this logical compaction. */
|
|
204
206
|
codexCompaction?: CodexCompactionContext;
|
|
205
207
|
/** Provider-visible tools for remote compaction transports that replay native tool history. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-agent-core",
|
|
4
|
-
"version": "17.2.
|
|
4
|
+
"version": "17.2.4",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "17.2.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "17.2.
|
|
40
|
-
"@oh-my-pi/pi-natives": "17.2.
|
|
41
|
-
"@oh-my-pi/pi-utils": "17.2.
|
|
42
|
-
"@oh-my-pi/pi-wire": "17.2.
|
|
43
|
-
"@oh-my-pi/snapcompact": "17.2.
|
|
38
|
+
"@oh-my-pi/pi-ai": "17.2.4",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "17.2.4",
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.2.4",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.2.4",
|
|
42
|
+
"@oh-my-pi/pi-wire": "17.2.4",
|
|
43
|
+
"@oh-my-pi/snapcompact": "17.2.4",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -2454,6 +2454,7 @@ async function executeToolCalls(
|
|
|
2454
2454
|
let isError = false;
|
|
2455
2455
|
let caughtError: unknown;
|
|
2456
2456
|
let completedToolExecution = false;
|
|
2457
|
+
let executionStarted = false;
|
|
2457
2458
|
|
|
2458
2459
|
await runInActiveSpan(toolSpan, async () => {
|
|
2459
2460
|
try {
|
|
@@ -2487,6 +2488,7 @@ async function executeToolCalls(
|
|
|
2487
2488
|
providerMetadata: toolCall.providerMetadata,
|
|
2488
2489
|
})
|
|
2489
2490
|
: undefined;
|
|
2491
|
+
executionStarted = true;
|
|
2490
2492
|
const rawResult = await tool.execute(
|
|
2491
2493
|
toolCall.id,
|
|
2492
2494
|
executionArgs,
|
|
@@ -2557,12 +2559,12 @@ async function executeToolCalls(
|
|
|
2557
2559
|
const interrupted = interruptState.triggered;
|
|
2558
2560
|
const perToolAborted = record.signal.aborted;
|
|
2559
2561
|
const abortedDuringExecution = perToolAborted && isError && !completedToolExecution;
|
|
2560
|
-
if (interrupted &&
|
|
2561
|
-
// This tool's own signal fired AND it failed to produce a result
|
|
2562
|
-
//
|
|
2563
|
-
//
|
|
2562
|
+
if (interrupted && abortedDuringExecution) {
|
|
2563
|
+
// This tool's own signal fired AND it failed to produce a result. The
|
|
2564
|
+
// execution may already have performed partial work before throwing on
|
|
2565
|
+
// abort, so preserve that distinction in the placeholder metadata.
|
|
2564
2566
|
record.skipped = true;
|
|
2565
|
-
emitToolResult(record, createSkippedToolResult(interruptState.source), true);
|
|
2567
|
+
emitToolResult(record, createSkippedToolResult(interruptState.source, executionStarted), true);
|
|
2566
2568
|
} else {
|
|
2567
2569
|
// No interrupt on this signal, or the tool finished before the interrupt landed
|
|
2568
2570
|
// (`completedToolExecution`) — even if the signal aborted around completion. Keep
|
|
@@ -2703,7 +2705,7 @@ async function executeToolCalls(
|
|
|
2703
2705
|
toolName: record.toolCall.name,
|
|
2704
2706
|
status: "skipped",
|
|
2705
2707
|
});
|
|
2706
|
-
emitToolResult(record, createSkippedToolResult(interruptState.source), true);
|
|
2708
|
+
emitToolResult(record, createSkippedToolResult(interruptState.source, false), true);
|
|
2707
2709
|
}
|
|
2708
2710
|
}
|
|
2709
2711
|
|
|
@@ -2723,17 +2725,34 @@ async function executeToolCalls(
|
|
|
2723
2725
|
* (#4321): a provider-side stream error after tool-call emission (e.g. Codex
|
|
2724
2726
|
* websocket close) was surfaced by the CLI as if the local tool had failed.
|
|
2725
2727
|
*
|
|
2726
|
-
* `source` names the
|
|
2727
|
-
*
|
|
2728
|
-
*
|
|
2728
|
+
* `source` names the state that prevented execution — either an assistant-side
|
|
2729
|
+
* turn termination (`assistant_stop_*`) or a mid-batch interrupt that skipped a
|
|
2730
|
+
* still-pending call to service queued steering/peer input (`interrupt_skipped`).
|
|
2731
|
+
* `upstreamError` is the provider-reported message when the turn ended with
|
|
2732
|
+
* `stopReason === "error"`.
|
|
2729
2733
|
*/
|
|
2730
2734
|
export interface SyntheticToolResultDetails {
|
|
2731
2735
|
__synthetic: true;
|
|
2732
|
-
source:
|
|
2736
|
+
source:
|
|
2737
|
+
| "assistant_stop_aborted"
|
|
2738
|
+
| "assistant_stop_error"
|
|
2739
|
+
| "assistant_stop_skipped"
|
|
2740
|
+
| "assistant_stop_length"
|
|
2741
|
+
| "interrupt_skipped";
|
|
2733
2742
|
executed: false;
|
|
2734
2743
|
upstreamError?: string;
|
|
2735
2744
|
}
|
|
2736
2745
|
|
|
2746
|
+
/**
|
|
2747
|
+
* Metadata for an interrupt-aborted call that entered `tool.execute()` but
|
|
2748
|
+
* threw before returning a usable result. It may have performed partial work.
|
|
2749
|
+
*/
|
|
2750
|
+
interface InterruptedToolResultDetails {
|
|
2751
|
+
__interrupted: true;
|
|
2752
|
+
source: "interrupt_skipped";
|
|
2753
|
+
execution: "started";
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2737
2756
|
/**
|
|
2738
2757
|
* Narrow an {@link AgentMessage} to a synthetic {@link ToolResultMessage} —
|
|
2739
2758
|
* a tool_result emitted for a tool call the assistant never invoked (see
|
|
@@ -2844,7 +2863,10 @@ function createToolSignalAbortedResult(signal: AbortSignal): AgentToolResult<unk
|
|
|
2844
2863
|
};
|
|
2845
2864
|
}
|
|
2846
2865
|
|
|
2847
|
-
function createSkippedToolResult(
|
|
2866
|
+
function createSkippedToolResult(
|
|
2867
|
+
source: SteeringInterruptSource | "irc" | undefined,
|
|
2868
|
+
executionStarted: boolean,
|
|
2869
|
+
): AgentToolResult<SyntheticToolResultDetails | InterruptedToolResultDetails> {
|
|
2848
2870
|
let reason = "pending steering message";
|
|
2849
2871
|
let blocker = "queued message";
|
|
2850
2872
|
if (source === "user") {
|
|
@@ -2864,6 +2886,8 @@ function createSkippedToolResult(source: SteeringInterruptSource | "irc" | undef
|
|
|
2864
2886
|
text: `Skipped due to ${reason}. Do not count this skipped result as completed work or verification. After the ${blocker} is handled on the next step, retry the skipped tool if it is still needed.`,
|
|
2865
2887
|
},
|
|
2866
2888
|
],
|
|
2867
|
-
details:
|
|
2889
|
+
details: executionStarted
|
|
2890
|
+
? { __interrupted: true, source: "interrupt_skipped", execution: "started" }
|
|
2891
|
+
: { __synthetic: true, source: "interrupt_skipped", executed: false },
|
|
2868
2892
|
};
|
|
2869
2893
|
}
|
|
@@ -13,7 +13,9 @@ import { applyCodexResponsesLiteShape } from "@oh-my-pi/pi-ai/providers/openai-c
|
|
|
13
13
|
import {
|
|
14
14
|
createOpenAICodexCompactionRequestContext,
|
|
15
15
|
createOpenAICodexCompatibilityMetadata,
|
|
16
|
+
type OpenAICodexCompactionBody,
|
|
16
17
|
type OpenAICodexCompatibilityMetadata,
|
|
18
|
+
openCodexCompactionEventStream,
|
|
17
19
|
} from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
18
20
|
import {
|
|
19
21
|
getOpenAIPromptCacheKey,
|
|
@@ -128,6 +130,14 @@ function isOpenAiV2CompatibleModel(model: Model): boolean {
|
|
|
128
130
|
return api === "openai-responses" || api === "azure-openai-responses" || api === "openai-codex-responses";
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
function shouldUseCodexProviderTransport(model: Model): model is Model<"openai-codex-responses"> {
|
|
134
|
+
return (
|
|
135
|
+
model.api === "openai-codex-responses" &&
|
|
136
|
+
model.remoteCompaction?.v2Endpoint === undefined &&
|
|
137
|
+
model.remoteCompaction?.streamingEndpoint === undefined
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
131
141
|
function resolveOpenAiResponsesEndpoint(baseUrl: string | undefined): string {
|
|
132
142
|
const rawBase = baseUrl && baseUrl.length > 0 ? baseUrl : "https://api.openai.com/v1";
|
|
133
143
|
const normalizedBase = rawBase.replace(/\/+$/, "");
|
|
@@ -228,6 +238,7 @@ export async function requestCompactionV2Streaming(
|
|
|
228
238
|
retryWait?: (delayMs: number, signal?: AbortSignal) => Promise<void>;
|
|
229
239
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
230
240
|
codexCompaction?: CodexCompactionContext;
|
|
241
|
+
preferWebsockets?: boolean;
|
|
231
242
|
},
|
|
232
243
|
): Promise<CompactionV2Response> {
|
|
233
244
|
const endpoint = getCompactionV2Endpoint(model);
|
|
@@ -238,31 +249,29 @@ export async function requestCompactionV2Streaming(
|
|
|
238
249
|
const fetchImpl = options?.fetch ?? globalThis.fetch;
|
|
239
250
|
const retryWait = options?.retryWait ?? ((delayMs: number) => Bun.sleep(delayMs));
|
|
240
251
|
const isCodexResponses = compactionV2Api(model) === "openai-codex-responses" || model.provider === "openai-codex";
|
|
241
|
-
const codexMetadata =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
+
const codexMetadata =
|
|
253
|
+
isCodexResponses && !shouldUseCodexProviderTransport(model)
|
|
254
|
+
? createOpenAICodexCompatibilityMetadata({
|
|
255
|
+
sessionId: request.sessionId,
|
|
256
|
+
providerSessionState: options?.providerSessionState,
|
|
257
|
+
requestKind: "compaction",
|
|
258
|
+
compaction: createOpenAICodexCompactionRequestContext({
|
|
259
|
+
context: options?.codexCompaction,
|
|
260
|
+
implementation: "responses_compaction_v2",
|
|
261
|
+
}),
|
|
262
|
+
})
|
|
263
|
+
: undefined;
|
|
252
264
|
let lastError: Error | undefined;
|
|
253
265
|
|
|
254
266
|
for (let attempt = 0; attempt <= V2_COMPACTION_MAX_RETRIES; attempt++) {
|
|
255
267
|
const timeoutSignal = withRequestTimeout(signal, options?.timeoutMs ?? V2_COMPACTION_TIMEOUT_MS);
|
|
256
268
|
try {
|
|
257
|
-
return await attemptCompactionV2Streaming(
|
|
258
|
-
endpoint,
|
|
259
|
-
apiKey,
|
|
260
|
-
model,
|
|
261
|
-
request,
|
|
262
|
-
fetchImpl,
|
|
263
|
-
timeoutSignal,
|
|
269
|
+
return await attemptCompactionV2Streaming(endpoint, apiKey, model, request, fetchImpl, timeoutSignal, {
|
|
264
270
|
codexMetadata,
|
|
265
|
-
|
|
271
|
+
providerSessionState: options?.providerSessionState,
|
|
272
|
+
codexCompaction: options?.codexCompaction,
|
|
273
|
+
preferWebsockets: options?.preferWebsockets,
|
|
274
|
+
});
|
|
266
275
|
} catch (err) {
|
|
267
276
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
268
277
|
if (signal?.aborted) throw error;
|
|
@@ -292,15 +301,20 @@ async function attemptCompactionV2Streaming(
|
|
|
292
301
|
model: Model,
|
|
293
302
|
request: CompactionV2Request,
|
|
294
303
|
fetchImpl: FetchImpl,
|
|
295
|
-
signal
|
|
296
|
-
|
|
304
|
+
signal: AbortSignal | undefined,
|
|
305
|
+
options: {
|
|
306
|
+
codexMetadata?: OpenAICodexCompatibilityMetadata;
|
|
307
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
308
|
+
codexCompaction?: CodexCompactionContext;
|
|
309
|
+
preferWebsockets?: boolean;
|
|
310
|
+
},
|
|
297
311
|
): Promise<CompactionV2Response> {
|
|
298
312
|
// Faithful to Codex: append the compaction trigger as the final input item
|
|
299
313
|
// of an otherwise-normal Responses request, then stream the result. `store`
|
|
300
314
|
// stays false — compaction must never persist a server-side response object.
|
|
301
315
|
const cacheOptions = { sessionId: request.sessionId, promptCacheKey: request.promptCacheKey };
|
|
302
316
|
const promptCacheKey = getOpenAIPromptCacheKey(cacheOptions);
|
|
303
|
-
const body:
|
|
317
|
+
const body: OpenAICodexCompactionBody = {
|
|
304
318
|
model: request.model,
|
|
305
319
|
input: [...request.input, COMPACTION_TRIGGER_ITEM],
|
|
306
320
|
instructions: request.instructions,
|
|
@@ -318,8 +332,8 @@ async function attemptCompactionV2Streaming(
|
|
|
318
332
|
...(promptCacheKey ? { prompt_cache_key: promptCacheKey } : {}),
|
|
319
333
|
...(request.tools && request.tools.length > 0 ? { tools: request.tools, tool_choice: "auto" } : {}),
|
|
320
334
|
};
|
|
321
|
-
if (codexMetadata) {
|
|
322
|
-
body.client_metadata = codexMetadata.clientMetadata;
|
|
335
|
+
if (options.codexMetadata) {
|
|
336
|
+
body.client_metadata = options.codexMetadata.clientMetadata;
|
|
323
337
|
}
|
|
324
338
|
// Responses Lite models take the same rewrite on the compaction stream:
|
|
325
339
|
// instructions/tools ride as input items (codex-rs `compact_remote_v2`
|
|
@@ -327,9 +341,27 @@ async function attemptCompactionV2Streaming(
|
|
|
327
341
|
if (model.useResponsesLite) {
|
|
328
342
|
applyCodexResponsesLiteShape(body);
|
|
329
343
|
}
|
|
344
|
+
|
|
345
|
+
if (shouldUseCodexProviderTransport(model)) {
|
|
346
|
+
const eventStream = await openCodexCompactionEventStream(model, body, {
|
|
347
|
+
apiKey,
|
|
348
|
+
signal,
|
|
349
|
+
fetch: fetchImpl,
|
|
350
|
+
sessionId: request.sessionId,
|
|
351
|
+
providerSessionState: options.providerSessionState,
|
|
352
|
+
preferWebsockets: options.preferWebsockets,
|
|
353
|
+
responsesLite: model.useResponsesLite,
|
|
354
|
+
codexCompaction: createOpenAICodexCompactionRequestContext({
|
|
355
|
+
context: options.codexCompaction,
|
|
356
|
+
implementation: "responses_compaction_v2",
|
|
357
|
+
}),
|
|
358
|
+
});
|
|
359
|
+
return collectCompactionV2Events(eventStream, request);
|
|
360
|
+
}
|
|
361
|
+
|
|
330
362
|
const response = await fetchImpl(endpoint, {
|
|
331
363
|
method: "POST",
|
|
332
|
-
headers: buildCompactionV2Headers(model, apiKey, request, codexMetadata),
|
|
364
|
+
headers: buildCompactionV2Headers(model, apiKey, request, options.codexMetadata),
|
|
333
365
|
body: stringifyJson(body),
|
|
334
366
|
signal,
|
|
335
367
|
});
|
|
@@ -400,6 +432,33 @@ function buildCompactionV2Headers(
|
|
|
400
432
|
return headers;
|
|
401
433
|
}
|
|
402
434
|
|
|
435
|
+
interface CompactionV2CollectionState {
|
|
436
|
+
outputItemCount: number;
|
|
437
|
+
compactionItems: Array<Record<string, unknown>>;
|
|
438
|
+
sawCompleted: boolean;
|
|
439
|
+
usage: CompactionV2Usage | undefined;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function createCompactionV2CollectionState(): CompactionV2CollectionState {
|
|
443
|
+
return {
|
|
444
|
+
outputItemCount: 0,
|
|
445
|
+
compactionItems: [],
|
|
446
|
+
sawCompleted: false,
|
|
447
|
+
usage: undefined,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function collectCompactionV2Events(
|
|
452
|
+
events: AsyncIterable<Record<string, unknown>>,
|
|
453
|
+
request: CompactionV2Request,
|
|
454
|
+
): Promise<CompactionV2Response> {
|
|
455
|
+
const state = createCompactionV2CollectionState();
|
|
456
|
+
for await (const event of events) {
|
|
457
|
+
handleCompactionV2Event(event, undefined, state);
|
|
458
|
+
}
|
|
459
|
+
return finishCompactionV2Collection(state, request);
|
|
460
|
+
}
|
|
461
|
+
|
|
403
462
|
async function collectCompactionV2Output(
|
|
404
463
|
response: Response,
|
|
405
464
|
request: CompactionV2Request,
|
|
@@ -409,13 +468,7 @@ async function collectCompactionV2Output(
|
|
|
409
468
|
throw new Error("No response body for V2 compaction streaming");
|
|
410
469
|
}
|
|
411
470
|
|
|
412
|
-
const state =
|
|
413
|
-
outputItemCount: 0,
|
|
414
|
-
compactionItems: [] as Array<Record<string, unknown>>,
|
|
415
|
-
sawCompleted: false,
|
|
416
|
-
usage: undefined as CompactionV2Usage | undefined,
|
|
417
|
-
};
|
|
418
|
-
|
|
471
|
+
const state = createCompactionV2CollectionState();
|
|
419
472
|
try {
|
|
420
473
|
const decoder = new TextDecoder();
|
|
421
474
|
let buffer = "";
|
|
@@ -462,6 +515,13 @@ async function collectCompactionV2Output(
|
|
|
462
515
|
reader.releaseLock();
|
|
463
516
|
}
|
|
464
517
|
|
|
518
|
+
return finishCompactionV2Collection(state, request);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function finishCompactionV2Collection(
|
|
522
|
+
state: CompactionV2CollectionState,
|
|
523
|
+
request: CompactionV2Request,
|
|
524
|
+
): CompactionV2Response {
|
|
465
525
|
if (!state.sawCompleted) {
|
|
466
526
|
throw new Error("V2 compaction stream closed before response.completed");
|
|
467
527
|
}
|
|
@@ -477,7 +537,6 @@ async function collectCompactionV2Output(
|
|
|
477
537
|
compactionItem,
|
|
478
538
|
request.retainedMessageBudget,
|
|
479
539
|
);
|
|
480
|
-
|
|
481
540
|
return {
|
|
482
541
|
compactionItem,
|
|
483
542
|
replacementHistory,
|
|
@@ -490,12 +549,7 @@ async function collectCompactionV2Output(
|
|
|
490
549
|
function handleCompactionV2SseEvent(
|
|
491
550
|
data: string,
|
|
492
551
|
eventName: string | undefined,
|
|
493
|
-
state:
|
|
494
|
-
outputItemCount: number;
|
|
495
|
-
compactionItems: Array<Record<string, unknown>>;
|
|
496
|
-
sawCompleted: boolean;
|
|
497
|
-
usage: CompactionV2Usage | undefined;
|
|
498
|
-
},
|
|
552
|
+
state: CompactionV2CollectionState,
|
|
499
553
|
): void {
|
|
500
554
|
if (data === "[DONE]") return;
|
|
501
555
|
let event: Record<string, unknown>;
|
|
@@ -504,7 +558,14 @@ function handleCompactionV2SseEvent(
|
|
|
504
558
|
} catch (err) {
|
|
505
559
|
throw new Error(`V2 compaction stream parse failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
506
560
|
}
|
|
561
|
+
handleCompactionV2Event(event, eventName, state);
|
|
562
|
+
}
|
|
507
563
|
|
|
564
|
+
function handleCompactionV2Event(
|
|
565
|
+
event: Record<string, unknown>,
|
|
566
|
+
eventName: string | undefined,
|
|
567
|
+
state: CompactionV2CollectionState,
|
|
568
|
+
): void {
|
|
508
569
|
const type = typeof event.type === "string" ? event.type : eventName;
|
|
509
570
|
if (type === "response.output_item.done") {
|
|
510
571
|
state.outputItemCount++;
|
|
@@ -809,6 +809,8 @@ export interface SummaryOptions {
|
|
|
809
809
|
promptCacheKey?: string;
|
|
810
810
|
/** Mutable provider state used to keep Codex compaction on the live session identity. */
|
|
811
811
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
812
|
+
/** Whether Codex remote compaction should prefer the provider WebSocket transport. */
|
|
813
|
+
preferWebsockets?: boolean;
|
|
812
814
|
/** Classification shared by every provider request in this logical compaction. */
|
|
813
815
|
codexCompaction?: CodexCompactionContext;
|
|
814
816
|
/** Provider-visible tools for remote compaction transports that replay native tool history. */
|
|
@@ -1432,6 +1434,7 @@ export async function compact(
|
|
|
1432
1434
|
sessionId: options?.sessionId,
|
|
1433
1435
|
promptCacheKey: options?.promptCacheKey,
|
|
1434
1436
|
providerSessionState: options?.providerSessionState,
|
|
1437
|
+
preferWebsockets: options?.preferWebsockets,
|
|
1435
1438
|
codexCompaction: options?.codexCompaction,
|
|
1436
1439
|
tools: options?.tools,
|
|
1437
1440
|
fetch: options?.fetch,
|
|
@@ -1509,6 +1512,7 @@ export async function compact(
|
|
|
1509
1512
|
requestCompactionV2Streaming(model, key, request, signal, {
|
|
1510
1513
|
fetch: summaryOptions.fetch,
|
|
1511
1514
|
providerSessionState: summaryOptions.providerSessionState,
|
|
1515
|
+
preferWebsockets: summaryOptions.preferWebsockets,
|
|
1512
1516
|
codexCompaction: summaryOptions.codexCompaction,
|
|
1513
1517
|
}),
|
|
1514
1518
|
{ signal },
|