agentfootprint 7.14.0 → 7.16.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/dist/adapters/hosting/agentcore.js +350 -0
- package/dist/adapters/hosting/agentcore.js.map +1 -0
- package/dist/adapters/llm/BrowserOpenAIProvider.js +7 -3
- package/dist/adapters/llm/BrowserOpenAIProvider.js.map +1 -1
- package/dist/adapters/llm/OpenAIProvider.js +11 -3
- package/dist/adapters/llm/OpenAIProvider.js.map +1 -1
- package/dist/adapters/memory/agentcore.js +125 -1
- package/dist/adapters/memory/agentcore.js.map +1 -1
- package/dist/adapters/security/agentcore.js +0 -0
- package/dist/adapters/security/agentcore.js.map +1 -0
- package/dist/conventions.js +7 -0
- package/dist/conventions.js.map +1 -1
- package/dist/core/Agent.js +56 -2
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +86 -1
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/buildAgentChart.js +12 -1
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +10 -1
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/compaction/errors.js +40 -0
- package/dist/core/agent/compaction/errors.js.map +1 -0
- package/dist/core/agent/compaction/index.js +24 -0
- package/dist/core/agent/compaction/index.js.map +1 -0
- package/dist/core/agent/compaction/strategy.js +152 -0
- package/dist/core/agent/compaction/strategy.js.map +1 -0
- package/dist/core/agent/compaction/summarize.js +103 -0
- package/dist/core/agent/compaction/summarize.js.map +1 -0
- package/dist/core/agent/compaction/turns.js +153 -0
- package/dist/core/agent/compaction/turns.js.map +1 -0
- package/dist/core/agent/compaction/types.js +21 -0
- package/dist/core/agent/compaction/types.js.map +1 -0
- package/dist/core/agent/stages/compact.js +130 -0
- package/dist/core/agent/stages/compact.js.map +1 -0
- package/dist/esm/adapters/hosting/agentcore.d.ts +199 -0
- package/dist/esm/adapters/hosting/agentcore.js +321 -0
- package/dist/esm/adapters/hosting/agentcore.js.map +1 -0
- package/dist/esm/adapters/llm/BrowserOpenAIProvider.js +7 -3
- package/dist/esm/adapters/llm/BrowserOpenAIProvider.js.map +1 -1
- package/dist/esm/adapters/llm/OpenAIProvider.js +11 -3
- package/dist/esm/adapters/llm/OpenAIProvider.js.map +1 -1
- package/dist/esm/adapters/memory/agentcore.d.ts +88 -2
- package/dist/esm/adapters/memory/agentcore.js +125 -1
- package/dist/esm/adapters/memory/agentcore.js.map +1 -1
- package/dist/esm/adapters/security/agentcore.d.ts +157 -0
- package/dist/esm/adapters/security/agentcore.js +0 -0
- package/dist/esm/adapters/security/agentcore.js.map +1 -0
- package/dist/esm/conventions.d.ts +7 -0
- package/dist/esm/conventions.js +7 -0
- package/dist/esm/conventions.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +8 -1
- package/dist/esm/core/Agent.js +56 -2
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +43 -0
- package/dist/esm/core/agent/AgentBuilder.js +86 -1
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.d.ts +13 -0
- package/dist/esm/core/agent/buildAgentChart.js +12 -1
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +10 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/compaction/errors.d.ts +26 -0
- package/dist/esm/core/agent/compaction/errors.js +36 -0
- package/dist/esm/core/agent/compaction/errors.js.map +1 -0
- package/dist/esm/core/agent/compaction/index.d.ts +18 -0
- package/dist/esm/core/agent/compaction/index.js +18 -0
- package/dist/esm/core/agent/compaction/index.js.map +1 -0
- package/dist/esm/core/agent/compaction/strategy.d.ts +103 -0
- package/dist/esm/core/agent/compaction/strategy.js +148 -0
- package/dist/esm/core/agent/compaction/strategy.js.map +1 -0
- package/dist/esm/core/agent/compaction/summarize.d.ts +61 -0
- package/dist/esm/core/agent/compaction/summarize.js +96 -0
- package/dist/esm/core/agent/compaction/summarize.js.map +1 -0
- package/dist/esm/core/agent/compaction/turns.d.ts +85 -0
- package/dist/esm/core/agent/compaction/turns.js +145 -0
- package/dist/esm/core/agent/compaction/turns.js.map +1 -0
- package/dist/esm/core/agent/compaction/types.d.ts +148 -0
- package/dist/esm/core/agent/compaction/types.js +20 -0
- package/dist/esm/core/agent/compaction/types.js.map +1 -0
- package/dist/esm/core/agent/stages/compact.d.ts +59 -0
- package/dist/esm/core/agent/stages/compact.js +126 -0
- package/dist/esm/core/agent/stages/compact.js.map +1 -0
- package/dist/esm/core/agent/types.d.ts +11 -0
- package/dist/esm/hosting/httpHost.d.ts +121 -0
- package/dist/esm/hosting/httpHost.js +248 -0
- package/dist/esm/hosting/httpHost.js.map +1 -0
- package/dist/esm/hosting/index.d.ts +8 -1
- package/dist/esm/hosting/index.js +7 -1
- package/dist/esm/hosting/index.js.map +1 -1
- package/dist/esm/hosting/nodeHost.d.ts +23 -16
- package/dist/esm/hosting/nodeHost.js +32 -199
- package/dist/esm/hosting/nodeHost.js.map +1 -1
- package/dist/esm/hosting-providers.d.ts +50 -0
- package/dist/esm/hosting-providers.js +50 -0
- package/dist/esm/hosting-providers.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/mcp/gatewayTransport.d.ts +103 -0
- package/dist/esm/lib/mcp/gatewayTransport.js +123 -0
- package/dist/esm/lib/mcp/gatewayTransport.js.map +1 -0
- package/dist/esm/lib/mcp/index.d.ts +2 -1
- package/dist/esm/lib/mcp/index.js +1 -0
- package/dist/esm/lib/mcp/index.js.map +1 -1
- package/dist/esm/lib/mcp/mcpClient.js +10 -2
- package/dist/esm/lib/mcp/mcpClient.js.map +1 -1
- package/dist/esm/lib/mcp/types.d.ts +45 -1
- package/dist/esm/memory/store/types.d.ts +22 -0
- package/dist/esm/memory-providers.d.ts +1 -1
- package/dist/esm/memory-providers.js.map +1 -1
- package/dist/esm/recorders/core/CompactionMeter.d.ts +78 -0
- package/dist/esm/recorders/core/CompactionMeter.js +101 -0
- package/dist/esm/recorders/core/CompactionMeter.js.map +1 -0
- package/dist/esm/security/index.d.ts +10 -1
- package/dist/esm/security/index.js +14 -1
- package/dist/esm/security/index.js.map +1 -1
- package/dist/esm/tool-providers/index.d.ts +6 -2
- package/dist/esm/tool-providers/index.js +5 -1
- package/dist/esm/tool-providers/index.js.map +1 -1
- package/dist/hosting/httpHost.js +276 -0
- package/dist/hosting/httpHost.js.map +1 -0
- package/dist/hosting/index.js +10 -1
- package/dist/hosting/index.js.map +1 -1
- package/dist/hosting/nodeHost.js +34 -224
- package/dist/hosting/nodeHost.js.map +1 -1
- package/dist/hosting-providers.js +57 -0
- package/dist/hosting-providers.js.map +1 -0
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/mcp/gatewayTransport.js +129 -0
- package/dist/lib/mcp/gatewayTransport.js.map +1 -0
- package/dist/lib/mcp/index.js +4 -1
- package/dist/lib/mcp/index.js.map +1 -1
- package/dist/lib/mcp/mcpClient.js +10 -2
- package/dist/lib/mcp/mcpClient.js.map +1 -1
- package/dist/memory-providers.js.map +1 -1
- package/dist/recorders/core/CompactionMeter.js +105 -0
- package/dist/recorders/core/CompactionMeter.js.map +1 -0
- package/dist/security/index.js +16 -2
- package/dist/security/index.js.map +1 -1
- package/dist/tool-providers/index.js +7 -1
- package/dist/tool-providers/index.js.map +1 -1
- package/dist/types/adapters/hosting/agentcore.d.ts +200 -0
- package/dist/types/adapters/hosting/agentcore.d.ts.map +1 -0
- package/dist/types/adapters/llm/BrowserOpenAIProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/OpenAIProvider.d.ts.map +1 -1
- package/dist/types/adapters/memory/agentcore.d.ts +88 -2
- package/dist/types/adapters/memory/agentcore.d.ts.map +1 -1
- package/dist/types/adapters/security/agentcore.d.ts +158 -0
- package/dist/types/adapters/security/agentcore.d.ts.map +1 -0
- package/dist/types/conventions.d.ts +7 -0
- package/dist/types/conventions.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts +8 -1
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +43 -0
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentChart.d.ts +13 -0
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/compaction/errors.d.ts +27 -0
- package/dist/types/core/agent/compaction/errors.d.ts.map +1 -0
- package/dist/types/core/agent/compaction/index.d.ts +19 -0
- package/dist/types/core/agent/compaction/index.d.ts.map +1 -0
- package/dist/types/core/agent/compaction/strategy.d.ts +104 -0
- package/dist/types/core/agent/compaction/strategy.d.ts.map +1 -0
- package/dist/types/core/agent/compaction/summarize.d.ts +62 -0
- package/dist/types/core/agent/compaction/summarize.d.ts.map +1 -0
- package/dist/types/core/agent/compaction/turns.d.ts +86 -0
- package/dist/types/core/agent/compaction/turns.d.ts.map +1 -0
- package/dist/types/core/agent/compaction/types.d.ts +149 -0
- package/dist/types/core/agent/compaction/types.d.ts.map +1 -0
- package/dist/types/core/agent/stages/compact.d.ts +60 -0
- package/dist/types/core/agent/stages/compact.d.ts.map +1 -0
- package/dist/types/core/agent/types.d.ts +11 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/hosting/httpHost.d.ts +122 -0
- package/dist/types/hosting/httpHost.d.ts.map +1 -0
- package/dist/types/hosting/index.d.ts +8 -1
- package/dist/types/hosting/index.d.ts.map +1 -1
- package/dist/types/hosting/nodeHost.d.ts +23 -16
- package/dist/types/hosting/nodeHost.d.ts.map +1 -1
- package/dist/types/hosting-providers.d.ts +51 -0
- package/dist/types/hosting-providers.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/mcp/gatewayTransport.d.ts +104 -0
- package/dist/types/lib/mcp/gatewayTransport.d.ts.map +1 -0
- package/dist/types/lib/mcp/index.d.ts +2 -1
- package/dist/types/lib/mcp/index.d.ts.map +1 -1
- package/dist/types/lib/mcp/mcpClient.d.ts.map +1 -1
- package/dist/types/lib/mcp/types.d.ts +45 -1
- package/dist/types/lib/mcp/types.d.ts.map +1 -1
- package/dist/types/memory/store/types.d.ts +22 -0
- package/dist/types/memory/store/types.d.ts.map +1 -1
- package/dist/types/memory-providers.d.ts +1 -1
- package/dist/types/memory-providers.d.ts.map +1 -1
- package/dist/types/recorders/core/CompactionMeter.d.ts +79 -0
- package/dist/types/recorders/core/CompactionMeter.d.ts.map +1 -0
- package/dist/types/security/index.d.ts +10 -1
- package/dist/types/security/index.d.ts.map +1 -1
- package/dist/types/tool-providers/index.d.ts +6 -2
- package/dist/types/tool-providers/index.d.ts.map +1 -1
- package/package.json +14 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compaction/turns — where a turn boundary is, and which turns may fold.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Pure functions over the window (no scope, no I/O, no clock).
|
|
5
|
+
* Role: core/ layer. The whole safety argument of compaction lives here:
|
|
6
|
+
* a fold that splits an assistant's `tool_use` from its
|
|
7
|
+
* `tool_result` produces a request the vendor rejects, and a fold
|
|
8
|
+
* that swallows an unanswered question destroys the referent of the
|
|
9
|
+
* answer that has not arrived yet.
|
|
10
|
+
* Emits: N/A.
|
|
11
|
+
*
|
|
12
|
+
* Testable on its own — see `test/core/agent/compaction-turns.test.ts`.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Segment a window into turns.
|
|
16
|
+
*
|
|
17
|
+
* A new turn starts at any non-`tool` message; `tool` messages join the turn
|
|
18
|
+
* in progress. A leading `tool` message (only reachable from a hand-built
|
|
19
|
+
* history) starts its own turn rather than being silently dropped.
|
|
20
|
+
*/
|
|
21
|
+
export function segmentTurns(history) {
|
|
22
|
+
const turns = [];
|
|
23
|
+
let current = [];
|
|
24
|
+
let start = 0;
|
|
25
|
+
const flush = () => {
|
|
26
|
+
if (current.length === 0)
|
|
27
|
+
return;
|
|
28
|
+
turns.push({ index: turns.length, start, length: current.length, messages: current });
|
|
29
|
+
current = [];
|
|
30
|
+
};
|
|
31
|
+
for (let i = 0; i < history.length; i++) {
|
|
32
|
+
const msg = history[i];
|
|
33
|
+
if (msg.role !== 'tool' || current.length === 0) {
|
|
34
|
+
flush();
|
|
35
|
+
start = i;
|
|
36
|
+
}
|
|
37
|
+
current.push(msg);
|
|
38
|
+
}
|
|
39
|
+
flush();
|
|
40
|
+
return turns;
|
|
41
|
+
}
|
|
42
|
+
/** Every tool_call id that has a matching `role: 'tool'` message. */
|
|
43
|
+
export function answeredCallIds(history) {
|
|
44
|
+
const answered = new Set();
|
|
45
|
+
for (const msg of history) {
|
|
46
|
+
if (msg.role === 'tool' && msg.toolCallId)
|
|
47
|
+
answered.add(msg.toolCallId);
|
|
48
|
+
}
|
|
49
|
+
return answered;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Why this turn may NOT fold, or `undefined` when it may.
|
|
53
|
+
*
|
|
54
|
+
* Order matters only for which reason gets reported first; every check is
|
|
55
|
+
* independent. `paused-tool` / `pending-check-in` are separated from
|
|
56
|
+
* `unresolved-tool-call` on purpose: they are the same shape but a different
|
|
57
|
+
* fact about the world, and "we are waiting on a human" is what the person
|
|
58
|
+
* reading the trace needs to see.
|
|
59
|
+
*/
|
|
60
|
+
export function refusalFor(turn, ctx) {
|
|
61
|
+
for (const msg of turn.messages) {
|
|
62
|
+
if (msg.role === 'system')
|
|
63
|
+
return 'system-envelope';
|
|
64
|
+
}
|
|
65
|
+
const paused = ctx.pausedToolCallId;
|
|
66
|
+
if (paused !== undefined && paused.length > 0) {
|
|
67
|
+
for (const msg of turn.messages) {
|
|
68
|
+
const holdsPaused = msg.toolCallId === paused || (msg.toolCalls ?? []).some((tc) => tc.id === paused);
|
|
69
|
+
if (holdsPaused)
|
|
70
|
+
return ctx.pausedCheckIn === true ? 'pending-check-in' : 'paused-tool';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const msg of turn.messages) {
|
|
74
|
+
for (const call of msg.toolCalls ?? []) {
|
|
75
|
+
if (!ctx.answeredCallIds.has(call.id))
|
|
76
|
+
return 'unresolved-tool-call';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Choose the fold span: the LONGEST CONTIGUOUS run of foldable candidate
|
|
83
|
+
* turns, starting at the oldest foldable one.
|
|
84
|
+
*
|
|
85
|
+
* Contiguity is not fussiness — it is what keeps the conversation in order.
|
|
86
|
+
* A fold replaces its span with ONE summary message; if the span skipped over
|
|
87
|
+
* an unfoldable turn, that turn would end up after a summary of things that
|
|
88
|
+
* happened before it. So an unfoldable turn at the front is stepped over (the
|
|
89
|
+
* fold "takes the next oldest instead") and an unfoldable turn in the middle
|
|
90
|
+
* ends the span. Everything not folded keeps its position.
|
|
91
|
+
*
|
|
92
|
+
* @param turns the window's turn segmentation
|
|
93
|
+
* @param keepRecent how many trailing turns are off-limits
|
|
94
|
+
* @param ctx foldability inputs
|
|
95
|
+
* @param isSummaryTurn true for a turn that is a summary a prior fold wrote
|
|
96
|
+
*/
|
|
97
|
+
export function planFold(turns, keepRecent, ctx, isSummaryTurn) {
|
|
98
|
+
const refusals = [];
|
|
99
|
+
const candidateCount = Math.max(0, turns.length - keepRecent);
|
|
100
|
+
for (let i = candidateCount; i < turns.length; i++) {
|
|
101
|
+
const turn = turns[i];
|
|
102
|
+
refusals.push({ reason: 'inside-keep-window', turnIndex: i, messageIndex: turn.start });
|
|
103
|
+
}
|
|
104
|
+
if (candidateCount === 0)
|
|
105
|
+
return { from: -1, to: -1, refusals };
|
|
106
|
+
const before = [];
|
|
107
|
+
let from = -1;
|
|
108
|
+
let to = -1;
|
|
109
|
+
for (let i = 0; i < candidateCount; i++) {
|
|
110
|
+
const turn = turns[i];
|
|
111
|
+
const reason = refusalFor(turn, ctx);
|
|
112
|
+
if (reason === undefined) {
|
|
113
|
+
if (from === -1)
|
|
114
|
+
from = i;
|
|
115
|
+
to = i;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
before.push({ reason, turnIndex: i, messageIndex: turn.start });
|
|
119
|
+
if (from !== -1)
|
|
120
|
+
break; // an unfoldable turn ENDS the span
|
|
121
|
+
}
|
|
122
|
+
// A span that is nothing but one existing summary is not worth a call:
|
|
123
|
+
// re-summarizing a summary spends tokens to lose detail and names nothing
|
|
124
|
+
// new. It becomes foldable again as soon as a real turn joins it.
|
|
125
|
+
if (from !== -1 && from === to && isSummaryTurn(turns[from])) {
|
|
126
|
+
return {
|
|
127
|
+
from: -1,
|
|
128
|
+
to: -1,
|
|
129
|
+
refusals: [
|
|
130
|
+
...before,
|
|
131
|
+
{ reason: 'only-existing-summary', turnIndex: from, messageIndex: turns[from].start },
|
|
132
|
+
...refusals,
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return { from, to, refusals: [...before, ...refusals] };
|
|
137
|
+
}
|
|
138
|
+
/** Total characters of message content in a window. Exact; not tokens. */
|
|
139
|
+
export function windowChars(history) {
|
|
140
|
+
let total = 0;
|
|
141
|
+
for (const msg of history)
|
|
142
|
+
total += msg.content.length;
|
|
143
|
+
return total;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=turns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turns.js","sourceRoot":"","sources":["../../../../../src/core/agent/compaction/turns.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compaction/types — the public shape of `.compaction()` and the record it
|
|
3
|
+
* writes into the ledger.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Value objects (no behavior) + one resolved-config type.
|
|
6
|
+
* Role: core/ layer. The law this feature exists to keep is stated once,
|
|
7
|
+
* here, because every other file in this folder implements a piece
|
|
8
|
+
* of it: **compaction edits the WINDOW, never the LEDGER.**
|
|
9
|
+
* Emits: N/A (types only).
|
|
10
|
+
*
|
|
11
|
+
* The window is `scope.history` — the array `call-llm` hands the provider.
|
|
12
|
+
* The ledger is footprintjs's commit log, which is append-only: the turns a
|
|
13
|
+
* fold removes from the window were committed by `seed#0` / `tool-calls#N`
|
|
14
|
+
* BEFORE the fold and stay in those bundles byte-identical forever. A fold
|
|
15
|
+
* therefore cannot destroy history even in principle; it can only stop
|
|
16
|
+
* re-sending it. The summary is a CLAIM about the past, so it is filed as a
|
|
17
|
+
* claim — its own recorded step, naming every `runtimeStageId` it folded.
|
|
18
|
+
*/
|
|
19
|
+
import type { LLMProvider } from '../../../adapters/types.js';
|
|
20
|
+
/**
|
|
21
|
+
* What `.compaction({...})` accepts.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const agent = Agent.create({ provider: anthropic(), model: 'claude-sonnet-4-5' })
|
|
26
|
+
* .compaction({
|
|
27
|
+
* thresholdTokens: 120_000,
|
|
28
|
+
* summarizer: anthropic(), // usually the cheap one
|
|
29
|
+
* model: 'claude-haiku-4-5',
|
|
30
|
+
* keepRecentTurns: 6,
|
|
31
|
+
* })
|
|
32
|
+
* .build();
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export interface CompactionOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Fold when the LAST call's adapter-reported input tokens exceed this.
|
|
38
|
+
*
|
|
39
|
+
* REQUIRED, with no default. A default budget here would be a number the
|
|
40
|
+
* library invented for a window whose size only the consumer's model and
|
|
41
|
+
* wallet know — and every run would silently inherit it.
|
|
42
|
+
*/
|
|
43
|
+
readonly thresholdTokens: number;
|
|
44
|
+
/**
|
|
45
|
+
* How many of the most recent turns are never folded. Default 6.
|
|
46
|
+
*
|
|
47
|
+
* The recent turns are what the model is actually reasoning over; folding
|
|
48
|
+
* them is how a compacting agent loses the thread.
|
|
49
|
+
*/
|
|
50
|
+
readonly keepRecentTurns?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The provider that writes the summary. Explicitly chosen — the library
|
|
53
|
+
* never quietly bills your main model for compaction.
|
|
54
|
+
*/
|
|
55
|
+
readonly summarizer: LLMProvider;
|
|
56
|
+
/**
|
|
57
|
+
* Model id for the summarizer call. Defaults to the agent's own model, so
|
|
58
|
+
* `summarizer: anthropic()` alone works; name a cheap model to spend less.
|
|
59
|
+
*/
|
|
60
|
+
readonly model?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Resolved form — defaults applied at build time, validated once. */
|
|
63
|
+
export interface ResolvedCompaction {
|
|
64
|
+
readonly thresholdTokens: number;
|
|
65
|
+
readonly keepRecentTurns: number;
|
|
66
|
+
readonly summarizer: LLMProvider;
|
|
67
|
+
readonly model: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Why a turn refused to fold. Every one of these is NAMED in the commit —
|
|
71
|
+
* a fold that took less than it could have has to say why, or the next
|
|
72
|
+
* person debugging an over-budget window has to guess.
|
|
73
|
+
*/
|
|
74
|
+
export type FoldRefusalReason =
|
|
75
|
+
/** The turn holds a `role: 'system'` message. The envelope never folds. */
|
|
76
|
+
'system-envelope'
|
|
77
|
+
/**
|
|
78
|
+
* An assistant `tool_use` in this turn has no matching `tool_result` in the
|
|
79
|
+
* window. Folding an unanswered question destroys the answer's referent —
|
|
80
|
+
* and the referent may still arrive (a paused run resumes).
|
|
81
|
+
*/
|
|
82
|
+
| 'unresolved-tool-call'
|
|
83
|
+
/** The turn holds the tool call this run is currently paused on. */
|
|
84
|
+
| 'paused-tool'
|
|
85
|
+
/** The turn holds a tool call waiting on a human check-in decision. */
|
|
86
|
+
| 'pending-check-in'
|
|
87
|
+
/** Inside `keepRecentTurns` — the recent window is never a candidate. */
|
|
88
|
+
| 'inside-keep-window'
|
|
89
|
+
/**
|
|
90
|
+
* The only foldable candidate is a summary a previous fold wrote. Folding a
|
|
91
|
+
* summary of a summary with nothing new to add spends a call to lose detail.
|
|
92
|
+
*/
|
|
93
|
+
| 'only-existing-summary'
|
|
94
|
+
/** The summarizer threw. No fold this iteration; the window stays big. */
|
|
95
|
+
| 'summarizer-failed'
|
|
96
|
+
/**
|
|
97
|
+
* The summary came back no smaller than the span it would replace, so the
|
|
98
|
+
* fold was abandoned. Measured in chars on both sides — the same unit, an
|
|
99
|
+
* exact comparison, not a token guess. Folding here would spend a call to
|
|
100
|
+
* make the window BIGGER and lose the detail as well.
|
|
101
|
+
*/
|
|
102
|
+
| 'summary-not-smaller';
|
|
103
|
+
/** One named refusal, positioned so a reader can find the turn. */
|
|
104
|
+
export interface FoldRefusal {
|
|
105
|
+
readonly reason: FoldRefusalReason;
|
|
106
|
+
/** Index of the turn in this iteration's turn segmentation. */
|
|
107
|
+
readonly turnIndex: number;
|
|
108
|
+
/** Index of the turn's first message in the pre-fold window. */
|
|
109
|
+
readonly messageIndex: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* What one visit to the compaction stage put in the ledger. Appended to
|
|
113
|
+
* `scope.compactions`, so the run's whole compaction story is one array in
|
|
114
|
+
* the commit log — including the visits that folded NOTHING, which are the
|
|
115
|
+
* interesting ones.
|
|
116
|
+
*
|
|
117
|
+
* On `windowChars*` vs tokens: the char counts are EXACT and measured here.
|
|
118
|
+
* There is deliberately no `tokensAfter` — nothing can count the tokens of a
|
|
119
|
+
* window that has not been sent yet, and inventing one would be exactly the
|
|
120
|
+
* guess this feature exists to refuse. The honest "after" is the NEXT call's
|
|
121
|
+
* `stream.llm_end` usage.
|
|
122
|
+
*/
|
|
123
|
+
export interface CompactionRecord {
|
|
124
|
+
/** ReAct iteration this visit belongs to. */
|
|
125
|
+
readonly iteration: number;
|
|
126
|
+
/** Adapter-reported input tokens of the last call — what tripped the check. */
|
|
127
|
+
readonly measuredTokens: number;
|
|
128
|
+
/** The budget it was compared against. */
|
|
129
|
+
readonly thresholdTokens: number;
|
|
130
|
+
/** True when the measurement was over budget (a fold was attempted). */
|
|
131
|
+
readonly overBudget: boolean;
|
|
132
|
+
/** `runtimeStageId`s of the stages that appended the folded messages. */
|
|
133
|
+
readonly foldedStageIds: readonly string[];
|
|
134
|
+
/** How many messages left the window. */
|
|
135
|
+
readonly foldedMessageCount: number;
|
|
136
|
+
/** Window size in chars before / after this visit. Exact, and not tokens. */
|
|
137
|
+
readonly windowCharsBefore: number;
|
|
138
|
+
readonly windowCharsAfter: number;
|
|
139
|
+
/** Length of the summary text the summarizer produced (0 when none). */
|
|
140
|
+
readonly summaryChars: number;
|
|
141
|
+
/** What the summarizer call itself cost, when it reported usage. */
|
|
142
|
+
readonly summarizerTokens?: {
|
|
143
|
+
readonly input: number;
|
|
144
|
+
readonly output: number;
|
|
145
|
+
};
|
|
146
|
+
/** Every turn that refused to fold, named. */
|
|
147
|
+
readonly refusals: readonly FoldRefusal[];
|
|
148
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compaction/types — the public shape of `.compaction()` and the record it
|
|
3
|
+
* writes into the ledger.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Value objects (no behavior) + one resolved-config type.
|
|
6
|
+
* Role: core/ layer. The law this feature exists to keep is stated once,
|
|
7
|
+
* here, because every other file in this folder implements a piece
|
|
8
|
+
* of it: **compaction edits the WINDOW, never the LEDGER.**
|
|
9
|
+
* Emits: N/A (types only).
|
|
10
|
+
*
|
|
11
|
+
* The window is `scope.history` — the array `call-llm` hands the provider.
|
|
12
|
+
* The ledger is footprintjs's commit log, which is append-only: the turns a
|
|
13
|
+
* fold removes from the window were committed by `seed#0` / `tool-calls#N`
|
|
14
|
+
* BEFORE the fold and stay in those bundles byte-identical forever. A fold
|
|
15
|
+
* therefore cannot destroy history even in principle; it can only stop
|
|
16
|
+
* re-sending it. The summary is a CLAIM about the past, so it is filed as a
|
|
17
|
+
* claim — its own recorded step, naming every `runtimeStageId` it folded.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/agent/compaction/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compact — the loop-head stage that keeps the window inside budget.
|
|
3
|
+
*
|
|
4
|
+
* Reads the last call's ADAPTER-REPORTED input tokens from the compaction
|
|
5
|
+
* meter and, when they exceed `thresholdTokens`, hands the window to a window
|
|
6
|
+
* strategy. Runs once per ReAct iteration boundary, as the loop target, so
|
|
7
|
+
* everything downstream in the turn — the injection engine's triggers, all
|
|
8
|
+
* three context slots, and the wire itself — sees ONE window. No component
|
|
9
|
+
* gets a different past than the model does.
|
|
10
|
+
*
|
|
11
|
+
* The law: **it edits the window, never the ledger.** The turns a fold removes
|
|
12
|
+
* were committed by `seed#0` / `tool-calls#N` before this stage ran and stay
|
|
13
|
+
* in those bundles byte-identical; footprintjs's commit log is append-only, so
|
|
14
|
+
* a fold cannot erase history even in principle. What it can do is stop
|
|
15
|
+
* re-sending it — and say so, in its own commit, naming every runtimeStageId
|
|
16
|
+
* it folded. A compacted run is still a provable run.
|
|
17
|
+
*
|
|
18
|
+
* This file is deliberately all WIRING. The decision — what the window should
|
|
19
|
+
* become — lives behind `WindowStrategy` (compaction/strategy.ts, internal),
|
|
20
|
+
* so the duty to record, emit and cost what happened stays in exactly one
|
|
21
|
+
* place instead of being re-implemented per strategy. `.compaction()` is the
|
|
22
|
+
* only strategy that ships.
|
|
23
|
+
*
|
|
24
|
+
* Emits (existing vocabulary only — no new event types):
|
|
25
|
+
* agentfootprint.context.evicted — one per message leaving the window
|
|
26
|
+
* agentfootprint.context.budget_pressure — one per over-budget visit
|
|
27
|
+
* agentfootprint.cost.tick — when a pricingTable is set: the
|
|
28
|
+
* summarizer's call is a real
|
|
29
|
+
* billed call and counts
|
|
30
|
+
*
|
|
31
|
+
* The summarizer call is deliberately NOT bracketed with
|
|
32
|
+
* `stream.llm_start` / `llm_end`: those carry an `iteration` and every
|
|
33
|
+
* consumer pairs them by it, so a second bracket inside one iteration would
|
|
34
|
+
* corrupt the pairing. Its cost rides the cost channel and its tokens are
|
|
35
|
+
* recorded in the commit instead.
|
|
36
|
+
*/
|
|
37
|
+
import type { TypedScope } from 'footprintjs';
|
|
38
|
+
import type { PricingTable } from '../../../adapters/types.js';
|
|
39
|
+
import type { CompactionMeterHandle } from '../../../recorders/core/CompactionMeter.js';
|
|
40
|
+
import type { ResolvedCompaction } from '../compaction/types.js';
|
|
41
|
+
import type { AgentState } from '../types.js';
|
|
42
|
+
export interface CompactStageDeps {
|
|
43
|
+
/** Resolved `.compaction()` config (defaults already applied). */
|
|
44
|
+
readonly config: ResolvedCompaction;
|
|
45
|
+
/** The meter this stage reads. Attached inline by `Agent.createExecutor`. */
|
|
46
|
+
readonly meter: CompactionMeterHandle;
|
|
47
|
+
/** The agent's own model — the summarizer's default. */
|
|
48
|
+
readonly defaultModel: string;
|
|
49
|
+
/** The MAIN provider's name, for the unmeasurable refusal. */
|
|
50
|
+
readonly providerName: string;
|
|
51
|
+
/** Optional pricing adapter, so a summarizer call is costed like any other. */
|
|
52
|
+
readonly pricingTable?: PricingTable;
|
|
53
|
+
/** Optional cumulative USD cap per run. */
|
|
54
|
+
readonly costBudget?: number;
|
|
55
|
+
/** Injectable clock (tests pin survivalMs). */
|
|
56
|
+
readonly now?: () => number;
|
|
57
|
+
}
|
|
58
|
+
/** Build the compaction stage function. */
|
|
59
|
+
export declare function buildCompactStage(deps: CompactStageDeps): (scope: TypedScope<AgentState>) => Promise<void>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compact — the loop-head stage that keeps the window inside budget.
|
|
3
|
+
*
|
|
4
|
+
* Reads the last call's ADAPTER-REPORTED input tokens from the compaction
|
|
5
|
+
* meter and, when they exceed `thresholdTokens`, hands the window to a window
|
|
6
|
+
* strategy. Runs once per ReAct iteration boundary, as the loop target, so
|
|
7
|
+
* everything downstream in the turn — the injection engine's triggers, all
|
|
8
|
+
* three context slots, and the wire itself — sees ONE window. No component
|
|
9
|
+
* gets a different past than the model does.
|
|
10
|
+
*
|
|
11
|
+
* The law: **it edits the window, never the ledger.** The turns a fold removes
|
|
12
|
+
* were committed by `seed#0` / `tool-calls#N` before this stage ran and stay
|
|
13
|
+
* in those bundles byte-identical; footprintjs's commit log is append-only, so
|
|
14
|
+
* a fold cannot erase history even in principle. What it can do is stop
|
|
15
|
+
* re-sending it — and say so, in its own commit, naming every runtimeStageId
|
|
16
|
+
* it folded. A compacted run is still a provable run.
|
|
17
|
+
*
|
|
18
|
+
* This file is deliberately all WIRING. The decision — what the window should
|
|
19
|
+
* become — lives behind `WindowStrategy` (compaction/strategy.ts, internal),
|
|
20
|
+
* so the duty to record, emit and cost what happened stays in exactly one
|
|
21
|
+
* place instead of being re-implemented per strategy. `.compaction()` is the
|
|
22
|
+
* only strategy that ships.
|
|
23
|
+
*
|
|
24
|
+
* Emits (existing vocabulary only — no new event types):
|
|
25
|
+
* agentfootprint.context.evicted — one per message leaving the window
|
|
26
|
+
* agentfootprint.context.budget_pressure — one per over-budget visit
|
|
27
|
+
* agentfootprint.cost.tick — when a pricingTable is set: the
|
|
28
|
+
* summarizer's call is a real
|
|
29
|
+
* billed call and counts
|
|
30
|
+
*
|
|
31
|
+
* The summarizer call is deliberately NOT bracketed with
|
|
32
|
+
* `stream.llm_start` / `llm_end`: those carry an `iteration` and every
|
|
33
|
+
* consumer pairs them by it, so a second bracket inside one iteration would
|
|
34
|
+
* corrupt the pairing. Its cost rides the cost channel and its tokens are
|
|
35
|
+
* recorded in the commit instead.
|
|
36
|
+
*/
|
|
37
|
+
import { typedEmit } from '../../../recorders/core/typedEmit.js';
|
|
38
|
+
import { fnv1a } from '../../slots/helpers.js';
|
|
39
|
+
import { emitCostTick } from '../../cost.js';
|
|
40
|
+
import { CompactionUnmeasurableError } from '../compaction/errors.js';
|
|
41
|
+
import { summarizeOldestStrategy } from '../compaction/strategy.js';
|
|
42
|
+
import { answeredCallIds, segmentTurns } from '../compaction/turns.js';
|
|
43
|
+
/** Build the compaction stage function. */
|
|
44
|
+
export function buildCompactStage(deps) {
|
|
45
|
+
const { config, meter } = deps;
|
|
46
|
+
const now = deps.now ?? (() => Date.now());
|
|
47
|
+
const strategy = summarizeOldestStrategy(config, deps.defaultModel);
|
|
48
|
+
// Dedup latch for the strategy's warning: a summarizer that is down is down
|
|
49
|
+
// for the whole run, and one warning is a warning while ten is noise.
|
|
50
|
+
let warned = false;
|
|
51
|
+
return async (scope) => {
|
|
52
|
+
const metered = meter.lastCall();
|
|
53
|
+
// Iteration 1: nothing has been sent, so nothing has been counted. A
|
|
54
|
+
// compactor that acted here would be guessing, which is the one thing
|
|
55
|
+
// this feature refuses to do.
|
|
56
|
+
if (metered === undefined)
|
|
57
|
+
return;
|
|
58
|
+
if (metered.input === 0 && metered.output === 0) {
|
|
59
|
+
throw new CompactionUnmeasurableError(deps.providerName);
|
|
60
|
+
}
|
|
61
|
+
if (metered.input <= config.thresholdTokens)
|
|
62
|
+
return;
|
|
63
|
+
// ── Over budget. Ask the strategy what the window should become. ──
|
|
64
|
+
const history = (scope.history ?? []).slice();
|
|
65
|
+
const pausedToolCallId = scope.pausedToolCallId;
|
|
66
|
+
const foldability = {
|
|
67
|
+
answeredCallIds: answeredCallIds(history),
|
|
68
|
+
...(pausedToolCallId !== undefined && { pausedToolCallId }),
|
|
69
|
+
...(scope.pausedCheckIn === true && { pausedCheckIn: true }),
|
|
70
|
+
};
|
|
71
|
+
const result = await strategy.plan({
|
|
72
|
+
history,
|
|
73
|
+
turns: segmentTurns(history),
|
|
74
|
+
origins: meter.origins(),
|
|
75
|
+
measuredTokens: metered.input,
|
|
76
|
+
foldability,
|
|
77
|
+
iteration: scope.iteration ?? 1,
|
|
78
|
+
signal: scope.$getEnv?.()?.signal,
|
|
79
|
+
now,
|
|
80
|
+
});
|
|
81
|
+
// ── Apply. The stage owns every side effect. ─────────────────────
|
|
82
|
+
if (result.window !== undefined && result.rebase !== undefined) {
|
|
83
|
+
// Tell the meter before writing: the fold is the one window shape it
|
|
84
|
+
// cannot infer (the set trap JSON-round-trips every write).
|
|
85
|
+
meter.rebaseForFold(result.rebase.headCount, result.rebase.keptTailCount, now());
|
|
86
|
+
scope.history = result.window;
|
|
87
|
+
}
|
|
88
|
+
const prior = scope.compactions ?? [];
|
|
89
|
+
scope.compactions = [...prior, result.record];
|
|
90
|
+
// One eviction per message that left, in the slot vocabulary consumers
|
|
91
|
+
// already subscribe to. The hash uses the SAME formula the messages slot
|
|
92
|
+
// used to report the piece as injected, so evicted and injected refer to
|
|
93
|
+
// the same piece by the same name.
|
|
94
|
+
for (const eviction of result.evictions) {
|
|
95
|
+
const msg = history[eviction.index];
|
|
96
|
+
if (msg === undefined)
|
|
97
|
+
continue;
|
|
98
|
+
typedEmit(scope, 'agentfootprint.context.evicted', {
|
|
99
|
+
slot: 'messages',
|
|
100
|
+
contentHash: fnv1a(`${msg.role}:${eviction.index}:${msg.content}`),
|
|
101
|
+
reason: 'budget',
|
|
102
|
+
survivalMs: eviction.survivalMs,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// `capTokens` / `projectedTokens` are the historical names on
|
|
106
|
+
// `BudgetPressureRecord`; the slots measure them in chars, and this
|
|
107
|
+
// measures them in the tokens the decision was actually made on.
|
|
108
|
+
typedEmit(scope, 'agentfootprint.context.budget_pressure', {
|
|
109
|
+
slot: 'messages',
|
|
110
|
+
capTokens: config.thresholdTokens,
|
|
111
|
+
projectedTokens: metered.input,
|
|
112
|
+
overflowBy: Math.max(0, metered.input - config.thresholdTokens),
|
|
113
|
+
planAction: result.planAction,
|
|
114
|
+
});
|
|
115
|
+
// A summarizer call is real money. It counts against the same budget.
|
|
116
|
+
if (result.spend !== undefined) {
|
|
117
|
+
emitCostTick(scope, deps.pricingTable, deps.costBudget, result.spend.model, result.spend.usage);
|
|
118
|
+
}
|
|
119
|
+
if (result.warning !== undefined && !warned) {
|
|
120
|
+
warned = true;
|
|
121
|
+
// eslint-disable-next-line no-console
|
|
122
|
+
console.warn(`[agentfootprint compaction] ${result.warning}`);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=compact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compact.js","sourceRoot":"","sources":["../../../../../src/core/agent/stages/compact.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,6 +18,7 @@ import type { CredentialProvider } from '../../identity/types.js';
|
|
|
18
18
|
import type { ToolArgValidationMode } from './toolArgsValidation.js';
|
|
19
19
|
import type { ThinkingBlock } from '../../thinking/types.js';
|
|
20
20
|
import type { ReliabilityScope } from '../../reliability/types.js';
|
|
21
|
+
import type { CompactionRecord } from './compaction/types.js';
|
|
21
22
|
/**
|
|
22
23
|
* Dials for the deferred observer queue (RFC-001) — only meaningful with
|
|
23
24
|
* `observerDelivery: 'deferred'` (passing them without it throws at
|
|
@@ -407,6 +408,16 @@ export interface AgentState {
|
|
|
407
408
|
/** The base system prompt `.configure()` resolved for THIS run, replacing
|
|
408
409
|
* `.system(...)`. Same rule: absent unless a resolver returned one. */
|
|
409
410
|
resolvedInstructions?: string;
|
|
411
|
+
/** One record per OVER-BUDGET visit to the compaction stage — including the
|
|
412
|
+
* visits that folded nothing, which are the interesting ones. Written only
|
|
413
|
+
* by an agent built with `.compaction()`; absent otherwise, so an agent
|
|
414
|
+
* without it commits exactly the keys it always did.
|
|
415
|
+
*
|
|
416
|
+
* This array is the fold's half of the law: the window shrank, and the
|
|
417
|
+
* ledger says which stages' messages left it, how many, and what the last
|
|
418
|
+
* call actually measured. The messages themselves are untouched in the
|
|
419
|
+
* commit bundles that wrote them. */
|
|
420
|
+
compactions?: readonly CompactionRecord[];
|
|
410
421
|
/** Set when a `PermissionChecker` returns `{ result: 'halt', ... }`.
|
|
411
422
|
* `Agent.run()` reads these at the API boundary and throws a typed
|
|
412
423
|
* `PolicyHaltError` carrying the same context — the chart $break's
|