@arnilo/prism 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -5
- package/README.md +12 -11
- package/dist/agent-approval.d.ts +4 -0
- package/dist/agent-approval.js +5 -1
- package/dist/agent-definitions.js +1 -0
- package/dist/agent-run-lifecycle.js +39 -4
- package/dist/agent-run-state.d.ts +18 -0
- package/dist/agent-run-state.js +39 -9
- package/dist/agent-session/helpers.js +6 -1
- package/dist/agent-session/session/assemble.js +159 -7
- package/dist/agent-session/session/persist.d.ts +16 -0
- package/dist/agent-session/session/persist.js +64 -4
- package/dist/agent-session/session/provider-round.d.ts +3 -3
- package/dist/agent-session/session/provider-round.js +12 -6
- package/dist/agent-session/session/tool-round.js +5 -1
- package/dist/agent-session/session/types.d.ts +22 -1
- package/dist/agent-session/session.d.ts +16 -0
- package/dist/agent-session/session.js +42 -3
- package/dist/artifacts.d.ts +39 -1
- package/dist/artifacts.js +73 -0
- package/dist/attention-compiler.d.ts +121 -0
- package/dist/attention-compiler.js +479 -0
- package/dist/checkpoints.js +7 -11
- package/dist/cli-init.js +20 -6
- package/dist/context-budget.d.ts +20 -1
- package/dist/context-budget.js +10 -1
- package/dist/contracts-core/agent.d.ts +7 -0
- package/dist/contracts-core/attention.d.ts +66 -0
- package/dist/contracts-core/attention.js +2 -0
- package/dist/contracts-core/compaction.d.ts +59 -0
- package/dist/contracts-core/compaction.js +77 -1
- package/dist/contracts-core/content.d.ts +5 -0
- package/dist/contracts-core/loop.d.ts +42 -0
- package/dist/contracts-core/provider.d.ts +4 -0
- package/dist/contracts-core/run-limits.d.ts +2 -0
- package/dist/contracts-core.d.ts +1 -0
- package/dist/contracts-core.js +1 -0
- package/dist/contracts-protocol.d.ts +44 -3
- package/dist/contracts-run-state.d.ts +32 -5
- package/dist/evidence-grounding.d.ts +29 -0
- package/dist/evidence-grounding.js +162 -0
- package/dist/host-composition.d.ts +91 -0
- package/dist/host-composition.js +279 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +7 -4
- package/dist/input.d.ts +13 -1
- package/dist/input.js +40 -1
- package/dist/provider-events.d.ts +3 -1
- package/dist/provider-events.js +2 -2
- package/dist/providers/transport.d.ts +3 -1
- package/dist/providers/transport.js +36 -0
- package/dist/redaction.js +18 -2
- package/dist/run-bundle.d.ts +89 -0
- package/dist/run-bundle.js +149 -0
- package/dist/secure-agent.d.ts +2 -0
- package/dist/secure-agent.js +6 -1
- package/dist/testing/state-concurrency-conformance.js +5 -12
- package/dist/tool-result-fold.d.ts +12 -0
- package/dist/tool-result-fold.js +13 -6
- package/dist/tools.d.ts +10 -0
- package/dist/tools.js +41 -0
- package/docs/acp-agent.md +42 -11
- package/docs/acp.md +2 -1
- package/docs/ag-ui.md +10 -3
- package/docs/agent-definitions.md +9 -1
- package/docs/agent-events.md +4 -1
- package/docs/agent-loops.md +33 -0
- package/docs/agent-session-runtime.md +8 -7
- package/docs/attention-compiler.md +272 -0
- package/docs/cli-rpc.md +4 -2
- package/docs/coding-agent-tools.md +1 -1
- package/docs/coding-security.md +6 -3
- package/docs/coding-tools.md +0 -1
- package/docs/coding-workspaces.md +22 -0
- package/docs/compaction-and-retry.md +36 -4
- package/docs/compaction-observational-memory.md +63 -10
- package/docs/connected-apps.md +116 -0
- package/docs/context-and-skills.md +17 -2
- package/docs/conversations.md +1 -1
- package/docs/core.md +1 -1
- package/docs/dev-inspector.md +4 -0
- package/docs/device-adapters.md +1 -0
- package/docs/diagrams.md +6 -6
- package/docs/document-reader.md +18 -10
- package/docs/documents.md +40 -11
- package/docs/durable-runs.md +87 -0
- package/docs/enterprise-postgres-state.md +6 -2
- package/docs/evaluations.md +168 -4
- package/docs/execution-timeline.md +186 -0
- package/docs/guardrails.md +33 -0
- package/docs/history/0.7.0-primitive-review.md +254 -0
- package/docs/history/079-messaging-primitive-review.md +391 -0
- package/docs/history/080-messaging-followon-primitive-review.md +234 -0
- package/docs/history/081-connected-apps-primitive-review.md +74 -0
- package/docs/history/083-prism-work-primitive-review.md +84 -0
- package/docs/history/084-primitive-review.md +96 -0
- package/docs/history/085-honesty-and-cut-primitive-review.md +91 -0
- package/docs/history/README.md +5 -0
- package/docs/history/migration-0.0.md +2 -2
- package/docs/history/release-handoffs.md +75 -1
- package/docs/host-compositions.md +149 -0
- package/docs/host-security.md +2 -2
- package/docs/hosted-sandboxes.md +94 -0
- package/docs/index.md +82 -45
- package/docs/input-and-prompt-assembly.md +1 -0
- package/docs/knowledge-sync.md +84 -0
- package/docs/language-intelligence.md +1 -1
- package/docs/live-testing.md +8 -3
- package/docs/mcp-tools.md +3 -1
- package/docs/memory-fabric.md +416 -0
- package/docs/messaging-channel-operations.md +166 -0
- package/docs/messaging-channels.md +150 -0
- package/docs/migrate-to-0.5.md +1 -1
- package/docs/migrate-to-0.6.md +1 -0
- package/docs/migrate-to-0.7.md +345 -0
- package/docs/migrate-to-0.8.md +124 -0
- package/docs/migration.md +43 -1
- package/docs/model-registry.md +12 -2
- package/docs/model-routing.md +79 -4
- package/docs/multi-agent-patterns.md +20 -6
- package/docs/observability.md +52 -1
- package/docs/openapi-tools.md +1 -1
- package/docs/operations.md +14 -4
- package/docs/options-index.md +47 -3
- package/docs/peer-dependencies.md +12 -10
- package/docs/postgres-persistence.md +1 -1
- package/docs/process-sessions.md +3 -1
- package/docs/prompt-registry.md +1 -1
- package/docs/provider-caching.md +4 -2
- package/docs/provider-conformance.md +1 -1
- package/docs/provider-layer.md +2 -2
- package/docs/provider-packages.md +22 -22
- package/docs/providers/bedrock.md +71 -7
- package/docs/providers/neuralwatt.md +5 -1
- package/docs/providers/openai.md +1 -1
- package/docs/rag.md +24 -8
- package/docs/realtime-voice.md +87 -0
- package/docs/release-and-install.md +53 -45
- package/docs/run-bundle.md +92 -0
- package/docs/runs-and-usage.md +17 -2
- package/docs/server.md +7 -3
- package/docs/sheets.md +9 -9
- package/docs/signal-channel.md +112 -0
- package/docs/speech.md +7 -1
- package/docs/sqlite-persistence.md +1 -1
- package/docs/supervisors.md +33 -5
- package/docs/telegram-channel.md +157 -0
- package/docs/testing.md +2 -2
- package/docs/thinking-and-reasoning.md +3 -1
- package/docs/tools.md +6 -5
- package/docs/web-tools.md +2 -1
- package/docs/wiki.md +1 -1
- package/docs/work-artifacts-and-review.md +14 -4
- package/docs/work-connectors.md +12 -10
- package/docs/work-sandbox.md +115 -0
- package/docs/work-tools.md +50 -18
- package/docs/workflows.md +69 -1
- package/docs/working-and-semantic-memory.md +25 -14
- package/package.json +5 -3
- package/templates/README.md +2 -0
- package/templates/business-worker/README.md.tmpl +19 -0
- package/templates/business-worker/env.example.tmpl +1 -0
- package/templates/business-worker/gitignore.tmpl +11 -0
- package/templates/business-worker/manifest.json +12 -0
- package/templates/business-worker/package.json.tmpl +23 -0
- package/templates/business-worker/src/agent.ts.tmpl +92 -0
- package/templates/business-worker/src/index.ts.tmpl +13 -0
- package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
- package/templates/business-worker/tsconfig.json.tmpl +15 -0
- package/templates/personal-assistant/README.md.tmpl +18 -0
- package/templates/personal-assistant/env.example.tmpl +1 -0
- package/templates/personal-assistant/gitignore.tmpl +11 -0
- package/templates/personal-assistant/manifest.json +11 -0
- package/templates/personal-assistant/package.json.tmpl +23 -0
- package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
- package/templates/personal-assistant/src/index.ts.tmpl +13 -0
- package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
- package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/** Attention Compiler (plan 074 Tasks 2–3): frozen contracts, the ratio gate, and the two
|
|
2
|
+
* sticky stages (thinking strip, then old tool-result stubs). Creation is synchronous and
|
|
3
|
+
* fails closed; `compileAttention` measures the assembled request once, rewrites only the
|
|
4
|
+
* `history` / `toolResults` groups, and never touches the store, the om ledger, or the
|
|
5
|
+
* frozen prefix. The assembly branch lives in `input.ts`; opt-in agent wiring (Task 5) is not
|
|
6
|
+
* part of this module. */
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { estimateMessageBytes, estimateMessageTokens, estimateTextBytes, measureInputCost, } from "./context-budget.js";
|
|
9
|
+
import { assertCompactionTrigger } from "./contracts-core/compaction.js";
|
|
10
|
+
import { capToolResultSummary, foldedToolResultHeader, inferToolResultTurns, toolResultFoldText, } from "./tool-result-fold.js";
|
|
11
|
+
export const ATTENTION_BUDGET_ERROR_CODE = "attention_budget_exceeded";
|
|
12
|
+
/** C9: still over `triggerRatio` after every eligible stage — host should compact, not delete. */
|
|
13
|
+
export class AttentionBudgetError extends Error {
|
|
14
|
+
code = ATTENTION_BUDGET_ERROR_CODE;
|
|
15
|
+
constructor(message = "attention budget exceeded: still over triggerRatio after stub stages") {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "AttentionBudgetError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function isAttentionBudgetError(error) {
|
|
21
|
+
return error instanceof Error && error.code === ATTENTION_BUDGET_ERROR_CODE;
|
|
22
|
+
}
|
|
23
|
+
export const DEFAULT_ATTENTION_TRIGGER_RATIO = 0.75;
|
|
24
|
+
export const DEFAULT_ATTENTION_COMPACT_RATIO = 0.9;
|
|
25
|
+
export const DEFAULT_ATTENTION_THINKING_KEEP_TURNS = 1;
|
|
26
|
+
export const DEFAULT_ATTENTION_KEEP_LAST = 3;
|
|
27
|
+
export const DEFAULT_ATTENTION_RESERVE_TOKENS = 1024;
|
|
28
|
+
/** Matches the run allow-list caps in `tools.ts`: no more tool names than a run could hold. */
|
|
29
|
+
const MAX_EXCLUDE_TOOL_NAMES = 1_024;
|
|
30
|
+
const MAX_TOOL_NAME_CHARS = 256;
|
|
31
|
+
function resolveRatio(value, fallback, name) {
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
return fallback;
|
|
34
|
+
if (!Number.isFinite(value) || value <= 0 || value >= 1) {
|
|
35
|
+
throw new TypeError(`${name} must be a number in (0, 1)`);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function resolveCount(value, fallback, name) {
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
return fallback;
|
|
42
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
43
|
+
throw new TypeError(`${name} must be a non-negative safe integer`);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
export function resolveAttentionReserveTokens(value) {
|
|
48
|
+
return resolveCount(value, DEFAULT_ATTENTION_RESERVE_TOKENS, "attentionCompiler.reserveTokens");
|
|
49
|
+
}
|
|
50
|
+
/** C2: host `maxInputTokens` wins; otherwise `contextWindow - (maxOutputTokens ?? 0) - reserve`.
|
|
51
|
+
* Shared with the `input_ratio` compaction trigger (Task 4). Throws when no cap can be derived. */
|
|
52
|
+
export function resolveInputCap(options = {}, model) {
|
|
53
|
+
const reserveTokens = resolveAttentionReserveTokens(options.reserveTokens);
|
|
54
|
+
if (options.maxInputTokens !== undefined) {
|
|
55
|
+
if (!Number.isSafeInteger(options.maxInputTokens) || options.maxInputTokens < 1) {
|
|
56
|
+
throw new TypeError("attentionCompiler.maxInputTokens must be a positive safe integer");
|
|
57
|
+
}
|
|
58
|
+
return options.maxInputTokens;
|
|
59
|
+
}
|
|
60
|
+
const window = model?.limits?.contextWindow;
|
|
61
|
+
if (!Number.isSafeInteger(window) || window <= 0) {
|
|
62
|
+
throw new TypeError("attentionCompiler requires maxInputTokens or model.limits.contextWindow");
|
|
63
|
+
}
|
|
64
|
+
const maxOutputTokens = model?.limits?.maxOutputTokens;
|
|
65
|
+
if (maxOutputTokens !== undefined && (!Number.isSafeInteger(maxOutputTokens) || maxOutputTokens < 0)) {
|
|
66
|
+
throw new TypeError("attentionCompiler requires model.limits.maxOutputTokens to be a non-negative safe integer");
|
|
67
|
+
}
|
|
68
|
+
const cap = window - (maxOutputTokens ?? 0) - reserveTokens;
|
|
69
|
+
if (cap <= 0) {
|
|
70
|
+
throw new TypeError(`attentionCompiler resolved a non-positive input cap (${cap}): raise contextWindow or lower reserveTokens`);
|
|
71
|
+
}
|
|
72
|
+
return cap;
|
|
73
|
+
}
|
|
74
|
+
function resolveExcludeTools(value) {
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return Object.freeze([]);
|
|
77
|
+
if (!Array.isArray(value))
|
|
78
|
+
throw new TypeError("attentionCompiler.excludeTools must be an array of tool names");
|
|
79
|
+
if (value.length > MAX_EXCLUDE_TOOL_NAMES) {
|
|
80
|
+
throw new TypeError(`attentionCompiler.excludeTools exceeds ${MAX_EXCLUDE_TOOL_NAMES} entries`);
|
|
81
|
+
}
|
|
82
|
+
const out = [];
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
for (const name of value) {
|
|
85
|
+
if (typeof name !== "string" || name.length === 0 || name.length > MAX_TOOL_NAME_CHARS) {
|
|
86
|
+
throw new TypeError(`attentionCompiler.excludeTools entries must be non-empty strings of at most ${MAX_TOOL_NAME_CHARS} characters`);
|
|
87
|
+
}
|
|
88
|
+
if (!seen.has(name)) {
|
|
89
|
+
seen.add(name);
|
|
90
|
+
out.push(name);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze(out);
|
|
94
|
+
}
|
|
95
|
+
/** Validate compiler options + resolve the input cap. No provider I/O; unknown or
|
|
96
|
+
* unresolvable input throws here rather than on the first turn. */
|
|
97
|
+
export function createAttentionCompiler(options = {}, context = {}) {
|
|
98
|
+
if (typeof options !== "object" || options === null)
|
|
99
|
+
throw new TypeError("attentionCompiler options must be an object");
|
|
100
|
+
const triggerRatio = resolveRatio(options.triggerRatio, DEFAULT_ATTENTION_TRIGGER_RATIO, "attentionCompiler.triggerRatio");
|
|
101
|
+
const compactRatio = resolveRatio(options.compactRatio, DEFAULT_ATTENTION_COMPACT_RATIO, "attentionCompiler.compactRatio");
|
|
102
|
+
if (compactRatio <= triggerRatio) {
|
|
103
|
+
throw new TypeError(`attentionCompiler.compactRatio (${compactRatio}) must exceed triggerRatio (${triggerRatio})`);
|
|
104
|
+
}
|
|
105
|
+
const thinkingKeepTurns = resolveCount(options.thinkingKeepTurns, DEFAULT_ATTENTION_THINKING_KEEP_TURNS, "attentionCompiler.thinkingKeepTurns");
|
|
106
|
+
const keepLast = resolveCount(options.keepLast, DEFAULT_ATTENTION_KEEP_LAST, "attentionCompiler.keepLast");
|
|
107
|
+
const inputCap = resolveInputCap(options, context.model);
|
|
108
|
+
const reserveTokens = resolveAttentionReserveTokens(options.reserveTokens);
|
|
109
|
+
const excludeTools = resolveExcludeTools(options.excludeTools);
|
|
110
|
+
const trigger = context.compactionTrigger;
|
|
111
|
+
if (trigger !== undefined) {
|
|
112
|
+
assertCompactionTrigger(trigger);
|
|
113
|
+
if (trigger.type === "input_ratio" && trigger.ratio <= triggerRatio) {
|
|
114
|
+
throw new TypeError(`compaction input_ratio (${trigger.ratio}) must exceed attentionCompiler.triggerRatio (${triggerRatio})`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return Object.freeze({ inputCap, reserveTokens, triggerRatio, compactRatio, thinkingKeepTurns, keepLast, excludeTools });
|
|
118
|
+
}
|
|
119
|
+
/* ------------------------------------------------------------------------------------------------
|
|
120
|
+
* Opt-in wiring (Task 5)
|
|
121
|
+
* ------------------------------------------------------------------------------------------------ */
|
|
122
|
+
/** Effective agent setting: `true` → defaults, object → as given, anything falsy → off. */
|
|
123
|
+
function enabledAttentionSetting(value) {
|
|
124
|
+
if (value === true)
|
|
125
|
+
return {};
|
|
126
|
+
if (value === undefined || value === false)
|
|
127
|
+
return undefined;
|
|
128
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
129
|
+
throw new TypeError("attentionCompiler must be a boolean or an options object");
|
|
130
|
+
}
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
/** Run overlays may only lower the gate (higher ratios) and deepen the stages (lower keep
|
|
134
|
+
* counts); extending `excludeTools` protects more rows, so it is additive-only. Cap inputs
|
|
135
|
+
* stay config-level: moving them would move the gate itself. */
|
|
136
|
+
function mergeAttentionRunOverlay(base, overlay) {
|
|
137
|
+
const gate = (key, fallback) => {
|
|
138
|
+
const value = overlay[key];
|
|
139
|
+
if (value === undefined)
|
|
140
|
+
return undefined;
|
|
141
|
+
const floor = base[key] ?? fallback;
|
|
142
|
+
if (value < floor) {
|
|
143
|
+
throw new TypeError(`RunOptions.attentionCompiler.${key} (${String(value)}) must not be more aggressive than the agent setting (${floor})`);
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
};
|
|
147
|
+
const depth = (key, fallback) => {
|
|
148
|
+
const value = overlay[key];
|
|
149
|
+
if (value === undefined)
|
|
150
|
+
return undefined;
|
|
151
|
+
const ceiling = base[key] ?? fallback;
|
|
152
|
+
if (value > ceiling) {
|
|
153
|
+
throw new TypeError(`RunOptions.attentionCompiler.${key} (${String(value)}) must not protect more than the agent setting (${ceiling})`);
|
|
154
|
+
}
|
|
155
|
+
return value;
|
|
156
|
+
};
|
|
157
|
+
if (overlay.maxInputTokens !== undefined || overlay.reserveTokens !== undefined) {
|
|
158
|
+
throw new TypeError("RunOptions.attentionCompiler must not set maxInputTokens or reserveTokens: the input cap is agent-config only");
|
|
159
|
+
}
|
|
160
|
+
const triggerRatio = gate("triggerRatio", DEFAULT_ATTENTION_TRIGGER_RATIO);
|
|
161
|
+
const compactRatio = gate("compactRatio", DEFAULT_ATTENTION_COMPACT_RATIO);
|
|
162
|
+
const keepLast = depth("keepLast", DEFAULT_ATTENTION_KEEP_LAST);
|
|
163
|
+
const thinkingKeepTurns = depth("thinkingKeepTurns", DEFAULT_ATTENTION_THINKING_KEEP_TURNS);
|
|
164
|
+
const excludeTools = [...(base.excludeTools ?? []), ...(overlay.excludeTools ?? [])];
|
|
165
|
+
return {
|
|
166
|
+
...base,
|
|
167
|
+
...(triggerRatio !== undefined && { triggerRatio }),
|
|
168
|
+
...(compactRatio !== undefined && { compactRatio }),
|
|
169
|
+
...(keepLast !== undefined && { keepLast }),
|
|
170
|
+
...(thinkingKeepTurns !== undefined && { thinkingKeepTurns }),
|
|
171
|
+
...(excludeTools.length > 0 && { excludeTools }),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** Resolve the run's compiler from the agent setting plus an optional run overlay, validating
|
|
175
|
+
* both eagerly (no provider I/O) so a typo fails at run start, not on some later turn (C12).
|
|
176
|
+
* Returns `undefined` when the compiler is off — the assembly path then allocates nothing. */
|
|
177
|
+
export function resolveRunAttentionCompiler(agent, run, model) {
|
|
178
|
+
const enabled = enabledAttentionSetting(agent);
|
|
179
|
+
if (enabled === undefined) {
|
|
180
|
+
if (run !== undefined && run !== false) {
|
|
181
|
+
throw new TypeError("RunOptions.attentionCompiler requires AgentConfig.attentionCompiler: a run may disable or relax the compiler, never enable it");
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
if (run === false)
|
|
186
|
+
return undefined;
|
|
187
|
+
if (run === undefined || run === true)
|
|
188
|
+
return createAttentionCompiler(enabled, { model });
|
|
189
|
+
if (typeof run !== "object" || run === null || Array.isArray(run)) {
|
|
190
|
+
throw new TypeError("RunOptions.attentionCompiler must be false or an options object");
|
|
191
|
+
}
|
|
192
|
+
return createAttentionCompiler(mergeAttentionRunOverlay(enabled, run), { model });
|
|
193
|
+
}
|
|
194
|
+
export function createAttentionStickyFrontier() {
|
|
195
|
+
return { thinking: new Set(), toolCallIds: new Set() };
|
|
196
|
+
}
|
|
197
|
+
/** Host decision/effect payloads are never stubbed (C6): any of these metadata keys on a tool
|
|
198
|
+
* message protects the row. Hosts with decision tools should list them in `excludeTools`. */
|
|
199
|
+
const PROTECTED_TOOL_RESULT_METADATA_KEYS = [
|
|
200
|
+
"approval",
|
|
201
|
+
"approvalId",
|
|
202
|
+
"prismApproval",
|
|
203
|
+
"decision",
|
|
204
|
+
"decisions",
|
|
205
|
+
"pendingDecisions",
|
|
206
|
+
"elicitation",
|
|
207
|
+
];
|
|
208
|
+
const THINKING_KEY_BYTES = 32;
|
|
209
|
+
/**
|
|
210
|
+
* Measure once, then mutate oldest-first until back under `triggerRatio` (C4), or throw
|
|
211
|
+
* `AttentionBudgetError` when every eligible row is exhausted (C9). Front-of-frozen-prefix
|
|
212
|
+
* (instructions, summaries, input) is never touched, and neither are the caller's arrays.
|
|
213
|
+
*/
|
|
214
|
+
export async function compileAttention(options) {
|
|
215
|
+
const { compiler, groups } = options;
|
|
216
|
+
const turn = options.turn ?? 1;
|
|
217
|
+
const frontier = options.frontier;
|
|
218
|
+
const triggerTokens = compiler.triggerRatio * compiler.inputCap;
|
|
219
|
+
const usedAtStart = measureInputCost({ groups, context: options.context, skills: options.skills, tools: options.tools }).tokens;
|
|
220
|
+
let used = usedAtStart;
|
|
221
|
+
const over = () => used >= triggerTokens;
|
|
222
|
+
const history = [...groups.history];
|
|
223
|
+
const toolResults = [...groups.toolResults];
|
|
224
|
+
let droppedThinkingTurns = 0;
|
|
225
|
+
let stubbedToolResults = 0;
|
|
226
|
+
let stubbedBytes = 0;
|
|
227
|
+
let leftEligible = 0;
|
|
228
|
+
// Stage 1 — strip thinking from every assistant turn except the newest `thinkingKeepTurns`.
|
|
229
|
+
const rowTurns = inferToolResultTurns(history);
|
|
230
|
+
for (const index of thinkingTargetIndexes(history, compiler.thinkingKeepTurns)) {
|
|
231
|
+
const message = history[index];
|
|
232
|
+
if (!message)
|
|
233
|
+
continue;
|
|
234
|
+
const key = thinkingKey(message, options.redactor);
|
|
235
|
+
const sticky = frontier?.thinking.has(key) === true;
|
|
236
|
+
if (!sticky && !over()) {
|
|
237
|
+
leftEligible += 1;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const stripped = { ...message, content: message.content.filter((part) => part.type !== "thinking") };
|
|
241
|
+
history[index] = stripped;
|
|
242
|
+
used -= estimateMessageTokens(message) - estimateMessageTokens(stripped);
|
|
243
|
+
droppedThinkingTurns += 1;
|
|
244
|
+
rememberThinking(frontier, key);
|
|
245
|
+
}
|
|
246
|
+
// Stage 2 — stub the oldest tool results beyond the newest `keepLast` rows, across history
|
|
247
|
+
// and the in-flight group (which is always newer than history).
|
|
248
|
+
for (const target of toolResultTargets({ history, toolResults, rowTurns, turn, compiler, fold: options.fold })) {
|
|
249
|
+
const sticky = frontier?.toolCallIds.has(target.toolCallId) === true;
|
|
250
|
+
if (!sticky && !over()) {
|
|
251
|
+
leftEligible += 1;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const stubbed = await stubToolResultMessage(target, options);
|
|
255
|
+
const before = estimateMessageTokens(target.message);
|
|
256
|
+
const after = estimateMessageTokens(stubbed);
|
|
257
|
+
if (after >= before && !sticky) {
|
|
258
|
+
// The stub would cost more than the payload it replaces: leave the row alone.
|
|
259
|
+
leftEligible += 1;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const beforeBytes = estimateMessageBytes(target.message);
|
|
263
|
+
target.update(stubbed);
|
|
264
|
+
used -= before - after;
|
|
265
|
+
stubbedToolResults += 1;
|
|
266
|
+
stubbedBytes += Math.max(0, beforeBytes - estimateMessageBytes(stubbed));
|
|
267
|
+
rememberToolCall(frontier, target.toolCallId);
|
|
268
|
+
}
|
|
269
|
+
if (over()) {
|
|
270
|
+
throw new AttentionBudgetError(`attention budget exceeded: estimated ${used} tokens >= ${Math.ceil(triggerTokens)} (triggerRatio ${compiler.triggerRatio} of inputCap ${compiler.inputCap}) after dropping ${droppedThinkingTurns} thinking turns and stubbing ${stubbedToolResults} tool results`);
|
|
271
|
+
}
|
|
272
|
+
const mutated = droppedThinkingTurns > 0 || stubbedToolResults > 0;
|
|
273
|
+
return {
|
|
274
|
+
groups: mutated ? { ...groups, history, toolResults } : groups,
|
|
275
|
+
mutated,
|
|
276
|
+
report: {
|
|
277
|
+
used: usedAtStart,
|
|
278
|
+
usedAfter: Math.max(0, used),
|
|
279
|
+
inputCap: compiler.inputCap,
|
|
280
|
+
triggerRatio: compiler.triggerRatio,
|
|
281
|
+
droppedThinkingTurns,
|
|
282
|
+
stubbedToolResults,
|
|
283
|
+
stubbedBytes,
|
|
284
|
+
truncated: leftEligible > 0,
|
|
285
|
+
...(options.runId === undefined ? {} : { runId: options.runId }),
|
|
286
|
+
...(options.sessionId === undefined ? {} : { sessionId: options.sessionId }),
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/** Thinking-bearing assistant-message indexes, oldest first, excluding the newest `keepTurns`.
|
|
291
|
+
* Counts thinking turns, not assistant messages, so a thinking-free reply does not push an
|
|
292
|
+
* older reasoning block out of the keep window. */
|
|
293
|
+
function thinkingTargetIndexes(history, keepTurns) {
|
|
294
|
+
const withThinking = history.flatMap((message, index) => message.role === "assistant" && message.content.some((part) => part.type === "thinking") ? [index] : []);
|
|
295
|
+
return withThinking.slice(0, Math.max(0, withThinking.length - keepTurns));
|
|
296
|
+
}
|
|
297
|
+
/** Identity of one assistant turn's thinking, hashed so the frontier retains no model output. */
|
|
298
|
+
function thinkingKey(message, redactor) {
|
|
299
|
+
const text = message.content
|
|
300
|
+
.flatMap((part) => (part.type === "thinking" ? [`${part.signature ?? ""}\u0000${part.text}`] : []))
|
|
301
|
+
.join("\u0001");
|
|
302
|
+
return digest(redactor?.redact(text) ?? text);
|
|
303
|
+
}
|
|
304
|
+
/** Rows beyond the newest `keepLast`, in oldest-first order, minus everything C6 protects. */
|
|
305
|
+
function toolResultTargets(options) {
|
|
306
|
+
const rows = [];
|
|
307
|
+
const add = (message, rowTurn, update) => {
|
|
308
|
+
const block = toolResultBlock(message);
|
|
309
|
+
if (!block)
|
|
310
|
+
return;
|
|
311
|
+
rows.push({ message, block, toolCallId: block.toolCallId, toolName: block.name, rowTurn, update });
|
|
312
|
+
};
|
|
313
|
+
for (let index = 0; index < options.history.length; index += 1) {
|
|
314
|
+
const message = options.history[index];
|
|
315
|
+
if (message)
|
|
316
|
+
add(message, options.rowTurns[index] ?? options.turn, (next) => void (options.history[index] = next));
|
|
317
|
+
}
|
|
318
|
+
for (let index = 0; index < options.toolResults.length; index += 1) {
|
|
319
|
+
const message = options.toolResults[index];
|
|
320
|
+
if (message)
|
|
321
|
+
add(message, options.turn, (next) => void (options.toolResults[index] = next));
|
|
322
|
+
}
|
|
323
|
+
const excluded = new Set(options.compiler.excludeTools);
|
|
324
|
+
const keepFrom = Math.max(0, rows.length - options.compiler.keepLast);
|
|
325
|
+
return rows.slice(0, keepFrom).filter((target) => {
|
|
326
|
+
const { block, message } = target;
|
|
327
|
+
if (block.error !== undefined && block.error !== null)
|
|
328
|
+
return false;
|
|
329
|
+
if (excluded.has(target.toolName))
|
|
330
|
+
return false;
|
|
331
|
+
if (isProtectedToolResult(message.metadata))
|
|
332
|
+
return false;
|
|
333
|
+
const fold = options.fold;
|
|
334
|
+
if (!fold)
|
|
335
|
+
return true;
|
|
336
|
+
if (options.turn - target.rowTurn < fold.minAgeTurns)
|
|
337
|
+
return false;
|
|
338
|
+
return estimateTextBytes(toolResultFoldText(block.result, block.error, message.content)) >= fold.minBytes;
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
async function stubToolResultMessage(target, options) {
|
|
342
|
+
const { block, message } = target;
|
|
343
|
+
const text = toolResultFoldText(block.result, block.error, message.content);
|
|
344
|
+
const fold = options.fold;
|
|
345
|
+
const summary = fold
|
|
346
|
+
? capToolResultSummary(String(await fold.summarize({
|
|
347
|
+
sessionId: options.sessionId ?? "",
|
|
348
|
+
runId: options.runId ?? "",
|
|
349
|
+
turn: target.rowTurn,
|
|
350
|
+
toolCallId: target.toolCallId,
|
|
351
|
+
toolName: target.toolName,
|
|
352
|
+
text,
|
|
353
|
+
})), fold.maxSummaryBytes)
|
|
354
|
+
: attentionStubText(options.redactor?.redact(text) ?? text);
|
|
355
|
+
return {
|
|
356
|
+
...message,
|
|
357
|
+
content: message.content.map((part) => part.type === "tool_result" ? { ...part, result: foldedToolResultHeader(target.toolName, target.toolCallId, summary) } : part),
|
|
358
|
+
metadata: { ...message.metadata, prismFolded: true },
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/** Deterministic default stub (C5): byte count plus a digest of the already-redacted text, so
|
|
362
|
+
* the model keeps a stable placeholder and never sees the payload again. */
|
|
363
|
+
function attentionStubText(redactedText) {
|
|
364
|
+
return `omitted ${estimateTextBytes(redactedText)} bytes (sha256 ${digest(redactedText)})`;
|
|
365
|
+
}
|
|
366
|
+
function toolResultBlock(message) {
|
|
367
|
+
const block = message.content.find((part) => part.type === "tool_result");
|
|
368
|
+
return block?.type === "tool_result" ? block : undefined;
|
|
369
|
+
}
|
|
370
|
+
function isProtectedToolResult(metadata) {
|
|
371
|
+
if (!metadata)
|
|
372
|
+
return false;
|
|
373
|
+
return PROTECTED_TOOL_RESULT_METADATA_KEYS.some((key) => metadata[key] !== undefined);
|
|
374
|
+
}
|
|
375
|
+
function digest(text) {
|
|
376
|
+
return createHash("sha256").update(text).digest("hex").slice(0, THINKING_KEY_BYTES);
|
|
377
|
+
}
|
|
378
|
+
/* ------------------------------------------------------------------------------------------------
|
|
379
|
+
* Frontier hygiene, persistence, and the truncated-turn follow-up (plan 074, Further Actions)
|
|
380
|
+
* ------------------------------------------------------------------------------------------------ */
|
|
381
|
+
/** Live and persisted frontier caps. Far above any realistic session (one entry per mutation),
|
|
382
|
+
* so eviction only ever drops mutations whose rows have long left the input window. */
|
|
383
|
+
const MAX_ATTENTION_THINKING_KEYS = 256;
|
|
384
|
+
const MAX_ATTENTION_TOOL_CALL_IDS = 256;
|
|
385
|
+
const MAX_ATTENTION_TOOL_CALL_ID_CHARS = 256;
|
|
386
|
+
const ATTENTION_STICKY_SCHEMA_VERSION = 1;
|
|
387
|
+
/** Keep the newest `max` keys: a session that runs long enough to overflow the cap should
|
|
388
|
+
* re-apply its recent mutations, not its oldest. */
|
|
389
|
+
function trimNewest(set, max) {
|
|
390
|
+
while (set.size > max) {
|
|
391
|
+
const oldest = set.values().next().value;
|
|
392
|
+
if (oldest === undefined)
|
|
393
|
+
break;
|
|
394
|
+
set.delete(oldest);
|
|
395
|
+
}
|
|
396
|
+
return [...set];
|
|
397
|
+
}
|
|
398
|
+
/** Record one mutation, keeping the live frontier inside its cap. The evicted key is the
|
|
399
|
+
* oldest one, whose row has almost certainly left the input window; if it ever comes back the
|
|
400
|
+
* compiler re-decides (and the deterministic stub re-applies byte-identically when over). */
|
|
401
|
+
function rememberThinking(frontier, key) {
|
|
402
|
+
if (!frontier)
|
|
403
|
+
return;
|
|
404
|
+
frontier.thinking.add(key);
|
|
405
|
+
trimNewest(frontier.thinking, MAX_ATTENTION_THINKING_KEYS);
|
|
406
|
+
}
|
|
407
|
+
function rememberToolCall(frontier, toolCallId) {
|
|
408
|
+
if (!frontier)
|
|
409
|
+
return;
|
|
410
|
+
frontier.toolCallIds.add(toolCallId);
|
|
411
|
+
trimNewest(frontier.toolCallIds, MAX_ATTENTION_TOOL_CALL_IDS);
|
|
412
|
+
}
|
|
413
|
+
/** Bounded snapshot of a live frontier; caller-owned (the runtime persists it, the compiler
|
|
414
|
+
* never writes anywhere). */
|
|
415
|
+
export function serializeAttentionStickyFrontier(frontier) {
|
|
416
|
+
return {
|
|
417
|
+
v: ATTENTION_STICKY_SCHEMA_VERSION,
|
|
418
|
+
thinking: trimNewest(frontier.thinking, MAX_ATTENTION_THINKING_KEYS),
|
|
419
|
+
toolCallIds: trimNewest(frontier.toolCallIds, MAX_ATTENTION_TOOL_CALL_IDS),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const THINKING_KEY_PATTERN = /^[0-9a-f]{32}$/;
|
|
423
|
+
/** Validate a persisted frontier from an untrusted store (plan 074 P3). Malformed *entries* are
|
|
424
|
+
* dropped one by one — a key the compiler cannot trust simply re-decides on the next turn —
|
|
425
|
+
* while a malformed *shape* yields `undefined` so the caller starts from an empty frontier.
|
|
426
|
+
* Never throws: a resume must not fail because a checkpoint was hand-edited. */
|
|
427
|
+
export function parseAttentionStickyFrontier(value) {
|
|
428
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
429
|
+
return undefined;
|
|
430
|
+
const record = value;
|
|
431
|
+
if (!Array.isArray(record.thinking) || !Array.isArray(record.toolCallIds))
|
|
432
|
+
return undefined;
|
|
433
|
+
const thinking = record.thinking.filter((key) => typeof key === "string" && THINKING_KEY_PATTERN.test(key));
|
|
434
|
+
const toolCallIds = record.toolCallIds.filter((id) => typeof id === "string" && id.length > 0 && id.length <= MAX_ATTENTION_TOOL_CALL_ID_CHARS && !id.includes("\u0000"));
|
|
435
|
+
return {
|
|
436
|
+
v: ATTENTION_STICKY_SCHEMA_VERSION,
|
|
437
|
+
// The tail is the newest window; a long-lived checkpoint cannot grow past the live caps.
|
|
438
|
+
thinking: thinking.slice(-MAX_ATTENTION_THINKING_KEYS),
|
|
439
|
+
toolCallIds: toolCallIds.slice(-MAX_ATTENTION_TOOL_CALL_IDS),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
/** Rebuild a frontier from a restored snapshot; callers hold the session that owns it. */
|
|
443
|
+
export function restoreAttentionStickyFrontier(persisted) {
|
|
444
|
+
const frontier = createAttentionStickyFrontier();
|
|
445
|
+
for (const key of persisted.thinking)
|
|
446
|
+
frontier.thinking.add(key);
|
|
447
|
+
for (const id of persisted.toolCallIds)
|
|
448
|
+
frontier.toolCallIds.add(id);
|
|
449
|
+
return frontier;
|
|
450
|
+
}
|
|
451
|
+
/** Consecutive `truncated` turns that arm compaction by default. */
|
|
452
|
+
export const DEFAULT_ATTENTION_TRUNCATION_THRESHOLD = 2;
|
|
453
|
+
export function createAttentionTruncationTrigger(options = {}) {
|
|
454
|
+
const threshold = options.threshold ?? DEFAULT_ATTENTION_TRUNCATION_THRESHOLD;
|
|
455
|
+
if (!Number.isSafeInteger(threshold) || threshold < 1) {
|
|
456
|
+
throw new TypeError("attention truncation threshold must be a positive safe integer");
|
|
457
|
+
}
|
|
458
|
+
let streak = 0;
|
|
459
|
+
return {
|
|
460
|
+
trigger: {
|
|
461
|
+
type: "custom",
|
|
462
|
+
shouldCompact: () => {
|
|
463
|
+
if (streak < threshold)
|
|
464
|
+
return false;
|
|
465
|
+
streak = 0;
|
|
466
|
+
return true;
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
observe: (report) => {
|
|
470
|
+
streak = report.truncated === true ? streak + 1 : 0;
|
|
471
|
+
return streak;
|
|
472
|
+
},
|
|
473
|
+
streak: () => streak,
|
|
474
|
+
reset: () => {
|
|
475
|
+
streak = 0;
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
//# sourceMappingURL=attention-compiler.js.map
|
package/dist/checkpoints.js
CHANGED
|
@@ -23,8 +23,11 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
23
23
|
}
|
|
24
24
|
const id = recordKey(input);
|
|
25
25
|
const existing = records.get(id);
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// A foreign-owned record is indistinguishable from a CAS miss: never an
|
|
27
|
+
// ownership-shaped error (plan 080 Task 3).
|
|
28
|
+
if (existing && !ownershipMatches(input, existing)) {
|
|
29
|
+
throw new CheckpointConflictError(`Checkpoint compare-and-swap failed (expected ${input.expectedVersion ?? 0}, current ${existing.version})`);
|
|
30
|
+
}
|
|
28
31
|
if (input.expectedVersion !== undefined && input.expectedVersion !== (existing?.version ?? 0)) {
|
|
29
32
|
throw new CheckpointConflictError(`Checkpoint compare-and-swap failed (expected ${input.expectedVersion}, current ${existing?.version ?? 0})`);
|
|
30
33
|
}
|
|
@@ -66,8 +69,7 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
66
69
|
const record = records.get(recordKey(input));
|
|
67
70
|
if (!record)
|
|
68
71
|
return null;
|
|
69
|
-
|
|
70
|
-
return record;
|
|
72
|
+
return ownershipMatches(input, record) ? record : null;
|
|
71
73
|
},
|
|
72
74
|
async listCheckpoints(query = {}) {
|
|
73
75
|
throwIfAborted(query.signal);
|
|
@@ -94,8 +96,7 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
94
96
|
const record = records.get(id);
|
|
95
97
|
if (!record)
|
|
96
98
|
return false;
|
|
97
|
-
|
|
98
|
-
return records.delete(id);
|
|
99
|
+
return ownershipMatches(input, record) ? records.delete(id) : false;
|
|
99
100
|
},
|
|
100
101
|
};
|
|
101
102
|
}
|
|
@@ -121,11 +122,6 @@ function ownershipFilterMatches(expected, actual) {
|
|
|
121
122
|
(expected.accountId === undefined || expected.accountId === actual.accountId) &&
|
|
122
123
|
(expected.userId === undefined || expected.userId === actual.userId));
|
|
123
124
|
}
|
|
124
|
-
function assertOwnership(expected, actual) {
|
|
125
|
-
if (!ownershipMatches(expected, actual)) {
|
|
126
|
-
throw new CheckpointConflictError("Checkpoint ownership mismatch");
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
125
|
function cloneJson(value, label) {
|
|
130
126
|
try {
|
|
131
127
|
const encoded = JSON.stringify(value);
|
package/dist/cli-init.js
CHANGED
|
@@ -283,6 +283,7 @@ export async function createInitProject(options, runtime = {
|
|
|
283
283
|
version,
|
|
284
284
|
templateName,
|
|
285
285
|
provider,
|
|
286
|
+
templateDir,
|
|
286
287
|
});
|
|
287
288
|
}
|
|
288
289
|
else {
|
|
@@ -360,12 +361,25 @@ async function planTemplateFiles(templateDir) {
|
|
|
360
361
|
return files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
361
362
|
}
|
|
362
363
|
function buildTokensForTemplate(input) {
|
|
363
|
-
const dependencies = {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
364
|
+
const dependencies = {};
|
|
365
|
+
let packages = ["@arnilo/prism", "@arnilo/prism-memory", "@arnilo/prism-web-tools", "@arnilo/prism-workflows"];
|
|
366
|
+
if (input.templateDir) {
|
|
367
|
+
const manifestPath = join(input.templateDir, "manifest.json");
|
|
368
|
+
if (existsSync(manifestPath)) {
|
|
369
|
+
try {
|
|
370
|
+
const raw = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
371
|
+
if (Array.isArray(raw.packages) && raw.packages.length > 0) {
|
|
372
|
+
packages = raw.packages;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// fallback
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
for (const pkg of packages) {
|
|
381
|
+
dependencies[pkg] = input.version;
|
|
382
|
+
}
|
|
369
383
|
const dependencyLines = Object.entries(dependencies)
|
|
370
384
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
371
385
|
.map(([name, ver]) => ` ${JSON.stringify(name)}: ${JSON.stringify(ver)}`)
|
package/dist/context-budget.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ContextBlock, InputAssemblyLayout, Message, ProviderRequest, Skill, ToolDefinition } from "./contracts.js";
|
|
2
|
-
import { type LoadedSkillSet, type SkillsDisclosure } from "./skill-disclosure.js";
|
|
2
|
+
import { type LoadedSkillSet, type SkillRenderContext, type SkillsDisclosure } from "./skill-disclosure.js";
|
|
3
3
|
/**
|
|
4
4
|
* Host-supplied token estimator. Budget-only: it never reaches billing, provider
|
|
5
5
|
* usage, or the wire — it decides what the assembler evicts and nothing else.
|
|
@@ -76,3 +76,22 @@ export declare function applyContextBudget(options: {
|
|
|
76
76
|
readonly demotedSkillBodies: readonly string[];
|
|
77
77
|
readonly report: ContextBudgetReport;
|
|
78
78
|
};
|
|
79
|
+
/** Everything the assembler will send, in the order it will send it. */
|
|
80
|
+
export interface MeasureInputCostOptions {
|
|
81
|
+
readonly groups: ContextBudgetMessageGroups;
|
|
82
|
+
readonly context?: readonly ContextBlock[];
|
|
83
|
+
readonly skills?: readonly Skill[];
|
|
84
|
+
readonly tools?: readonly ToolDefinition[];
|
|
85
|
+
readonly skillContext?: SkillRenderContext;
|
|
86
|
+
readonly demotedBodies?: ReadonlySet<string>;
|
|
87
|
+
readonly estimateTokens?: TokenEstimator;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* One O(n) cost measurement of the whole request (groups, context, skills, tool declarations).
|
|
91
|
+
* Shared with the attention-compiler gate, which measures once per turn and then subtracts a
|
|
92
|
+
* per-mutation delta instead of re-measuring — the same trick `applyContextBudget` uses.
|
|
93
|
+
*/
|
|
94
|
+
export declare function measureInputCost(options: MeasureInputCostOptions): {
|
|
95
|
+
tokens: number;
|
|
96
|
+
bytes: number;
|
|
97
|
+
};
|
package/dist/context-budget.js
CHANGED
|
@@ -71,7 +71,7 @@ export function applyContextBudget(options) {
|
|
|
71
71
|
const historyCursor = { index: 0 };
|
|
72
72
|
// Measure once, then subtract each dropped item's own estimate (dropNext computes it
|
|
73
73
|
// with the same estimators) — avoids an O(n²) re-scan of the full keep-set per drop.
|
|
74
|
-
const kept =
|
|
74
|
+
const kept = measureInputCost({ groups, context, skills, tools, skillContext, demotedBodies, estimateTokens });
|
|
75
75
|
while (overBudget(kept, budget)) {
|
|
76
76
|
const drop = dropNext(groups, context, skills, layout, skillContext, demotedBodies, historyCursor, estimateTokens);
|
|
77
77
|
if (!drop) {
|
|
@@ -171,6 +171,15 @@ function omission(kind, id, message, estimateTokens) {
|
|
|
171
171
|
byteLength: estimateMessageBytes(message),
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* One O(n) cost measurement of the whole request (groups, context, skills, tool declarations).
|
|
176
|
+
* Shared with the attention-compiler gate, which measures once per turn and then subtracts a
|
|
177
|
+
* per-mutation delta instead of re-measuring — the same trick `applyContextBudget` uses.
|
|
178
|
+
*/
|
|
179
|
+
export function measureInputCost(options) {
|
|
180
|
+
return measureAll(options.groups, options.context ?? [], options.skills ?? [], options.tools, options.skillContext ?? {}, options.demotedBodies ?? EMPTY_DEMOTED_BODIES, options.estimateTokens ?? estimateTextTokens);
|
|
181
|
+
}
|
|
182
|
+
const EMPTY_DEMOTED_BODIES = new Set();
|
|
174
183
|
/**
|
|
175
184
|
* Resolves the budget's estimator, validating each return value: a host estimator that
|
|
176
185
|
* yields NaN/negative/non-finite tokens would make every eviction decision unsound, so it
|
|
@@ -33,6 +33,8 @@ export interface AgentDefinition {
|
|
|
33
33
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
34
34
|
/** Optional escape hatch. When present, overrides declarative resolution. */
|
|
35
35
|
create?(config?: AgentConfig): Promise<Agent> | Agent;
|
|
36
|
+
/** Opt-in attention compiler for the agent this definition resolves to (plan 074 C1). */
|
|
37
|
+
readonly attentionCompiler?: import("./attention.js").AttentionCompilerSetting;
|
|
36
38
|
}
|
|
37
39
|
/** Input to {@link resolveAgentDefinition}. All fields are optional; the host
|
|
38
40
|
* controls scope by which registries it passes. */
|
|
@@ -64,6 +66,9 @@ export interface AgentConfig {
|
|
|
64
66
|
readonly toolsSearch?: import("../tool-search.js").ToolsSearchOptions;
|
|
65
67
|
/** Opt-in projection-only fold for aged large tool results in provider view; store untouched. */
|
|
66
68
|
readonly toolResultFold?: import("../tool-result-fold.js").ToolResultFoldOptions;
|
|
69
|
+
/** Opt-in attention compiler (plan 074): `true` for defaults, an object to tune ratios/depth.
|
|
70
|
+
* Omitted keeps today's request bytes; per-run options may only relax this setting. */
|
|
71
|
+
readonly attentionCompiler?: import("./attention.js").AttentionCompilerSetting;
|
|
67
72
|
readonly inputBuilder?: InputBuilder;
|
|
68
73
|
readonly promptBuilder?: PromptBuilder;
|
|
69
74
|
readonly middleware?: MiddlewareRegistry;
|
|
@@ -116,6 +121,8 @@ export interface SecureAgentOptions extends Omit<AgentConfig, "tools" | "validat
|
|
|
116
121
|
readonly limits: RunLimits;
|
|
117
122
|
readonly definitionRevision: string;
|
|
118
123
|
readonly runState: Omit<AgentRunStateOptions, "definitionRevision" | "interruptBeforeTool">;
|
|
124
|
+
/** Optional host composition readiness assertions evaluated on creation. */
|
|
125
|
+
readonly composition?: import("../host-composition.js").HostCompositionOptions;
|
|
119
126
|
}
|
|
120
127
|
export interface Agent {
|
|
121
128
|
readonly config: AgentConfig;
|