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.
@@ -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 response = await input.ctx.modelRegistry.complete(
60
- model,
61
- {
62
- messages: [{
63
- role: "user",
64
- content: [{ type: "text", text: input.prompt }],
65
- timestamp: input.now(),
66
- }],
67
- },
68
- {
69
- maxTokens,
70
- signal: input.event.signal,
71
- cacheRetention: "none",
72
- sessionId: randomUUID(),
73
- },
74
- );
75
- if (input.event.signal.aborted) throw new Error("Compaction summary generation aborted");
76
- const stopReason = responseStopReason(response);
77
- if (stopReason === "length") throw new Error("Compaction summary hit the model output limit");
78
- if (stopReason === "error" || stopReason === "aborted") {
79
- throw new Error(`Compaction summary stopped with ${stopReason}`);
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-alpha.4";
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";