maka-agent 0.2.0-dev.12.20260901 → 0.2.0-dev.14.20260902
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/THIRD_PARTY_NOTICES.txt +2 -2
- package/dist/acp/maka-acp-agent.js +6 -3
- package/dist/acp/session-registry.js +307 -0
- package/dist/acp/stdio-server.js +20 -1
- package/dist/cli-core.js +15 -2
- package/dist/pi-transcript.js +1 -1
- package/dist/pi-tui-layout.js +1 -1
- package/dist/pi-tui-mcp-status.js +1 -1
- package/dist/pi-tui-runner.js +1 -1
- package/dist/runtime-host-access-command.js +37 -4
- package/dist/runtime-host-capability-provider-command.js +16 -2
- package/dist/runtime-host-cli-context.js +73 -3
- package/dist/runtime-host-cli.js +37 -6
- package/dist/runtime-host-run-command.js +3 -1
- package/dist/runtime-host-update-command.js +59 -31
- package/dist/tui-copy-catalog.js +4 -2
- package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
- package/node_modules/@maka/core/dist/attachments.js +12 -0
- package/node_modules/@maka/core/dist/connection-error-copy.js +17 -34
- package/node_modules/@maka/core/dist/events.js +0 -13
- package/node_modules/@maka/core/dist/maka-wordmark.js +23 -0
- package/node_modules/@maka/core/dist/model-catalog.js +22 -1
- package/node_modules/@maka/core/dist/runtime-event.js +1 -0
- package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +50 -1
- package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +9 -1
- package/node_modules/@maka/core/dist/runtime-policy/model-catalog-entry-codec.js +10 -1
- package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +75 -0
- package/node_modules/@maka/core/dist/runtime-policy.js +4 -2
- package/node_modules/@maka/core/dist/session.js +1 -0
- package/node_modules/@maka/core/dist/settings.js +10 -2
- package/node_modules/@maka/core/dist/usage-record-schema.js +11 -1
- package/node_modules/@maka/core/package.json +2 -1
- package/node_modules/@maka/mcp/dist/index.js +1 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +52 -80
- package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +178 -140
- package/node_modules/@maka/runtime/dist/archive-read-tool.js +26 -5
- package/node_modules/@maka/runtime/dist/builtin-tools.js +27 -2
- package/node_modules/@maka/runtime/dist/context-budget-policy.js +0 -12
- package/node_modules/@maka/runtime/dist/conversation-copy.js +66 -43
- package/node_modules/@maka/runtime/dist/durable-tool-result-projection.js +31 -0
- package/node_modules/@maka/runtime/dist/history-compact-error.js +10 -2
- package/node_modules/@maka/runtime/dist/history-compact-input-fit.js +7 -1
- package/node_modules/@maka/runtime/dist/history-compaction.js +13 -22
- package/node_modules/@maka/runtime/dist/model-history.js +41 -16
- package/node_modules/@maka/runtime/dist/provider-image-overflow-recovery.js +12 -31
- package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +3 -0
- package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +13 -2
- package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -6
- package/node_modules/@maka/runtime/dist/shell-tools.js +77 -1
- package/node_modules/@maka/runtime/dist/tool-availability.js +74 -5
- package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +333 -52
- package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +34 -3
- package/node_modules/@maka/runtime/dist/tool-runtime.js +12 -0
- package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +0 -3
- package/node_modules/@maka/runtime-host/dist/client/catalog-reader.js +40 -13
- package/node_modules/@maka/runtime-host/dist/client/connection.js +50 -31
- package/node_modules/@maka/runtime-host/dist/client/host-profile.js +48 -4
- package/node_modules/@maka/runtime-host/dist/client/index.js +2 -2
- package/node_modules/@maka/runtime-host/dist/client/owner-connection-code.js +32 -1
- package/node_modules/@maka/runtime-host/dist/client/peer-client.js +22 -4
- package/node_modules/@maka/runtime-host/dist/operator/access-management-frame.js +10 -1
- package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +56 -12
- package/node_modules/@maka/runtime-host/dist/peer-mesh/owner.js +3 -0
- package/node_modules/@maka/runtime-host/dist/protocol/configuration.js +55 -9
- package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +37 -0
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +17 -1
- package/node_modules/@maka/runtime-host/dist/protocol/network-proxy.js +1 -7
- package/node_modules/@maka/runtime-host/dist/protocol/operations.js +2 -0
- package/node_modules/@maka/runtime-host/dist/protocol/runtime-policy.js +48 -1
- package/node_modules/@maka/runtime-host/dist/protocol/session-collaboration.js +4 -2
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +2 -12
- package/node_modules/@maka/runtime-host/dist/server/access-authority.js +2 -1
- package/node_modules/@maka/runtime-host/dist/server/agent-graph-execution-coordinator.js +1 -2
- package/node_modules/@maka/runtime-host/dist/server/canonical-turn-snapshot.js +0 -9
- package/node_modules/@maka/runtime-host/dist/server/configuration-coordinator.js +20 -1
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +46 -24
- package/node_modules/@maka/runtime-host/dist/server/execution-service.js +4 -1
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +15 -0
- package/node_modules/@maka/runtime-host/dist/server/listener-set.js +8 -4
- package/node_modules/@maka/runtime-host/dist/server/network-proxy-coordinator.js +0 -1
- package/node_modules/@maka/runtime-host/dist/server/peer-listener.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +28 -1
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +3 -3
- package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +35 -3
- package/node_modules/@maka/runtime-host/dist/server/session-sidecar-purge.js +16 -0
- package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +189 -6
- package/node_modules/@maka/storage/dist/runtime-policy-stores.js +4 -1
- package/node_modules/@maka/storage/dist/settings-store.js +16 -25
- package/node_modules/fast-uri/index.js +215 -47
- package/node_modules/fast-uri/lib/schemes.js +9 -4
- package/node_modules/fast-uri/lib/utils.js +396 -92
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/qs/.github/THREAT_MODEL.md +3 -3
- package/node_modules/qs/CHANGELOG.md +15 -0
- package/node_modules/qs/README.md +19 -1
- package/node_modules/qs/dist/qs.js +21 -21
- package/node_modules/qs/eslint.config.mjs +1 -1
- package/node_modules/qs/lib/parse.js +3 -4
- package/node_modules/qs/lib/stringify.js +23 -8
- package/node_modules/qs/lib/utils.js +10 -3
- package/node_modules/qs/package.json +4 -4
- package/package.json +1 -1
|
@@ -29,8 +29,9 @@ import { baseToolResultProjection, reduceEffectiveModelProjections, } from './mo
|
|
|
29
29
|
import { buildRuntimeEventModelReplayPlan, collectToolActivityTurnIds, compatibleProviderReasoningReplayEventIds, } from './model-history.js';
|
|
30
30
|
import { toolSchemaCharsForDiagnostics } from './request-shape.js';
|
|
31
31
|
import { estimateNextRequestTokens, exceedsHighWater, planHistoryCompaction, } from './history-compaction.js';
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
32
|
+
import { resolveSelectedModelContextWindow } from './context-budget-policy.js';
|
|
33
|
+
import { MATERIALIZED_IMAGE_TOKENS } from '@maka/core/attachments';
|
|
34
|
+
import { collectHistoricalImageToolResults, isInlineImageFilePart, omitHistoricalImageToolResults, } from './provider-image-overflow-recovery.js';
|
|
34
35
|
export class AiSdkCompaction {
|
|
35
36
|
input;
|
|
36
37
|
sessionId;
|
|
@@ -532,7 +533,9 @@ export class AiSdkCompaction {
|
|
|
532
533
|
* Mid-turn capacity compaction eligibility (issue #882 PR 1). Explicit
|
|
533
534
|
* opt-in via `historyCompact.midTurn.enabled`; requires the checkpoint
|
|
534
535
|
* writer seams plus the durable turn-ledger read, the persisted head anchor
|
|
535
|
-
* for this turn
|
|
536
|
+
* for this turn. A model that declares no context window still gets this
|
|
537
|
+
* state: reactive recovery runs off a real provider rejection and needs no
|
|
538
|
+
* window at all.
|
|
536
539
|
*/
|
|
537
540
|
buildMidTurnCapacityCompactState(input) {
|
|
538
541
|
const policy = this.input.contextBudget;
|
|
@@ -556,13 +559,19 @@ export class AiSdkCompaction {
|
|
|
556
559
|
!isHistoryCompactContentEvent(headAnchor)) {
|
|
557
560
|
return undefined;
|
|
558
561
|
}
|
|
559
|
-
const capacity = resolveContextBudgetCapacity(this.input.connection, this.input.modelId, policy);
|
|
560
|
-
if (capacity === undefined)
|
|
561
|
-
return undefined;
|
|
562
562
|
const priorContentEvents = (input.runtimeContext ?? [])
|
|
563
563
|
.filter((event) => event.turnId !== input.turnId)
|
|
564
564
|
.filter(isHistoryCompactContentEvent);
|
|
565
|
-
|
|
565
|
+
const state = new MidTurnCapacityCompactState(headAnchor, priorContentEvents, input.runtimeContextRunHeaders ?? [], resolveSelectedModelContextWindow(this.input.connection, this.input.modelId));
|
|
566
|
+
// Seed the turn's FIRST request with the last request the provider
|
|
567
|
+
// actually counted, so step 0 is estimated like every later step instead
|
|
568
|
+
// of guessing the whole payload at char/4.
|
|
569
|
+
const persisted = persistedRequestAnchor(input.runtimeContext ?? [], state.priorRunHeaders, this.input.modelId, this.targetConnectionId);
|
|
570
|
+
if (persisted) {
|
|
571
|
+
state.lastRequestInputTokens = persisted.inputTokens;
|
|
572
|
+
state.lastRequestPayloadChars = persisted.payloadChars;
|
|
573
|
+
}
|
|
574
|
+
return state;
|
|
566
575
|
}
|
|
567
576
|
/**
|
|
568
577
|
* Request-projection stage for the mid-turn capacity invariant: between
|
|
@@ -573,15 +582,12 @@ export class AiSdkCompaction {
|
|
|
573
582
|
* `[compact block, verbatim head anchor]`.
|
|
574
583
|
*
|
|
575
584
|
* This hook never terminates the turn: every failure fails open with a
|
|
576
|
-
* diagnostic and
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
* go out exceeds the window. The trigger threshold here is deliberately
|
|
581
|
-
* approximate — a missed or spurious trigger is recoverable; the verdict is
|
|
582
|
-
* not, so it does not live here.
|
|
585
|
+
* diagnostic and the request goes out. The trigger threshold is approximate
|
|
586
|
+
* on purpose — a missed or spurious trigger costs at most one compaction,
|
|
587
|
+
* and whether the request actually fits is the provider's answer, not a
|
|
588
|
+
* local estimate's.
|
|
583
589
|
*/
|
|
584
|
-
buildMidTurnCapacityCompactProjection(turnId, state, queue, providerTools,
|
|
590
|
+
buildMidTurnCapacityCompactProjection(turnId, state, queue, providerTools, onDiagnosticPatch, origin, memoryCompactionDecision, onMemoryCompaction, abortSignal) {
|
|
585
591
|
if (!state)
|
|
586
592
|
return undefined;
|
|
587
593
|
const policy = this.input.contextBudget;
|
|
@@ -596,10 +602,6 @@ export class AiSdkCompaction {
|
|
|
596
602
|
projectedMessages =
|
|
597
603
|
projectHistoricalImageOmissions(projectedMessages ?? incomingMessages, state.omittedImageToolResults) ?? projectedMessages;
|
|
598
604
|
const keepProjection = () => projectedMessages ? { messages: projectedMessages } : undefined;
|
|
599
|
-
// Step 0 is shaped by the pre_turn path; the mid-turn trigger only runs
|
|
600
|
-
// between steps, once completed-step usage and events exist.
|
|
601
|
-
if (options.stepNumber < 1 || state.exhaustedDetail)
|
|
602
|
-
return keepProjection();
|
|
603
605
|
// Real usage for the last finished step, read synchronously from the
|
|
604
606
|
// SDK's own step results (the same numbers the finish-step chunk
|
|
605
607
|
// carries) — no coupling to how far the stream consumer has advanced.
|
|
@@ -619,14 +621,22 @@ export class AiSdkCompaction {
|
|
|
619
621
|
// request's chars would under-estimate the retry by the whole previous
|
|
620
622
|
// step growth — so a missing baseline forces the whole-payload cold
|
|
621
623
|
// start, exactly like a missing usage sample.
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
624
|
+
//
|
|
625
|
+
// Before the first step finishes nothing in-send has overwritten the
|
|
626
|
+
// seeded pair, so leave it alone rather than clearing a coherent anchor.
|
|
627
|
+
if (options.completedSteps.length > 0) {
|
|
628
|
+
const lastStepInputTokens = options.completedSteps.at(-1)?.usage?.inputTokens;
|
|
629
|
+
state.lastRequestInputTokens =
|
|
630
|
+
state.lastRequestPayloadChars !== undefined &&
|
|
631
|
+
lastStepInputTokens !== undefined &&
|
|
632
|
+
Number.isFinite(lastStepInputTokens) &&
|
|
633
|
+
lastStepInputTokens > 0
|
|
634
|
+
? lastStepInputTokens
|
|
635
|
+
: undefined;
|
|
636
|
+
}
|
|
637
|
+
// The turn's first request folds as a pre_turn boundary, like the
|
|
638
|
+
// reactive step-0 recovery; later steps fold mid_turn.
|
|
639
|
+
const phase = options.stepNumber === 0 ? 'pre_turn' : 'mid_turn';
|
|
630
640
|
// A skipped trigger is never silent: every failure-driven skip records a
|
|
631
641
|
// failedOpen decision.
|
|
632
642
|
const failOpen = (failOpenReason) => {
|
|
@@ -635,7 +645,7 @@ export class AiSdkCompaction {
|
|
|
635
645
|
stage: 'activeStep',
|
|
636
646
|
sourceKind: 'runtimeEvents',
|
|
637
647
|
decision: 'failedOpen',
|
|
638
|
-
phase
|
|
648
|
+
phase,
|
|
639
649
|
boundaryKind: 'historyCompact',
|
|
640
650
|
reason: 'context_limit',
|
|
641
651
|
failOpenReason,
|
|
@@ -644,12 +654,10 @@ export class AiSdkCompaction {
|
|
|
644
654
|
});
|
|
645
655
|
return keepProjection();
|
|
646
656
|
};
|
|
647
|
-
// A shaping failure
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const shapeFailure = (detail, diagnosticReason) => {
|
|
652
|
-
state.lastShapeFailure = { stepNumber: options.stepNumber, detail, diagnosticReason };
|
|
657
|
+
// A shaping failure records the step so the rescue re-entry does not
|
|
658
|
+
// re-run a shaper that already attempted and failed this step.
|
|
659
|
+
const shapeFailure = (diagnosticReason) => {
|
|
660
|
+
state.lastShapeFailure = { stepNumber: options.stepNumber };
|
|
653
661
|
return failOpen(diagnosticReason);
|
|
654
662
|
};
|
|
655
663
|
// Trigger estimate: the last request's input tokens plus a SIGNED char/4 delta of
|
|
@@ -661,21 +669,27 @@ export class AiSdkCompaction {
|
|
|
661
669
|
// over-trigger; that is the recoverable direction, and the verdict owner
|
|
662
670
|
// re-measures the post-shaping payload.
|
|
663
671
|
const measuredMessages = projectedMessages ?? incomingMessages;
|
|
664
|
-
|
|
665
|
-
|
|
672
|
+
// Price the request dispatch will actually build, not the pre-dispatch
|
|
673
|
+
// inputs: a finalization step adds prompt fragments and sends no tools.
|
|
674
|
+
const dispatch = options.resolveDispatch(options.activeTools);
|
|
675
|
+
const activeToolsForStep = dispatch.activeTools;
|
|
676
|
+
const payloadChars = midTurnRequestPayloadChars(measuredMessages, providerTools, activeToolsForStep, dispatch.systemPromptChars, charsPerToken);
|
|
666
677
|
const forcedEstimate = state.forcedTriggerEstimate;
|
|
667
678
|
state.forcedTriggerEstimate = undefined;
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
679
|
+
const anchored = requestEstimateAnchor(state, payloadChars);
|
|
680
|
+
// The turn's FIRST request only gets a trigger when a previous turn left
|
|
681
|
+
// a usable anchor. Without one the estimate is the whole payload at
|
|
682
|
+
// chars/4 — the same guess the pre_turn gate already spends, and far too
|
|
683
|
+
// crude to start a summarizer on. With one, step 0 is judged exactly like
|
|
684
|
+
// every later step.
|
|
685
|
+
if (options.stepNumber < 1 &&
|
|
686
|
+
forcedEstimate === undefined &&
|
|
687
|
+
anchored.priorUsageTokens === undefined) {
|
|
688
|
+
return keepProjection();
|
|
689
|
+
}
|
|
690
|
+
const estimate = forcedEstimate ?? estimateNextRequestTokens({ ...anchored, charsPerToken });
|
|
677
691
|
if (forcedEstimate === undefined &&
|
|
678
|
-
!exceedsHighWater(estimate, state.capacity
|
|
692
|
+
(state.capacity === undefined || !exceedsHighWater(estimate, state.capacity, reserveTokens))) {
|
|
679
693
|
return keepProjection();
|
|
680
694
|
}
|
|
681
695
|
// Fold a safe completed prefix of the durable turn ledger into a
|
|
@@ -684,6 +698,7 @@ export class AiSdkCompaction {
|
|
|
684
698
|
// keep the raw projection on skip/fail, apply the fold on success.
|
|
685
699
|
const outcome = await this.compactActiveRequestHistory({
|
|
686
700
|
turnId,
|
|
701
|
+
phase,
|
|
687
702
|
origin,
|
|
688
703
|
state,
|
|
689
704
|
queue,
|
|
@@ -691,13 +706,13 @@ export class AiSdkCompaction {
|
|
|
691
706
|
referencePayloadChars: payloadChars,
|
|
692
707
|
providerTools,
|
|
693
708
|
activeToolsForStep,
|
|
694
|
-
systemPromptChars,
|
|
709
|
+
systemPromptChars: dispatch.systemPromptChars,
|
|
695
710
|
memoryCompactionDecision,
|
|
696
711
|
onMemoryCompaction,
|
|
697
712
|
abortSignal,
|
|
698
713
|
});
|
|
699
714
|
if (outcome.decision === 'fail') {
|
|
700
|
-
return shapeFailure(outcome.
|
|
715
|
+
return shapeFailure(outcome.diagnosticReason);
|
|
701
716
|
}
|
|
702
717
|
acceptedProjection = {
|
|
703
718
|
sourceSignatures: incomingMessages.map(modelMessageSignature),
|
|
@@ -729,7 +744,6 @@ export class AiSdkCompaction {
|
|
|
729
744
|
if (state.malformedSummaryFailure) {
|
|
730
745
|
return {
|
|
731
746
|
decision: 'fail',
|
|
732
|
-
detail: state.malformedSummaryFailure,
|
|
733
747
|
diagnosticReason: state.malformedSummaryFailure,
|
|
734
748
|
};
|
|
735
749
|
}
|
|
@@ -777,14 +791,12 @@ export class AiSdkCompaction {
|
|
|
777
791
|
if (abortSignal?.aborted) {
|
|
778
792
|
return {
|
|
779
793
|
decision: 'fail',
|
|
780
|
-
detail: 'no_safe_completed_span',
|
|
781
794
|
diagnosticReason: 'ledger_wait_aborted',
|
|
782
795
|
};
|
|
783
796
|
}
|
|
784
797
|
if (queue.consumerDetached) {
|
|
785
798
|
return {
|
|
786
799
|
decision: 'fail',
|
|
787
|
-
detail: 'no_safe_completed_span',
|
|
788
800
|
diagnosticReason: 'ledger_wait_aborted',
|
|
789
801
|
};
|
|
790
802
|
}
|
|
@@ -799,7 +811,6 @@ export class AiSdkCompaction {
|
|
|
799
811
|
catch {
|
|
800
812
|
return {
|
|
801
813
|
decision: 'fail',
|
|
802
|
-
detail: 'no_safe_completed_span',
|
|
803
814
|
diagnosticReason: 'ledger_read_failed',
|
|
804
815
|
};
|
|
805
816
|
}
|
|
@@ -811,7 +822,6 @@ export class AiSdkCompaction {
|
|
|
811
822
|
if (!currentTurnEvents.some((event) => event.id === state.headAnchor.id)) {
|
|
812
823
|
return {
|
|
813
824
|
decision: 'fail',
|
|
814
|
-
detail: 'no_safe_completed_span',
|
|
815
825
|
diagnosticReason: 'head_anchor_not_durable',
|
|
816
826
|
};
|
|
817
827
|
}
|
|
@@ -822,7 +832,7 @@ export class AiSdkCompaction {
|
|
|
822
832
|
phase: input.phase ?? 'mid_turn',
|
|
823
833
|
orderedEvents,
|
|
824
834
|
headAnchor: { runtimeEventId: state.headAnchor.id, turnId },
|
|
825
|
-
reserveTailEvents:
|
|
835
|
+
reserveTailEvents: 1,
|
|
826
836
|
charsPerToken,
|
|
827
837
|
now: this.now(),
|
|
828
838
|
...(compactPolicy.highWaterName !== undefined
|
|
@@ -851,7 +861,9 @@ export class AiSdkCompaction {
|
|
|
851
861
|
...(previousCheckpoint ? { previousCheckpoint } : {}),
|
|
852
862
|
newlyFoldedRuntimeEvents: [...newlyFoldedRuntimeEvents],
|
|
853
863
|
inputBudget: {
|
|
854
|
-
|
|
864
|
+
...(state.capacity !== undefined
|
|
865
|
+
? { maxEstimatedTokens: Math.max(1, state.capacity - reserveTokens) }
|
|
866
|
+
: {}),
|
|
855
867
|
charsPerToken,
|
|
856
868
|
},
|
|
857
869
|
...(abortSignal ? { abortSignal } : {}),
|
|
@@ -866,9 +878,6 @@ export class AiSdkCompaction {
|
|
|
866
878
|
}
|
|
867
879
|
return {
|
|
868
880
|
decision: 'fail',
|
|
869
|
-
detail: isMalformedHistoryCompactSummaryReason(diagnosticReason)
|
|
870
|
-
? diagnosticReason
|
|
871
|
-
: plan.reason,
|
|
872
881
|
diagnosticReason,
|
|
873
882
|
};
|
|
874
883
|
}
|
|
@@ -886,7 +895,6 @@ export class AiSdkCompaction {
|
|
|
886
895
|
(replayPlan.hasProviderNativeSemantics && !this.canReplayProviderNative(replayPlan))) {
|
|
887
896
|
return {
|
|
888
897
|
decision: 'fail',
|
|
889
|
-
detail: 'no_safe_completed_span',
|
|
890
898
|
diagnosticReason: 'replacement_unmaterializable',
|
|
891
899
|
};
|
|
892
900
|
}
|
|
@@ -896,11 +904,10 @@ export class AiSdkCompaction {
|
|
|
896
904
|
// request that overflowed for reactive recovery): a materialized
|
|
897
905
|
// replacement that is not smaller proves the summarizer's OUTPUT is
|
|
898
906
|
// unusable, reported as summarizer_failed via replacement_not_smaller.
|
|
899
|
-
const replacedPayloadChars = midTurnRequestPayloadChars(replacementMessages, providerTools, activeToolsForStep, systemPromptChars);
|
|
907
|
+
const replacedPayloadChars = midTurnRequestPayloadChars(replacementMessages, providerTools, activeToolsForStep, systemPromptChars, charsPerToken);
|
|
900
908
|
if (replacedPayloadChars >= input.referencePayloadChars) {
|
|
901
909
|
return {
|
|
902
910
|
decision: 'fail',
|
|
903
|
-
detail: 'summarizer_failed',
|
|
904
911
|
diagnosticReason: 'replacement_not_smaller',
|
|
905
912
|
};
|
|
906
913
|
}
|
|
@@ -911,7 +918,6 @@ export class AiSdkCompaction {
|
|
|
911
918
|
if (!replayFit.fits) {
|
|
912
919
|
return {
|
|
913
920
|
decision: 'fail',
|
|
914
|
-
detail: 'head_anchor_exceeds_capacity',
|
|
915
921
|
diagnosticReason: `replay_rejected_${replayFit.reason}`,
|
|
916
922
|
};
|
|
917
923
|
}
|
|
@@ -924,7 +930,6 @@ export class AiSdkCompaction {
|
|
|
924
930
|
catch {
|
|
925
931
|
return {
|
|
926
932
|
decision: 'fail',
|
|
927
|
-
detail: 'summarizer_failed',
|
|
928
933
|
diagnosticReason: 'write_failed',
|
|
929
934
|
};
|
|
930
935
|
}
|
|
@@ -956,8 +961,8 @@ export class AiSdkCompaction {
|
|
|
956
961
|
* latch (pi's `_overflowRecoveryAttempted`). Returns the compacted messages
|
|
957
962
|
* to resend, or undefined when recovery is impossible or already spent, in
|
|
958
963
|
* which case the caller surfaces the real provider error rather than a
|
|
959
|
-
* fabricated success or a synthesized
|
|
960
|
-
*
|
|
964
|
+
* fabricated success or a locally synthesized verdict (the provider — not the
|
|
965
|
+
* runtime — rejected the request). Non-context-length
|
|
961
966
|
* errors and turns without the mid-turn seam never reach compaction, so the
|
|
962
967
|
* default (no seam) behavior is already better than the old fake end_turn.
|
|
963
968
|
*/
|
|
@@ -987,7 +992,7 @@ export class AiSdkCompaction {
|
|
|
987
992
|
// The cold-start fallback only covers a send whose verdict owner never
|
|
988
993
|
// ran request projection (defensive; step 0 records the baseline too).
|
|
989
994
|
const referencePayloadChars = state.lastRequestPayloadChars ??
|
|
990
|
-
midTurnRequestPayloadChars(input.currentMessages, input.providerTools, input.activeTools, input.systemPromptChars);
|
|
995
|
+
midTurnRequestPayloadChars(input.currentMessages, input.providerTools, input.activeTools, input.systemPromptChars, this.input.contextBudget?.charsPerToken ?? 4);
|
|
991
996
|
const phase = input.stepNumber === 0 ? 'pre_turn' : 'mid_turn';
|
|
992
997
|
const outcome = await this.compactActiveRequestHistory({
|
|
993
998
|
turnId: input.turnId,
|
|
@@ -1039,8 +1044,11 @@ export class AiSdkCompaction {
|
|
|
1039
1044
|
// Reset the baseline: the capacity hook's usage anchor is only coherent
|
|
1040
1045
|
// paired with the payload chars of the SAME request, and a missing
|
|
1041
1046
|
// baseline forces the whole-payload cold-start estimate instead of a
|
|
1042
|
-
// stale pairing against the dead attempt.
|
|
1047
|
+
// stale pairing against the dead attempt. The cross-turn seed goes with
|
|
1048
|
+
// it: falling back to an even older request's anchor pairs worse, not
|
|
1049
|
+
// better.
|
|
1043
1050
|
state.lastRequestPayloadChars = undefined;
|
|
1051
|
+
state.lastRequestInputTokens = undefined;
|
|
1044
1052
|
return { messages: outcome.replacementMessages };
|
|
1045
1053
|
}
|
|
1046
1054
|
/**
|
|
@@ -1048,8 +1056,8 @@ export class AiSdkCompaction {
|
|
|
1048
1056
|
* invariant. Every request-projection stage only shapes; this wrapper measures the
|
|
1049
1057
|
* FINAL outgoing (messages, tools) payload — the bytes the provider will
|
|
1050
1058
|
* actually see, after capacity compaction, active tool-result pruning, and
|
|
1051
|
-
* semantic/active-full compaction have all run — and
|
|
1052
|
-
*
|
|
1059
|
+
* semantic/active-full compaction have all run — and spends the last
|
|
1060
|
+
* chance to shrink it:
|
|
1053
1061
|
*
|
|
1054
1062
|
* - estimate = the last request's real INPUT tokens + signed char/4 delta
|
|
1055
1063
|
* against the previous request's measured payload (recorded here on
|
|
@@ -1059,50 +1067,43 @@ export class AiSdkCompaction {
|
|
|
1059
1067
|
* cold start rather than a zero baseline;
|
|
1060
1068
|
* - over the window with no capacity attempt this step (the approximate
|
|
1061
1069
|
* trigger missed, e.g. growth the trigger under-weighted), force ONE
|
|
1062
|
-
* capacity re-entry
|
|
1063
|
-
* still rescue, and one bounded re-entry preserves termination;
|
|
1064
|
-
* - still over the window → context_budget_exhausted, with the terminal
|
|
1065
|
-
* detail taken from this step's capacity outcome: a replacement that
|
|
1066
|
-
* remains too large is head_anchor_exceeds_capacity (the irreducible
|
|
1067
|
-
* remainder exceeds capacity); a recorded shaping failure keeps its own
|
|
1068
|
-
* detail and diagnostic reason.
|
|
1070
|
+
* capacity re-entry.
|
|
1069
1071
|
*
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1072
|
+
* Still over afterwards, the request goes out anyway: only the provider
|
|
1073
|
+
* knows whether it fits, and a rejection is recovered from.
|
|
1072
1074
|
*/
|
|
1073
|
-
|
|
1074
|
-
const { shaped, reentry, state, providerTools,
|
|
1075
|
+
buildMidTurnFinalRequestRescue(input) {
|
|
1076
|
+
const { shaped, reentry, state, providerTools, charsPerToken } = input;
|
|
1075
1077
|
return async (options) => {
|
|
1076
1078
|
let result = await Promise.resolve(shaped(options));
|
|
1077
1079
|
const omissionProjection = projectHistoricalImageOmissions(result?.messages ?? options.messages, state.omittedImageToolResults);
|
|
1078
1080
|
if (omissionProjection) {
|
|
1079
1081
|
result = { ...(result ?? {}), messages: omissionProjection };
|
|
1080
1082
|
}
|
|
1081
|
-
const finalPayloadChars = () =>
|
|
1083
|
+
const finalPayloadChars = () => {
|
|
1084
|
+
// Measure the dispatched shape, so the payload recorded as the anchor's
|
|
1085
|
+
// pair describes the same request the provider counts.
|
|
1086
|
+
const dispatch = options.resolveDispatch(result?.activeTools ?? options.activeTools);
|
|
1087
|
+
return midTurnRequestPayloadChars(result?.messages ?? options.messages, providerTools, dispatch.activeTools, dispatch.systemPromptChars, charsPerToken);
|
|
1088
|
+
};
|
|
1082
1089
|
let payloadChars = finalPayloadChars();
|
|
1083
|
-
|
|
1084
|
-
|
|
1090
|
+
// Same rule as the trigger: the turn's first request is measured only
|
|
1091
|
+
// when a previous turn left a usable anchor to measure it against.
|
|
1092
|
+
const anchoredAtStepZero = requestEstimateAnchor(state, payloadChars).priorUsageTokens !== undefined;
|
|
1093
|
+
if (state.capacity !== undefined && (options.stepNumber >= 1 || anchoredAtStepZero)) {
|
|
1085
1094
|
const estimateFinal = () => estimateNextRequestTokens({
|
|
1086
|
-
...(state
|
|
1087
|
-
? { priorUsageTokens: state.lastRequestInputTokens }
|
|
1088
|
-
: {}),
|
|
1089
|
-
appendedChars: payloadChars - (state.lastRequestPayloadChars ?? payloadChars),
|
|
1095
|
+
...requestEstimateAnchor(state, payloadChars),
|
|
1090
1096
|
charsPerToken,
|
|
1091
|
-
coldStartChars: payloadChars,
|
|
1092
1097
|
});
|
|
1093
|
-
|
|
1098
|
+
const estimate = estimateFinal();
|
|
1094
1099
|
const capacityAttemptedThisStep = state.replacedStepNumber === options.stepNumber ||
|
|
1095
1100
|
state.lastShapeFailure?.stepNumber === options.stepNumber;
|
|
1096
|
-
if (
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
//
|
|
1100
|
-
//
|
|
1101
|
-
//
|
|
1102
|
-
// only the capacity + prune shapers over the already-shaped
|
|
1103
|
-
// projection; a second attempt after a same-step failure is
|
|
1104
|
-
// pointless (the failure was not a trigger miss) and would double
|
|
1105
|
-
// recorder counters and summarizer calls.
|
|
1101
|
+
if (estimate > state.capacity && !capacityAttemptedThisStep) {
|
|
1102
|
+
// One bounded capacity re-entry. Re-run only the capacity + prune
|
|
1103
|
+
// shapers over the already-shaped projection; a second attempt
|
|
1104
|
+
// after a same-step failure is pointless (the failure was not a
|
|
1105
|
+
// trigger miss) and would double recorder counters and summarizer
|
|
1106
|
+
// calls.
|
|
1106
1107
|
state.forcedTriggerEstimate = estimate;
|
|
1107
1108
|
const reshaped = await Promise.resolve(reentry({
|
|
1108
1109
|
...options,
|
|
@@ -1118,33 +1119,6 @@ export class AiSdkCompaction {
|
|
|
1118
1119
|
};
|
|
1119
1120
|
}
|
|
1120
1121
|
payloadChars = finalPayloadChars();
|
|
1121
|
-
estimate = estimateFinal();
|
|
1122
|
-
}
|
|
1123
|
-
if (estimate > state.capacity.tokens) {
|
|
1124
|
-
const failure = state.lastShapeFailure?.stepNumber === options.stepNumber
|
|
1125
|
-
? state.lastShapeFailure
|
|
1126
|
-
: undefined;
|
|
1127
|
-
const replacedThisStep = state.replacedStepNumber === options.stepNumber;
|
|
1128
|
-
const detail = replacedThisStep
|
|
1129
|
-
? 'head_anchor_exceeds_capacity'
|
|
1130
|
-
: (failure?.detail ?? 'no_safe_completed_span');
|
|
1131
|
-
const diagnosticReason = replacedThisStep
|
|
1132
|
-
? 'head_anchor_exceeds_capacity'
|
|
1133
|
-
: (failure?.diagnosticReason ?? 'no_safe_completed_span');
|
|
1134
|
-
state.exhaustedDetail = detail;
|
|
1135
|
-
onDiagnosticPatch({
|
|
1136
|
-
...compactionDecisionDiagnosticPatch({
|
|
1137
|
-
stage: 'activeStep',
|
|
1138
|
-
sourceKind: 'runtimeEvents',
|
|
1139
|
-
decision: 'unchanged',
|
|
1140
|
-
phase: 'mid_turn',
|
|
1141
|
-
boundaryKind: 'historyCompact',
|
|
1142
|
-
reason: 'context_budget_exhausted',
|
|
1143
|
-
skippedReasonCounts: { [diagnosticReason]: 1 },
|
|
1144
|
-
}),
|
|
1145
|
-
});
|
|
1146
|
-
abortController?.abort(new Error(`mid-turn context budget exhausted: ${detail}`));
|
|
1147
|
-
return result;
|
|
1148
1122
|
}
|
|
1149
1123
|
}
|
|
1150
1124
|
state.lastRequestPayloadChars = payloadChars;
|
|
@@ -1250,6 +1224,9 @@ export class MidTurnCapacityCompactState {
|
|
|
1250
1224
|
* Raw serialized chars of the final provider request. Overflow recovery
|
|
1251
1225
|
* uses this as its shrink-reference baseline because it must compare the
|
|
1252
1226
|
* actual rejected projection with a candidate replacement.
|
|
1227
|
+
*
|
|
1228
|
+
* Seeded before the turn's first request from the anchor a previous turn
|
|
1229
|
+
* persisted, so it can describe a request from an earlier send.
|
|
1253
1230
|
*/
|
|
1254
1231
|
lastRequestPayloadChars;
|
|
1255
1232
|
/**
|
|
@@ -1260,14 +1237,16 @@ export class MidTurnCapacityCompactState {
|
|
|
1260
1237
|
* them twice. Undefined when the last step's usage is missing or unusable
|
|
1261
1238
|
* (no positive input count); estimates then fall back to the whole-payload
|
|
1262
1239
|
* cold-start path — an unusable sample is unknown, never zero.
|
|
1240
|
+
*
|
|
1241
|
+
* Written and cleared as a pair with `lastRequestPayloadChars`, seeded from
|
|
1242
|
+
* the previous turn's persisted anchor — see `LastRequestAnchor` for why the
|
|
1243
|
+
* two only mean anything together.
|
|
1263
1244
|
*/
|
|
1264
1245
|
lastRequestInputTokens;
|
|
1265
1246
|
/** Latest durable checkpoint (loaded or written) for roll-forward summaries. */
|
|
1266
1247
|
previousCheckpoint;
|
|
1267
1248
|
/** Checkpoint accepted during this send; pins every later durable projection. */
|
|
1268
1249
|
projectionCheckpoint;
|
|
1269
|
-
/** Set when the turn must end with a context_budget_exhausted outcome. */
|
|
1270
|
-
exhaustedDetail;
|
|
1271
1250
|
/**
|
|
1272
1251
|
* Step whose request the capacity hook replaced. Semantic/active-full
|
|
1273
1252
|
* compaction yields on that exact step so one step never runs two
|
|
@@ -1290,15 +1269,16 @@ export class MidTurnCapacityCompactState {
|
|
|
1290
1269
|
/** Exact historical image results omitted after a provider overflow. */
|
|
1291
1270
|
omittedImageToolResults = new Map();
|
|
1292
1271
|
/**
|
|
1293
|
-
* The capacity hook's most recent shaping failure. The
|
|
1294
|
-
*
|
|
1295
|
-
*
|
|
1296
|
-
* shaper that already attempted and failed this step.
|
|
1272
|
+
* The step of the capacity hook's most recent shaping failure. The rescue
|
|
1273
|
+
* re-entry reads it so it never re-runs a shaper that already attempted and
|
|
1274
|
+
* failed on the same step.
|
|
1297
1275
|
*/
|
|
1298
1276
|
lastShapeFailure;
|
|
1299
1277
|
/** Malformed summaries spend one bounded repair budget for this whole Turn. */
|
|
1300
1278
|
malformedSummaryFailure;
|
|
1301
|
-
constructor(headAnchor, priorContentEvents, priorRunHeaders,
|
|
1279
|
+
constructor(headAnchor, priorContentEvents, priorRunHeaders,
|
|
1280
|
+
/** The model's declared context window, absent when it declares none. */
|
|
1281
|
+
capacity) {
|
|
1302
1282
|
this.headAnchor = headAnchor;
|
|
1303
1283
|
this.priorContentEvents = priorContentEvents;
|
|
1304
1284
|
this.priorRunHeaders = priorRunHeaders;
|
|
@@ -1308,21 +1288,79 @@ export class MidTurnCapacityCompactState {
|
|
|
1308
1288
|
/**
|
|
1309
1289
|
* Char measure of the FULL provider-visible request input: the system prompt
|
|
1310
1290
|
* (sent through the separate `system` field), the (projected) messages, and
|
|
1311
|
-
* the serialized schemas of the active tool subset.
|
|
1312
|
-
*
|
|
1313
|
-
*
|
|
1314
|
-
* commensurable and
|
|
1291
|
+
* the serialized schemas of the active tool subset. Media is billed in tokens
|
|
1292
|
+
* converted to chars, so the whole measure stays in one unit. The capacity
|
|
1293
|
+
* trigger and the rescue re-entry both measure with this ONE function, so
|
|
1294
|
+
* their comparisons against `lastRequestPayloadChars` are commensurable and
|
|
1315
1295
|
* same-turn tool-schema growth (a `tool_search` activation) is counted like
|
|
1316
1296
|
* any other payload growth. The system prompt is constant between adjacent
|
|
1317
1297
|
* requests — signed deltas cancel it — but the cold-start estimate (no usable
|
|
1318
1298
|
* usage sample) is the whole payload, so omitting it would under-estimate by
|
|
1319
|
-
* exactly the system prompt
|
|
1299
|
+
* exactly the system prompt.
|
|
1300
|
+
*
|
|
1301
|
+
* A media part is worth what the provider charges for it, not what it
|
|
1302
|
+
* serializes to: materialized bytes reach the request as base64 or a byte map,
|
|
1303
|
+
* where a 500 KB screenshot serializes to ~667K chars. Measuring that string
|
|
1304
|
+
* makes one image look like a whole context window, which is how an affordable
|
|
1305
|
+
* request became a terminal verdict (#4458). Billing it at the same constant
|
|
1306
|
+
* the ledger's ruler uses keeps the two measures commensurable.
|
|
1320
1307
|
*/
|
|
1321
|
-
function midTurnRequestPayloadChars(messages, providerTools, activeTools, systemPromptChars) {
|
|
1308
|
+
function midTurnRequestPayloadChars(messages, providerTools, activeTools, systemPromptChars, charsPerToken) {
|
|
1309
|
+
let mediaParts = 0;
|
|
1310
|
+
const serializedMessages = JSON.stringify(messages, (_key, value) => {
|
|
1311
|
+
if (!isInlineImageFilePart(value))
|
|
1312
|
+
return value;
|
|
1313
|
+
mediaParts += 1;
|
|
1314
|
+
return { type: value.type, mediaType: value.mediaType };
|
|
1315
|
+
});
|
|
1322
1316
|
return (Math.max(0, Math.floor(systemPromptChars)) +
|
|
1323
|
-
|
|
1317
|
+
(serializedMessages?.length ?? 0) +
|
|
1318
|
+
mediaParts * MATERIALIZED_IMAGE_TOKENS * Math.max(1, charsPerToken) +
|
|
1324
1319
|
toolSchemaCharsForDiagnostics(providerTools, activeTools));
|
|
1325
1320
|
}
|
|
1321
|
+
/**
|
|
1322
|
+
* The newest `LastRequestAnchor` persisted in the prior context.
|
|
1323
|
+
*
|
|
1324
|
+
* Reverse scan, and the FIRST anchor-bearing usage record decides — an older
|
|
1325
|
+
* anchor describes a request further from the one about to go out, so a
|
|
1326
|
+
* rejected newest anchor means cold start, never a fallback to an older one.
|
|
1327
|
+
* The synthetic `token_usage` a manual `/compact` writes carries no anchor and
|
|
1328
|
+
* is skipped for free.
|
|
1329
|
+
*
|
|
1330
|
+
* The anchor is a token count in the anchoring model's tokenizer, so it only
|
|
1331
|
+
* transfers to a request going to the same model over the same connection.
|
|
1332
|
+
*/
|
|
1333
|
+
function persistedRequestAnchor(events, runHeaders, modelId, connectionId) {
|
|
1334
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1335
|
+
const event = events[index];
|
|
1336
|
+
const anchor = event?.actions?.tokenUsage?.lastRequestAnchor;
|
|
1337
|
+
if (!anchor)
|
|
1338
|
+
continue;
|
|
1339
|
+
const header = runHeaders.find((candidate) => candidate.runId === event?.runId);
|
|
1340
|
+
if (!header || header.modelId !== modelId || header.llmConnectionId !== connectionId) {
|
|
1341
|
+
return undefined;
|
|
1342
|
+
}
|
|
1343
|
+
return anchor;
|
|
1344
|
+
}
|
|
1345
|
+
return undefined;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* The estimate inputs for the request about to go out: the paired anchor and
|
|
1349
|
+
* the signed char delta against the payload that anchor was reported for.
|
|
1350
|
+
*
|
|
1351
|
+
* A delta wider than the whole payload means the pairing has already failed, so
|
|
1352
|
+
* drop it and cold start: the whole payload against a zero anchor.
|
|
1353
|
+
*/
|
|
1354
|
+
function requestEstimateAnchor(state, payloadChars) {
|
|
1355
|
+
const anchor = state.lastRequestInputTokens;
|
|
1356
|
+
const baseline = state.lastRequestPayloadChars;
|
|
1357
|
+
if (anchor === undefined || baseline === undefined)
|
|
1358
|
+
return { appendedChars: payloadChars };
|
|
1359
|
+
const appendedChars = payloadChars - baseline;
|
|
1360
|
+
if (Math.abs(appendedChars) > payloadChars)
|
|
1361
|
+
return { appendedChars: payloadChars };
|
|
1362
|
+
return { priorUsageTokens: anchor, appendedChars };
|
|
1363
|
+
}
|
|
1326
1364
|
/**
|
|
1327
1365
|
* The `decision: 'replaced'` diagnostic patch for a durable active-send fold,
|
|
1328
1366
|
* shared by the proactive (`reason: 'context_limit'`) and reactive
|