pi-openai-toolkit 0.20.8 → 0.21.1
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/README.md +8 -26
- package/README.zh.md +8 -26
- package/config.schema.json +5 -93
- package/docs/configuration.md +19 -31
- package/docs/internals.md +14 -23
- package/package.json +3 -13
- package/src/compact-client.ts +48 -25
- package/src/compaction-errors.ts +96 -0
- package/src/config/legacy.ts +519 -717
- package/src/config/migration.ts +11 -8
- package/src/config/notifications.ts +36 -28
- package/src/config/policy.ts +10 -18
- package/src/config/v2.ts +1167 -1380
- package/src/config.ts +9 -18
- package/src/context-management/manual-compact.ts +0 -11
- package/src/debug.ts +5 -4
- package/src/extension-runtime.ts +61 -15
- package/src/payload-rewrite.ts +2 -5
- package/src/remote-v2-client.ts +11 -14
- package/src/responses-websocket/transport.ts +136 -65
- package/src/runtime.ts +33 -0
- package/src/types.ts +14 -97
- package/tsconfig.check.json +0 -4
- package/src/auto-mode/classifier.ts +0 -161
- package/src/auto-mode/extension.ts +0 -691
- package/src/auto-mode/model-selection-guard.ts +0 -52
- package/src/auto-mode/policy.ts +0 -146
- package/src/auto-mode/prompt.ts +0 -279
- package/src/auto-mode/reviewer.ts +0 -325
- package/src/auto-mode/tool-review-tui.ts +0 -211
- package/src/auto-mode/transcript.ts +0 -135
- package/src/auto-mode/types.ts +0 -137
package/src/config.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { CONFIG_PATH, normalizeLegacyConfig } from "./config/legacy";
|
|
|
7
7
|
import { resolveV2Config } from "./config/v2";
|
|
8
8
|
import {
|
|
9
9
|
CONFIG_FEATURES,
|
|
10
|
+
RETIRED_AUTO_MODE_ISSUE,
|
|
10
11
|
createPolicyDefaults,
|
|
11
12
|
type ConfigDocumentSnapshot,
|
|
12
13
|
type ConfigFeature,
|
|
@@ -116,7 +117,6 @@ const LEGACY_PATHS: Record<string, string> = {
|
|
|
116
117
|
"imageGeneration.enabled": "imageGeneration.enabled",
|
|
117
118
|
"imageGeneration.defaultModel": "imageGeneration.models",
|
|
118
119
|
"imageGeneration.allowedModels": "imageGeneration.models",
|
|
119
|
-
"autoMode.available": "autoMode.models",
|
|
120
120
|
"compatibility.transport": "compaction.gatewayContextModels",
|
|
121
121
|
"diagnostics.level": "compaction.debug",
|
|
122
122
|
"diagnostics.notifyOnLoad": "compaction.notifyOnLoad",
|
|
@@ -155,13 +155,17 @@ function legacyIssues(loaded: LoadedToolkitConfig, modelKey: string | undefined)
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
const issues = loaded.warnings.filter((warning) => !warning.startsWith("Ignoring webSearch.routes.")).map((warning): ConfigIssue => {
|
|
158
|
+
// A removed feature is retired, not invalid: it never blocks another feature.
|
|
159
|
+
if (warning.startsWith("Ignoring autoMode:")) {
|
|
160
|
+
return { severity: "warning", code: RETIRED_AUTO_MODE_ISSUE, path: "autoMode", feature: "document" };
|
|
161
|
+
}
|
|
158
162
|
const match = /^Ignoring ([^:]+):/.exec(warning);
|
|
159
163
|
const field = warning.startsWith("Ignoring compaction.responsesApis entry ") ? "compaction.responsesApis" : match?.[1] ?? "$";
|
|
160
164
|
const root = field.split(".")[0];
|
|
161
165
|
const diagnostic = Object.entries(LEGACY_PATHS).some(([leaf, input]) => leaf.startsWith("diagnostics.") && input === field);
|
|
162
166
|
const feature: ConfigFeature | "document" = diagnostic ? "diagnostics"
|
|
163
167
|
: field === "compaction.gatewayContextModels" ? "compatibility" : root === "compaction" ? "context"
|
|
164
|
-
: root === "webSearch" || root === "imageGeneration"
|
|
168
|
+
: root === "webSearch" || root === "imageGeneration" ? root : "document";
|
|
165
169
|
const supersededDefault = field === "webSearch.defaultRoute" && legacyRouteKey(raw, modelKey) !== undefined;
|
|
166
170
|
const warningOnly = warning.endsWith("unknown field.") || !match
|
|
167
171
|
|| warning.includes("expected at least one model id") || supersededDefault;
|
|
@@ -209,13 +213,6 @@ function fromLegacy(loaded: LoadedToolkitConfig, model: WebSearchModel | undefin
|
|
|
209
213
|
allowedModels: [...config.imageGeneration.models],
|
|
210
214
|
};
|
|
211
215
|
policy.responsesWebSocket = { enabled: false };
|
|
212
|
-
const { enabled, models, reviewerModel, classifier, ...auto } = config.autoMode;
|
|
213
|
-
policy.autoMode = {
|
|
214
|
-
...structuredClone(auto),
|
|
215
|
-
available: enabled && modelKey !== undefined && models.includes(modelKey),
|
|
216
|
-
reviewerModel: reviewerModel ?? null,
|
|
217
|
-
classifier: { ...classifier, model: classifier.model ?? null },
|
|
218
|
-
};
|
|
219
216
|
policy.compatibility.transport = modelKey && compaction.gatewayContextModels.includes(modelKey) ? "codex-gateway" : "standard";
|
|
220
217
|
policy.diagnostics = {
|
|
221
218
|
level: compaction.debug ? "debug" : "info",
|
|
@@ -230,7 +227,6 @@ function fromLegacy(loaded: LoadedToolkitConfig, model: WebSearchModel | undefin
|
|
|
230
227
|
for (const leaf of leafPaths(policy)) {
|
|
231
228
|
let input = LEGACY_PATHS[leaf] ?? leaf;
|
|
232
229
|
if (leaf === "context.mode" && !compaction.enabled) input = "compaction.enabled";
|
|
233
|
-
if (leaf === "autoMode.available" && !enabled) input = "autoMode.enabled";
|
|
234
230
|
if (leaf === "webSearch.route") {
|
|
235
231
|
if (!config.webSearch.enabled) input = "webSearch.enabled";
|
|
236
232
|
else if (route.source === "exact") {
|
|
@@ -251,9 +247,8 @@ function fromLegacy(loaded: LoadedToolkitConfig, model: WebSearchModel | undefin
|
|
|
251
247
|
}
|
|
252
248
|
|
|
253
249
|
/** Internal engine adapter. All interpretation of document names stays in this module. */
|
|
254
|
-
function engineConfig(policy: EffectiveToolkitPolicy,
|
|
255
|
-
const { context, diagnostics, imageGeneration
|
|
256
|
-
const { available, reviewerModel, classifier, ...auto } = autoMode;
|
|
250
|
+
function engineConfig(policy: EffectiveToolkitPolicy, gatewayKeys: string[]): ToolkitConfig {
|
|
251
|
+
const { context, diagnostics, imageGeneration } = policy;
|
|
257
252
|
return {
|
|
258
253
|
compaction: {
|
|
259
254
|
enabled: context.mode !== "pi",
|
|
@@ -276,10 +271,6 @@ function engineConfig(policy: EffectiveToolkitPolicy, modelKey: string | undefin
|
|
|
276
271
|
webSearch: policy.webSearch.route === "unmanaged" ? { enabled: false, models: [] }
|
|
277
272
|
: { enabled: true, models: [], defaultRoute: policy.webSearch.route },
|
|
278
273
|
imageGeneration: { enabled: imageGeneration.enabled, models: [...imageGeneration.allowedModels] },
|
|
279
|
-
autoMode: {
|
|
280
|
-
...structuredClone(auto), enabled: available, models: available && modelKey ? [modelKey] : [],
|
|
281
|
-
reviewerModel: reviewerModel ?? undefined, classifier: { ...classifier, model: classifier.model ?? undefined },
|
|
282
|
-
},
|
|
283
274
|
};
|
|
284
275
|
}
|
|
285
276
|
|
|
@@ -302,7 +293,7 @@ export function resolveToolkitConfig(loaded: LoadedToolkitConfig, model?: ExactM
|
|
|
302
293
|
for (const origin of Object.values(resolution.origins)) if (origin.kind !== "builtin") origin.source = source;
|
|
303
294
|
}
|
|
304
295
|
const gatewayModelKeys = v2 ? v2.gatewayModelKeys : [...loaded.config.compaction.gatewayContextModels];
|
|
305
|
-
const config = format === "v2" ? engineConfig(resolution.policy,
|
|
296
|
+
const config = format === "v2" ? engineConfig(resolution.policy, gatewayModelKeys) : structuredClone(loaded.config);
|
|
306
297
|
return freeze({ ...resolution, config, format, source, modelKey, gatewayModelKeys, snapshot: structuredClone(loaded) });
|
|
307
298
|
}
|
|
308
299
|
|
|
@@ -2,8 +2,6 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
3
3
|
import { buildSessionContext, estimateTokens, type ExtensionAPI, type ExtensionContext, type SessionBeforeCompactEvent, type SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { resolveToolkitConfig, type ResolvedToolkitConfig } from "../config";
|
|
5
|
-
import { isAutoModeEligible } from "../auto-mode/policy";
|
|
6
|
-
import { protectAutoModeSelection } from "../auto-mode/model-selection-guard";
|
|
7
5
|
import { parseModelSpec } from "../runtime";
|
|
8
6
|
import { THINKING_LEVELS } from "../types";
|
|
9
7
|
import { resolveCodexContextProvider } from "./codex-provider";
|
|
@@ -88,7 +86,6 @@ export class ManagedManualCompact implements ContextRolloverHandoff {
|
|
|
88
86
|
private sourcePolicy: ResolvedToolkitConfig | undefined;
|
|
89
87
|
private instructions = "";
|
|
90
88
|
private attemptSignal: AbortSignal | undefined;
|
|
91
|
-
private releaseApproval: (() => void) | undefined;
|
|
92
89
|
private selecting: string | undefined;
|
|
93
90
|
private selectionPromise: Promise<boolean> | undefined;
|
|
94
91
|
private expectedThinking: ThinkingLevel | undefined;
|
|
@@ -123,8 +120,6 @@ export class ManagedManualCompact implements ContextRolloverHandoff {
|
|
|
123
120
|
private clearActivity(): void {
|
|
124
121
|
clearTimeout(this.deliveryTimer);
|
|
125
122
|
this.deliveryTimer = undefined;
|
|
126
|
-
this.releaseApproval?.();
|
|
127
|
-
this.releaseApproval = undefined;
|
|
128
123
|
this.sourcePolicy = undefined;
|
|
129
124
|
}
|
|
130
125
|
private belongs(ctx: ExtensionContext, record = this.record): boolean {
|
|
@@ -193,12 +188,6 @@ export class ManagedManualCompact implements ContextRolloverHandoff {
|
|
|
193
188
|
if (!originalProvider.ok || !targetProvider.ok || !sameContextScope(originalProvider.provider, targetProvider.provider)) {
|
|
194
189
|
throw new Error("checkpoint model must authenticate to the same context backend/account");
|
|
195
190
|
}
|
|
196
|
-
const protection = protectAutoModeSelection(this.pi, {
|
|
197
|
-
sessionId: this.record!.sessionId, operationId: this.record!.operationId, target,
|
|
198
|
-
eligible: !targetPolicy.invalidFeatures.includes("autoMode") && isAutoModeEligible(target, targetPolicy.config.autoMode),
|
|
199
|
-
});
|
|
200
|
-
this.releaseApproval = protection.release;
|
|
201
|
-
if (!protection.allowed) throw new Error("checkpoint model would disable the engaged approval gate; allowlist it or choose another model");
|
|
202
191
|
this.notify(ctx, `checkpoint handoff requested (${this.record!.originalModel} → ${targetKey}); native compaction is intentionally cancelled.`);
|
|
203
192
|
} catch (error) {
|
|
204
193
|
if (!this.current(ctx, generation)) return;
|
package/src/debug.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as os from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
@@ -157,7 +158,7 @@ export function writeDebugArtifact(
|
|
|
157
158
|
ensureDir(targetDir);
|
|
158
159
|
|
|
159
160
|
const timestamp = new Date().toISOString();
|
|
160
|
-
const fileName = `${timestamp.replace(/[.:]/g, "-")}-${kind}.json`;
|
|
161
|
+
const fileName = `${timestamp.replace(/[.:]/g, "-")}-${kind}-${randomUUID()}.json`;
|
|
161
162
|
const filePath = path.join(targetDir, fileName);
|
|
162
163
|
const criticallyRedactedData = redactCriticalValue(data);
|
|
163
164
|
const envelope: DebugArtifactEnvelope = {
|
|
@@ -174,7 +175,7 @@ export function writeDebugArtifact(
|
|
|
174
175
|
data: settings.redactSensitiveData ? redactValue(criticallyRedactedData) : criticallyRedactedData,
|
|
175
176
|
};
|
|
176
177
|
|
|
177
|
-
fs.writeFileSync(filePath, `${JSON.stringify(envelope, null, 2)}\n`, "utf8");
|
|
178
|
+
fs.writeFileSync(filePath, `${JSON.stringify(envelope, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
178
179
|
return filePath;
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -206,7 +207,7 @@ export function writeReplayFailureArtifact(
|
|
|
206
207
|
ensureDir(targetDir);
|
|
207
208
|
|
|
208
209
|
const timestamp = new Date().toISOString();
|
|
209
|
-
const filePath = path.join(targetDir, `${timestamp.replace(/[.:]/g, "-")}-replay-failure.json`);
|
|
210
|
+
const filePath = path.join(targetDir, `${timestamp.replace(/[.:]/g, "-")}-replay-failure-${randomUUID()}.json`);
|
|
210
211
|
const data = redactCriticalValue({
|
|
211
212
|
event: "before_provider_request.rewrite-failed",
|
|
212
213
|
reason: details.reason,
|
|
@@ -227,7 +228,7 @@ export function writeReplayFailureArtifact(
|
|
|
227
228
|
redaction: { enabled: true },
|
|
228
229
|
data,
|
|
229
230
|
};
|
|
230
|
-
fs.writeFileSync(filePath, `${JSON.stringify(envelope, null, 2)}\n`, "utf8");
|
|
231
|
+
fs.writeFileSync(filePath, `${JSON.stringify(envelope, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
231
232
|
return filePath;
|
|
232
233
|
} catch {
|
|
233
234
|
// The replay failure path itself must never be masked by an artifact write
|
package/src/extension-runtime.ts
CHANGED
|
@@ -35,6 +35,8 @@ import {
|
|
|
35
35
|
type UnprojectedCompactionReason,
|
|
36
36
|
} from "./compaction-projection";
|
|
37
37
|
import { CODEX_GATEWAY_FORWARD_HEADERS } from "./responses-headers";
|
|
38
|
+
import { executeNativeCompaction, type NativeCompactionClientResult } from "./compact-client";
|
|
39
|
+
import { qualifiesForStandaloneCompaction, type CompactionErrorInfo } from "./compaction-errors";
|
|
38
40
|
import { resolveLatestNativeCompactionEntry } from "./details-store";
|
|
39
41
|
import {
|
|
40
42
|
getPiContextHookProjector,
|
|
@@ -57,6 +59,7 @@ import {
|
|
|
57
59
|
resolveEffectiveModel,
|
|
58
60
|
resolveNativeCompactionEnvironment,
|
|
59
61
|
resolveRemoteCompactionExecution,
|
|
62
|
+
isStandaloneCompactionEligible,
|
|
60
63
|
parseModelSpec,
|
|
61
64
|
type RemoteCompactionExecution,
|
|
62
65
|
} from "./runtime";
|
|
@@ -70,6 +73,8 @@ import {
|
|
|
70
73
|
createNativeCompactionDetails,
|
|
71
74
|
createNativeCompactionResult,
|
|
72
75
|
COMPACTION_EXTENSION_ID,
|
|
76
|
+
LEGACY_NATIVE_COMPACTION_STRATEGY,
|
|
77
|
+
REMOTE_V2_COMPACTION_STRATEGY,
|
|
73
78
|
getLatestDeferredToolCarryover,
|
|
74
79
|
getRemoteV2InputProvenance,
|
|
75
80
|
isNativeCompactionDetails,
|
|
@@ -107,6 +112,7 @@ function compactGatewayModels(resolved: ResolvedToolkitConfig): readonly string[
|
|
|
107
112
|
type CompactionDependencies = {
|
|
108
113
|
loadConfig: typeof loadToolkitConfig;
|
|
109
114
|
remoteCompact: typeof executeRemoteV2Compaction;
|
|
115
|
+
standaloneCompact: typeof executeNativeCompaction;
|
|
110
116
|
nativeFallback: typeof runNativeFallbackCompaction;
|
|
111
117
|
contextWindows: CodexContextWindowManager;
|
|
112
118
|
manualCompact?: ManagedManualCompact;
|
|
@@ -132,7 +138,7 @@ type ContextToolSyncOutcome = {
|
|
|
132
138
|
type ResponsesCompactOutcome =
|
|
133
139
|
| { outcome: "success"; compaction: CompactionResult<NativeCompactionDetails> }
|
|
134
140
|
| { outcome: "aborted" }
|
|
135
|
-
| { outcome: "failed" }
|
|
141
|
+
| { outcome: "failed"; remoteFailure?: CompactionErrorInfo & { reason: string; status?: number }; protocol?: string }
|
|
136
142
|
| {
|
|
137
143
|
outcome: "unprojected-input";
|
|
138
144
|
reason: UnprojectedCompactionReason;
|
|
@@ -343,6 +349,8 @@ async function runResponsesNativeCompact(
|
|
|
343
349
|
execution: RemoteCompactionExecution,
|
|
344
350
|
remoteCompact: typeof executeRemoteV2Compaction,
|
|
345
351
|
projectCompactionContext: CompactionContextProjection | undefined,
|
|
352
|
+
/** Only ordinary compaction supplies this; managed recovery remains V2-only. */
|
|
353
|
+
standaloneCompact?: typeof executeNativeCompaction,
|
|
346
354
|
): Promise<ResponsesCompactOutcome> {
|
|
347
355
|
const { consumer, compactor } = execution;
|
|
348
356
|
const instructions = buildCompactionInstructions(ctx.getSystemPrompt(), event.customInstructions);
|
|
@@ -477,7 +485,9 @@ async function runResponsesNativeCompact(
|
|
|
477
485
|
request = { ...request, ...extras };
|
|
478
486
|
}
|
|
479
487
|
|
|
480
|
-
|
|
488
|
+
if (event.signal?.aborted) return { outcome: "aborted" };
|
|
489
|
+
let protocol: "remote-v2" | "standalone-compact" = "remote-v2";
|
|
490
|
+
let compactResult: Awaited<ReturnType<typeof executeRemoteV2Compaction>> | NativeCompactionClientResult = await remoteCompact({
|
|
481
491
|
runtime: compactor,
|
|
482
492
|
request,
|
|
483
493
|
signal: event.signal,
|
|
@@ -485,24 +495,46 @@ async function runResponsesNativeCompact(
|
|
|
485
495
|
context: ctx,
|
|
486
496
|
});
|
|
487
497
|
|
|
488
|
-
if (compactResult.ok
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
498
|
+
if (!compactResult.ok) {
|
|
499
|
+
if (compactResult.reason === "aborted" || event.signal?.aborted) return { outcome: "aborted" };
|
|
500
|
+
const tryStandalone = !!standaloneCompact && isStandaloneCompactionEligible(compactor) && qualifiesForStandaloneCompaction(compactResult);
|
|
501
|
+
writeDebugArtifact("compaction-event", {
|
|
502
|
+
event: "session_before_compact.remote-v2-failure",
|
|
503
|
+
protocol,
|
|
504
|
+
reason: compactResult.reason,
|
|
505
|
+
status: compactResult.status,
|
|
506
|
+
errorCode: compactResult.errorCode,
|
|
507
|
+
errorType: compactResult.errorType,
|
|
508
|
+
errorParam: compactResult.errorParam,
|
|
509
|
+
errorMessage: compactResult.errorMessage,
|
|
510
|
+
nextStep: tryStandalone ? "standalone-compact" : "native-summary",
|
|
511
|
+
}, config, ctx);
|
|
512
|
+
if (tryStandalone && standaloneCompact) {
|
|
513
|
+
if (event.signal?.aborted) return { outcome: "aborted" };
|
|
514
|
+
protocol = "standalone-compact";
|
|
515
|
+
compactResult = await standaloneCompact({ runtime: compactor, request, signal: event.signal, settings: config, context: ctx });
|
|
516
|
+
}
|
|
517
|
+
if (!compactResult.ok) {
|
|
518
|
+
if (compactResult.reason === "aborted" || event.signal?.aborted) return { outcome: "aborted" };
|
|
519
|
+
const remoteFailure = {
|
|
493
520
|
reason: compactResult.reason,
|
|
494
521
|
status: compactResult.status,
|
|
522
|
+
errorCode: compactResult.errorCode,
|
|
523
|
+
errorType: compactResult.errorType,
|
|
524
|
+
errorParam: compactResult.errorParam,
|
|
495
525
|
errorMessage: compactResult.errorMessage,
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
526
|
+
};
|
|
527
|
+
if (protocol === "standalone-compact") writeDebugArtifact("compaction-event", {
|
|
528
|
+
event: "session_before_compact.standalone-compact-failure", protocol, ...remoteFailure, nextStep: "native-summary",
|
|
529
|
+
}, config, ctx);
|
|
530
|
+
return { outcome: "failed", remoteFailure, protocol };
|
|
531
|
+
}
|
|
501
532
|
}
|
|
502
533
|
|
|
503
534
|
let details: NativeCompactionDetails;
|
|
504
535
|
try {
|
|
505
536
|
details = createNativeCompactionDetails({
|
|
537
|
+
strategy: protocol === "standalone-compact" ? LEGACY_NATIVE_COMPACTION_STRATEGY : REMOTE_V2_COMPACTION_STRATEGY,
|
|
506
538
|
provider: consumer.provider,
|
|
507
539
|
api: consumer.api,
|
|
508
540
|
model: consumer.model,
|
|
@@ -542,7 +574,7 @@ async function runResponsesNativeCompact(
|
|
|
542
574
|
config,
|
|
543
575
|
ctx,
|
|
544
576
|
);
|
|
545
|
-
return { outcome: "failed" };
|
|
577
|
+
return { outcome: "failed", remoteFailure: { reason: "invalid-native-details" }, protocol };
|
|
546
578
|
}
|
|
547
579
|
|
|
548
580
|
const compaction = createNativeCompactionResult({
|
|
@@ -554,7 +586,8 @@ async function runResponsesNativeCompact(
|
|
|
554
586
|
writeDebugArtifact(
|
|
555
587
|
"compaction-event",
|
|
556
588
|
{
|
|
557
|
-
event:
|
|
589
|
+
event: `session_before_compact.${protocol}-success`,
|
|
590
|
+
protocol,
|
|
558
591
|
consumer: {
|
|
559
592
|
provider: consumer.provider,
|
|
560
593
|
api: consumer.api,
|
|
@@ -829,6 +862,7 @@ async function handleSessionBeforeCompact(
|
|
|
829
862
|
resolution.execution,
|
|
830
863
|
dependencies.remoteCompact,
|
|
831
864
|
dependencies.projectCompactionContext,
|
|
865
|
+
dependencies.standaloneCompact,
|
|
832
866
|
);
|
|
833
867
|
if (responsesOutcome.outcome === "success") {
|
|
834
868
|
return { compaction: responsesOutcome.compaction };
|
|
@@ -865,7 +899,18 @@ async function handleSessionBeforeCompact(
|
|
|
865
899
|
);
|
|
866
900
|
return { cancel: true };
|
|
867
901
|
}
|
|
868
|
-
//
|
|
902
|
+
// Only an actual exhausted remote attempt warns, never an input/routing skip.
|
|
903
|
+
if (event.signal?.aborted) return { cancel: true };
|
|
904
|
+
if (responsesOutcome.remoteFailure) {
|
|
905
|
+
const failure = responsesOutcome.remoteFailure;
|
|
906
|
+
writeDebugArtifact("compaction-event", {
|
|
907
|
+
event: "session_before_compact.remote-to-native-summary",
|
|
908
|
+
protocol: responsesOutcome.protocol,
|
|
909
|
+
...failure,
|
|
910
|
+
nextStep: "native-summary",
|
|
911
|
+
}, config, ctx);
|
|
912
|
+
notifyWarning(ctx, `Remote compaction failed (${failure.reason}${failure.status ? `, HTTP ${failure.status}` : ""}${failure.errorCode ? `, ${failure.errorCode}` : ""}); continuing with a native text summary. No encrypted remote checkpoint was produced.${failure.errorMessage ? ` ${failure.errorMessage}` : ""}`);
|
|
913
|
+
}
|
|
869
914
|
} else {
|
|
870
915
|
writeDebugArtifact(
|
|
871
916
|
"compaction-event",
|
|
@@ -1380,6 +1425,7 @@ export default function registerCompactionExtension(
|
|
|
1380
1425
|
const manualCompact = new ManagedManualCompact(pi, contextWindows);
|
|
1381
1426
|
const dependencies: CompactionDependencies = {
|
|
1382
1427
|
remoteCompact: executeRemoteV2Compaction,
|
|
1428
|
+
standaloneCompact: executeNativeCompaction,
|
|
1383
1429
|
nativeFallback: runNativeFallbackCompaction,
|
|
1384
1430
|
contextWindows,
|
|
1385
1431
|
...overrides,
|
package/src/payload-rewrite.ts
CHANGED
|
@@ -118,11 +118,8 @@ function cloneStructuredValue(value: unknown): unknown {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
if (isRecord(value)) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
clone[key] = cloneStructuredValue(nested);
|
|
124
|
-
}
|
|
125
|
-
return clone;
|
|
121
|
+
// Define own data properties, including provider JSON keys such as __proto__.
|
|
122
|
+
return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, cloneStructuredValue(nested)]));
|
|
126
123
|
}
|
|
127
124
|
|
|
128
125
|
throw new Error(`Unsupported structured value: ${typeof value}`);
|
package/src/remote-v2-client.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { writeDebugArtifact } from "./debug";
|
|
3
|
+
import { decodeCompactionError, readCompactionResponseText, type CompactionErrorInfo } from "./compaction-errors";
|
|
3
4
|
import { buildResponsesRequestHeaders } from "./responses-headers";
|
|
4
5
|
import type { NativeCompactionRuntime } from "./runtime";
|
|
5
6
|
import type { NativeCompactionRequestBody, ResponsesInputItem } from "./serializer";
|
|
@@ -55,7 +56,7 @@ export type RemoteV2CompactionClientSuccess = {
|
|
|
55
56
|
response: RemoteV2ResponseEnvelope;
|
|
56
57
|
};
|
|
57
58
|
|
|
58
|
-
export type RemoteV2CompactionClientFailure = {
|
|
59
|
+
export type RemoteV2CompactionClientFailure = CompactionErrorInfo & {
|
|
59
60
|
ok: false;
|
|
60
61
|
reason: RemoteV2CompactionClientFailureReason;
|
|
61
62
|
status?: number;
|
|
@@ -213,14 +214,6 @@ function getEventType(event: ParsedSseEvent): string | undefined {
|
|
|
213
214
|
return isRecord(event.data) && isNonEmptyString(event.data.type) ? event.data.type : event.event;
|
|
214
215
|
}
|
|
215
216
|
|
|
216
|
-
function getErrorMessage(value: unknown): string | undefined {
|
|
217
|
-
if (!isRecord(value)) return undefined;
|
|
218
|
-
if (isNonEmptyString(value.message)) return value.message;
|
|
219
|
-
if (isRecord(value.error) && isNonEmptyString(value.error.message)) return value.error.message;
|
|
220
|
-
if (isNonEmptyString(value.error)) return value.error;
|
|
221
|
-
return undefined;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
217
|
function writeCompactArtifact(
|
|
225
218
|
data: unknown,
|
|
226
219
|
settings: CompactionConfig | undefined,
|
|
@@ -397,7 +390,7 @@ function reconcileSseCheckpoint(events: readonly ParsedSseEvent[]): CheckpointRe
|
|
|
397
390
|
ok: false,
|
|
398
391
|
reason: "error-event",
|
|
399
392
|
responseJson: errorEvent.data,
|
|
400
|
-
errorMessage:
|
|
393
|
+
errorMessage: decodeCompactionError(errorEvent.data).errorMessage,
|
|
401
394
|
};
|
|
402
395
|
}
|
|
403
396
|
|
|
@@ -668,7 +661,7 @@ export async function executeRemoteV2Compaction(
|
|
|
668
661
|
});
|
|
669
662
|
|
|
670
663
|
if (!response.ok) {
|
|
671
|
-
const responseText = await response
|
|
664
|
+
const responseText = await readCompactionResponseText(response, signal);
|
|
672
665
|
let responseJson: unknown;
|
|
673
666
|
if (responseText.trim()) {
|
|
674
667
|
try {
|
|
@@ -681,6 +674,7 @@ export async function executeRemoteV2Compaction(
|
|
|
681
674
|
ok: false,
|
|
682
675
|
reason: "non-2xx",
|
|
683
676
|
status: response.status,
|
|
677
|
+
...decodeCompactionError(responseJson),
|
|
684
678
|
responseText: responseText || undefined,
|
|
685
679
|
responseJson,
|
|
686
680
|
};
|
|
@@ -719,10 +713,13 @@ export async function executeRemoteV2Compaction(
|
|
|
719
713
|
}
|
|
720
714
|
|
|
721
715
|
if (!events) {
|
|
716
|
+
let bodyError: CompactionErrorInfo = {};
|
|
717
|
+
try { bodyError = decodeCompactionError(JSON.parse(responseText)); } catch { /* Not a JSON error. */ }
|
|
722
718
|
const failure: RemoteV2CompactionClientFailure = {
|
|
723
719
|
ok: false,
|
|
724
720
|
reason: "invalid-sse",
|
|
725
721
|
status: response.status,
|
|
722
|
+
...bodyError,
|
|
726
723
|
responseText,
|
|
727
724
|
};
|
|
728
725
|
writeCompactArtifact(
|
|
@@ -744,7 +741,7 @@ export async function executeRemoteV2Compaction(
|
|
|
744
741
|
ok: false,
|
|
745
742
|
reason: reconciliation.reason,
|
|
746
743
|
status: response.status,
|
|
747
|
-
|
|
744
|
+
...decodeCompactionError(reconciliation.responseJson),
|
|
748
745
|
responseJson: reconciliation.responseJson,
|
|
749
746
|
};
|
|
750
747
|
writeCompactArtifact(
|
|
@@ -793,12 +790,12 @@ export async function executeRemoteV2Compaction(
|
|
|
793
790
|
);
|
|
794
791
|
return success;
|
|
795
792
|
} catch (error) {
|
|
796
|
-
const failure: RemoteV2CompactionClientFailure = isAbortError(error)
|
|
793
|
+
const failure: RemoteV2CompactionClientFailure = signal?.aborted || isAbortError(error)
|
|
797
794
|
? { ok: false, reason: "aborted" }
|
|
798
795
|
: {
|
|
799
796
|
ok: false,
|
|
800
797
|
reason: "network-error",
|
|
801
|
-
errorMessage:
|
|
798
|
+
errorMessage: "Compaction transport failed; no remote checkpoint was produced.",
|
|
802
799
|
};
|
|
803
800
|
writeCompactArtifact(
|
|
804
801
|
{
|