ds4-context-engine 0.3.0-alpha.4 → 0.3.0-beta.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 +25 -11
- package/docs/ADR/058-bounded-manifest-storage.md +31 -0
- package/docs/ADR/README.md +1 -0
- package/docs/ARCHITECTURE.md +14 -9
- package/docs/COMPACTION.md +8 -8
- package/docs/CONTEXT_MANIFEST.md +10 -1
- package/docs/CONTEXT_PERSISTENCE_TOOL.md +2 -2
- package/docs/DOGFOODING_0.3.0_ALPHA.md +36 -10
- package/docs/DOGFOODING_0.3.0_BETA.md +331 -0
- package/docs/RELEASE_READINESS_0.2.0.md +1 -1
- package/docs/RELEASING.md +5 -5
- package/docs/STORAGE.md +16 -2
- package/docs/STORAGE_MAINTENANCE.md +121 -0
- package/docs/releases/0.3.0-alpha.4.md +5 -3
- package/docs/releases/0.3.0-alpha.5.md +60 -0
- package/docs/releases/0.3.0-beta.1.md +81 -0
- package/package.json +6 -2
- package/scripts/ds4-context-storage.mjs +148 -0
- package/src/extension/commands.ts +83 -2
- package/src/extension/runtime.ts +81 -24
- package/src/pi-adapter/compaction-adapter.ts +107 -23
- package/src/pi-adapter/compaction-coordinator.ts +425 -117
- package/src/pi-adapter/summary-generator.ts +140 -22
- package/src/pi-adapter/version.ts +1 -1
|
@@ -12,6 +12,17 @@ import {
|
|
|
12
12
|
type SummaryValidationResult,
|
|
13
13
|
} from "ds4-context-core/compaction/summary-contract";
|
|
14
14
|
|
|
15
|
+
export const COMPACTION_TRANSPORT_MAX_ATTEMPTS = 3;
|
|
16
|
+
const COMPACTION_TRANSPORT_RETRY_DELAYS_MS = [200, 500] as const;
|
|
17
|
+
|
|
18
|
+
export interface CompactionTransportRetryDiagnostic {
|
|
19
|
+
stage: "segment" | "aggregate";
|
|
20
|
+
failedAttempt: number;
|
|
21
|
+
nextAttempt: number;
|
|
22
|
+
maxAttempts: number;
|
|
23
|
+
delayMs: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
export interface GenerateValidatedSummaryInput {
|
|
16
27
|
stage: "segment" | "aggregate";
|
|
17
28
|
prompt: string;
|
|
@@ -23,6 +34,7 @@ export interface GenerateValidatedSummaryInput {
|
|
|
23
34
|
event: SessionBeforeCompactEvent;
|
|
24
35
|
ctx: ExtensionContext;
|
|
25
36
|
now: () => number;
|
|
37
|
+
onTransportRetry?: (diagnostic: CompactionTransportRetryDiagnostic) => void;
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
export interface GeneratedSummary {
|
|
@@ -50,33 +62,139 @@ function responseStopReason(response: unknown): string | undefined {
|
|
|
50
62
|
return typeof response.stopReason === "string" ? response.stopReason : undefined;
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
function responseErrorMessage(response: unknown): string | undefined {
|
|
66
|
+
if (!response || typeof response !== "object" || !("errorMessage" in response)) return undefined;
|
|
67
|
+
return typeof response.errorMessage === "string" ? response.errorMessage : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type ProviderFailureCategory =
|
|
71
|
+
| "aborted"
|
|
72
|
+
| "usage-limit"
|
|
73
|
+
| "rate-limit"
|
|
74
|
+
| "input-limit"
|
|
75
|
+
| "authentication"
|
|
76
|
+
| "transport"
|
|
77
|
+
| "provider-error";
|
|
78
|
+
|
|
79
|
+
function providerFailureCategory(value: unknown): ProviderFailureCategory {
|
|
80
|
+
const message = value instanceof Error
|
|
81
|
+
? value.message
|
|
82
|
+
: typeof value === "string"
|
|
83
|
+
? value
|
|
84
|
+
: "";
|
|
85
|
+
if (value instanceof Error && value.name === "AbortError") return "aborted";
|
|
86
|
+
if (/usage|quota|credit|billing/iu.test(message)) return "usage-limit";
|
|
87
|
+
if (/rate|too many requests|429/iu.test(message)) return "rate-limit";
|
|
88
|
+
if (/(?:context|prompt|input).{0,48}(?:exceed|limit|maximum|too (?:long|large)|tokens?)|tokens?.{0,48}(?:exceed|limit|maximum|too many)|maximum (?:context|input|prompt|length)/iu.test(message)) {
|
|
89
|
+
return "input-limit";
|
|
90
|
+
}
|
|
91
|
+
if (/auth|credential|api.?key|permission|forbidden|401|403/iu.test(message)) return "authentication";
|
|
92
|
+
if (/timeout|timed out|network|connection|socket|dns|fetch failed|econn(?:reset|refused|aborted)|etimedout|eai_again|enotfound|und_err/iu.test(message)) {
|
|
93
|
+
return "transport";
|
|
94
|
+
}
|
|
95
|
+
if (/abort|cancel/iu.test(message)) return "aborted";
|
|
96
|
+
return "provider-error";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function abortedError(): Error {
|
|
100
|
+
return new Error("Compaction summary generation aborted");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function waitForTransportRetry(signal: AbortSignal, milliseconds: number): Promise<void> {
|
|
104
|
+
if (signal.aborted) throw abortedError();
|
|
105
|
+
await new Promise<void>((resolve, reject) => {
|
|
106
|
+
const onAbort = (): void => {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
reject(abortedError());
|
|
109
|
+
};
|
|
110
|
+
const timer = setTimeout(() => {
|
|
111
|
+
signal.removeEventListener("abort", onAbort);
|
|
112
|
+
resolve();
|
|
113
|
+
}, milliseconds);
|
|
114
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function transportFailureSuffix(category: ProviderFailureCategory, attempts: number): string {
|
|
119
|
+
return category === "transport"
|
|
120
|
+
? `category=${category}; attempts=${attempts}`
|
|
121
|
+
: `category=${category}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
53
124
|
export async function generateValidatedSummary(
|
|
54
125
|
input: GenerateValidatedSummaryInput,
|
|
55
126
|
): Promise<GeneratedSummary> {
|
|
56
127
|
const model = input.ctx.model;
|
|
57
128
|
if (!model) throw new Error("Compaction summary generation requires an active model");
|
|
58
129
|
const maxTokens = Math.max(1, Math.min(input.maxSummaryTokens, model.maxTokens ?? input.maxSummaryTokens));
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
130
|
+
const retryUsages: Usage[] = [];
|
|
131
|
+
let response: Awaited<ReturnType<typeof input.ctx.modelRegistry.complete>>;
|
|
132
|
+
let attempt = 0;
|
|
133
|
+
for (;;) {
|
|
134
|
+
if (input.event.signal.aborted) throw abortedError();
|
|
135
|
+
attempt++;
|
|
136
|
+
try {
|
|
137
|
+
response = await input.ctx.modelRegistry.complete(
|
|
138
|
+
model,
|
|
139
|
+
{
|
|
140
|
+
messages: [{
|
|
141
|
+
role: "user",
|
|
142
|
+
content: [{ type: "text", text: input.prompt }],
|
|
143
|
+
timestamp: input.now(),
|
|
144
|
+
}],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
maxTokens,
|
|
148
|
+
signal: input.event.signal,
|
|
149
|
+
cacheRetention: "none",
|
|
150
|
+
sessionId: randomUUID(),
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (input.event.signal.aborted) throw abortedError();
|
|
155
|
+
const category = providerFailureCategory(error);
|
|
156
|
+
if (category !== "transport" || attempt >= COMPACTION_TRANSPORT_MAX_ATTEMPTS) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Compaction ${input.stage} request failed (${transportFailureSuffix(category, attempt)})`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const delayMs = COMPACTION_TRANSPORT_RETRY_DELAYS_MS[attempt - 1] ?? 500;
|
|
162
|
+
await waitForTransportRetry(input.event.signal, delayMs);
|
|
163
|
+
input.onTransportRetry?.({
|
|
164
|
+
stage: input.stage,
|
|
165
|
+
failedAttempt: attempt,
|
|
166
|
+
nextAttempt: attempt + 1,
|
|
167
|
+
maxAttempts: COMPACTION_TRANSPORT_MAX_ATTEMPTS,
|
|
168
|
+
delayMs,
|
|
169
|
+
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (input.event.signal.aborted) throw abortedError();
|
|
173
|
+
const stopReason = responseStopReason(response);
|
|
174
|
+
if (stopReason === "error") {
|
|
175
|
+
const category = providerFailureCategory(responseErrorMessage(response));
|
|
176
|
+
if (category === "transport" && attempt < COMPACTION_TRANSPORT_MAX_ATTEMPTS) {
|
|
177
|
+
retryUsages.push(response.usage);
|
|
178
|
+
const delayMs = COMPACTION_TRANSPORT_RETRY_DELAYS_MS[attempt - 1] ?? 500;
|
|
179
|
+
await waitForTransportRetry(input.event.signal, delayMs);
|
|
180
|
+
input.onTransportRetry?.({
|
|
181
|
+
stage: input.stage,
|
|
182
|
+
failedAttempt: attempt,
|
|
183
|
+
nextAttempt: attempt + 1,
|
|
184
|
+
maxAttempts: COMPACTION_TRANSPORT_MAX_ATTEMPTS,
|
|
185
|
+
delayMs,
|
|
186
|
+
});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Compaction ${input.stage} summary stopped with error (${transportFailureSuffix(category, attempt)})`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (stopReason === "aborted") {
|
|
194
|
+
throw new Error(`Compaction ${input.stage} summary stopped with aborted (category=aborted)`);
|
|
195
|
+
}
|
|
196
|
+
if (stopReason === "length") throw new Error("Compaction summary hit the model output limit");
|
|
197
|
+
break;
|
|
80
198
|
}
|
|
81
199
|
if (response.content.some((block) => block.type === "toolCall")) {
|
|
82
200
|
throw new Error("Compaction summarizer attempted to call a tool");
|
|
@@ -140,7 +258,7 @@ export async function generateValidatedSummary(
|
|
|
140
258
|
: "";
|
|
141
259
|
throw new Error(`Compaction ${input.stage} summary validation failed: ${codes}${repairDiagnostics}`);
|
|
142
260
|
}
|
|
143
|
-
return { content, validation, usage: response.usage };
|
|
261
|
+
return { content, validation, usage: sumUsage([...retryUsages, response.usage]) };
|
|
144
262
|
}
|
|
145
263
|
|
|
146
264
|
export function sumUsage(usages: readonly Usage[]): Usage {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const EXTENSION_VERSION = "0.3.0-
|
|
1
|
+
export const EXTENSION_VERSION = "0.3.0-beta.1";
|
|
2
2
|
export const SUPPORTED_PI_VERSION = "0.84.3";
|
|
3
3
|
export const OBSERVER_PLANNER_VERSION = "observer-model-aware-v1";
|
|
4
4
|
export const PLANNER_VERSION = "managed-learned-ranking-v1";
|