@buoy-gg/agent-core 7.0.40 → 7.0.42
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/lib/commonjs/blocks/receipts.js +1 -761
- package/lib/commonjs/blocks/runId.js +1 -31
- package/lib/commonjs/blocks/types.js +1 -140
- package/lib/commonjs/blocks/uiTool.js +6 -1405
- package/lib/commonjs/catalog/catalog.g.js +5 -4207
- package/lib/commonjs/catalog/catalog.source.json +656 -12
- package/lib/commonjs/catalog/catalog.types.g.js +1 -44
- package/lib/commonjs/catalog/normalizeParams.js +2 -332
- package/lib/commonjs/catalog/signature.js +1 -68
- package/lib/commonjs/catalog/snapshotReads.js +1 -263
- package/lib/commonjs/catalog/toProviderTools.js +2 -145
- package/lib/commonjs/catalog/validateParams.js +1 -129
- package/lib/commonjs/context/buildContextPack.js +1 -489
- package/lib/commonjs/effects/digest.js +1 -34
- package/lib/commonjs/effects/ledger.js +1 -325
- package/lib/commonjs/engine/askGate.js +1 -287
- package/lib/commonjs/engine/effectFor.js +1 -176
- package/lib/commonjs/engine/evidence.js +3 -111
- package/lib/commonjs/engine/historyBudget.js +1 -363
- package/lib/commonjs/engine/retrieve.js +1 -214
- package/lib/commonjs/engine/runAgentTurn.js +8 -1803
- package/lib/commonjs/engine/systemPrompt.js +54 -163
- package/lib/commonjs/engine/textToolCalls.js +1 -276
- package/lib/commonjs/engine/tokenCalibration.js +1 -81
- package/lib/commonjs/engine/verify.js +4 -297
- package/lib/commonjs/index.js +1 -384
- package/lib/commonjs/policy/labels.js +1 -319
- package/lib/commonjs/policy/policy.js +1 -148
- package/lib/commonjs/policy/redact.js +1 -172
- package/lib/commonjs/providers/anthropic.js +3 -445
- package/lib/commonjs/providers/openai.js +4 -324
- package/lib/commonjs/providers/problem.js +1 -98
- package/lib/commonjs/providers/sse.js +1 -240
- package/lib/commonjs/providers/streamTimer.js +1 -123
- package/lib/commonjs/providers/transport.js +1 -123
- package/lib/commonjs/providers/types.js +1 -6
- package/lib/commonjs/providers/xhrStream.js +1 -212
- package/lib/commonjs/realNow.js +1 -0
- package/lib/commonjs/session.js +1 -393
- package/lib/commonjs/types.js +0 -1
- package/lib/module/blocks/receipts.js +1 -755
- package/lib/module/blocks/runId.js +1 -26
- package/lib/module/blocks/types.js +1 -134
- package/lib/module/blocks/uiTool.js +6 -1396
- package/lib/module/catalog/catalog.g.js +5 -4203
- package/lib/module/catalog/catalog.source.json +656 -12
- package/lib/module/catalog/catalog.types.g.js +1 -40
- package/lib/module/catalog/normalizeParams.js +2 -326
- package/lib/module/catalog/signature.js +1 -63
- package/lib/module/catalog/snapshotReads.js +1 -259
- package/lib/module/catalog/toProviderTools.js +2 -139
- package/lib/module/catalog/validateParams.js +1 -124
- package/lib/module/context/buildContextPack.js +1 -485
- package/lib/module/effects/digest.js +1 -29
- package/lib/module/effects/ledger.js +1 -319
- package/lib/module/engine/askGate.js +1 -280
- package/lib/module/engine/effectFor.js +1 -173
- package/lib/module/engine/evidence.js +3 -105
- package/lib/module/engine/historyBudget.js +1 -355
- package/lib/module/engine/retrieve.js +1 -210
- package/lib/module/engine/runAgentTurn.js +8 -1794
- package/lib/module/engine/systemPrompt.js +54 -157
- package/lib/module/engine/textToolCalls.js +1 -270
- package/lib/module/engine/tokenCalibration.js +1 -75
- package/lib/module/engine/verify.js +4 -289
- package/lib/module/index.js +1 -35
- package/lib/module/policy/labels.js +1 -312
- package/lib/module/policy/policy.js +1 -142
- package/lib/module/policy/redact.js +1 -165
- package/lib/module/providers/anthropic.js +3 -441
- package/lib/module/providers/openai.js +4 -320
- package/lib/module/providers/problem.js +1 -92
- package/lib/module/providers/sse.js +1 -231
- package/lib/module/providers/streamTimer.js +1 -118
- package/lib/module/providers/transport.js +1 -119
- package/lib/module/providers/types.js +1 -4
- package/lib/module/providers/xhrStream.js +1 -207
- package/lib/module/realNow.js +1 -0
- package/lib/module/session.js +1 -369
- package/lib/module/types.js +0 -1
- package/lib/typescript/catalog/catalog.g.d.ts +3 -3
- package/lib/typescript/catalog/catalog.types.g.d.ts +7 -4
- package/lib/typescript/effects/ledger.d.ts +16 -1
- package/lib/typescript/providers/problem.d.ts +0 -20
- package/lib/typescript/providers/streamTimer.d.ts +0 -24
- package/lib/typescript/realNow.d.ts +18 -0
- package/lib/web/index.mjs +152 -0
- package/package.json +24 -3
- package/lib/commonjs/blocks/receipts.js.map +0 -1
- package/lib/commonjs/blocks/runId.js.map +0 -1
- package/lib/commonjs/blocks/types.js.map +0 -1
- package/lib/commonjs/blocks/uiTool.js.map +0 -1
- package/lib/commonjs/catalog/catalog.g.js.map +0 -1
- package/lib/commonjs/catalog/catalog.types.g.js.map +0 -1
- package/lib/commonjs/catalog/normalizeParams.js.map +0 -1
- package/lib/commonjs/catalog/signature.js.map +0 -1
- package/lib/commonjs/catalog/snapshotReads.js.map +0 -1
- package/lib/commonjs/catalog/toProviderTools.js.map +0 -1
- package/lib/commonjs/catalog/validateParams.js.map +0 -1
- package/lib/commonjs/context/buildContextPack.js.map +0 -1
- package/lib/commonjs/effects/digest.js.map +0 -1
- package/lib/commonjs/effects/ledger.js.map +0 -1
- package/lib/commonjs/engine/askGate.js.map +0 -1
- package/lib/commonjs/engine/effectFor.js.map +0 -1
- package/lib/commonjs/engine/evidence.js.map +0 -1
- package/lib/commonjs/engine/historyBudget.js.map +0 -1
- package/lib/commonjs/engine/retrieve.js.map +0 -1
- package/lib/commonjs/engine/runAgentTurn.js.map +0 -1
- package/lib/commonjs/engine/systemPrompt.js.map +0 -1
- package/lib/commonjs/engine/textToolCalls.js.map +0 -1
- package/lib/commonjs/engine/tokenCalibration.js.map +0 -1
- package/lib/commonjs/engine/verify.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/policy/labels.js.map +0 -1
- package/lib/commonjs/policy/policy.js.map +0 -1
- package/lib/commonjs/policy/redact.js.map +0 -1
- package/lib/commonjs/providers/anthropic.js.map +0 -1
- package/lib/commonjs/providers/openai.js.map +0 -1
- package/lib/commonjs/providers/problem.js.map +0 -1
- package/lib/commonjs/providers/sse.js.map +0 -1
- package/lib/commonjs/providers/streamTimer.js.map +0 -1
- package/lib/commonjs/providers/transport.js.map +0 -1
- package/lib/commonjs/providers/types.js.map +0 -1
- package/lib/commonjs/providers/xhrStream.js.map +0 -1
- package/lib/commonjs/session.js.map +0 -1
- package/lib/commonjs/types.js.map +0 -1
- package/lib/module/blocks/receipts.js.map +0 -1
- package/lib/module/blocks/runId.js.map +0 -1
- package/lib/module/blocks/types.js.map +0 -1
- package/lib/module/blocks/uiTool.js.map +0 -1
- package/lib/module/catalog/catalog.g.js.map +0 -1
- package/lib/module/catalog/catalog.types.g.js.map +0 -1
- package/lib/module/catalog/normalizeParams.js.map +0 -1
- package/lib/module/catalog/signature.js.map +0 -1
- package/lib/module/catalog/snapshotReads.js.map +0 -1
- package/lib/module/catalog/toProviderTools.js.map +0 -1
- package/lib/module/catalog/validateParams.js.map +0 -1
- package/lib/module/context/buildContextPack.js.map +0 -1
- package/lib/module/effects/digest.js.map +0 -1
- package/lib/module/effects/ledger.js.map +0 -1
- package/lib/module/engine/askGate.js.map +0 -1
- package/lib/module/engine/effectFor.js.map +0 -1
- package/lib/module/engine/evidence.js.map +0 -1
- package/lib/module/engine/historyBudget.js.map +0 -1
- package/lib/module/engine/retrieve.js.map +0 -1
- package/lib/module/engine/runAgentTurn.js.map +0 -1
- package/lib/module/engine/systemPrompt.js.map +0 -1
- package/lib/module/engine/textToolCalls.js.map +0 -1
- package/lib/module/engine/tokenCalibration.js.map +0 -1
- package/lib/module/engine/verify.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/policy/labels.js.map +0 -1
- package/lib/module/policy/policy.js.map +0 -1
- package/lib/module/policy/redact.js.map +0 -1
- package/lib/module/providers/anthropic.js.map +0 -1
- package/lib/module/providers/openai.js.map +0 -1
- package/lib/module/providers/problem.js.map +0 -1
- package/lib/module/providers/sse.js.map +0 -1
- package/lib/module/providers/streamTimer.js.map +0 -1
- package/lib/module/providers/transport.js.map +0 -1
- package/lib/module/providers/types.js.map +0 -1
- package/lib/module/providers/xhrStream.js.map +0 -1
- package/lib/module/session.js.map +0 -1
- package/lib/module/types.js.map +0 -1
- package/lib/typescript/blocks/receipts.d.ts.map +0 -1
- package/lib/typescript/blocks/runId.d.ts.map +0 -1
- package/lib/typescript/blocks/types.d.ts.map +0 -1
- package/lib/typescript/blocks/uiTool.d.ts.map +0 -1
- package/lib/typescript/catalog/catalog.g.d.ts.map +0 -1
- package/lib/typescript/catalog/catalog.types.g.d.ts.map +0 -1
- package/lib/typescript/catalog/normalizeParams.d.ts.map +0 -1
- package/lib/typescript/catalog/signature.d.ts.map +0 -1
- package/lib/typescript/catalog/snapshotReads.d.ts.map +0 -1
- package/lib/typescript/catalog/toProviderTools.d.ts.map +0 -1
- package/lib/typescript/catalog/validateParams.d.ts.map +0 -1
- package/lib/typescript/context/buildContextPack.d.ts.map +0 -1
- package/lib/typescript/effects/digest.d.ts.map +0 -1
- package/lib/typescript/effects/ledger.d.ts.map +0 -1
- package/lib/typescript/engine/askGate.d.ts.map +0 -1
- package/lib/typescript/engine/effectFor.d.ts.map +0 -1
- package/lib/typescript/engine/evidence.d.ts.map +0 -1
- package/lib/typescript/engine/historyBudget.d.ts.map +0 -1
- package/lib/typescript/engine/retrieve.d.ts.map +0 -1
- package/lib/typescript/engine/runAgentTurn.d.ts.map +0 -1
- package/lib/typescript/engine/systemPrompt.d.ts.map +0 -1
- package/lib/typescript/engine/textToolCalls.d.ts.map +0 -1
- package/lib/typescript/engine/tokenCalibration.d.ts.map +0 -1
- package/lib/typescript/engine/verify.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/policy/labels.d.ts.map +0 -1
- package/lib/typescript/policy/policy.d.ts.map +0 -1
- package/lib/typescript/policy/redact.d.ts.map +0 -1
- package/lib/typescript/providers/anthropic.d.ts.map +0 -1
- package/lib/typescript/providers/openai.d.ts.map +0 -1
- package/lib/typescript/providers/problem.d.ts.map +0 -1
- package/lib/typescript/providers/sse.d.ts.map +0 -1
- package/lib/typescript/providers/streamTimer.d.ts.map +0 -1
- package/lib/typescript/providers/transport.d.ts.map +0 -1
- package/lib/typescript/providers/types.d.ts.map +0 -1
- package/lib/typescript/providers/xhrStream.d.ts.map +0 -1
- package/lib/typescript/session.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
|
@@ -1,176 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.effectFor = effectFor;
|
|
7
|
-
var _policy = require("../policy/policy");
|
|
8
|
-
var _digest = require("../effects/digest");
|
|
9
|
-
/**
|
|
10
|
-
* Turn a completed mutation into a ledger entry.
|
|
11
|
-
*
|
|
12
|
-
* The shape is Scenarios' `extractEffect`, but the classification here is much
|
|
13
|
-
* more conservative, because auditing the catalog's undo claims found almost
|
|
14
|
-
* all of them unsound: the information needed to reverse a call usually does
|
|
15
|
-
* not survive the call. So an effect is only marked reversible when THIS
|
|
16
|
-
* module can point at the exact input the reversal needs.
|
|
17
|
-
*
|
|
18
|
-
* Everything else becomes `state-write` (changed something, no automatic
|
|
19
|
-
* inverse) or `transient` (one-shot). Both are shown to the user as such. A
|
|
20
|
-
* banner that says "4 changes" and then reverses one of them is worse than a
|
|
21
|
-
* banner that admits three of them are permanent.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The largest prior value an undo will hold. The ledger is persisted with the
|
|
26
|
-
* transcript under a 256KB cap; a payload past this is recorded as a visible,
|
|
27
|
-
* honestly-permanent change instead of silently evicting the conversation.
|
|
28
|
-
*/
|
|
29
|
-
const MAX_UNDO_JSON_CHARS = 64_000;
|
|
30
|
-
|
|
31
|
-
/** The `getQueryData` read the engine takes on both sides of a cache write. */
|
|
32
|
-
|
|
33
|
-
const cacheRead = v => v && typeof v === "object" ? v : undefined;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* How each query change is actually put back, named in the row so the bar is
|
|
37
|
-
* useful rather than merely honest. A cache edit's real inverse is a refetch:
|
|
38
|
-
* the tool's own description says a cache edit "lasts until the next
|
|
39
|
-
* successful refetch".
|
|
40
|
-
*/
|
|
41
|
-
const QUERY_RESTORES = {
|
|
42
|
-
setQueryData: "query.invalidate",
|
|
43
|
-
triggerError: "query.restoreError",
|
|
44
|
-
triggerLoading: "query.restoreLoading",
|
|
45
|
-
setOnline: "query.setOnline"
|
|
46
|
-
};
|
|
47
|
-
function effectFor(toolId, descriptor, params, result, before, /** The query cache read before and after a `setQueryData` — see runAgentTurn. */
|
|
48
|
-
cache) {
|
|
49
|
-
const label = (0, _policy.describeCall)(toolId, descriptor, params);
|
|
50
|
-
const rec = result ?? {};
|
|
51
|
-
|
|
52
|
-
// A network override rule. Reversible ONLY when we created it: `upsert` with
|
|
53
|
-
// an id that already exists UPDATES that rule in place, so deleting it would
|
|
54
|
-
// destroy a rule the user had set up themselves rather than undo our change.
|
|
55
|
-
// The model supplying no id is the create case.
|
|
56
|
-
if (toolId === "network" && descriptor.action === "upsertOverrideRule") {
|
|
57
|
-
const rule = rec.rule;
|
|
58
|
-
const ruleId = rule?.id ?? rec.id;
|
|
59
|
-
const wasCreate = params.id === undefined;
|
|
60
|
-
if (ruleId && wasCreate) return {
|
|
61
|
-
kind: "network-override",
|
|
62
|
-
ruleId,
|
|
63
|
-
label
|
|
64
|
-
};
|
|
65
|
-
return {
|
|
66
|
-
kind: "state-write",
|
|
67
|
-
toolId,
|
|
68
|
-
action: descriptor.action,
|
|
69
|
-
label
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Impersonation. `stopImpersonation` restores the identity but ALSO re-runs
|
|
74
|
-
// the data nuke — it clears react-query, redux, AsyncStorage and MMKV per
|
|
75
|
-
// the app's dataNukeSettings. So this is reversible in the sense that
|
|
76
|
-
// matters (the user stops being someone else) and the label has to say the
|
|
77
|
-
// rest out loud, because caches do not come back.
|
|
78
|
-
if (toolId === "impersonate" && descriptor.action === "startImpersonation") {
|
|
79
|
-
return {
|
|
80
|
-
kind: "impersonation",
|
|
81
|
-
label: `${label} — stopping also clears cached app data`
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Storage, reversible because we read the old value first (see captureBefore).
|
|
86
|
-
// `before` is undefined when the pre-read could not be made — an MMKV write
|
|
87
|
-
// with no instance, a secure key behind biometrics — and that must fall
|
|
88
|
-
// through to a one-off rather than claim an undo it cannot perform.
|
|
89
|
-
if (toolId === "storage" && typeof params.key === "string" && before) {
|
|
90
|
-
const isMmkv = descriptor.action.startsWith("mmkv.");
|
|
91
|
-
const writeAction = isMmkv ? "mmkv.set" : "async.setItem";
|
|
92
|
-
return {
|
|
93
|
-
kind: "storage-write",
|
|
94
|
-
toolId,
|
|
95
|
-
action: writeAction,
|
|
96
|
-
key: params.key,
|
|
97
|
-
instanceId: isMmkv && typeof params.instanceId === "string" ? params.instanceId : undefined,
|
|
98
|
-
before: before.had ? before.value : undefined,
|
|
99
|
-
wrote: typeof params.value === "string" ? params.value : undefined,
|
|
100
|
-
removeAction: isMmkv ? "mmkv.remove" : "async.removeItem",
|
|
101
|
-
label
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* A cache edit is a CHANGE, not a one-shot.
|
|
107
|
-
*
|
|
108
|
-
* `setQueryData` is the headline write of the whole product — "THE way to
|
|
109
|
-
* change what a server-backed screen shows" — and it fell through to
|
|
110
|
-
* `transient` here, which the changes bar deliberately does not count. So
|
|
111
|
-
* the sheet said "Anything it changes shows up in a bar at the top, and you
|
|
112
|
-
* can undo it" and then changed the screen in front of you with no row, no
|
|
113
|
-
* count and no undo. Same for `triggerError` and `triggerLoading`: the app
|
|
114
|
-
* is left showing a failure state that nothing on screen admits to putting
|
|
115
|
-
* there, which is exactly how a QA tester files a bug against a state Buoy
|
|
116
|
-
* created.
|
|
117
|
-
*
|
|
118
|
-
* `setQueryData` is now a real `query-write` above, because the engine
|
|
119
|
-
* reads the query's data before the write (captureStoreState) — the same
|
|
120
|
-
* pre-read that makes storage reversible. The rest — triggerError,
|
|
121
|
-
* triggerLoading, setOnline, a setQueryData whose pre-read failed — stay
|
|
122
|
-
* `state-write` (visible, honestly permanent): this module only claims an
|
|
123
|
-
* undo it can actually perform, and the label carries the real revert so
|
|
124
|
-
* the row is still useful.
|
|
125
|
-
*/
|
|
126
|
-
if (toolId === "query" && descriptor.action === "setQueryData") {
|
|
127
|
-
const prior = cacheRead(cache?.before);
|
|
128
|
-
const queryHash = typeof params.queryHash === "string" ? params.queryHash : prior?.queryHash;
|
|
129
|
-
if (prior?.found === true && typeof queryHash === "string") {
|
|
130
|
-
const had = prior.hasData !== false && prior.data !== undefined;
|
|
131
|
-
const beforeData = had ? prior.data : undefined;
|
|
132
|
-
if ((0, _digest.jsonLength)(beforeData) <= MAX_UNDO_JSON_CHARS) {
|
|
133
|
-
const after = cacheRead(cache?.after);
|
|
134
|
-
const wroteDigest = after?.found === true && after.hasData !== false ? (0, _digest.digestOf)(after.data) : undefined;
|
|
135
|
-
return {
|
|
136
|
-
kind: "query-write",
|
|
137
|
-
queryHash,
|
|
138
|
-
before: beforeData,
|
|
139
|
-
wroteDigest,
|
|
140
|
-
label
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
kind: "state-write",
|
|
145
|
-
toolId,
|
|
146
|
-
action: descriptor.action,
|
|
147
|
-
label: `${label} — prior value too large to hold for undo; undo with query.invalidate`
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (toolId === "query" && descriptor.effect !== "read") {
|
|
152
|
-
const restores = QUERY_RESTORES[descriptor.action];
|
|
153
|
-
return {
|
|
154
|
-
kind: "state-write",
|
|
155
|
-
toolId,
|
|
156
|
-
action: descriptor.action,
|
|
157
|
-
label: restores ? `${label} — undo with ${restores}` : label
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Wipes with no captured prior state are permanent. Say so rather than
|
|
162
|
-
// filing them under something Undo appears to cover.
|
|
163
|
-
if (descriptor.effect === "destructive") {
|
|
164
|
-
return {
|
|
165
|
-
kind: "state-write",
|
|
166
|
-
toolId,
|
|
167
|
-
action: descriptor.action,
|
|
168
|
-
label
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
kind: "transient",
|
|
173
|
-
label
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
//# sourceMappingURL=effectFor.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.effectFor=effectFor;var _policy=require("../policy/policy");var _digest=require("../effects/digest");const MAX_UNDO_JSON_CHARS=64e3;const cacheRead=n=>n&&typeof n==="object"?n:void 0;const QUERY_RESTORES={setQueryData:"query.invalidate",triggerError:"query.restoreError",triggerLoading:"query.restoreLoading",setOnline:"query.setOnline"};function effectFor(n,t,i,l,u,c){const r=(0,_policy.describeCall)(n,t,i);const d=l??{};if(n==="network"&&t.action==="upsertOverrideRule"){const e=d.rule;const a=e?.id??d.id;const s=i.id===void 0;if(a&&s)return{kind:"network-override",ruleId:a,label:r};return{kind:"state-write",toolId:n,action:t.action,label:r}}if(n==="impersonate"&&t.action==="startImpersonation"){return{kind:"impersonation",label:`${r} \u2014 stopping also clears cached app data`}}if(n==="storage"&&typeof i.key==="string"&&u){const e=t.action.startsWith("mmkv.");const a=e?"mmkv.set":"async.setItem";return{kind:"storage-write",toolId:n,action:a,key:i.key,instanceId:e&&typeof i.instanceId==="string"?i.instanceId:void 0,before:u.had?u.value:void 0,wrote:typeof i.value==="string"?i.value:void 0,removeAction:e?"mmkv.remove":"async.removeItem",label:r}}if(n==="query"&&t.action==="setQueryData"){const e=cacheRead(c?.before);const a=typeof i.queryHash==="string"?i.queryHash:e?.queryHash;if(e?.found===true&&typeof a==="string"){const s=e.hasData!==false&&e.data!==void 0;const f=s?e.data:void 0;if((0,_digest.jsonLength)(f)<=MAX_UNDO_JSON_CHARS){const o=cacheRead(c?.after);const y=o?.found===true&&o.hasData!==false?(0,_digest.digestOf)(o.data):void 0;return{kind:"query-write",queryHash:a,before:f,wroteDigest:y,label:r}}return{kind:"state-write",toolId:n,action:t.action,label:`${r} \u2014 prior value too large to hold for undo; undo with query.invalidate`}}}if(n==="query"&&typeof i.queryHash==="string"){const e=t.action==="triggerLoading"?"restoreLoading":t.action==="triggerError"?"restoreError":void 0;if(e){return{kind:"inverse-call",toolId:n,action:e,params:{queryHash:i.queryHash},label:r}}}if(n==="query"&&["refetch","invalidate","restoreLoading"].includes(t.action)){return{kind:"transient",label:r}}if(n==="network"&&(t.action==="setOverrideRuleEnabled"||t.action==="setOverridesEnabled")&&u?.had&&(u.value==="true"||u.value==="false")){const e=u.value==="true";const a=i.enabled===true;if(e===a)return void 0;const s=t.action==="setOverrideRuleEnabled"?{id:i.id,enabled:e}:{enabled:e};return{kind:"inverse-call",toolId:n,action:t.action,params:s,label:r}}if(n==="query"&&t.effect!=="read"){const e=QUERY_RESTORES[t.action];return{kind:"state-write",toolId:n,action:t.action,label:e?`${r} \u2014 undo with ${e}`:r}}if(t.effect==="destructive"){return{kind:"state-write",toolId:n,action:t.action,label:r}}return{kind:"transient",label:r}}
|
|
@@ -1,113 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.EvidenceStore=void 0;exports.compressedMarker=compressedMarker;exports.truncationMarker=truncationMarker;const DEFAULT_MAX_CHARS=4e6;const DEFAULT_MAX_ENTRIES=200;class EvidenceStore{entries=new Map;evictedRefs=new Set;chars=0;seq=0;constructor(e={}){this.maxChars=e.maxChars??DEFAULT_MAX_CHARS;this.maxEntries=e.maxEntries??DEFAULT_MAX_ENTRIES}stash(e){const t=`ev_${++this.seq}`;const s={...e,ref:t};this.entries.set(t,s);this.chars+=s.text.length;this.evict();return t}get(e){return this.entries.get(e)}wasEvicted(e){return this.evictedRefs.has(e)}get size(){return this.entries.size}get totalChars(){return this.chars}clear(){this.entries.clear();this.evictedRefs.clear();this.chars=0}evict(){for(const[e,t]of this.entries){const s=this.chars>this.maxChars||this.entries.size>this.maxEntries;if(!s||this.entries.size===1)return;this.entries.delete(e);this.evictedRefs.add(e);this.chars-=t.text.length}}}exports.EvidenceStore=EvidenceStore;const RETRIEVE_HINT="ask-buoy.retrieve";function truncationMarker(r,e){const t=r.toLocaleString("en-US");if(!e)return`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.EvidenceStore = void 0;
|
|
7
|
-
exports.compressedMarker = compressedMarker;
|
|
8
|
-
exports.truncationMarker = truncationMarker;
|
|
9
|
-
/**
|
|
10
|
-
* The evidence store: every tool result this conversation produced, kept in
|
|
11
|
-
* full so the model can go back to it.
|
|
12
|
-
*
|
|
13
|
-
* Two markers used to point nowhere. A result over 24 000 characters was cut
|
|
14
|
-
* with "ask for a narrower slice" — and no tool offers one, so the bank has
|
|
15
|
-
* cases tagged `capability` (Pokémon `stats` sit at offset ~180 000 of a
|
|
16
|
-
* 400 000-character detail; unreadable by design). And a result compressed
|
|
17
|
-
* out of memory said "ask again" — a re-dispatch returns the CURRENT value,
|
|
18
|
-
* not what the model saw before it wrote, which is the one it needs when a
|
|
19
|
-
* write is refused and it has to say what it changed FROM.
|
|
20
|
-
*
|
|
21
|
-
* So the full text is kept here, keyed by a short ref the markers carry, and
|
|
22
|
-
* `ask-buoy.retrieve` reads any part of it back: a path, a substring, a
|
|
23
|
-
* window. Borrowed from Strands' ContextOffloader (`retrieve_offloaded_content`
|
|
24
|
-
* with pattern / line_range), minus the storage layer — this is in-memory and
|
|
25
|
-
* per session on purpose: persisted transcripts deliberately carry no tool
|
|
26
|
-
* result payloads (see ask-buoy transcriptPersistence.ts), and this must not
|
|
27
|
-
* quietly change that.
|
|
28
|
-
*
|
|
29
|
-
* What goes in is what the MODEL was given: after `redact` (credential names
|
|
30
|
-
* and value shapes) and `stripSelfTraffic`. Never the raw adapter value.
|
|
31
|
-
*
|
|
32
|
-
* Bounded by bytes and entries, oldest out first; an evicted ref is remembered
|
|
33
|
-
* so the answer is "evicted" rather than "never existed" — the marker still
|
|
34
|
-
* names a real thing, and the honest reply is to call the tool again.
|
|
35
|
-
*/
|
|
3
|
+
[truncated \u2014 ${t} characters total. Ask for a narrower slice if you need more.]`;return`
|
|
36
4
|
|
|
37
|
-
const
|
|
38
|
-
const DEFAULT_MAX_ENTRIES = 200;
|
|
39
|
-
class EvidenceStore {
|
|
40
|
-
entries = new Map();
|
|
41
|
-
evictedRefs = new Set();
|
|
42
|
-
chars = 0;
|
|
43
|
-
seq = 0;
|
|
44
|
-
constructor(opts = {}) {
|
|
45
|
-
this.maxChars = opts.maxChars ?? DEFAULT_MAX_CHARS;
|
|
46
|
-
this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Keep a result. Returns its ref — `ev_<n>`, counting up for the life of the store. */
|
|
50
|
-
stash(input) {
|
|
51
|
-
const ref = `ev_${++this.seq}`;
|
|
52
|
-
const entry = {
|
|
53
|
-
...input,
|
|
54
|
-
ref
|
|
55
|
-
};
|
|
56
|
-
// A single result larger than the whole store still gets a ref — it is
|
|
57
|
-
// the case the store exists for — and simply evicts everything else.
|
|
58
|
-
this.entries.set(ref, entry);
|
|
59
|
-
this.chars += entry.text.length;
|
|
60
|
-
this.evict();
|
|
61
|
-
return ref;
|
|
62
|
-
}
|
|
63
|
-
get(ref) {
|
|
64
|
-
return this.entries.get(ref);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** True when the ref was real and has since been dropped for space. */
|
|
68
|
-
wasEvicted(ref) {
|
|
69
|
-
return this.evictedRefs.has(ref);
|
|
70
|
-
}
|
|
71
|
-
get size() {
|
|
72
|
-
return this.entries.size;
|
|
73
|
-
}
|
|
74
|
-
get totalChars() {
|
|
75
|
-
return this.chars;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** New conversation: nothing from the old one is retrievable. */
|
|
79
|
-
clear() {
|
|
80
|
-
this.entries.clear();
|
|
81
|
-
this.evictedRefs.clear();
|
|
82
|
-
this.chars = 0;
|
|
83
|
-
}
|
|
84
|
-
evict() {
|
|
85
|
-
// Map iteration is insertion order, so the first key is the oldest.
|
|
86
|
-
for (const [ref, entry] of this.entries) {
|
|
87
|
-
const over = this.chars > this.maxChars || this.entries.size > this.maxEntries;
|
|
88
|
-
if (!over || this.entries.size === 1) return;
|
|
89
|
-
this.entries.delete(ref);
|
|
90
|
-
this.evictedRefs.add(ref);
|
|
91
|
-
this.chars -= entry.text.length;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ── the markers ──────────────────────────────────────────────────────────────
|
|
97
|
-
exports.EvidenceStore = EvidenceStore;
|
|
98
|
-
const RETRIEVE_HINT = "ask-buoy.retrieve";
|
|
99
|
-
|
|
100
|
-
/** The tail of a result cut at the engine's cap. */
|
|
101
|
-
function truncationMarker(totalChars, ref) {
|
|
102
|
-
const size = totalChars.toLocaleString("en-US");
|
|
103
|
-
if (!ref) return `\n\n[truncated — ${size} characters total. Ask for a narrower slice if you need more.]`;
|
|
104
|
-
return `\n\n[truncated — ${size} characters total; ref ${ref}. Call ${RETRIEVE_HINT} {"ref":"${ref}"} to see its shape, then {"ref":"${ref}","path":"…"} or {"ref":"${ref}","pattern":"…"} to read the part you need.]`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** What a consumed result is replaced with when history is compressed. Keep the `[earlier result` prefix — historyBudget skips on it. */
|
|
108
|
-
function compressedMarker(totalChars, ref) {
|
|
109
|
-
const size = totalChars.toLocaleString("en-US");
|
|
110
|
-
if (!ref) return `[earlier result — ${size} characters, already read. Ask again if you need it.]`;
|
|
111
|
-
return `[earlier result — ${size} characters, already read; ref ${ref}. ${RETRIEVE_HINT} {"ref":"${ref}",…} re-reads exactly what you saw then; calling the tool again gives the CURRENT value.]`;
|
|
112
|
-
}
|
|
113
|
-
//# sourceMappingURL=evidence.js.map
|
|
5
|
+
[truncated \u2014 ${t} characters total; ref ${e}. Call ${RETRIEVE_HINT} {"ref":"${e}"} to see its shape, then {"ref":"${e}","path":"\u2026"} or {"ref":"${e}","pattern":"\u2026"} to read the part you need.]`}function compressedMarker(r,e){const t=r.toLocaleString("en-US");if(!e)return`[earlier result \u2014 ${t} characters, already read. Ask again if you need it.]`;return`[earlier result \u2014 ${t} characters, already read; ref ${e}. ${RETRIEVE_HINT} {"ref":"${e}",\u2026} re-reads exactly what you saw then; calling the tool again gives the CURRENT value.]`}
|
|
@@ -1,363 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.MAX_REQUEST_TOKENS = exports.MAX_REQUEST_CHARS = exports.MAX_HISTORY_TOKENS = exports.MAX_HISTORY_CHARS = void 0;
|
|
7
|
-
exports.budgetForRequest = budgetForRequest;
|
|
8
|
-
exports.compressConsumed = compressConsumed;
|
|
9
|
-
exports.messageSize = messageSize;
|
|
10
|
-
exports.trimHistory = trimHistory;
|
|
11
|
-
exports.trimHistoryReport = trimHistoryReport;
|
|
12
|
-
var _evidence = require("./evidence");
|
|
13
|
-
var _tokenCalibration = require("./tokenCalibration");
|
|
14
|
-
/**
|
|
15
|
-
* How much conversation the model is allowed to be sent, and what gets cut.
|
|
16
|
-
*
|
|
17
|
-
* Lives here, not in `session.ts`, because it is needed in TWO places and used
|
|
18
|
-
* to run in only one. The session trims once per turn, before the loop starts
|
|
19
|
-
* — and a turn is not one request. A twelve-step investigation appends an
|
|
20
|
-
* assistant message and a tool-results message per step, each result capped at
|
|
21
|
-
* 24,000 characters, so a long read chain could add well over a hundred
|
|
22
|
-
* thousand characters AFTER the only check had already happened and walk
|
|
23
|
-
* straight into the provider's context limit. The engine now applies the same
|
|
24
|
-
* budget before every request; the session still applies it when a turn opens.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* A long QA session must not die on the provider's context limit with an
|
|
29
|
-
* opaque error, so the history is kept under a budget.
|
|
30
|
-
*
|
|
31
|
-
* TWO STAGES, AND THE ORDER IS THE POINT. Dropping the oldest rounds was the
|
|
32
|
-
* only stage, and in a QA session the oldest round is usually the SETUP — the
|
|
33
|
-
* override that was armed, the store that was written, the user someone is
|
|
34
|
-
* impersonating. Drop it and the model can no longer explain the screen it is
|
|
35
|
-
* looking at, or that a "wrong" response is its own mock. The reply gets worse
|
|
36
|
-
* in exactly the sessions long enough to need trimming.
|
|
37
|
-
*
|
|
38
|
-
* So: COMPRESS first, DROP only if that was not enough.
|
|
39
|
-
*
|
|
40
|
-
* A tool result is "consumed" once the model has produced an assistant message
|
|
41
|
-
* after seeing it — it has already been reasoned over, and what remains is
|
|
42
|
-
* bulk. Replacing it with a marker keeps the round, its decision and its
|
|
43
|
-
* ordering while giving back nearly all the bytes. Three exemptions carry
|
|
44
|
-
* their weight:
|
|
45
|
-
*
|
|
46
|
-
* - Errors are never compressed. A failure is the diagnostic value.
|
|
47
|
-
* - The newest tool-results message is never compressed. The model is about
|
|
48
|
-
* to answer from it.
|
|
49
|
-
* - The last read before a WRITE is never compressed: that is the shape the
|
|
50
|
-
* model matched its edit to, and it is the thing it re-reads when the write
|
|
51
|
-
* is refused and it has to correct itself.
|
|
52
|
-
*
|
|
53
|
-
* Only if the history is still over budget do whole rounds get dropped, as
|
|
54
|
-
* before — a round starts at a user message, so the assistant/tool-result
|
|
55
|
-
* pairing every provider requires stays intact.
|
|
56
|
-
*/
|
|
57
|
-
/**
|
|
58
|
-
* The limits are in TOKENS — the unit the provider enforces — and turned into
|
|
59
|
-
* characters at the ratio the session has measured (engine/tokenCalibration.ts).
|
|
60
|
-
* The `_CHARS` constants below are those limits at the default 4 chars/token,
|
|
61
|
-
* kept for callers that have no calibration in hand (tests, one-shot use).
|
|
62
|
-
*/
|
|
63
|
-
const MAX_HISTORY_TOKENS = exports.MAX_HISTORY_TOKENS = 37_500;
|
|
64
|
-
/**
|
|
65
|
-
* The ceiling on a WHOLE request: tools, system prompt, messages and the room
|
|
66
|
-
* reserved for the reply. 100k tokens fits the smallest context Ask Buoy is
|
|
67
|
-
* pointed at in practice (a 128k-token gateway) with headroom, and leaves
|
|
68
|
-
* Anthropic's 200k far from the limit.
|
|
69
|
-
*/
|
|
70
|
-
const MAX_REQUEST_TOKENS = exports.MAX_REQUEST_TOKENS = 100_000;
|
|
71
|
-
const MAX_HISTORY_CHARS = exports.MAX_HISTORY_CHARS = MAX_HISTORY_TOKENS * _tokenCalibration.DEFAULT_CHARS_PER_TOKEN;
|
|
72
|
-
const MAX_REQUEST_CHARS = exports.MAX_REQUEST_CHARS = MAX_REQUEST_TOKENS * _tokenCalibration.DEFAULT_CHARS_PER_TOKEN;
|
|
73
|
-
const MIN_KEEP_MESSAGES = 4;
|
|
74
|
-
/** Below this a marker costs more than the payload it replaces. */
|
|
75
|
-
const MIN_COMPRESS_CHARS = 400;
|
|
76
|
-
function messageSize(m) {
|
|
77
|
-
try {
|
|
78
|
-
return JSON.stringify(m).length;
|
|
79
|
-
} catch {
|
|
80
|
-
return 0;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Replace consumed tool results with a marker. See the note above
|
|
86
|
-
* MAX_HISTORY_CHARS for what "consumed" means and why the exemptions exist.
|
|
87
|
-
*/
|
|
88
|
-
function compressConsumed(messages, exemptNewest = true) {
|
|
89
|
-
// The newest tool-results message: the model is about to answer from it.
|
|
90
|
-
// Unless the caller says otherwise — `budgetForRequest` hands in the rounds
|
|
91
|
-
// BEFORE the current one, whose last result was answered long ago.
|
|
92
|
-
let newest = messages.length;
|
|
93
|
-
if (exemptNewest) {
|
|
94
|
-
newest = -1;
|
|
95
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
96
|
-
if (messages[i].role === "tool-results") {
|
|
97
|
-
newest = i;
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (newest <= 0) return messages;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// The reads a later write depended on. An assistant turn that called a
|
|
105
|
-
// write was matching a shape it read in the round before, and that is the
|
|
106
|
-
// read it goes back to when the write is refused.
|
|
107
|
-
const beforeWrite = new Set();
|
|
108
|
-
for (let i = 0; i < messages.length; i++) {
|
|
109
|
-
const m = messages[i];
|
|
110
|
-
if (m.role !== "assistant" || !m.toolCalls?.length) continue;
|
|
111
|
-
if (!m.toolCalls.some(c => WRITE_ISH.test(JSON.stringify(c.input ?? {}) + c.name))) continue;
|
|
112
|
-
for (let j = i - 1; j >= 0 && j >= i - 3; j--) {
|
|
113
|
-
if (messages[j].role === "tool-results") {
|
|
114
|
-
beforeWrite.add(j);
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
let changed = false;
|
|
120
|
-
const out = messages.map((m, i) => {
|
|
121
|
-
if (m.role !== "tool-results" || i >= newest || beforeWrite.has(i)) return m;
|
|
122
|
-
let touched = false;
|
|
123
|
-
const results = m.results.map(r => {
|
|
124
|
-
if (r.isError || r.content.length < MIN_COMPRESS_CHARS) return r;
|
|
125
|
-
if (r.content.startsWith("[earlier result")) return r;
|
|
126
|
-
touched = true;
|
|
127
|
-
// The ref survives the compression: the marker names it, and a later
|
|
128
|
-
// `retrieve` reads the full text back. See engine/evidence.ts.
|
|
129
|
-
return {
|
|
130
|
-
...r,
|
|
131
|
-
content: (0, _evidence.compressedMarker)(r.content.length, r.ref)
|
|
132
|
-
};
|
|
133
|
-
});
|
|
134
|
-
if (!touched) return m;
|
|
135
|
-
changed = true;
|
|
136
|
-
return {
|
|
137
|
-
...m,
|
|
138
|
-
results
|
|
139
|
-
};
|
|
140
|
-
});
|
|
141
|
-
return changed ? out : messages;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Heuristic for "this call changed something". Deliberately loose: the cost of
|
|
146
|
-
* a false positive is one uncompressed read, the cost of a false negative is
|
|
147
|
-
* the model losing the shape it was editing.
|
|
148
|
-
*/
|
|
149
|
-
const WRITE_ISH = /set|write|dispatch|navigate|tap|override|restore|delete|remove|clear|save|run|impersonat|reload/i;
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* A round: a user message and everything up to the next one. `pinned` when
|
|
153
|
-
* one of its tool calls made a change that is still applied — see
|
|
154
|
-
* `dropRounds`.
|
|
155
|
-
*/
|
|
156
|
-
|
|
157
|
-
function splitRounds(messages, pinnedCallIds) {
|
|
158
|
-
const rounds = [];
|
|
159
|
-
for (const m of messages) {
|
|
160
|
-
if (m.role === "user" || rounds.length === 0) rounds.push({
|
|
161
|
-
messages: [],
|
|
162
|
-
size: 0,
|
|
163
|
-
pinned: false
|
|
164
|
-
});
|
|
165
|
-
const round = rounds[rounds.length - 1];
|
|
166
|
-
round.messages.push(m);
|
|
167
|
-
round.size += messageSize(m);
|
|
168
|
-
if (pinnedCallIds?.size && m.role === "assistant" && m.toolCalls?.some(c => pinnedCallIds.has(c.id))) round.pinned = true;
|
|
169
|
-
}
|
|
170
|
-
return rounds;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Drop whole rounds, oldest first, until `keepSize + dropped` fits — but the
|
|
175
|
-
* PINNED rounds last.
|
|
176
|
-
*
|
|
177
|
-
* Dropping oldest-first was the whole rule, and in a QA session the oldest
|
|
178
|
-
* round is usually the SETUP: the override that was armed, the store that
|
|
179
|
-
* was written, the user someone is impersonating. Lose it and the model can
|
|
180
|
-
* no longer explain the screen it is looking at, or that a "wrong" response
|
|
181
|
-
* is its own mock — and the changes list in the prompt names the change but
|
|
182
|
-
* not the reasoning around it. The ledger knows which changes are still
|
|
183
|
-
* applied, so a round that made one is kept (compressed, like any other) for
|
|
184
|
-
* as long as an unpinned round remains to drop instead. Only when nothing
|
|
185
|
-
* else is left do pinned rounds go, oldest first, and the caller says so.
|
|
186
|
-
* Strands' `pin-message.ts` is the same idea, keyed on message metadata.
|
|
187
|
-
*
|
|
188
|
-
* `minMessages` is the floor the session-level trim keeps (a history that
|
|
189
|
-
* opens mid-exchange is rejected by every provider; the floor is applied on
|
|
190
|
-
* STARTING a drop, so a drop always runs to the next user boundary).
|
|
191
|
-
*/
|
|
192
|
-
function dropRounds(rounds, budget, keepSize, minMessages) {
|
|
193
|
-
const remaining = [...rounds];
|
|
194
|
-
let size = remaining.reduce((n, r) => n + r.size, 0);
|
|
195
|
-
let droppedRounds = 0;
|
|
196
|
-
let droppedPinned = 0;
|
|
197
|
-
const messageCount = () => remaining.reduce((n, r) => n + r.messages.length, 0);
|
|
198
|
-
while (remaining.length > 0 && size + keepSize > budget && messageCount() > minMessages) {
|
|
199
|
-
let i = remaining.findIndex(r => !r.pinned);
|
|
200
|
-
if (i < 0) {
|
|
201
|
-
i = 0;
|
|
202
|
-
droppedPinned += 1;
|
|
203
|
-
}
|
|
204
|
-
size -= remaining[i].size;
|
|
205
|
-
remaining.splice(i, 1);
|
|
206
|
-
droppedRounds += 1;
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
kept: remaining.flatMap(r => r.messages),
|
|
210
|
-
droppedRounds,
|
|
211
|
-
droppedPinned
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Trim, and say how many whole rounds were dropped — the number the UI turns
|
|
217
|
-
* into "older messages were trimmed from the agent's memory".
|
|
218
|
-
*
|
|
219
|
-
* A round is a user message and everything up to the next one. Once a drop
|
|
220
|
-
* starts it ALWAYS runs to the next user boundary: the inner loop used to stop
|
|
221
|
-
* at `MIN_KEEP_MESSAGES` as well, so a history could come out starting with an
|
|
222
|
-
* assistant or tool-results message — which every provider rejects. The
|
|
223
|
-
* minimum-keep guard belongs on STARTING a drop, not on finishing one.
|
|
224
|
-
*/
|
|
225
|
-
function trimHistoryReport(messages, charsPerToken = _tokenCalibration.DEFAULT_CHARS_PER_TOKEN, /** Tool-call ids whose change is still applied — their rounds are dropped last. See dropRounds. */
|
|
226
|
-
pinnedCallIds) {
|
|
227
|
-
const limit = Math.floor(MAX_HISTORY_TOKENS * charsPerToken);
|
|
228
|
-
let total = 0;
|
|
229
|
-
for (const m of messages) total += messageSize(m);
|
|
230
|
-
if (total <= limit) return {
|
|
231
|
-
messages,
|
|
232
|
-
droppedRounds: 0,
|
|
233
|
-
droppedPinned: 0
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Compress before dropping — a compressed round still explains the screen.
|
|
237
|
-
const compressed = compressConsumed(messages);
|
|
238
|
-
let after = 0;
|
|
239
|
-
for (const m of compressed) after += messageSize(m);
|
|
240
|
-
if (after <= limit) return {
|
|
241
|
-
messages: compressed,
|
|
242
|
-
droppedRounds: 0,
|
|
243
|
-
droppedPinned: 0
|
|
244
|
-
};
|
|
245
|
-
const {
|
|
246
|
-
kept,
|
|
247
|
-
droppedRounds,
|
|
248
|
-
droppedPinned
|
|
249
|
-
} = dropRounds(splitRounds(compressed, pinnedCallIds), limit, 0, MIN_KEEP_MESSAGES);
|
|
250
|
-
return {
|
|
251
|
-
messages: kept,
|
|
252
|
-
droppedRounds,
|
|
253
|
-
droppedPinned
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/** Exported for the history tests; not part of the package surface. */
|
|
258
|
-
function trimHistory(messages) {
|
|
259
|
-
return trimHistoryReport(messages).messages;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Trim the working history of a turn that is still running.
|
|
264
|
-
*
|
|
265
|
-
* Differs from `trimHistoryReport` in one way that matters: the CURRENT round
|
|
266
|
-
* is never touched. A turn's own user message and the reads it has taken since
|
|
267
|
-
* are the thing being answered, so compressing or dropping them would make the
|
|
268
|
-
* request cheaper and the answer wrong. Everything before the last user
|
|
269
|
-
* message is fair game, in the same order as always — compress, then drop.
|
|
270
|
-
*
|
|
271
|
-
* `reserve` is what the request costs BEFORE any messages: the tool
|
|
272
|
-
* definitions (~8k tokens for a typical install, ~23k with everything
|
|
273
|
-
* installed), the system prompt, and the reply the model is allowed to write.
|
|
274
|
-
* Budgeting messages alone would leave the biggest fixed cost out of the sum.
|
|
275
|
-
*/
|
|
276
|
-
function budgetForRequest(messages, reserve,
|
|
277
|
-
/**
|
|
278
|
-
* A tighter ceiling on the messages than MAX_HISTORY_CHARS — set by the
|
|
279
|
-
* engine after the provider refused a request as too big. The proactive
|
|
280
|
-
* budget is an estimate; the provider's refusal is the truth.
|
|
281
|
-
*/
|
|
282
|
-
cap = MAX_HISTORY_CHARS, /** The session's measured ratio; scales the whole-request ceiling. */
|
|
283
|
-
charsPerToken = _tokenCalibration.DEFAULT_CHARS_PER_TOKEN, /** Tool-call ids whose change is still applied — their rounds are dropped last. See dropRounds. */
|
|
284
|
-
pinnedCallIds) {
|
|
285
|
-
/**
|
|
286
|
-
* `MAX_HISTORY_CHARS` is a MESSAGES budget and always was — subtracting the
|
|
287
|
-
* reserve from it is wrong by a wide margin. The tool block alone is ~94,000
|
|
288
|
-
* characters with every tool installed, and the system prompt another
|
|
289
|
-
* ~16,000, so `150,000 - reserve` left about 24,000 characters for the
|
|
290
|
-
* conversation and dropped every earlier round of a two-turn chat. Caught by
|
|
291
|
-
* the bank, which is exactly what it is for.
|
|
292
|
-
*
|
|
293
|
-
* So the messages budget is the smaller of the two ceilings: the one that
|
|
294
|
-
* has always applied, and whatever is left of the whole-request ceiling once
|
|
295
|
-
* the fixed cost is counted. In an ordinary install the first wins and
|
|
296
|
-
* nothing changes; the second only bites when the tools are huge.
|
|
297
|
-
*/
|
|
298
|
-
const budget = Math.min(cap, Math.floor(MAX_REQUEST_TOKENS * charsPerToken) - reserve);
|
|
299
|
-
let total = 0;
|
|
300
|
-
for (const m of messages) total += messageSize(m);
|
|
301
|
-
if (total <= budget) return {
|
|
302
|
-
messages,
|
|
303
|
-
droppedRounds: 0,
|
|
304
|
-
droppedPinned: 0
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
// Where the current round begins. Nothing from here on is eligible.
|
|
308
|
-
let currentRound = -1;
|
|
309
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
310
|
-
if (messages[i].role === "user") {
|
|
311
|
-
currentRound = i;
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if (currentRound <= 0) {
|
|
316
|
-
// The whole history is this one round and it is over the limit by
|
|
317
|
-
// itself. Its consumed results are compressed, the newest kept — see the
|
|
318
|
-
// last-resort note below; this is the same move with nothing to drop.
|
|
319
|
-
return {
|
|
320
|
-
messages: compressConsumed(messages),
|
|
321
|
-
droppedRounds: 0,
|
|
322
|
-
droppedPinned: 0
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
const earlier = messages.slice(0, currentRound);
|
|
326
|
-
const current = messages.slice(currentRound);
|
|
327
|
-
let currentSize = 0;
|
|
328
|
-
for (const m of current) currentSize += messageSize(m);
|
|
329
|
-
|
|
330
|
-
// `false`: the earlier rounds' last result was answered long ago — the
|
|
331
|
-
// newest-result exemption is about the round being answered NOW.
|
|
332
|
-
const compressed = compressConsumed(earlier, false);
|
|
333
|
-
let earlierSize = 0;
|
|
334
|
-
for (const m of compressed) earlierSize += messageSize(m);
|
|
335
|
-
if (earlierSize + currentSize <= budget) {
|
|
336
|
-
return {
|
|
337
|
-
messages: [...compressed, ...current],
|
|
338
|
-
droppedRounds: 0,
|
|
339
|
-
droppedPinned: 0
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
const {
|
|
343
|
-
kept: out,
|
|
344
|
-
droppedRounds,
|
|
345
|
-
droppedPinned
|
|
346
|
-
} = dropRounds(splitRounds(compressed, pinnedCallIds), budget, currentSize, 0);
|
|
347
|
-
earlierSize = out.reduce((n, m) => n + messageSize(m), 0);
|
|
348
|
-
/**
|
|
349
|
-
* LAST RESORT, inside the current round. Everything earlier is gone and the
|
|
350
|
-
* turn's own reads are still over the limit — a twelve-step investigation
|
|
351
|
-
* of 24k-character results does that by itself. The consumed ones (an
|
|
352
|
-
* assistant message came after them) are compressed the same way earlier
|
|
353
|
-
* rounds were, newest result and the reads before a write still exempt.
|
|
354
|
-
* Their refs make this lossless for the model: `retrieve` reads them back.
|
|
355
|
-
*/
|
|
356
|
-
const currentOut = out.length === 0 && earlierSize + currentSize > budget ? compressConsumed(current) : current;
|
|
357
|
-
return {
|
|
358
|
-
messages: [...out, ...currentOut],
|
|
359
|
-
droppedRounds,
|
|
360
|
-
droppedPinned
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
//# sourceMappingURL=historyBudget.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.MAX_REQUEST_TOKENS=exports.MAX_REQUEST_CHARS=exports.MAX_HISTORY_TOKENS=exports.MAX_HISTORY_CHARS=void 0;exports.budgetForRequest=budgetForRequest;exports.compressConsumed=compressConsumed;exports.messageSize=messageSize;exports.trimHistory=trimHistory;exports.trimHistoryReport=trimHistoryReport;var _evidence=require("./evidence");var _tokenCalibration=require("./tokenCalibration");const MAX_HISTORY_TOKENS=exports.MAX_HISTORY_TOKENS=37500;const MAX_REQUEST_TOKENS=exports.MAX_REQUEST_TOKENS=1e5;const MAX_HISTORY_CHARS=exports.MAX_HISTORY_CHARS=MAX_HISTORY_TOKENS*_tokenCalibration.DEFAULT_CHARS_PER_TOKEN;const MAX_REQUEST_CHARS=exports.MAX_REQUEST_CHARS=MAX_REQUEST_TOKENS*_tokenCalibration.DEFAULT_CHARS_PER_TOKEN;const MIN_KEEP_MESSAGES=4;const MIN_COMPRESS_CHARS=400;function messageSize(e){try{return JSON.stringify(e).length}catch{return 0}}function compressConsumed(e,c=true){let i=e.length;if(c){i=-1;for(let t=e.length-1;t>=0;t--){if(e[t].role==="tool-results"){i=t;break}}if(i<=0)return e}const u=new Set;for(let t=0;t<e.length;t++){const d=e[t];if(d.role!=="assistant"||!d.toolCalls?.length)continue;if(!d.toolCalls.some(s=>WRITE_ISH.test(JSON.stringify(s.input??{})+s.name)))continue;for(let s=t-1;s>=0&&s>=t-3;s--){if(e[s].role==="tool-results"){u.add(s);break}}}let o=false;const l=e.map((t,d)=>{if(t.role!=="tool-results"||d>=i||u.has(d))return t;let s=false;const n=t.results.map(r=>{if(r.isError||r.content.length<MIN_COMPRESS_CHARS)return r;if(r.content.startsWith("[earlier result"))return r;s=true;return{...r,content:(0,_evidence.compressedMarker)(r.content.length,r.ref)}});if(!s)return t;o=true;return{...t,results:n}});return o?l:e}const WRITE_ISH=/set|write|dispatch|navigate|tap|override|restore|delete|remove|clear|save|run|impersonat|reload/i;function splitRounds(e,c){const i=[];for(const u of e){if(u.role==="user"||i.length===0)i.push({messages:[],size:0,pinned:false});const o=i[i.length-1];o.messages.push(u);o.size+=messageSize(u);if(c?.size&&u.role==="assistant"&&u.toolCalls?.some(l=>c.has(l.id)))o.pinned=true}return i}function dropRounds(e,c,i,u){const o=[...e];let l=o.reduce((n,r)=>n+r.size,0);let t=0;let d=0;const s=()=>o.reduce((n,r)=>n+r.messages.length,0);while(o.length>0&&l+i>c&&s()>u){let n=o.findIndex(r=>!r.pinned);if(n<0){n=0;d+=1}l-=o[n].size;o.splice(n,1);t+=1}return{kept:o.flatMap(n=>n.messages),droppedRounds:t,droppedPinned:d}}function trimHistoryReport(e,c=_tokenCalibration.DEFAULT_CHARS_PER_TOKEN,i){const u=Math.floor(MAX_HISTORY_TOKENS*c);let o=0;for(const r of e)o+=messageSize(r);if(o<=u)return{messages:e,droppedRounds:0,droppedPinned:0};const l=compressConsumed(e);let t=0;for(const r of l)t+=messageSize(r);if(t<=u)return{messages:l,droppedRounds:0,droppedPinned:0};const{kept:d,droppedRounds:s,droppedPinned:n}=dropRounds(splitRounds(l,i),u,0,MIN_KEEP_MESSAGES);return{messages:d,droppedRounds:s,droppedPinned:n}}function trimHistory(e){return trimHistoryReport(e).messages}function budgetForRequest(e,c,i=MAX_HISTORY_CHARS,u=_tokenCalibration.DEFAULT_CHARS_PER_TOKEN,o){const l=Math.min(i,Math.floor(MAX_REQUEST_TOKENS*u)-c);let t=0;for(const p of e)t+=messageSize(p);if(t<=l)return{messages:e,droppedRounds:0,droppedPinned:0};let d=-1;for(let p=e.length-1;p>=0;p--){if(e[p].role==="user"){d=p;break}}if(d<=0){return{messages:compressConsumed(e),droppedRounds:0,droppedPinned:0}}const s=e.slice(0,d);const n=e.slice(d);let r=0;for(const p of n)r+=messageSize(p);const a=compressConsumed(s,false);let f=0;for(const p of a)f+=messageSize(p);if(f+r<=l){return{messages:[...a,...n],droppedRounds:0,droppedPinned:0}}const{kept:_,droppedRounds:R,droppedPinned:S}=dropRounds(splitRounds(a,o),l,r,0);f=_.reduce((p,A)=>p+messageSize(A),0);const E=_.length===0&&f+r>l?compressConsumed(n):n;return{messages:[..._,...E],droppedRounds:R,droppedPinned:S}}
|