@namzu/sdk 52.0.0 → 52.2.0
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 +29 -0
- package/README.md +1 -1
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +57 -17
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/connector/tools/adapter.d.ts +2 -2
- package/dist/contracts/a2a.d.ts +94 -94
- package/dist/manager/resident/learning.d.ts +6 -6
- package/dist/manager/resident/store.d.ts +4 -4
- package/dist/pals/activity/subscription-schema.d.ts +48 -48
- package/dist/pals/communication/ingress-schema.d.ts +192 -192
- package/dist/pals/communication/schema.d.ts +18 -18
- package/dist/peers/protocol.d.ts +36 -36
- package/dist/provider/capabilities.d.ts +9 -0
- package/dist/provider/capabilities.d.ts.map +1 -1
- package/dist/provider/capabilities.js +15 -0
- package/dist/provider/capabilities.js.map +1 -1
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +13 -0
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +10 -0
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/idle-timeout.d.ts.map +1 -1
- package/dist/provider/idle-timeout.js +5 -0
- package/dist/provider/idle-timeout.js.map +1 -1
- package/dist/provider/image-input-refusal.d.ts +9 -0
- package/dist/provider/image-input-refusal.d.ts.map +1 -0
- package/dist/provider/image-input-refusal.js +54 -0
- package/dist/provider/image-input-refusal.js.map +1 -0
- package/dist/provider/index.d.ts +2 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +2 -1
- package/dist/provider/index.js.map +1 -1
- package/dist/provider/retry.d.ts +7 -2
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +38 -4
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +2 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -0
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +1 -0
- package/dist/public-tools.js.map +1 -1
- package/dist/runtime/query/deferred-sandbox.d.ts +54 -0
- package/dist/runtime/query/deferred-sandbox.d.ts.map +1 -0
- package/dist/runtime/query/deferred-sandbox.js +75 -0
- package/dist/runtime/query/deferred-sandbox.js.map +1 -0
- package/dist/runtime/query/executor.d.ts +11 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +25 -3
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +2 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +94 -25
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +26 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +164 -21
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +10 -7
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.js +10 -7
- package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +42 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +119 -21
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/provider-rejected-image.d.ts +21 -1
- package/dist/runtime/query/iteration/provider-rejected-image.d.ts.map +1 -1
- package/dist/runtime/query/iteration/provider-rejected-image.js +53 -7
- package/dist/runtime/query/iteration/provider-rejected-image.js.map +1 -1
- package/dist/runtime/query/iteration/request-repair.d.ts +108 -0
- package/dist/runtime/query/iteration/request-repair.d.ts.map +1 -0
- package/dist/runtime/query/iteration/request-repair.js +593 -0
- package/dist/runtime/query/iteration/request-repair.js.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts +6 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +2 -2
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/request-rich-content.d.ts +5 -1
- package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
- package/dist/runtime/query/request-rich-content.js +67 -25
- package/dist/runtime/query/request-rich-content.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +1 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +1 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/tools/builtins/browser.d.ts +4 -4
- package/dist/tools/builtins/code-analysis.d.ts +49 -0
- package/dist/tools/builtins/code-analysis.d.ts.map +1 -0
- package/dist/tools/builtins/code-analysis.js +409 -0
- package/dist/tools/builtins/code-analysis.js.map +1 -0
- package/dist/tools/builtins/computer-use.d.ts +25 -25
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +12 -5
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/tools/builtins/index.d.ts +1 -0
- package/dist/tools/builtins/index.d.ts.map +1 -1
- package/dist/tools/builtins/index.js +1 -0
- package/dist/tools/builtins/index.js.map +1 -1
- package/dist/tools/builtins/lsp.d.ts +1 -1
- package/dist/tools/builtins/skill.d.ts +2 -2
- package/dist/types/code-analysis/index.d.ts +299 -0
- package/dist/types/code-analysis/index.d.ts.map +1 -0
- package/dist/types/code-analysis/index.js +30 -0
- package/dist/types/code-analysis/index.js.map +1 -0
- package/dist/types/plugin/index.d.ts +6 -6
- package/dist/types/provider/errors.d.ts.map +1 -1
- package/dist/types/provider/errors.js +30 -5
- package/dist/types/provider/errors.js.map +1 -1
- package/dist/types/provider/interface.d.ts +12 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/provider/stream.d.ts +1 -1
- package/dist/types/session/config.d.ts +14 -0
- package/dist/types/session/config.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +20 -5
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/session/records.d.ts +127 -127
- package/dist/types/tool/index.d.ts +9 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +2 -2
- package/src/compaction/verifier.ts +57 -20
- package/src/provider/capabilities.ts +17 -0
- package/src/provider/errors.ts +13 -0
- package/src/provider/fallback.ts +10 -0
- package/src/provider/idle-timeout.ts +5 -0
- package/src/provider/image-input-refusal.ts +54 -0
- package/src/provider/index.ts +2 -0
- package/src/provider/retry.ts +42 -4
- package/src/public-runtime.ts +28 -0
- package/src/public-tools.ts +5 -0
- package/src/runtime/query/deferred-sandbox.ts +110 -0
- package/src/runtime/query/executor.ts +27 -3
- package/src/runtime/query/index.ts +127 -49
- package/src/runtime/query/iteration/index.ts +188 -23
- package/src/runtime/query/iteration/phases/compaction-breaker.ts +10 -8
- package/src/runtime/query/iteration/phases/compaction.ts +151 -35
- package/src/runtime/query/iteration/provider-rejected-image.ts +71 -5
- package/src/runtime/query/iteration/request-repair.ts +743 -0
- package/src/runtime/query/iteration/stream-turn.ts +11 -1
- package/src/runtime/query/request-rich-content.ts +77 -27
- package/src/runtime/query/tooling.ts +2 -0
- package/src/tools/builtins/code-analysis.ts +486 -0
- package/src/tools/builtins/computer-use.ts +13 -4
- package/src/tools/builtins/index.ts +5 -0
- package/src/types/code-analysis/index.ts +325 -0
- package/src/types/provider/errors.ts +33 -5
- package/src/types/provider/interface.ts +13 -0
- package/src/types/provider/stream.ts +1 -1
- package/src/types/session/config.ts +14 -0
- package/src/types/session/events.ts +25 -5
- package/src/types/tool/index.ts +9 -0
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from '../../../constants/tools/index.js'
|
|
16
16
|
import { withStreamedMessageIdentity } from '../../../manager/session/streamed-message-identity.js'
|
|
17
17
|
import { renderSkillsSection } from '../../../persona/assembler.js'
|
|
18
|
-
import { resolveProviderCapabilities } from '../../../provider/capabilities.js'
|
|
18
|
+
import { modelReadsImages, resolveProviderCapabilities } from '../../../provider/capabilities.js'
|
|
19
19
|
import { collectChatCompletion } from '../../../provider/collect-chat-completion.js'
|
|
20
20
|
import { renderToolSchema } from '../../../registry/tool/schema.js'
|
|
21
21
|
import { formatCompletionNotification } from '../../../scheduler/completion-inbox.js'
|
|
@@ -54,6 +54,7 @@ import { stableDigest } from '../../../utils/hash.js'
|
|
|
54
54
|
import { generateMessageId } from '../../../utils/id.js'
|
|
55
55
|
import { parseStructuredResultJson } from '../../../utils/structured-result-json.js'
|
|
56
56
|
import { createCallbackInference } from '../callback-inference.js'
|
|
57
|
+
import { SandboxAcquisitionStopped } from '../deferred-sandbox.js'
|
|
57
58
|
import { deliverDurableInbound } from '../durable-inbound.js'
|
|
58
59
|
import type { ToolCallOutcome } from '../executor.js'
|
|
59
60
|
import { projectObservationContext } from '../observation-context.js'
|
|
@@ -80,17 +81,27 @@ import { runAdvisoryPhase } from './phases/advisory.js'
|
|
|
80
81
|
import { runIterationCheckpoint } from './phases/checkpoint.js'
|
|
81
82
|
import { compactionBreaker } from './phases/compaction-breaker.js'
|
|
82
83
|
import {
|
|
84
|
+
AUTOMATIC_COMPACTION_FAILED,
|
|
85
|
+
OVERFLOW_RELIEF_LIMIT,
|
|
83
86
|
activeContextWindow,
|
|
87
|
+
clearCompactionErrorNote,
|
|
88
|
+
compactionErrorNote,
|
|
84
89
|
measureContext,
|
|
85
90
|
relieveOverflow,
|
|
86
91
|
runCompactionCheck,
|
|
92
|
+
stopOnRefill,
|
|
87
93
|
} from './phases/compaction.js'
|
|
88
94
|
import { runHandoffPause } from './phases/handoff.js'
|
|
89
95
|
import type { IterationContext } from './phases/index.js'
|
|
90
96
|
import { runPlanGate } from './phases/plan.js'
|
|
91
97
|
import { runToolReview } from './phases/tool-review.js'
|
|
92
98
|
import { refreshWorkingMemory, splitWorkingMemoryForRequest } from './phases/working-memory.js'
|
|
93
|
-
import {
|
|
99
|
+
import {
|
|
100
|
+
type TextOnlyModelRecovery,
|
|
101
|
+
type TextOnlyModelRefusal,
|
|
102
|
+
streamWithProviderRejectedImageRecovery,
|
|
103
|
+
} from './provider-rejected-image.js'
|
|
104
|
+
import { type MediaRemoval, RequestRepair } from './request-repair.js'
|
|
94
105
|
import {
|
|
95
106
|
type StepShaping,
|
|
96
107
|
appendWorkContext,
|
|
@@ -141,6 +152,11 @@ const CLOSING_RESPONSE_GUIDANCE =
|
|
|
141
152
|
|
|
142
153
|
export { awaitedJobGraceMs, settleGraceMs } from './outstanding-work.js'
|
|
143
154
|
|
|
155
|
+
/** One provider's model, as {@link IterationOrchestrator} remembers it. */
|
|
156
|
+
function textOnlyKey(providerId: string, model: string): string {
|
|
157
|
+
return `${providerId}\u0000${model}`
|
|
158
|
+
}
|
|
159
|
+
|
|
144
160
|
export class IterationOrchestrator {
|
|
145
161
|
private ctx: IterationContext
|
|
146
162
|
private advisoryTurn:
|
|
@@ -188,6 +204,73 @@ export class IterationOrchestrator {
|
|
|
188
204
|
}
|
|
189
205
|
/** Rich tool blocks already reported; durable history is scanned every turn. */
|
|
190
206
|
private readonly warnedRichToolResults = new Set<string>()
|
|
207
|
+
/** What this run has learned about requests its provider rejects as invalid, and how to repair them. */
|
|
208
|
+
private readonly requestRepair = new RequestRepair({
|
|
209
|
+
debug: (message) => this.ctx.log.debug(message),
|
|
210
|
+
onMediaRemoved: (removal) => this.noteMediaRemoved(removal),
|
|
211
|
+
servedBy: () => this.ctx.servingMember?.(),
|
|
212
|
+
})
|
|
213
|
+
/**
|
|
214
|
+
* Provider and model pairs that refused a request for holding an image; they
|
|
215
|
+
* get none for the rest of the run. Keyed by both: one model id can be served
|
|
216
|
+
* by two members of a fallback chain, and only the one that refused is known.
|
|
217
|
+
*/
|
|
218
|
+
private readonly textOnlyModels = new Set<string>()
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The member a request for `model` goes to first. A fallback chain keeps
|
|
222
|
+
* serving from the member it last moved to, so that member, not the head,
|
|
223
|
+
* is the one whose reading of images counts.
|
|
224
|
+
*/
|
|
225
|
+
private servingModel(model: string): { readonly providerId: string; readonly model: string } {
|
|
226
|
+
const serving = this.ctx.servingMember?.()
|
|
227
|
+
return {
|
|
228
|
+
providerId: serving?.providerId ?? this.ctx.provider.id,
|
|
229
|
+
model: serving?.model ?? model,
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Whether a request for `model` may carry images: the driver's catalogue and this run's own evidence agree. */
|
|
234
|
+
private modelReadsImages(model: string): boolean {
|
|
235
|
+
const serving = this.servingModel(model)
|
|
236
|
+
return (
|
|
237
|
+
!this.textOnlyModels.has(textOnlyKey(serving.providerId, serving.model)) &&
|
|
238
|
+
modelReadsImages(this.ctx.provider, model)
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** What a request for `model` does when its model turns out to read text only. */
|
|
243
|
+
private textOnlyRecovery(model: string): TextOnlyModelRecovery {
|
|
244
|
+
return {
|
|
245
|
+
servedBy: () => this.servingModel(model),
|
|
246
|
+
onAccepted: (refused) => this.noteTextOnlyModel(refused),
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* A provider refused a request for holding an image its model cannot read, and
|
|
252
|
+
* the retry without images was accepted. Stop sending images to it, and say so
|
|
253
|
+
* once, so the host can remember the model beyond this run.
|
|
254
|
+
*/
|
|
255
|
+
private async noteTextOnlyModel(refused: TextOnlyModelRefusal): Promise<void> {
|
|
256
|
+
const key = textOnlyKey(refused.providerId, refused.model ?? '')
|
|
257
|
+
if (this.textOnlyModels.has(key)) return
|
|
258
|
+
this.textOnlyModels.add(key)
|
|
259
|
+
const { providerId, model } = refused
|
|
260
|
+
this.ctx.log.warn('Capability mismatch: the model cannot read images', {
|
|
261
|
+
[GENAI.SYSTEM]: providerId,
|
|
262
|
+
[GENAI.REQUEST_MODEL]: model ?? '',
|
|
263
|
+
})
|
|
264
|
+
await this.ctx.emitEvent?.({
|
|
265
|
+
type: 'capability_warning',
|
|
266
|
+
turnId: this.ctx.recorder.turnId,
|
|
267
|
+
capability: 'vision',
|
|
268
|
+
contentSource: 'history',
|
|
269
|
+
providerId,
|
|
270
|
+
...(model !== undefined ? { model } : {}),
|
|
271
|
+
message: `Model '${model ?? 'unknown'}' on provider '${providerId}' refused a request that held an image, so images are left out of its requests. They stay in the conversation and are sent again to a model that reads them.`,
|
|
272
|
+
})
|
|
273
|
+
}
|
|
191
274
|
/**
|
|
192
275
|
* The previous iteration held a `stopWhen` decision open for a worker.
|
|
193
276
|
*
|
|
@@ -442,16 +525,19 @@ export class IterationOrchestrator {
|
|
|
442
525
|
// checkpoint names the message the turn is answering.
|
|
443
526
|
await this.ctx.checkpointMgr.markLoopStart?.(recorder)
|
|
444
527
|
|
|
445
|
-
//
|
|
528
|
+
// Context-overflow relief is a two-rung ladder per *stuck point*, not per
|
|
529
|
+
// turn, as in the reference terminal (at most two reactive compactions per
|
|
530
|
+
// overflow): the first relief, then one deeper pass if the retry still
|
|
531
|
+
// overflows.
|
|
446
532
|
//
|
|
447
|
-
// The
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
let
|
|
533
|
+
// The limit exists so that an overflow that survives both rungs — an
|
|
534
|
+
// irreducible prompt — stops instead of looping. It was never meant to
|
|
535
|
+
// disarm the mechanism for the rest of the turn, which is what a
|
|
536
|
+
// turn-scoped flag did: one relief at iteration 3 left iteration 40 to die
|
|
537
|
+
// on an overflow with obvious moves left. The count is cleared by a turn
|
|
538
|
+
// that actually succeeded, which is the evidence that the turn is no
|
|
539
|
+
// longer stuck.
|
|
540
|
+
let overflowReliefs = 0
|
|
455
541
|
// The output cap a provider told us fits beside this prompt. One request's
|
|
456
542
|
// worth: it is cleared by a call that went through.
|
|
457
543
|
let outputCapOverride: number | undefined
|
|
@@ -814,6 +900,7 @@ export class IterationOrchestrator {
|
|
|
814
900
|
this.projectObservations(requestHistory),
|
|
815
901
|
this.ctx.turnConfig.maxRequestRichContentBytes ??
|
|
816
902
|
DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
|
|
903
|
+
{ imagesReadable: this.modelReadsImages(stepModel) },
|
|
817
904
|
)
|
|
818
905
|
appendWorkContext(shaping, messages, iterationNum, step)
|
|
819
906
|
if (closingDirective) messages.push(closingDirective)
|
|
@@ -989,6 +1076,8 @@ export class IterationOrchestrator {
|
|
|
989
1076
|
stepMessageId,
|
|
990
1077
|
{
|
|
991
1078
|
onAccepted: (identity) => this.acceptProviderRejectedImage(identity),
|
|
1079
|
+
textOnly: this.textOnlyRecovery(stepModel),
|
|
1080
|
+
repair: this.requestRepair,
|
|
992
1081
|
},
|
|
993
1082
|
Boolean(
|
|
994
1083
|
this.ctx.reviewAnswer || this.ctx.structuredOutput?.review || this.ctx.advisoryCtx,
|
|
@@ -1042,7 +1131,8 @@ export class IterationOrchestrator {
|
|
|
1042
1131
|
|
|
1043
1132
|
// The turn went through, so the turn is not sitting on an
|
|
1044
1133
|
// irreducible prompt any more. Re-arm relief for the next one.
|
|
1045
|
-
|
|
1134
|
+
overflowReliefs = 0
|
|
1135
|
+
clearCompactionErrorNote(this.ctx)
|
|
1046
1136
|
outputCapOverride = undefined
|
|
1047
1137
|
if (response.finishReason !== 'length') this.outputContinuations = 0
|
|
1048
1138
|
|
|
@@ -1895,6 +1985,12 @@ export class IterationOrchestrator {
|
|
|
1895
1985
|
yield* this.ctx.drainPending()
|
|
1896
1986
|
} catch (err) {
|
|
1897
1987
|
const cancelled = this.ctx.abortController.signal.aborted
|
|
1988
|
+
// A deferred sandbox that was cancelled or ran out of time. The
|
|
1989
|
+
// turn settles it as `cancelled` / `timeout` in `query()`, which is
|
|
1990
|
+
// not a failure of this iteration: it is recorded the way a turn
|
|
1991
|
+
// stopped by its own abort or timeout is (a cancelled step, no
|
|
1992
|
+
// error span), so the ledger and the stop reason agree.
|
|
1993
|
+
const sandboxStopped = err instanceof SandboxAcquisitionStopped
|
|
1898
1994
|
|
|
1899
1995
|
// This iteration gets a step too, and it is the one the
|
|
1900
1996
|
// argument three hundred lines above was actually about.
|
|
@@ -1955,12 +2051,13 @@ export class IterationOrchestrator {
|
|
|
1955
2051
|
startedAt: stepStartedAt,
|
|
1956
2052
|
usageBefore,
|
|
1957
2053
|
costBefore,
|
|
1958
|
-
unfinished:
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2054
|
+
unfinished:
|
|
2055
|
+
cancelled || sandboxStopped
|
|
2056
|
+
? { finishReason: 'cancelled' }
|
|
2057
|
+
: {
|
|
2058
|
+
finishReason: 'error',
|
|
2059
|
+
failure: describeStepFailure(err, this.ctx.provider.id),
|
|
2060
|
+
},
|
|
1964
2061
|
})
|
|
1965
2062
|
}
|
|
1966
2063
|
|
|
@@ -2020,22 +2117,58 @@ export class IterationOrchestrator {
|
|
|
2020
2117
|
}
|
|
2021
2118
|
|
|
2022
2119
|
if (
|
|
2023
|
-
!overflowRelieved &&
|
|
2024
2120
|
!(err instanceof AnswerReviewFailure) &&
|
|
2025
2121
|
classifyProviderError(err, this.ctx.provider.id).code === 'context_length_exceeded'
|
|
2026
2122
|
) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2123
|
+
// The rapid-refill check comes first, before the first reactive relief
|
|
2124
|
+
// of an overflow, as in the reference terminal. A relief that follows
|
|
2125
|
+
// the previous compaction within the refill window counts toward the
|
|
2126
|
+
// limit, so a tool that re-fills the window every time it is called
|
|
2127
|
+
// stops the turn instead of being cleared and called again without end.
|
|
2128
|
+
// The deeper rung of the same overflow is not counted again.
|
|
2129
|
+
if (overflowReliefs === 0 && (await stopOnRefill(this.ctx))) {
|
|
2130
|
+
if (iterationActivity) {
|
|
2131
|
+
this.ctx.activityStore.fail(iterationActivity.id, toErrorMessage(err))
|
|
2132
|
+
}
|
|
2133
|
+
if (this.stopForCompactionBreaker()) break
|
|
2134
|
+
}
|
|
2135
|
+
// A rung that sheds too little to change the verdict is not worth a
|
|
2136
|
+
// provider call; climb to the next one at once.
|
|
2137
|
+
let shed = false
|
|
2138
|
+
while (!shed && overflowReliefs < OVERFLOW_RELIEF_LIMIT) {
|
|
2139
|
+
overflowReliefs++
|
|
2140
|
+
shed = await relieveOverflow(this.ctx, { deeper: overflowReliefs > 1 })
|
|
2141
|
+
}
|
|
2029
2142
|
if (shed) {
|
|
2143
|
+
// Clearing results is a compaction too: only summaries and reducers
|
|
2144
|
+
// recorded themselves, so a clear-only relief was invisible to the
|
|
2145
|
+
// breaker.
|
|
2146
|
+
compactionBreaker(this.ctx)?.recordCompaction(iterationNum)
|
|
2030
2147
|
this.ctx.log.info('Retrying the turn after relieving a context overflow', {
|
|
2031
2148
|
[NAMZU.TURN_ID]: recorder.turnId,
|
|
2032
2149
|
[NAMZU.ITERATION]: iterationNum,
|
|
2150
|
+
'namzu.runtime.relief_rung': overflowReliefs,
|
|
2033
2151
|
})
|
|
2034
2152
|
if (iterationActivity) {
|
|
2035
2153
|
this.ctx.activityStore.complete(iterationActivity.id)
|
|
2036
2154
|
}
|
|
2037
2155
|
continue
|
|
2038
2156
|
}
|
|
2157
|
+
// Out of rungs. The conversation is kept; say why compaction could not
|
|
2158
|
+
// make room when it errored, as the reference does.
|
|
2159
|
+
const cause = compactionErrorNote(this.ctx)
|
|
2160
|
+
if (cause !== undefined && err instanceof Error) {
|
|
2161
|
+
try {
|
|
2162
|
+
err.message = `${err.message}${AUTOMATIC_COMPACTION_FAILED}${cause}`
|
|
2163
|
+
} catch {
|
|
2164
|
+
// A frozen error keeps its own message; the cause stays in the log.
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
if (sandboxStopped) {
|
|
2170
|
+
if (iterationActivity) this.ctx.activityStore.complete(iterationActivity.id)
|
|
2171
|
+
throw err
|
|
2039
2172
|
}
|
|
2040
2173
|
|
|
2041
2174
|
if (iterationActivity) {
|
|
@@ -2643,6 +2776,32 @@ export class IterationOrchestrator {
|
|
|
2643
2776
|
.some((t) => t.state !== 'completed' && t.state !== 'failed' && t.state !== 'canceled')
|
|
2644
2777
|
}
|
|
2645
2778
|
|
|
2779
|
+
/** Media the provider could not process was left out of a request that then went through. */
|
|
2780
|
+
private async noteMediaRemoved(removal: MediaRemoval): Promise<void> {
|
|
2781
|
+
this.ctx.log.warn('Media the provider rejected was left out of the request', {
|
|
2782
|
+
[GENAI.SYSTEM]: this.ctx.provider.id,
|
|
2783
|
+
'namzu.request.media_removed': removal.count,
|
|
2784
|
+
})
|
|
2785
|
+
const noun = removal.count === 1 ? 'a file' : `${removal.count} files`
|
|
2786
|
+
const it = removal.count === 1 ? 'it' : 'they'
|
|
2787
|
+
await this.ctx.emitEvent?.({
|
|
2788
|
+
type: 'capability_warning',
|
|
2789
|
+
turnId: this.ctx.recorder.turnId,
|
|
2790
|
+
capability: removal.kind === 'document' ? 'documents' : 'vision',
|
|
2791
|
+
// The person's own latest attachment is named as such; a count limit is
|
|
2792
|
+
// not a fault in any file.
|
|
2793
|
+
contentSource: removal.current
|
|
2794
|
+
? 'media-removed-current'
|
|
2795
|
+
: removal.limit
|
|
2796
|
+
? 'media-limit'
|
|
2797
|
+
: 'media-removed',
|
|
2798
|
+
providerId: this.ctx.provider.id,
|
|
2799
|
+
message: removal.limit
|
|
2800
|
+
? `The conversation holds more files than the provider accepts at once, so ${noun} (the oldest) ${removal.count === 1 ? 'was' : 'were'} left out of the request. The conversation still holds ${it}.`
|
|
2801
|
+
: `The provider could not process ${noun}${removal.current ? ' attached to your latest message' : ' in this conversation'}, so ${it} ${removal.count === 1 ? 'was' : 'were'} left out of the request. The conversation still holds ${it}.`,
|
|
2802
|
+
})
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2646
2805
|
private async acceptProviderRejectedImage(identity: RequestImageIdentity): Promise<void> {
|
|
2647
2806
|
const repaired = markProviderRejectedImage(this.ctx.recorder.messages, identity)
|
|
2648
2807
|
if (repaired.count === 0) {
|
|
@@ -2700,6 +2859,7 @@ export class IterationOrchestrator {
|
|
|
2700
2859
|
const finalMessages = projectRequestRichContent(
|
|
2701
2860
|
this.projectObservations(finalHistory),
|
|
2702
2861
|
this.ctx.turnConfig.maxRequestRichContentBytes ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
|
|
2862
|
+
{ imagesReadable: this.modelReadsImages(model) },
|
|
2703
2863
|
)
|
|
2704
2864
|
appendWorkContext(this.stepShaping(), finalMessages, this.steps.length + 1, { model })
|
|
2705
2865
|
// The closing directive is the last thing the model reads: after
|
|
@@ -2746,8 +2906,13 @@ export class IterationOrchestrator {
|
|
|
2746
2906
|
signal: this.ctx.abortController.signal,
|
|
2747
2907
|
} satisfies import('../../../types/provider/index.js').ChatCompletionParams
|
|
2748
2908
|
const response = await collectChatCompletion(
|
|
2749
|
-
streamWithProviderRejectedImageRecovery(
|
|
2750
|
-
this.
|
|
2909
|
+
streamWithProviderRejectedImageRecovery(
|
|
2910
|
+
this.ctx.provider,
|
|
2911
|
+
finalParams,
|
|
2912
|
+
(identity) => this.acceptProviderRejectedImage(identity),
|
|
2913
|
+
undefined,
|
|
2914
|
+
this.textOnlyRecovery(model),
|
|
2915
|
+
this.requestRepair,
|
|
2751
2916
|
),
|
|
2752
2917
|
)
|
|
2753
2918
|
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - Three failures in a row (a pass that ran and shed nothing): every one of
|
|
5
5
|
* them paid for a summary or a reducer call and left the history as it was,
|
|
6
|
-
* so a fourth would do the same.
|
|
7
|
-
* turn carries on
|
|
8
|
-
*
|
|
6
|
+
* so a fourth would do the same. Proactive compaction is skipped from then on
|
|
7
|
+
* and the turn carries on, silently as far as the person is concerned (a log
|
|
8
|
+
* line and a `breaker_open` event, no sentence); a pass the provider forces
|
|
9
|
+
* by rejecting the prompt is not gated, so the reactive ladder still runs.
|
|
9
10
|
* - Three compactions in a row, each followed by the context filling back up
|
|
10
11
|
* within three iterations: something in the loop (a huge file read, a tool
|
|
11
12
|
* that prints without limit) refills the window faster than any summary can
|
|
@@ -27,12 +28,13 @@ export const COMPACTION_REFILL_ITERATIONS = 3
|
|
|
27
28
|
/** Consecutive refills that stop the turn. */
|
|
28
29
|
export const COMPACTION_REFILL_LIMIT = 3
|
|
29
30
|
|
|
30
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* One short line, shown once, when the turn stops on refills. It names the
|
|
33
|
+
* cause and a step that keeps the conversation: the reference terminal also
|
|
34
|
+
* offers to start over here, and Namzu never does.
|
|
35
|
+
*/
|
|
31
36
|
export const COMPACTION_REFILL_MESSAGE =
|
|
32
|
-
'The context filled up again right after being compacted, three times in a row. A file or tool output is probably too
|
|
33
|
-
/** One short line, shown once, when compaction stops after failures. */
|
|
34
|
-
export const COMPACTION_FAILURE_MESSAGE =
|
|
35
|
-
'Automatic compaction stopped after three failed attempts. Run /compact yourself, or start fresh with /clear.'
|
|
37
|
+
'The context filled up again right after being compacted, three times in a row. A file being read or a tool output is probably too large for the context window. Try reading it in smaller parts.'
|
|
36
38
|
|
|
37
39
|
export class CompactionBreaker {
|
|
38
40
|
private failures = 0
|
|
@@ -16,17 +16,14 @@ import { serializeState } from '../../../../compaction/serializer.js'
|
|
|
16
16
|
import { buildCompactionMessage, isCompactionMessage } from '../../../../compaction/summary.js'
|
|
17
17
|
import { estimateMessagesTokens } from '../../../../compaction/token-estimate.js'
|
|
18
18
|
import { buildVerifiedSummaryWithBoundedProvider } from '../../../../compaction/verifier.js'
|
|
19
|
+
import type { CompactionConfig } from '../../../../config/runtime.js'
|
|
19
20
|
import { CHARS_PER_TOKEN } from '../../../../constants/limits.js'
|
|
20
21
|
import { NAMZU } from '../../../../constants/telemetry/index.js'
|
|
21
22
|
import { invariants } from '../../../../invariants/index.js'
|
|
22
23
|
import { resolveTaskModel } from '../../../../model-router/task-router.js'
|
|
23
24
|
import type { Message } from '../../../../types/message/index.js'
|
|
24
25
|
import { applyLifecycleHookResults } from '../../plugin-hooks.js'
|
|
25
|
-
import {
|
|
26
|
-
COMPACTION_FAILURE_MESSAGE,
|
|
27
|
-
COMPACTION_REFILL_MESSAGE,
|
|
28
|
-
compactionBreaker,
|
|
29
|
-
} from './compaction-breaker.js'
|
|
26
|
+
import { COMPACTION_REFILL_MESSAGE, compactionBreaker } from './compaction-breaker.js'
|
|
30
27
|
import type { IterationContext } from './context.js'
|
|
31
28
|
import { isWorkingMemoryMessage } from './working-memory.js'
|
|
32
29
|
|
|
@@ -223,6 +220,73 @@ export function measureContext(ctx: IterationContext): {
|
|
|
223
220
|
return { tokens: estimateTokens(ctx), source: 'estimate' }
|
|
224
221
|
}
|
|
225
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Reactive compactions allowed for one overflow, as in the reference
|
|
225
|
+
* terminal: the first relief, then at most one deeper pass.
|
|
226
|
+
*/
|
|
227
|
+
export const OVERFLOW_RELIEF_LIMIT = 2
|
|
228
|
+
|
|
229
|
+
/** Round-groups that must remain for the deeper pass to be worth running. */
|
|
230
|
+
export const DEEPER_PASS_MIN_GROUPS = 3
|
|
231
|
+
|
|
232
|
+
export interface OverflowReliefOptions {
|
|
233
|
+
/**
|
|
234
|
+
* The second rung. Keep only the newest round-group verbatim instead of the
|
|
235
|
+
* configured recent window (when at least {@link DEEPER_PASS_MIN_GROUPS}
|
|
236
|
+
* groups remain), and clear oversized tool results even inside the recent
|
|
237
|
+
* window, with the same placeholder and spill pointer a stale result gets.
|
|
238
|
+
*/
|
|
239
|
+
readonly deeper?: boolean
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* A round-group starts at each assistant message and runs to the next one, so
|
|
244
|
+
* its tool results stay with the call that made them. The newest group is the
|
|
245
|
+
* last assistant message and everything after it; the opening user messages
|
|
246
|
+
* ride with the first group.
|
|
247
|
+
*/
|
|
248
|
+
function roundGroups(messages: readonly Message[]): number {
|
|
249
|
+
let groups = 0
|
|
250
|
+
for (const message of messages) if (message.role === 'assistant') groups++
|
|
251
|
+
return groups
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The deeper rung's configuration: no tool result protected, and a recent
|
|
256
|
+
* window no larger than the first rung's. With three or more groups the
|
|
257
|
+
* window shrinks to the newest group, but never grows past what the
|
|
258
|
+
* configuration already kept: a newest group bigger than that would make the
|
|
259
|
+
* "deeper" pass shed less than the first one.
|
|
260
|
+
*/
|
|
261
|
+
export function deepenConfig(
|
|
262
|
+
config: CompactionConfig,
|
|
263
|
+
messages: readonly Message[],
|
|
264
|
+
): CompactionConfig {
|
|
265
|
+
let lastAssistant = -1
|
|
266
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
267
|
+
if (messages[i]?.role === 'assistant') {
|
|
268
|
+
lastAssistant = i
|
|
269
|
+
break
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const deeper = { ...config, keepRecentToolResults: 0 }
|
|
273
|
+
if (lastAssistant < 0 || roundGroups(messages) < DEEPER_PASS_MIN_GROUPS) return deeper
|
|
274
|
+
const newest = messages.slice(lastAssistant)
|
|
275
|
+
if (config.keepRecentTokens !== undefined) {
|
|
276
|
+
return {
|
|
277
|
+
...deeper,
|
|
278
|
+
keepRecentTokens: Math.max(
|
|
279
|
+
1,
|
|
280
|
+
Math.min(config.keepRecentTokens, estimateMessagesTokens(newest)),
|
|
281
|
+
),
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
...deeper,
|
|
286
|
+
keepRecentMessages: Math.max(1, Math.min(config.keepRecentMessages, newest.length)),
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
226
290
|
/**
|
|
227
291
|
* Shed history because the PROVIDER said the prompt is too long.
|
|
228
292
|
*
|
|
@@ -241,12 +305,18 @@ export function measureContext(ctx: IterationContext): {
|
|
|
241
305
|
* @returns whether anything was actually shed. `false` means retrying would
|
|
242
306
|
* send the same prompt again, so the caller must not.
|
|
243
307
|
*/
|
|
244
|
-
export async function relieveOverflow(
|
|
308
|
+
export async function relieveOverflow(
|
|
309
|
+
ctx: IterationContext,
|
|
310
|
+
options: OverflowReliefOptions = {},
|
|
311
|
+
): Promise<boolean> {
|
|
312
|
+
// The cause belongs to the rung that is about to run: an earlier rung's
|
|
313
|
+
// summary failure must not be blamed for a later rung that compacted cleanly.
|
|
314
|
+
clearCompactionErrorNote(ctx)
|
|
245
315
|
const before = ctx.recorder.messages.length
|
|
246
316
|
const beforeChars = totalChars(ctx.recorder.messages)
|
|
247
317
|
const beforeTokens = estimateMessagesTokens(ctx.recorder.messages)
|
|
248
318
|
|
|
249
|
-
await runCompactionCheck(ctx, { force: true })
|
|
319
|
+
await runCompactionCheck(ctx, { force: true, ...(options.deeper ? { deeper: true } : {}) })
|
|
250
320
|
|
|
251
321
|
const shed = beforeChars - totalChars(ctx.recorder.messages)
|
|
252
322
|
const tokensShed = beforeTokens - estimateMessagesTokens(ctx.recorder.messages)
|
|
@@ -278,6 +348,31 @@ export async function relieveOverflow(ctx: IterationContext): Promise<boolean> {
|
|
|
278
348
|
return true
|
|
279
349
|
}
|
|
280
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Why the last compaction attempt errored, kept for the turn so that when
|
|
353
|
+
* relief runs out the failure it ends on can say so, as the reference
|
|
354
|
+
* terminal's " · automatic compaction failed: <cause>" does.
|
|
355
|
+
*/
|
|
356
|
+
const compactionErrors = new WeakMap<object, string>()
|
|
357
|
+
const MAX_COMPACTION_CAUSE_CHARS = 300
|
|
358
|
+
/** The sentence the cause is appended with. */
|
|
359
|
+
export const AUTOMATIC_COMPACTION_FAILED = ' · automatic compaction failed: '
|
|
360
|
+
|
|
361
|
+
function noteCompactionError(ctx: IterationContext, error: unknown): void {
|
|
362
|
+
const text = error instanceof Error ? error.message : String(error)
|
|
363
|
+
if (text.length > 0) compactionErrors.set(ctx.recorder, text.slice(0, MAX_COMPACTION_CAUSE_CHARS))
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** The turn's last compaction error, or `undefined` when none errored. */
|
|
367
|
+
export function compactionErrorNote(ctx: IterationContext): string | undefined {
|
|
368
|
+
return compactionErrors.get(ctx.recorder)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** A turn that got through has nothing left to explain. */
|
|
372
|
+
export function clearCompactionErrorNote(ctx: IterationContext): void {
|
|
373
|
+
compactionErrors.delete(ctx.recorder)
|
|
374
|
+
}
|
|
375
|
+
|
|
281
376
|
function totalChars(messages: readonly { content: unknown }[]): number {
|
|
282
377
|
let total = 0
|
|
283
378
|
for (const msg of messages) total += measureContentChars(msg.content)
|
|
@@ -335,8 +430,11 @@ async function declined(
|
|
|
335
430
|
/**
|
|
336
431
|
* The third refill in a row: stop the turn instead of compacting again.
|
|
337
432
|
* Returns true when this pass must not run. Only with `circuitBreaker`.
|
|
433
|
+
* The loop also asks it before the first reactive relief of an overflow, as
|
|
434
|
+
* the reference terminal does (`rhr` runs first in its withheld-overflow
|
|
435
|
+
* handling), so a re-read loop made only of overflows is counted too.
|
|
338
436
|
*/
|
|
339
|
-
async function stopOnRefill(ctx: IterationContext): Promise<boolean> {
|
|
437
|
+
export async function stopOnRefill(ctx: IterationContext): Promise<boolean> {
|
|
340
438
|
const breaker = compactionBreaker(ctx)
|
|
341
439
|
if (!breaker) return false
|
|
342
440
|
// Already announced: the turn is on its way out; do not say it twice.
|
|
@@ -418,6 +516,7 @@ async function applyReducer(
|
|
|
418
516
|
'namzu.runtime.reason': reduction.reason,
|
|
419
517
|
'exception.message': message,
|
|
420
518
|
})
|
|
519
|
+
noteCompactionError(ctx, error)
|
|
421
520
|
await declined(ctx, 'reducer_threw', before, message)
|
|
422
521
|
return
|
|
423
522
|
}
|
|
@@ -549,7 +648,7 @@ interface CompactionPass {
|
|
|
549
648
|
|
|
550
649
|
export async function runCompactionCheck(
|
|
551
650
|
ctx: IterationContext,
|
|
552
|
-
options?: { force?: boolean },
|
|
651
|
+
options?: { force?: boolean; deeper?: boolean },
|
|
553
652
|
): Promise<void> {
|
|
554
653
|
const pass: CompactionPass = {
|
|
555
654
|
attempted: false,
|
|
@@ -585,11 +684,12 @@ export async function runCompactionCheck(
|
|
|
585
684
|
async function runCompactionCheckInner(
|
|
586
685
|
ctx: IterationContext,
|
|
587
686
|
pass: CompactionPass,
|
|
588
|
-
options?: { force?: boolean },
|
|
687
|
+
options?: { force?: boolean; deeper?: boolean },
|
|
589
688
|
): Promise<void> {
|
|
590
|
-
const
|
|
591
|
-
if (!
|
|
592
|
-
if (
|
|
689
|
+
const configured = ctx.compactionConfig
|
|
690
|
+
if (!configured) return
|
|
691
|
+
if (configured.strategy === 'disabled') return
|
|
692
|
+
const config = options?.deeper ? deepenConfig(configured, ctx.recorder.messages) : configured
|
|
593
693
|
|
|
594
694
|
const measured = measureContext(ctx)
|
|
595
695
|
const estimatedTokens = measured.tokens
|
|
@@ -616,11 +716,14 @@ async function runCompactionCheckInner(
|
|
|
616
716
|
const startAt = salience ? Math.min(config.softTarget ?? DEFAULT_SOFT_TARGET, trigger) : trigger
|
|
617
717
|
if (!options?.force && usage < startAt) return
|
|
618
718
|
|
|
619
|
-
// Three failed passes in a row: stop
|
|
620
|
-
//
|
|
719
|
+
// Three failed passes in a row: stop proactive compaction for the rest of
|
|
720
|
+
// the turn. A pass the provider forced is never gated.
|
|
621
721
|
const breaker = compactionBreaker(ctx)
|
|
622
722
|
if (breaker?.failuresOpen && !options?.force) {
|
|
623
723
|
if (breaker.takeFailureNotice()) {
|
|
724
|
+
// Silent for the person, as in the reference terminal: a log line and the
|
|
725
|
+
// event for a host that wants it, but no sentence to show. Only the
|
|
726
|
+
// proactive pass stops; a real overflow still runs the reactive ladder.
|
|
624
727
|
ctx.log.warn('Compaction failed three times in a row — switched off for this turn', {
|
|
625
728
|
[NAMZU.TURN_ID]: ctx.recorder.turnId,
|
|
626
729
|
})
|
|
@@ -630,7 +733,6 @@ async function runCompactionCheckInner(
|
|
|
630
733
|
iteration: ctx.recorder.currentIteration,
|
|
631
734
|
cause: 'breaker_open',
|
|
632
735
|
messages: ctx.recorder.messages.length,
|
|
633
|
-
error: COMPACTION_FAILURE_MESSAGE,
|
|
634
736
|
})
|
|
635
737
|
}
|
|
636
738
|
return
|
|
@@ -851,25 +953,39 @@ async function runCompactionCheckInner(
|
|
|
851
953
|
// to a cheap model while the bill is written against the expensive one
|
|
852
954
|
// is a mistake with no symptom.
|
|
853
955
|
const compactionModel = resolveTaskModel('compaction', ctx.taskRouter, ctx.turnConfig.model)
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
956
|
+
try {
|
|
957
|
+
compactedContent = await buildVerifiedSummaryWithBoundedProvider(
|
|
958
|
+
manager,
|
|
959
|
+
olderMessages as Message[],
|
|
960
|
+
ctx.provider,
|
|
961
|
+
config,
|
|
962
|
+
(usage) =>
|
|
963
|
+
ctx.recorder.accumulateUsage(usage, {
|
|
964
|
+
providerId: ctx.recorder.servingProviderId,
|
|
965
|
+
model: compactionModel,
|
|
966
|
+
}),
|
|
967
|
+
// The one model call a turn makes that the user never asked for. It
|
|
968
|
+
// reads a transcript and writes a summary, which is the cheapest
|
|
969
|
+
// thing a small model does well, and it fires on exactly the long
|
|
970
|
+
// runs where the primary model is most expensive. `taskRouter` had
|
|
971
|
+
// been accepted, validated and threaded through four types since it
|
|
972
|
+
// was added, and nothing ever consulted it.
|
|
973
|
+
compactionModel,
|
|
974
|
+
ctx.abortController.signal,
|
|
975
|
+
)
|
|
976
|
+
} catch (error) {
|
|
977
|
+
// The model step only ADDS to the extracted state, so a failed one (a
|
|
978
|
+
// timeout, a provider error, its own overflow after the ladder in the
|
|
979
|
+
// verifier ran out) is not a reason to leave the history as it is. Cancellation
|
|
980
|
+
// is the caller's, and still stops the pass.
|
|
981
|
+
if (ctx.abortController.signal.aborted) throw error
|
|
982
|
+
noteCompactionError(ctx, error)
|
|
983
|
+
ctx.log.warn('The compaction summary call failed — using the extracted state alone', {
|
|
984
|
+
[NAMZU.TURN_ID]: ctx.recorder.turnId,
|
|
985
|
+
'exception.message': error instanceof Error ? error.message : String(error),
|
|
986
|
+
})
|
|
987
|
+
compactedContent = serializeState(manager.getState())
|
|
988
|
+
}
|
|
873
989
|
} else {
|
|
874
990
|
compactedContent = serializeState(manager.getState())
|
|
875
991
|
}
|