@code-yeongyu/senpi 2026.8.30-2 → 2026.8.30-3

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +51 -1
  2. package/dist/core/agent-session.d.ts +2 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +77 -10
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/builtin/compaction/context-pipeline.d.ts +1 -0
  7. package/dist/core/extensions/builtin/compaction/context-pipeline.d.ts.map +1 -1
  8. package/dist/core/extensions/builtin/compaction/context-pipeline.js +6 -2
  9. package/dist/core/extensions/builtin/compaction/context-pipeline.js.map +1 -1
  10. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  11. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +3 -2
  12. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  13. package/dist/core/extensions/builtin/compaction/idle-retry.d.ts +1 -1
  14. package/dist/core/extensions/builtin/compaction/idle-retry.d.ts.map +1 -1
  15. package/dist/core/extensions/builtin/compaction/idle-retry.js +1 -1
  16. package/dist/core/extensions/builtin/compaction/idle-retry.js.map +1 -1
  17. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  18. package/dist/core/extensions/builtin/compaction/index.js +34 -10
  19. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  20. package/dist/core/extensions/builtin/compaction/model-usability-budget.d.ts +30 -0
  21. package/dist/core/extensions/builtin/compaction/model-usability-budget.d.ts.map +1 -0
  22. package/dist/core/extensions/builtin/compaction/model-usability-budget.js +74 -0
  23. package/dist/core/extensions/builtin/compaction/model-usability-budget.js.map +1 -0
  24. package/dist/core/extensions/builtin/compaction/orchestration.d.ts +3 -13
  25. package/dist/core/extensions/builtin/compaction/orchestration.d.ts.map +1 -1
  26. package/dist/core/extensions/builtin/compaction/orchestration.js +50 -83
  27. package/dist/core/extensions/builtin/compaction/orchestration.js.map +1 -1
  28. package/dist/core/extensions/builtin/compaction/policy.d.ts +8 -1
  29. package/dist/core/extensions/builtin/compaction/policy.d.ts.map +1 -1
  30. package/dist/core/extensions/builtin/compaction/policy.js +12 -3
  31. package/dist/core/extensions/builtin/compaction/policy.js.map +1 -1
  32. package/dist/core/extensions/builtin/compaction/token-budget-reminder.d.ts +8 -2
  33. package/dist/core/extensions/builtin/compaction/token-budget-reminder.d.ts.map +1 -1
  34. package/dist/core/extensions/builtin/compaction/token-budget-reminder.js +12 -5
  35. package/dist/core/extensions/builtin/compaction/token-budget-reminder.js.map +1 -1
  36. package/dist/core/extensions/builtin/compaction/tool-admission.d.ts +12 -21
  37. package/dist/core/extensions/builtin/compaction/tool-admission.d.ts.map +1 -1
  38. package/dist/core/extensions/builtin/compaction/tool-admission.js +27 -63
  39. package/dist/core/extensions/builtin/compaction/tool-admission.js.map +1 -1
  40. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +11 -0
  41. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts.map +1 -1
  42. package/dist/core/extensions/builtin/terminal/runtime-session.js +18 -2
  43. package/dist/core/extensions/builtin/terminal/runtime-session.js.map +1 -1
  44. package/dist/core/sdk.d.ts.map +1 -1
  45. package/dist/core/sdk.js +5 -0
  46. package/dist/core/sdk.js.map +1 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/modes/index.d.ts +1 -1
  52. package/dist/modes/index.d.ts.map +1 -1
  53. package/dist/modes/index.js +1 -1
  54. package/dist/modes/index.js.map +1 -1
  55. package/dist/modes/interactive/interactive-host-runtime.d.ts +13 -2
  56. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  57. package/dist/modes/interactive/interactive-host-runtime.js +317 -72
  58. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  59. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  60. package/dist/modes/rpc/connection-handler.js +17 -4
  61. package/dist/modes/rpc/connection-handler.js.map +1 -1
  62. package/dist/modes/rpc/rpc-client.d.ts +10 -0
  63. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  64. package/dist/modes/rpc/rpc-client.js +32 -10
  65. package/dist/modes/rpc/rpc-client.js.map +1 -1
  66. package/docs/rpc.md +4 -0
  67. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  68. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  69. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  70. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  71. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  72. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  73. package/node_modules/@earendil-works/pi-pty/dist/screen-operations.d.ts +42 -0
  74. package/node_modules/@earendil-works/pi-pty/dist/screen-operations.d.ts.map +1 -0
  75. package/node_modules/@earendil-works/pi-pty/dist/screen-operations.js +34 -0
  76. package/node_modules/@earendil-works/pi-pty/dist/screen-operations.js.map +1 -0
  77. package/node_modules/@earendil-works/pi-pty/dist/screen-text.d.ts +8 -0
  78. package/node_modules/@earendil-works/pi-pty/dist/screen-text.d.ts.map +1 -0
  79. package/node_modules/@earendil-works/pi-pty/dist/screen-text.js +51 -0
  80. package/node_modules/@earendil-works/pi-pty/dist/screen-text.js.map +1 -0
  81. package/node_modules/@earendil-works/pi-pty/dist/screen.d.ts +45 -0
  82. package/node_modules/@earendil-works/pi-pty/dist/screen.d.ts.map +1 -1
  83. package/node_modules/@earendil-works/pi-pty/dist/screen.js +178 -55
  84. package/node_modules/@earendil-works/pi-pty/dist/screen.js.map +1 -1
  85. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  86. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  87. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  88. package/package.json +7 -7
@@ -1,17 +1,13 @@
1
- import { tmpdir } from "node:os";
2
- import { join } from "node:path";
1
+ import { copyContextProvenance } from "@earendil-works/pi-ai";
3
2
  import { estimateTokens } from "../../../compaction/index.js";
4
3
  import { shouldWarmAtIdle } from "./idle.js";
5
4
  import * as policy from "./policy.js";
6
5
  import { isWarmResultStale, isWithinGraceBand, resolveSpeculationLeadTokens } from "./speculation-lead.js";
7
- import { admitToolResult, estimateAdmissionMarkerTokens, resolveToolResultAdmissionCapTokens, } from "./tool-admission.js";
8
- export function estimateTextTokens(text) {
9
- return estimateTokens({ role: "user", content: text, timestamp: 0 });
10
- }
6
+ import { admitToolResult, admitToolResultWithinBudget, resolveToolResultAdmissionCapTokens } from "./tool-admission.js";
11
7
  export function resolveCompactionGeometry(input) {
12
8
  const thresholdTokens = input.contextWindow * policy.computeEffectiveThreshold(input.contextWindow, input.lastYield);
13
9
  return {
14
- reserveTokens: policy.resolveEffectiveReserveTokens(input.contextWindow, input.settings.reserveTokens, input.settings.reserveScalingEnabled !== false),
10
+ reserveTokens: policy.resolveEffectiveReserveTokens(input.contextWindow, input.settings),
15
11
  thresholdTokens,
16
12
  leadTokens: resolveSpeculationLeadTokens(thresholdTokens, input.settings.speculativeLeadTokens),
17
13
  };
@@ -21,6 +17,25 @@ export function shouldDeferGraceBand(input) {
21
17
  input.graceBandEnabled !== false &&
22
18
  isWithinGraceBand(input.tokens, input.thresholdTokens, input.leadTokens, input.contextWindow, input.reserveTokens));
23
19
  }
20
+ export function injectTokenBudgetReminder(messages, reminder) {
21
+ if (!reminder)
22
+ return messages;
23
+ for (let index = messages.length - 1; index >= 0; index--) {
24
+ const message = messages[index];
25
+ if (message?.role !== "user")
26
+ continue;
27
+ const content = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
28
+ const firstPart = content[0];
29
+ if (firstPart?.type === "text" && firstPart.text === reminder)
30
+ return messages;
31
+ const reminded = copyContextProvenance(message, {
32
+ ...message,
33
+ content: [{ type: "text", text: reminder }, ...content],
34
+ });
35
+ return messages.map((candidate, candidateIndex) => (candidateIndex === index ? reminded : candidate));
36
+ }
37
+ return messages;
38
+ }
24
39
  export function resolveBeforeAgentStartMessage(input) {
25
40
  if (!input.reminder || input.reminderEnabled === false)
26
41
  return input.message;
@@ -41,96 +56,48 @@ export function resolveIdleWarmAction(decision, job) {
41
56
  const currentTokens = decision.usage?.tokens ?? 0;
42
57
  return isWarmResultStale(job.armedAtTokens, currentTokens, decision.settings.keepRecentTokens) ? "replace" : "none";
43
58
  }
44
- export function admitContextToolResult(text, contextWindow, spillDir, capTokens) {
45
- const result = admitToolResult({ text, contextWindow, spillDir, capTokens });
46
- return { text: result.text, admitted: result.spilled, spillPath: result.spillPath };
47
- }
48
- export function buildToolResultOmissionLine(omitted) {
49
- return `[tool-result admission: ${omitted.length} later text part(s) omitted (~${omitted.reduce((sum, item) => sum + item.tokens, 0)} tokens); full outputs at: ${omitted.map((item) => item.path).join("; ")} - read with the read tool]`;
50
- }
51
- export function estimateToolResultOmissionTokens(omitted) {
52
- return estimateTextTokens(buildToolResultOmissionLine(omitted));
59
+ export function admitContextToolResult(text, contextWindow) {
60
+ const result = admitToolResult({ text, contextWindow });
61
+ return { text: result.text, admitted: result.projected };
53
62
  }
54
- export function resolveMultipartRetainedBound(capTokens, omission, textPartCount) {
55
- return Math.max(capTokens, estimateTextTokens(`${"\n".repeat(Math.max(1, textPartCount))}${omission}`));
56
- }
57
- export function admitContextToolResults(messages, contextWindow, enabled, capOverride, spillDir = join(tmpdir(), "senpi-tool-spill")) {
63
+ export function admitContextToolResults(messages, contextWindow, enabled) {
58
64
  if (!enabled)
59
65
  return messages;
60
66
  return messages.map((message) => {
61
67
  if (message.role !== "toolResult")
62
68
  return message;
63
69
  if (typeof message.content === "string") {
64
- const admitted = admitContextToolResult(message.content, contextWindow, spillDir);
70
+ const admitted = admitContextToolResult(message.content, contextWindow);
65
71
  return admitted.admitted ? { ...message, content: [{ type: "text", text: admitted.text }] } : message;
66
72
  }
67
- const resultCap = capOverride ?? resolveToolResultAdmissionCapTokens(contextWindow);
68
- const joinedText = message.content
69
- .filter((part) => part.type === "text")
70
- .map((part) => part.text ?? "")
71
- .join("\n");
72
- if (estimateTextTokens(joinedText) <= resultCap)
73
+ const textTokens = message.content.map((part) => part.type === "text" ? estimateTokens({ role: "user", content: part.text, timestamp: 0 }) : 0);
74
+ const totalTextTokens = textTokens.reduce((total, tokens) => total + tokens, 0);
75
+ const capTokens = resolveToolResultAdmissionCapTokens(contextWindow);
76
+ if (totalTextTokens <= capTokens)
73
77
  return message;
74
- let changed = false;
75
- const textParts = message.content.filter((part) => part.type === "text");
76
- const hasSharedOmission = textParts.some((part) => part.text) && textParts.length >= 2;
77
- let remainingTokens = resultCap;
78
- const omitted = [];
79
- const content = message.content.map((part) => {
80
- if (part.type !== "text" || !part.text)
78
+ const preservedTokens = textTokens.reduce((total, tokens) => total + (tokens <= capTokens ? tokens : 0), 0);
79
+ const preserveUnderCap = preservedTokens <= capTokens;
80
+ let remainingBudget = preserveUnderCap ? capTokens - preservedTokens : capTokens;
81
+ let remainingOversizedTokens = preserveUnderCap
82
+ ? textTokens.reduce((total, tokens) => total + (tokens > capTokens ? tokens : 0), 0)
83
+ : totalTextTokens;
84
+ let projected = false;
85
+ const content = message.content.map((part, index) => {
86
+ if (part.type !== "text")
87
+ return part;
88
+ const partTokens = textTokens[index] ?? 0;
89
+ if (preserveUnderCap && partTokens <= capTokens)
81
90
  return part;
82
- const partTokens = estimateTextTokens(part.text);
83
- const omissionReserve = hasSharedOmission
84
- ? 256 + estimateAdmissionMarkerTokens(partTokens, join(spillDir, "tool-result-0000000000-000000.txt"))
85
- : 0;
86
- if (partTokens <= remainingTokens - omissionReserve) {
87
- remainingTokens -= partTokens;
91
+ const budget = Math.floor((remainingBudget * partTokens) / Math.max(1, remainingOversizedTokens));
92
+ const admitted = admitToolResultWithinBudget(part.text, budget);
93
+ remainingOversizedTokens -= partTokens;
94
+ remainingBudget -= estimateTokens({ role: "user", content: admitted.text, timestamp: 0 });
95
+ if (!admitted.projected)
88
96
  return part;
89
- }
90
- const admitted = admitContextToolResult(part.text, contextWindow, spillDir, Math.max(0, remainingTokens - omissionReserve));
91
- changed = true;
92
- remainingTokens = Math.max(0, remainingTokens - estimateTextTokens(admitted.text));
93
- if (admitted.spillPath)
94
- omitted.push({ tokens: partTokens, path: admitted.spillPath });
97
+ projected = true;
95
98
  return { ...part, text: admitted.text };
96
99
  });
97
- let omissionCost = 0;
98
- if (omitted.length > 0) {
99
- const omission = buildToolResultOmissionLine(omitted);
100
- omissionCost = resolveMultipartRetainedBound(resultCap, omission, textParts.length);
101
- const lastText = content.findLastIndex((part) => part.type === "text" && part.text);
102
- const target = lastText >= 0 ? lastText : content.findIndex((part) => part.type === "text");
103
- const targetPart = target >= 0 ? content[target] : undefined;
104
- if (target >= 0 && targetPart?.type === "text") {
105
- content[target] = { ...targetPart, text: `${targetPart.text ?? ""}\n${omission}` };
106
- }
107
- }
108
- // Contract: total retained text tokens per tool result <= cap, and every omitted byte is reachable via a spill path named within the result.
109
- const aggregateTokens = () => estimateTextTokens(content
110
- .filter((part) => part.type === "text")
111
- .map((part) => part.text ?? "")
112
- .join("\n"));
113
- const retainedLimit = omissionCost || resultCap;
114
- while (aggregateTokens() > retainedLimit) {
115
- const target = content.findLastIndex((part) => {
116
- if (part.type !== "text" || !part.text)
117
- return false;
118
- const omissionIndex = part.text.indexOf("[tool-result admission:");
119
- return omissionIndex < 0 || part.text.slice(0, omissionIndex).trim().length > 0;
120
- });
121
- if (target < 0)
122
- break;
123
- const part = content[target];
124
- if (part.type !== "text")
125
- break;
126
- const text = part.text ?? "";
127
- const omissionIndex = text.indexOf("[tool-result admission:");
128
- const prefix = omissionIndex >= 0 ? text.slice(0, omissionIndex) : text;
129
- const suffix = omissionIndex >= 0 ? text.slice(omissionIndex) : "";
130
- const nextText = `${prefix.slice(0, Math.floor(prefix.length / 2))}${suffix}`;
131
- content[target] = { ...part, text: nextText === text ? "" : nextText };
132
- }
133
- return changed ? { ...message, content } : message;
100
+ return projected ? { ...message, content } : message;
134
101
  });
135
102
  }
136
103
  //# sourceMappingURL=orchestration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"orchestration.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/orchestration.ts"],"names":[],"mappings":"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","sourcesContent":["import { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport { type CompactionPreparation, estimateTokens } from \"../../../compaction/index.ts\";\nimport type { BeforeAgentStartEventResult } from \"../../types.ts\";\nimport { type IdleCompactionDecision, shouldWarmAtIdle } from \"./idle.ts\";\nimport * as policy from \"./policy.ts\";\nimport { isWarmResultStale, isWithinGraceBand, resolveSpeculationLeadTokens } from \"./speculation-lead.ts\";\nimport {\n\tadmitToolResult,\n\testimateAdmissionMarkerTokens,\n\tresolveToolResultAdmissionCapTokens,\n} from \"./tool-admission.ts\";\n\nexport interface CompactionGeometry {\n\treserveTokens: number;\n\tthresholdTokens: number;\n\tleadTokens: number;\n}\n\nexport function estimateTextTokens(text: string): number {\n\treturn estimateTokens({ role: \"user\", content: text, timestamp: 0 });\n}\n\nexport function resolveCompactionGeometry(input: {\n\tcontextWindow: number;\n\tsettings: CompactionPreparation[\"settings\"];\n\tlastYield?: { savedTokens: number; tokensBefore: number };\n}): CompactionGeometry {\n\tconst thresholdTokens = input.contextWindow * policy.computeEffectiveThreshold(input.contextWindow, input.lastYield);\n\treturn {\n\t\treserveTokens: policy.resolveEffectiveReserveTokens(\n\t\t\tinput.contextWindow,\n\t\t\tinput.settings.reserveTokens,\n\t\t\tinput.settings.reserveScalingEnabled !== false,\n\t\t),\n\t\tthresholdTokens,\n\t\tleadTokens: resolveSpeculationLeadTokens(thresholdTokens, input.settings.speculativeLeadTokens),\n\t};\n}\n\nexport function shouldDeferGraceBand(input: {\n\ttokens: number;\n\tthresholdTokens: number;\n\tleadTokens: number;\n\tcontextWindow: number;\n\treserveTokens: number;\n\tcompactionInFlight: boolean;\n\tgraceBandEnabled?: boolean;\n}): boolean {\n\treturn (\n\t\tinput.compactionInFlight &&\n\t\tinput.graceBandEnabled !== false &&\n\t\tisWithinGraceBand(input.tokens, input.thresholdTokens, input.leadTokens, input.contextWindow, input.reserveTokens)\n\t);\n}\n\nexport function resolveBeforeAgentStartMessage(input: {\n\tmessage?: BeforeAgentStartEventResult[\"message\"];\n\treminder?: string;\n\treminderEnabled?: boolean;\n}): BeforeAgentStartEventResult[\"message\"] | undefined {\n\tif (!input.reminder || input.reminderEnabled === false) return input.message;\n\tif (!input.message) return undefined;\n\treturn { ...input.message, content: `${input.message.content}\\n\\n${input.reminder}` };\n}\n\nexport function resolveReminderSystemPrompt(input: {\n\tsystemPrompt: string;\n\treminder?: string;\n\treminderEnabled?: boolean;\n}): string | undefined {\n\tif (!input.reminder || input.reminderEnabled === false) return undefined;\n\treturn `${input.systemPrompt}\\n\\n${input.reminder}`;\n}\n\nexport function resolveIdleWarmAction(\n\tdecision: IdleCompactionDecision,\n\tjob: { armedAtTokens: number } | undefined,\n): \"none\" | \"start\" | \"replace\" {\n\tif (!shouldWarmAtIdle(decision)) return \"none\";\n\tif (!job) return \"start\";\n\tconst currentTokens = decision.usage?.tokens ?? 0;\n\treturn isWarmResultStale(job.armedAtTokens, currentTokens, decision.settings.keepRecentTokens) ? \"replace\" : \"none\";\n}\n\nexport function admitContextToolResult(\n\ttext: string,\n\tcontextWindow: number,\n\tspillDir: string,\n\tcapTokens?: number,\n): { text: string; admitted: boolean; spillPath?: string } {\n\tconst result = admitToolResult({ text, contextWindow, spillDir, capTokens });\n\treturn { text: result.text, admitted: result.spilled, spillPath: result.spillPath };\n}\n\nexport function buildToolResultOmissionLine(omitted: ReadonlyArray<{ tokens: number; path: string }>): string {\n\treturn `[tool-result admission: ${omitted.length} later text part(s) omitted (~${omitted.reduce((sum, item) => sum + item.tokens, 0)} tokens); full outputs at: ${omitted.map((item) => item.path).join(\"; \")} - read with the read tool]`;\n}\n\nexport function estimateToolResultOmissionTokens(omitted: ReadonlyArray<{ tokens: number; path: string }>): number {\n\treturn estimateTextTokens(buildToolResultOmissionLine(omitted));\n}\n\nexport function resolveMultipartRetainedBound(capTokens: number, omission: string, textPartCount: number): number {\n\treturn Math.max(capTokens, estimateTextTokens(`${\"\\n\".repeat(Math.max(1, textPartCount))}${omission}`));\n}\n\nexport function admitContextToolResults(\n\tmessages: AgentMessage[],\n\tcontextWindow: number,\n\tenabled: boolean,\n\tcapOverride?: number,\n\tspillDir = join(tmpdir(), \"senpi-tool-spill\"),\n): AgentMessage[] {\n\tif (!enabled) return messages;\n\treturn messages.map((message) => {\n\t\tif (message.role !== \"toolResult\") return message;\n\t\tif (typeof message.content === \"string\") {\n\t\t\tconst admitted = admitContextToolResult(message.content, contextWindow, spillDir);\n\t\t\treturn admitted.admitted ? { ...message, content: [{ type: \"text\" as const, text: admitted.text }] } : message;\n\t\t}\n\t\tconst resultCap = capOverride ?? resolveToolResultAdmissionCapTokens(contextWindow);\n\t\tconst joinedText = message.content\n\t\t\t.filter((part) => part.type === \"text\")\n\t\t\t.map((part) => part.text ?? \"\")\n\t\t\t.join(\"\\n\");\n\t\tif (estimateTextTokens(joinedText) <= resultCap) return message;\n\t\tlet changed = false;\n\t\tconst textParts = message.content.filter((part) => part.type === \"text\");\n\t\tconst hasSharedOmission = textParts.some((part) => part.text) && textParts.length >= 2;\n\t\tlet remainingTokens = resultCap;\n\t\tconst omitted: Array<{ tokens: number; path: string }> = [];\n\t\tconst content = message.content.map((part) => {\n\t\t\tif (part.type !== \"text\" || !part.text) return part;\n\t\t\tconst partTokens = estimateTextTokens(part.text);\n\t\t\tconst omissionReserve = hasSharedOmission\n\t\t\t\t? 256 + estimateAdmissionMarkerTokens(partTokens, join(spillDir, \"tool-result-0000000000-000000.txt\"))\n\t\t\t\t: 0;\n\t\t\tif (partTokens <= remainingTokens - omissionReserve) {\n\t\t\t\tremainingTokens -= partTokens;\n\t\t\t\treturn part;\n\t\t\t}\n\t\t\tconst admitted = admitContextToolResult(\n\t\t\t\tpart.text,\n\t\t\t\tcontextWindow,\n\t\t\t\tspillDir,\n\t\t\t\tMath.max(0, remainingTokens - omissionReserve),\n\t\t\t);\n\t\t\tchanged = true;\n\t\t\tremainingTokens = Math.max(0, remainingTokens - estimateTextTokens(admitted.text));\n\t\t\tif (admitted.spillPath) omitted.push({ tokens: partTokens, path: admitted.spillPath });\n\t\t\treturn { ...part, text: admitted.text };\n\t\t});\n\t\tlet omissionCost = 0;\n\t\tif (omitted.length > 0) {\n\t\t\tconst omission = buildToolResultOmissionLine(omitted);\n\t\t\tomissionCost = resolveMultipartRetainedBound(resultCap, omission, textParts.length);\n\t\t\tconst lastText = content.findLastIndex((part) => part.type === \"text\" && part.text);\n\t\t\tconst target = lastText >= 0 ? lastText : content.findIndex((part) => part.type === \"text\");\n\t\t\tconst targetPart = target >= 0 ? content[target] : undefined;\n\t\t\tif (target >= 0 && targetPart?.type === \"text\") {\n\t\t\t\tcontent[target] = { ...targetPart, text: `${targetPart.text ?? \"\"}\\n${omission}` };\n\t\t\t}\n\t\t}\n\t\t// Contract: total retained text tokens per tool result <= cap, and every omitted byte is reachable via a spill path named within the result.\n\t\tconst aggregateTokens = () =>\n\t\t\testimateTextTokens(\n\t\t\t\tcontent\n\t\t\t\t\t.filter((part) => part.type === \"text\")\n\t\t\t\t\t.map((part) => part.text ?? \"\")\n\t\t\t\t\t.join(\"\\n\"),\n\t\t\t);\n\t\tconst retainedLimit = omissionCost || resultCap;\n\t\twhile (aggregateTokens() > retainedLimit) {\n\t\t\tconst target = content.findLastIndex((part) => {\n\t\t\t\tif (part.type !== \"text\" || !part.text) return false;\n\t\t\t\tconst omissionIndex = part.text.indexOf(\"[tool-result admission:\");\n\t\t\t\treturn omissionIndex < 0 || part.text.slice(0, omissionIndex).trim().length > 0;\n\t\t\t});\n\t\t\tif (target < 0) break;\n\t\t\tconst part = content[target];\n\t\t\tif (part.type !== \"text\") break;\n\t\t\tconst text = part.text ?? \"\";\n\t\t\tconst omissionIndex = text.indexOf(\"[tool-result admission:\");\n\t\t\tconst prefix = omissionIndex >= 0 ? text.slice(0, omissionIndex) : text;\n\t\t\tconst suffix = omissionIndex >= 0 ? text.slice(omissionIndex) : \"\";\n\t\t\tconst nextText = `${prefix.slice(0, Math.floor(prefix.length / 2))}${suffix}`;\n\t\t\tcontent[target] = { ...part, text: nextText === text ? \"\" : nextText };\n\t\t}\n\t\treturn changed ? { ...message, content } : message;\n\t});\n}\n"]}
1
+ {"version":3,"file":"orchestration.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/orchestration.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport { copyContextProvenance } from \"@earendil-works/pi-ai\";\nimport { type CompactionPreparation, estimateTokens } from \"../../../compaction/index.ts\";\nimport type { BeforeAgentStartEventResult } from \"../../types.ts\";\nimport { type IdleCompactionDecision, shouldWarmAtIdle } from \"./idle.ts\";\nimport * as policy from \"./policy.ts\";\nimport { isWarmResultStale, isWithinGraceBand, resolveSpeculationLeadTokens } from \"./speculation-lead.ts\";\nimport { admitToolResult, admitToolResultWithinBudget, resolveToolResultAdmissionCapTokens } from \"./tool-admission.ts\";\n\nexport interface CompactionGeometry {\n\treserveTokens: number;\n\tthresholdTokens: number;\n\tleadTokens: number;\n}\n\nexport function resolveCompactionGeometry(input: {\n\tcontextWindow: number;\n\tsettings: CompactionPreparation[\"settings\"];\n\tlastYield?: { savedTokens: number; tokensBefore: number };\n}): CompactionGeometry {\n\tconst thresholdTokens = input.contextWindow * policy.computeEffectiveThreshold(input.contextWindow, input.lastYield);\n\treturn {\n\t\treserveTokens: policy.resolveEffectiveReserveTokens(input.contextWindow, input.settings),\n\t\tthresholdTokens,\n\t\tleadTokens: resolveSpeculationLeadTokens(thresholdTokens, input.settings.speculativeLeadTokens),\n\t};\n}\n\nexport function shouldDeferGraceBand(input: {\n\ttokens: number;\n\tthresholdTokens: number;\n\tleadTokens: number;\n\tcontextWindow: number;\n\treserveTokens: number;\n\tcompactionInFlight: boolean;\n\tgraceBandEnabled?: boolean;\n}): boolean {\n\treturn (\n\t\tinput.compactionInFlight &&\n\t\tinput.graceBandEnabled !== false &&\n\t\tisWithinGraceBand(input.tokens, input.thresholdTokens, input.leadTokens, input.contextWindow, input.reserveTokens)\n\t);\n}\n\nexport function injectTokenBudgetReminder(messages: AgentMessage[], reminder?: string): AgentMessage[] {\n\tif (!reminder) return messages;\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (message?.role !== \"user\") continue;\n\t\tconst content =\n\t\t\ttypeof message.content === \"string\" ? [{ type: \"text\" as const, text: message.content }] : message.content;\n\t\tconst firstPart = content[0];\n\t\tif (firstPart?.type === \"text\" && firstPart.text === reminder) return messages;\n\t\tconst reminded = copyContextProvenance(message, {\n\t\t\t...message,\n\t\t\tcontent: [{ type: \"text\" as const, text: reminder }, ...content],\n\t\t});\n\t\treturn messages.map((candidate, candidateIndex) => (candidateIndex === index ? reminded : candidate));\n\t}\n\treturn messages;\n}\n\nexport function resolveBeforeAgentStartMessage(input: {\n\tmessage?: BeforeAgentStartEventResult[\"message\"];\n\treminder?: string;\n\treminderEnabled?: boolean;\n}): BeforeAgentStartEventResult[\"message\"] | undefined {\n\tif (!input.reminder || input.reminderEnabled === false) return input.message;\n\tif (!input.message) return undefined;\n\treturn { ...input.message, content: `${input.message.content}\\n\\n${input.reminder}` };\n}\n\nexport function resolveReminderSystemPrompt(input: {\n\tsystemPrompt: string;\n\treminder?: string;\n\treminderEnabled?: boolean;\n}): string | undefined {\n\tif (!input.reminder || input.reminderEnabled === false) return undefined;\n\treturn `${input.systemPrompt}\\n\\n${input.reminder}`;\n}\n\nexport function resolveIdleWarmAction(\n\tdecision: IdleCompactionDecision,\n\tjob: { armedAtTokens: number } | undefined,\n): \"none\" | \"start\" | \"replace\" {\n\tif (!shouldWarmAtIdle(decision)) return \"none\";\n\tif (!job) return \"start\";\n\tconst currentTokens = decision.usage?.tokens ?? 0;\n\treturn isWarmResultStale(job.armedAtTokens, currentTokens, decision.settings.keepRecentTokens) ? \"replace\" : \"none\";\n}\n\nexport function admitContextToolResult(text: string, contextWindow: number): { text: string; admitted: boolean } {\n\tconst result = admitToolResult({ text, contextWindow });\n\treturn { text: result.text, admitted: result.projected };\n}\n\nexport function admitContextToolResults(\n\tmessages: AgentMessage[],\n\tcontextWindow: number,\n\tenabled: boolean,\n): AgentMessage[] {\n\tif (!enabled) return messages;\n\treturn messages.map((message) => {\n\t\tif (message.role !== \"toolResult\") return message;\n\t\tif (typeof message.content === \"string\") {\n\t\t\tconst admitted = admitContextToolResult(message.content, contextWindow);\n\t\t\treturn admitted.admitted ? { ...message, content: [{ type: \"text\", text: admitted.text }] } : message;\n\t\t}\n\n\t\tconst textTokens = message.content.map((part) =>\n\t\t\tpart.type === \"text\" ? estimateTokens({ role: \"user\", content: part.text, timestamp: 0 }) : 0,\n\t\t);\n\t\tconst totalTextTokens = textTokens.reduce((total, tokens) => total + tokens, 0);\n\t\tconst capTokens = resolveToolResultAdmissionCapTokens(contextWindow);\n\t\tif (totalTextTokens <= capTokens) return message;\n\n\t\tconst preservedTokens = textTokens.reduce((total, tokens) => total + (tokens <= capTokens ? tokens : 0), 0);\n\t\tconst preserveUnderCap = preservedTokens <= capTokens;\n\t\tlet remainingBudget = preserveUnderCap ? capTokens - preservedTokens : capTokens;\n\t\tlet remainingOversizedTokens = preserveUnderCap\n\t\t\t? textTokens.reduce((total, tokens) => total + (tokens > capTokens ? tokens : 0), 0)\n\t\t\t: totalTextTokens;\n\t\tlet projected = false;\n\t\tconst content = message.content.map((part, index) => {\n\t\t\tif (part.type !== \"text\") return part;\n\t\t\tconst partTokens = textTokens[index] ?? 0;\n\t\t\tif (preserveUnderCap && partTokens <= capTokens) return part;\n\t\t\tconst budget = Math.floor((remainingBudget * partTokens) / Math.max(1, remainingOversizedTokens));\n\t\t\tconst admitted = admitToolResultWithinBudget(part.text, budget);\n\t\t\tremainingOversizedTokens -= partTokens;\n\t\t\tremainingBudget -= estimateTokens({ role: \"user\", content: admitted.text, timestamp: 0 });\n\t\t\tif (!admitted.projected) return part;\n\t\t\tprojected = true;\n\t\t\treturn { ...part, text: admitted.text };\n\t\t});\n\t\treturn projected ? { ...message, content } : message;\n\t});\n}\n"]}
@@ -7,11 +7,18 @@ export interface CompactionYield {
7
7
  }
8
8
  export declare function baseThresholdRatioForWindow(contextWindow: number): number;
9
9
  export declare function resolveReserveTokens(contextWindow: number, configuredReserve: number): number;
10
+ /**
11
+ * The single effective reserve for one window. Every consumer that reasons about
12
+ * the usable prompt budget — the hard-limit valve, restoration, and deterministic
13
+ * fallback acceptance — must resolve it here so acceptance can never admit a
14
+ * context the hard valve would immediately compact again. Scaling is idempotent,
15
+ * so re-resolving an already-scaled reserve never double-scales it.
16
+ */
17
+ export declare function resolveEffectiveReserveTokens(contextWindow: number, settings: Pick<CompactionSettings, "reserveTokens" | "reserveScalingEnabled">): number;
10
18
  export declare function computeAdaptiveThresholdRatio(contextWindow: number, priorCompactionSavedTokens?: number): number;
11
19
  export declare function computeEffectiveThreshold(contextWindow: number, lastYield?: CompactionYield): number;
12
20
  export declare function computeEffectiveKeepRecentTokens(setting: number, contextWindow: number, thresholdRatio: number, margin?: number): number;
13
21
  export declare function shouldStartSpeculativeCompaction(usage: ContextUsage, contextWindow: number, settings: CompactionSettings, lastYield?: CompactionYield, leadTokens?: number): boolean;
14
- export declare function resolveEffectiveReserveTokens(contextWindow: number, configuredReserve: number, reserveScalingEnabled?: boolean): number;
15
22
  export declare function isAtHardLimit(usage: ContextUsage, contextWindow: number, reserveTokens: number, additionalTokens?: number): boolean;
16
23
  export declare function shouldTriggerCompaction(usage: ContextUsage, contextWindow: number, settings: CompactionSettings, lastYield?: CompactionYield): boolean;
17
24
  //# sourceMappingURL=policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAanD,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACrB;AAyCD,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAoBzE;AAED,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAK7F;AAED,wBAAgB,6BAA6B,CAAC,aAAa,EAAE,MAAM,EAAE,0BAA0B,CAAC,EAAE,MAAM,GAAG,MAAM,CAQhH;AAED,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,eAAe,GAAG,MAAM,CAMpG;AAED,wBAAgB,gCAAgC,CAC/C,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,MAAM,SAAO,GACX,MAAM,CAUR;AAED,wBAAgB,gCAAgC,CAC/C,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,kBAAkB,EAC5B,SAAS,CAAC,EAAE,eAAe,EAC3B,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAYT;AAED,wBAAgB,6BAA6B,CAC5C,aAAa,EAAE,MAAM,EACrB,iBAAiB,EAAE,MAAM,EACzB,qBAAqB,UAAO,GAC1B,MAAM,CAER;AAED,wBAAgB,aAAa,CAC5B,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,gBAAgB,SAAI,GAClB,OAAO,CAET;AAED,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,kBAAkB,EAC5B,SAAS,CAAC,EAAE,eAAe,GACzB,OAAO,CAMT"}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAanD,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACrB;AAyCD,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAoBzE;AAED,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAK7F;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC5C,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,IAAI,CAAC,kBAAkB,EAAE,eAAe,GAAG,uBAAuB,CAAC,GAC3E,MAAM,CAIR;AAED,wBAAgB,6BAA6B,CAAC,aAAa,EAAE,MAAM,EAAE,0BAA0B,CAAC,EAAE,MAAM,GAAG,MAAM,CAQhH;AAED,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,eAAe,GAAG,MAAM,CAMpG;AAED,wBAAgB,gCAAgC,CAC/C,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,MAAM,SAAO,GACX,MAAM,CAUR;AAED,wBAAgB,gCAAgC,CAC/C,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,kBAAkB,EAC5B,SAAS,CAAC,EAAE,eAAe,EAC3B,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAYT;AAED,wBAAgB,aAAa,CAC5B,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,gBAAgB,SAAI,GAClB,OAAO,CAET;AAED,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,kBAAkB,EAC5B,SAAS,CAAC,EAAE,eAAe,GACzB,OAAO,CAMT"}
@@ -62,6 +62,18 @@ export function baseThresholdRatioForWindow(contextWindow) {
62
62
  export function resolveReserveTokens(contextWindow, configuredReserve) {
63
63
  return Math.max(configuredReserve, Math.min(Math.floor(RESERVE_WINDOW_FRACTION * contextWindow), MAX_SCALED_RESERVE_TOKENS));
64
64
  }
65
+ /**
66
+ * The single effective reserve for one window. Every consumer that reasons about
67
+ * the usable prompt budget — the hard-limit valve, restoration, and deterministic
68
+ * fallback acceptance — must resolve it here so acceptance can never admit a
69
+ * context the hard valve would immediately compact again. Scaling is idempotent,
70
+ * so re-resolving an already-scaled reserve never double-scales it.
71
+ */
72
+ export function resolveEffectiveReserveTokens(contextWindow, settings) {
73
+ return settings.reserveScalingEnabled === false
74
+ ? settings.reserveTokens
75
+ : resolveReserveTokens(contextWindow, settings.reserveTokens);
76
+ }
65
77
  export function computeAdaptiveThresholdRatio(contextWindow, priorCompactionSavedTokens) {
66
78
  const ratio = baseThresholdRatioForWindow(contextWindow);
67
79
  if (priorCompactionSavedTokens === undefined) {
@@ -94,9 +106,6 @@ export function shouldStartSpeculativeCompaction(usage, contextWindow, settings,
94
106
  const fraction = settings.speculativeFraction ?? SPECULATIVE_FRACTION;
95
107
  return usage.tokens >= contextWindow * computeEffectiveThreshold(contextWindow, lastYield) * fraction;
96
108
  }
97
- export function resolveEffectiveReserveTokens(contextWindow, configuredReserve, reserveScalingEnabled = true) {
98
- return reserveScalingEnabled ? resolveReserveTokens(contextWindow, configuredReserve) : configuredReserve;
99
- }
100
109
  export function isAtHardLimit(usage, contextWindow, reserveTokens, additionalTokens = 0) {
101
110
  return usage.tokens !== null && usage.tokens + additionalTokens + reserveTokens >= contextWindow;
102
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CompactionSettings } from \"../../../compaction/index.ts\";\nimport type { ContextUsage } from \"../../types.ts\";\n\nconst MIN_ADAPTIVE_THRESHOLD_RATIO = 0.4;\nconst MAX_ADAPTIVE_THRESHOLD_RATIO = 0.85;\nconst HIGH_YIELD_SAVING_RATIO = 0.5;\nconst LOW_YIELD_SAVING_RATIO = 0.1;\nconst YIELD_ADJUSTMENT_RATIO = 0.05;\nconst MIN_EFFECTIVE_KEEP_RECENT_TOKENS = 1024;\nconst RESERVE_WINDOW_FRACTION = 0.04;\nconst MAX_SCALED_RESERVE_TOKENS = 49_152;\nconst KEEP_RECENT_WINDOW_FRACTION = 0.05;\nconst MAX_SCALED_KEEP_RECENT_TOKENS = 60_000;\n\nexport const SPECULATIVE_FRACTION = 0.75;\n\nexport interface CompactionYield {\n\tsavedTokens: number;\n\ttokensBefore: number;\n}\n\nfunction clampThresholdRatio(ratio: number): number {\n\treturn Math.min(MAX_ADAPTIVE_THRESHOLD_RATIO, Math.max(MIN_ADAPTIVE_THRESHOLD_RATIO, ratio));\n}\n\nfunction adjustThresholdRatio(ratio: number, savedTokens: number, contextWindow: number): number {\n\tif (contextWindow <= 0) {\n\t\treturn ratio;\n\t}\n\n\tconst savedRatio = savedTokens / contextWindow;\n\tif (savedRatio > HIGH_YIELD_SAVING_RATIO) {\n\t\treturn clampThresholdRatio(ratio - YIELD_ADJUSTMENT_RATIO);\n\t}\n\tif (savedRatio < LOW_YIELD_SAVING_RATIO) {\n\t\treturn Math.min(baseThresholdRatioForWindow(contextWindow), clampThresholdRatio(ratio + YIELD_ADJUSTMENT_RATIO));\n\t}\n\treturn ratio;\n}\n\nfunction adjustEffectiveThresholdRatio(\n\tratio: number,\n\tsavedTokens: number,\n\ttokensBefore: number,\n\tcontextWindow: number,\n): number {\n\tif (tokensBefore <= 0) {\n\t\treturn ratio;\n\t}\n\n\tconst savedRatio = savedTokens / tokensBefore;\n\tif (savedRatio > HIGH_YIELD_SAVING_RATIO) {\n\t\treturn ratio - YIELD_ADJUSTMENT_RATIO;\n\t}\n\tif (savedRatio < LOW_YIELD_SAVING_RATIO) {\n\t\treturn Math.min(baseThresholdRatioForWindow(contextWindow), ratio + YIELD_ADJUSTMENT_RATIO);\n\t}\n\treturn ratio;\n}\n\nexport function baseThresholdRatioForWindow(contextWindow: number): number {\n\tif (!(contextWindow > 0)) {\n\t\treturn 0.5;\n\t}\n\tif (contextWindow <= 16_000) {\n\t\treturn 0.45;\n\t}\n\tif (contextWindow <= 32_000) {\n\t\treturn 0.5;\n\t}\n\tif (contextWindow <= 64_000) {\n\t\treturn 0.55;\n\t}\n\tif (contextWindow <= 128_000) {\n\t\treturn 0.6;\n\t}\n\tif (contextWindow <= 512_000) {\n\t\treturn 0.7;\n\t}\n\treturn 0.8;\n}\n\nexport function resolveReserveTokens(contextWindow: number, configuredReserve: number): number {\n\treturn Math.max(\n\t\tconfiguredReserve,\n\t\tMath.min(Math.floor(RESERVE_WINDOW_FRACTION * contextWindow), MAX_SCALED_RESERVE_TOKENS),\n\t);\n}\n\nexport function computeAdaptiveThresholdRatio(contextWindow: number, priorCompactionSavedTokens?: number): number {\n\tconst ratio = baseThresholdRatioForWindow(contextWindow);\n\n\tif (priorCompactionSavedTokens === undefined) {\n\t\treturn ratio;\n\t}\n\n\treturn adjustThresholdRatio(ratio, priorCompactionSavedTokens, contextWindow);\n}\n\nexport function computeEffectiveThreshold(contextWindow: number, lastYield?: CompactionYield): number {\n\tlet ratio = computeAdaptiveThresholdRatio(contextWindow);\n\tif (lastYield) {\n\t\tratio = adjustEffectiveThresholdRatio(ratio, lastYield.savedTokens, lastYield.tokensBefore, contextWindow);\n\t}\n\treturn clampThresholdRatio(ratio);\n}\n\nexport function computeEffectiveKeepRecentTokens(\n\tsetting: number,\n\tcontextWindow: number,\n\tthresholdRatio: number,\n\tmargin = 0.05,\n): number {\n\tconst scaled =\n\t\tcontextWindow > 409_600 && setting >= 10_000\n\t\t\t? Math.max(\n\t\t\t\t\tsetting,\n\t\t\t\t\tMath.min(Math.floor(KEEP_RECENT_WINDOW_FRACTION * contextWindow), MAX_SCALED_KEEP_RECENT_TOKENS),\n\t\t\t\t)\n\t\t\t: setting;\n\tconst capped = Math.floor(contextWindow * (1 - thresholdRatio - margin));\n\treturn Math.min(scaled, Math.max(MIN_EFFECTIVE_KEEP_RECENT_TOKENS, capped));\n}\n\nexport function shouldStartSpeculativeCompaction(\n\tusage: ContextUsage,\n\tcontextWindow: number,\n\tsettings: CompactionSettings,\n\tlastYield?: CompactionYield,\n\tleadTokens?: number,\n): boolean {\n\tif (settings.speculativeEnabled === false || usage.tokens === null || contextWindow <= 0) {\n\t\treturn false;\n\t}\n\n\tif (typeof leadTokens === \"number\" && Number.isFinite(leadTokens) && leadTokens > 0) {\n\t\tconst leadTrigger = Math.max(0, contextWindow * computeEffectiveThreshold(contextWindow, lastYield) - leadTokens);\n\t\treturn usage.tokens >= leadTrigger;\n\t}\n\n\tconst fraction = settings.speculativeFraction ?? SPECULATIVE_FRACTION;\n\treturn usage.tokens >= contextWindow * computeEffectiveThreshold(contextWindow, lastYield) * fraction;\n}\n\nexport function resolveEffectiveReserveTokens(\n\tcontextWindow: number,\n\tconfiguredReserve: number,\n\treserveScalingEnabled = true,\n): number {\n\treturn reserveScalingEnabled ? resolveReserveTokens(contextWindow, configuredReserve) : configuredReserve;\n}\n\nexport function isAtHardLimit(\n\tusage: ContextUsage,\n\tcontextWindow: number,\n\treserveTokens: number,\n\tadditionalTokens = 0,\n): boolean {\n\treturn usage.tokens !== null && usage.tokens + additionalTokens + reserveTokens >= contextWindow;\n}\n\nexport function shouldTriggerCompaction(\n\tusage: ContextUsage,\n\tcontextWindow: number,\n\tsettings: CompactionSettings,\n\tlastYield?: CompactionYield,\n): boolean {\n\tif (!settings.enabled || usage.tokens === null || contextWindow <= 0) {\n\t\treturn false;\n\t}\n\n\treturn usage.tokens >= contextWindow * computeEffectiveThreshold(contextWindow, lastYield);\n}\n"]}
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CompactionSettings } from \"../../../compaction/index.ts\";\nimport type { ContextUsage } from \"../../types.ts\";\n\nconst MIN_ADAPTIVE_THRESHOLD_RATIO = 0.4;\nconst MAX_ADAPTIVE_THRESHOLD_RATIO = 0.85;\nconst HIGH_YIELD_SAVING_RATIO = 0.5;\nconst LOW_YIELD_SAVING_RATIO = 0.1;\nconst YIELD_ADJUSTMENT_RATIO = 0.05;\nconst MIN_EFFECTIVE_KEEP_RECENT_TOKENS = 1024;\nconst RESERVE_WINDOW_FRACTION = 0.04;\nconst MAX_SCALED_RESERVE_TOKENS = 49_152;\nconst KEEP_RECENT_WINDOW_FRACTION = 0.05;\nconst MAX_SCALED_KEEP_RECENT_TOKENS = 60_000;\n\nexport const SPECULATIVE_FRACTION = 0.75;\n\nexport interface CompactionYield {\n\tsavedTokens: number;\n\ttokensBefore: number;\n}\n\nfunction clampThresholdRatio(ratio: number): number {\n\treturn Math.min(MAX_ADAPTIVE_THRESHOLD_RATIO, Math.max(MIN_ADAPTIVE_THRESHOLD_RATIO, ratio));\n}\n\nfunction adjustThresholdRatio(ratio: number, savedTokens: number, contextWindow: number): number {\n\tif (contextWindow <= 0) {\n\t\treturn ratio;\n\t}\n\n\tconst savedRatio = savedTokens / contextWindow;\n\tif (savedRatio > HIGH_YIELD_SAVING_RATIO) {\n\t\treturn clampThresholdRatio(ratio - YIELD_ADJUSTMENT_RATIO);\n\t}\n\tif (savedRatio < LOW_YIELD_SAVING_RATIO) {\n\t\treturn Math.min(baseThresholdRatioForWindow(contextWindow), clampThresholdRatio(ratio + YIELD_ADJUSTMENT_RATIO));\n\t}\n\treturn ratio;\n}\n\nfunction adjustEffectiveThresholdRatio(\n\tratio: number,\n\tsavedTokens: number,\n\ttokensBefore: number,\n\tcontextWindow: number,\n): number {\n\tif (tokensBefore <= 0) {\n\t\treturn ratio;\n\t}\n\n\tconst savedRatio = savedTokens / tokensBefore;\n\tif (savedRatio > HIGH_YIELD_SAVING_RATIO) {\n\t\treturn ratio - YIELD_ADJUSTMENT_RATIO;\n\t}\n\tif (savedRatio < LOW_YIELD_SAVING_RATIO) {\n\t\treturn Math.min(baseThresholdRatioForWindow(contextWindow), ratio + YIELD_ADJUSTMENT_RATIO);\n\t}\n\treturn ratio;\n}\n\nexport function baseThresholdRatioForWindow(contextWindow: number): number {\n\tif (!(contextWindow > 0)) {\n\t\treturn 0.5;\n\t}\n\tif (contextWindow <= 16_000) {\n\t\treturn 0.45;\n\t}\n\tif (contextWindow <= 32_000) {\n\t\treturn 0.5;\n\t}\n\tif (contextWindow <= 64_000) {\n\t\treturn 0.55;\n\t}\n\tif (contextWindow <= 128_000) {\n\t\treturn 0.6;\n\t}\n\tif (contextWindow <= 512_000) {\n\t\treturn 0.7;\n\t}\n\treturn 0.8;\n}\n\nexport function resolveReserveTokens(contextWindow: number, configuredReserve: number): number {\n\treturn Math.max(\n\t\tconfiguredReserve,\n\t\tMath.min(Math.floor(RESERVE_WINDOW_FRACTION * contextWindow), MAX_SCALED_RESERVE_TOKENS),\n\t);\n}\n\n/**\n * The single effective reserve for one window. Every consumer that reasons about\n * the usable prompt budget — the hard-limit valve, restoration, and deterministic\n * fallback acceptance — must resolve it here so acceptance can never admit a\n * context the hard valve would immediately compact again. Scaling is idempotent,\n * so re-resolving an already-scaled reserve never double-scales it.\n */\nexport function resolveEffectiveReserveTokens(\n\tcontextWindow: number,\n\tsettings: Pick<CompactionSettings, \"reserveTokens\" | \"reserveScalingEnabled\">,\n): number {\n\treturn settings.reserveScalingEnabled === false\n\t\t? settings.reserveTokens\n\t\t: resolveReserveTokens(contextWindow, settings.reserveTokens);\n}\n\nexport function computeAdaptiveThresholdRatio(contextWindow: number, priorCompactionSavedTokens?: number): number {\n\tconst ratio = baseThresholdRatioForWindow(contextWindow);\n\n\tif (priorCompactionSavedTokens === undefined) {\n\t\treturn ratio;\n\t}\n\n\treturn adjustThresholdRatio(ratio, priorCompactionSavedTokens, contextWindow);\n}\n\nexport function computeEffectiveThreshold(contextWindow: number, lastYield?: CompactionYield): number {\n\tlet ratio = computeAdaptiveThresholdRatio(contextWindow);\n\tif (lastYield) {\n\t\tratio = adjustEffectiveThresholdRatio(ratio, lastYield.savedTokens, lastYield.tokensBefore, contextWindow);\n\t}\n\treturn clampThresholdRatio(ratio);\n}\n\nexport function computeEffectiveKeepRecentTokens(\n\tsetting: number,\n\tcontextWindow: number,\n\tthresholdRatio: number,\n\tmargin = 0.05,\n): number {\n\tconst scaled =\n\t\tcontextWindow > 409_600 && setting >= 10_000\n\t\t\t? Math.max(\n\t\t\t\t\tsetting,\n\t\t\t\t\tMath.min(Math.floor(KEEP_RECENT_WINDOW_FRACTION * contextWindow), MAX_SCALED_KEEP_RECENT_TOKENS),\n\t\t\t\t)\n\t\t\t: setting;\n\tconst capped = Math.floor(contextWindow * (1 - thresholdRatio - margin));\n\treturn Math.min(scaled, Math.max(MIN_EFFECTIVE_KEEP_RECENT_TOKENS, capped));\n}\n\nexport function shouldStartSpeculativeCompaction(\n\tusage: ContextUsage,\n\tcontextWindow: number,\n\tsettings: CompactionSettings,\n\tlastYield?: CompactionYield,\n\tleadTokens?: number,\n): boolean {\n\tif (settings.speculativeEnabled === false || usage.tokens === null || contextWindow <= 0) {\n\t\treturn false;\n\t}\n\n\tif (typeof leadTokens === \"number\" && Number.isFinite(leadTokens) && leadTokens > 0) {\n\t\tconst leadTrigger = Math.max(0, contextWindow * computeEffectiveThreshold(contextWindow, lastYield) - leadTokens);\n\t\treturn usage.tokens >= leadTrigger;\n\t}\n\n\tconst fraction = settings.speculativeFraction ?? SPECULATIVE_FRACTION;\n\treturn usage.tokens >= contextWindow * computeEffectiveThreshold(contextWindow, lastYield) * fraction;\n}\n\nexport function isAtHardLimit(\n\tusage: ContextUsage,\n\tcontextWindow: number,\n\treserveTokens: number,\n\tadditionalTokens = 0,\n): boolean {\n\treturn usage.tokens !== null && usage.tokens + additionalTokens + reserveTokens >= contextWindow;\n}\n\nexport function shouldTriggerCompaction(\n\tusage: ContextUsage,\n\tcontextWindow: number,\n\tsettings: CompactionSettings,\n\tlastYield?: CompactionYield,\n): boolean {\n\tif (!settings.enabled || usage.tokens === null || contextWindow <= 0) {\n\t\treturn false;\n\t}\n\n\treturn usage.tokens >= contextWindow * computeEffectiveThreshold(contextWindow, lastYield);\n}\n"]}
@@ -1,12 +1,17 @@
1
+ export interface TokenBudgetReminderLease {
2
+ compactionEpoch: number;
3
+ message: string;
4
+ }
1
5
  export interface TokenBudgetReminderState {
2
- lastFiredGeneration: number | null;
6
+ lastFiredEpoch: number | null;
7
+ lease?: TokenBudgetReminderLease;
3
8
  }
4
9
  export interface TokenBudgetReminderInput {
5
10
  contextTokens: number;
6
11
  contextWindow: number;
7
12
  thresholdTokens: number;
8
13
  leadTokens: number;
9
- compactionGeneration: number;
14
+ compactionEpoch: number;
10
15
  state: TokenBudgetReminderState;
11
16
  }
12
17
  export interface TokenBudgetReminderResult {
@@ -14,5 +19,6 @@ export interface TokenBudgetReminderResult {
14
19
  nextState: TokenBudgetReminderState;
15
20
  }
16
21
  export declare function createInitialReminderState(): TokenBudgetReminderState;
22
+ export declare function clearTokenBudgetReminderLease(state: TokenBudgetReminderState): TokenBudgetReminderState;
17
23
  export declare function computeTokenBudgetReminder(input: TokenBudgetReminderInput): TokenBudgetReminderResult;
18
24
  //# sourceMappingURL=token-budget-reminder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"token-budget-reminder.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/token-budget-reminder.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACxC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,wBAAwB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,KAAK,EAAE,wBAAwB,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,wBAAwB,CAAC;CACpC;AAED,wBAAgB,0BAA0B,IAAI,wBAAwB,CAErE;AAMD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,wBAAwB,GAAG,yBAAyB,CAUrG"}
1
+ {"version":3,"file":"token-budget-reminder.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/token-budget-reminder.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACxC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,CAAC,EAAE,wBAAwB,CAAC;CACjC;AAED,MAAM,WAAW,wBAAwB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,wBAAwB,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,wBAAwB,CAAC;CACpC;AAED,wBAAgB,0BAA0B,IAAI,wBAAwB,CAErE;AAMD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,wBAAwB,GAAG,wBAAwB,CAEvG;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,wBAAwB,GAAG,yBAAyB,CAcrG"}
@@ -1,18 +1,25 @@
1
1
  export function createInitialReminderState() {
2
- return { lastFiredGeneration: null };
2
+ return { lastFiredEpoch: null };
3
3
  }
4
4
  function formatReminderMessage(remaining) {
5
5
  return `[context budget] Approximately ${remaining} tokens remain before automatic compaction. Wrap up verbose exploration, prefer concise summaries over full dumps, and front-load conclusions.`;
6
6
  }
7
+ export function clearTokenBudgetReminderLease(state) {
8
+ return state.lease ? { lastFiredEpoch: state.lastFiredEpoch } : state;
9
+ }
7
10
  export function computeTokenBudgetReminder(input) {
8
11
  const remaining = input.thresholdTokens - input.contextTokens;
9
12
  const inZone = remaining > 0 && remaining <= 2 * input.leadTokens;
10
- if (!inZone || input.state.lastFiredGeneration === input.compactionGeneration) {
11
- return { nextState: input.state };
13
+ if (!inZone || input.state.lastFiredEpoch === input.compactionEpoch) {
14
+ return { nextState: clearTokenBudgetReminderLease(input.state) };
12
15
  }
16
+ const message = formatReminderMessage(remaining);
13
17
  return {
14
- message: formatReminderMessage(remaining),
15
- nextState: { lastFiredGeneration: input.compactionGeneration },
18
+ message,
19
+ nextState: {
20
+ lastFiredEpoch: input.compactionEpoch,
21
+ lease: { compactionEpoch: input.compactionEpoch, message },
22
+ },
16
23
  };
17
24
  }
18
25
  //# sourceMappingURL=token-budget-reminder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token-budget-reminder.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/token-budget-reminder.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,0BAA0B;IACzC,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC;AACtC,CAAC;AAED,SAAS,qBAAqB,CAAC,SAAiB;IAC/C,OAAO,kCAAkC,SAAS,gJAAgJ,CAAC;AACpM,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,KAA+B;IACzE,MAAM,SAAS,GAAG,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,aAAa,CAAC;IAC9D,MAAM,MAAM,GAAG,SAAS,GAAG,CAAC,IAAI,SAAS,IAAI,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC;IAClE,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,mBAAmB,KAAK,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC/E,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IACnC,CAAC;IACD,OAAO;QACN,OAAO,EAAE,qBAAqB,CAAC,SAAS,CAAC;QACzC,SAAS,EAAE,EAAE,mBAAmB,EAAE,KAAK,CAAC,oBAAoB,EAAE;KAC9D,CAAC;AACH,CAAC","sourcesContent":["export interface TokenBudgetReminderState {\n\tlastFiredGeneration: number | null;\n}\n\nexport interface TokenBudgetReminderInput {\n\tcontextTokens: number;\n\tcontextWindow: number;\n\tthresholdTokens: number;\n\tleadTokens: number;\n\tcompactionGeneration: number;\n\tstate: TokenBudgetReminderState;\n}\n\nexport interface TokenBudgetReminderResult {\n\tmessage?: string;\n\tnextState: TokenBudgetReminderState;\n}\n\nexport function createInitialReminderState(): TokenBudgetReminderState {\n\treturn { lastFiredGeneration: null };\n}\n\nfunction formatReminderMessage(remaining: number): string {\n\treturn `[context budget] Approximately ${remaining} tokens remain before automatic compaction. Wrap up verbose exploration, prefer concise summaries over full dumps, and front-load conclusions.`;\n}\n\nexport function computeTokenBudgetReminder(input: TokenBudgetReminderInput): TokenBudgetReminderResult {\n\tconst remaining = input.thresholdTokens - input.contextTokens;\n\tconst inZone = remaining > 0 && remaining <= 2 * input.leadTokens;\n\tif (!inZone || input.state.lastFiredGeneration === input.compactionGeneration) {\n\t\treturn { nextState: input.state };\n\t}\n\treturn {\n\t\tmessage: formatReminderMessage(remaining),\n\t\tnextState: { lastFiredGeneration: input.compactionGeneration },\n\t};\n}\n"]}
1
+ {"version":3,"file":"token-budget-reminder.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/token-budget-reminder.ts"],"names":[],"mappings":"AAwBA,MAAM,UAAU,0BAA0B;IACzC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC;AAED,SAAS,qBAAqB,CAAC,SAAiB;IAC/C,OAAO,kCAAkC,SAAS,gJAAgJ,CAAC;AACpM,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,KAA+B;IAC5E,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,KAA+B;IACzE,MAAM,SAAS,GAAG,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,aAAa,CAAC;IAC9D,MAAM,MAAM,GAAG,SAAS,GAAG,CAAC,IAAI,SAAS,IAAI,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC;IAClE,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,cAAc,KAAK,KAAK,CAAC,eAAe,EAAE,CAAC;QACrE,OAAO,EAAE,SAAS,EAAE,6BAA6B,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;IAClE,CAAC;IACD,MAAM,OAAO,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IACjD,OAAO;QACN,OAAO;QACP,SAAS,EAAE;YACV,cAAc,EAAE,KAAK,CAAC,eAAe;YACrC,KAAK,EAAE,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,OAAO,EAAE;SAC1D;KACD,CAAC;AACH,CAAC","sourcesContent":["export interface TokenBudgetReminderLease {\n\tcompactionEpoch: number;\n\tmessage: string;\n}\n\nexport interface TokenBudgetReminderState {\n\tlastFiredEpoch: number | null;\n\tlease?: TokenBudgetReminderLease;\n}\n\nexport interface TokenBudgetReminderInput {\n\tcontextTokens: number;\n\tcontextWindow: number;\n\tthresholdTokens: number;\n\tleadTokens: number;\n\tcompactionEpoch: number;\n\tstate: TokenBudgetReminderState;\n}\n\nexport interface TokenBudgetReminderResult {\n\tmessage?: string;\n\tnextState: TokenBudgetReminderState;\n}\n\nexport function createInitialReminderState(): TokenBudgetReminderState {\n\treturn { lastFiredEpoch: null };\n}\n\nfunction formatReminderMessage(remaining: number): string {\n\treturn `[context budget] Approximately ${remaining} tokens remain before automatic compaction. Wrap up verbose exploration, prefer concise summaries over full dumps, and front-load conclusions.`;\n}\n\nexport function clearTokenBudgetReminderLease(state: TokenBudgetReminderState): TokenBudgetReminderState {\n\treturn state.lease ? { lastFiredEpoch: state.lastFiredEpoch } : state;\n}\n\nexport function computeTokenBudgetReminder(input: TokenBudgetReminderInput): TokenBudgetReminderResult {\n\tconst remaining = input.thresholdTokens - input.contextTokens;\n\tconst inZone = remaining > 0 && remaining <= 2 * input.leadTokens;\n\tif (!inZone || input.state.lastFiredEpoch === input.compactionEpoch) {\n\t\treturn { nextState: clearTokenBudgetReminderLease(input.state) };\n\t}\n\tconst message = formatReminderMessage(remaining);\n\treturn {\n\t\tmessage,\n\t\tnextState: {\n\t\t\tlastFiredEpoch: input.compactionEpoch,\n\t\t\tlease: { compactionEpoch: input.compactionEpoch, message },\n\t\t},\n\t};\n}\n"]}
@@ -2,35 +2,26 @@
2
2
  * Always-on admission cap for tool results.
3
3
  *
4
4
  * Oversized tool output is the dominant driver of single-step context bursts, so it is
5
- * capped at the entrance instead of waiting for an emergency/compaction-budget pass
6
- * (see `tool-truncation.ts`). The full output is spilled to disk and the model gets a
7
- * head/tail excerpt plus a pointer it can re-read with the read tool.
5
+ * projected to a deterministic head/tail excerpt at the entrance instead of waiting for
6
+ * an emergency/compaction-budget pass (see `tool-truncation.ts`). Admission is derived
7
+ * from the supplied text on every request; model-visible marker text is never state.
8
8
  */
9
- export declare const TOOL_ADMISSION_MARKER_PREFIX = "[tool result truncated:";
10
- /** True when `text` already carries an admission marker line, at any position. */
11
- export declare function containsToolAdmissionMarker(text: string): boolean;
9
+ export declare const TOOL_ADMISSION_MARKER_PREFIX = "[tool result projected:";
12
10
  export interface AdmitToolResultInput {
13
- text: string;
14
- contextWindow: number;
15
- spillDir: string;
16
- capTokens?: number;
11
+ readonly text: string;
12
+ readonly contextWindow: number;
17
13
  }
18
14
  export interface AdmitToolResultOutput {
19
- text: string;
20
- spilled: boolean;
21
- spillPath?: string;
15
+ readonly text: string;
16
+ readonly projected: boolean;
22
17
  }
23
- /** Token budget a single tool result may occupy before it is spilled to disk. */
18
+ /** Token budget a single tool result may occupy before it is projected. */
24
19
  export declare function resolveToolResultAdmissionCapTokens(contextWindow: number): number;
25
- export declare function buildAdmissionMarker(keptTokens: number, totalTokens: number, spillPath: string): string;
26
- export declare function estimateAdmissionMarkerTokens(totalTokens: number, spillPath: string): number;
27
20
  /**
28
21
  * Cap a tool result at admission time. Under-cap text passes through untouched; over-cap
29
- * text is written to `spillDir` in full and replaced by a head/tail excerpt.
30
- *
31
- * Already-admitted text is returned unchanged: re-spilling an excerpt would persist the
32
- * excerpt (not the original output) and break the read-back pointer chain. This matters
33
- * when a model switch shrinks the context window below the cap the excerpt was built for.
22
+ * text becomes a deterministic, diskless head/tail projection. Re-evaluating projected
23
+ * text under a smaller model window is safe because admission never trusts marker text.
34
24
  */
25
+ export declare function admitToolResultWithinBudget(text: string, budgetTokens: number): AdmitToolResultOutput;
35
26
  export declare function admitToolResult(input: AdmitToolResultInput): AdmitToolResultOutput;
36
27
  //# sourceMappingURL=tool-admission.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-admission.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/tool-admission.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,eAAO,MAAM,4BAA4B,4BAA4B,CAAC;AAUtE,kFAAkF;AAClF,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,iFAAiF;AACjF,wBAAgB,mCAAmC,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAKjF;AAMD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvG;AAED,wBAAgB,6BAA6B,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5F;AAsBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,qBAAqB,CA0BlF"}
1
+ {"version":3,"file":"tool-admission.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/tool-admission.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,eAAO,MAAM,4BAA4B,4BAA4B,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC5B;AAED,2EAA2E;AAC3E,wBAAgB,mCAAmC,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAKjF;AAeD;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,qBAAqB,CAgBrG;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,qBAAqB,CAElF"}
@@ -2,91 +2,55 @@
2
2
  * Always-on admission cap for tool results.
3
3
  *
4
4
  * Oversized tool output is the dominant driver of single-step context bursts, so it is
5
- * capped at the entrance instead of waiting for an emergency/compaction-budget pass
6
- * (see `tool-truncation.ts`). The full output is spilled to disk and the model gets a
7
- * head/tail excerpt plus a pointer it can re-read with the read tool.
5
+ * projected to a deterministic head/tail excerpt at the entrance instead of waiting for
6
+ * an emergency/compaction-budget pass (see `tool-truncation.ts`). Admission is derived
7
+ * from the supplied text on every request; model-visible marker text is never state.
8
8
  */
9
- import { createHash } from "node:crypto";
10
- import { mkdirSync, writeFileSync } from "node:fs";
11
- import { join } from "node:path";
12
9
  import { estimateTokens } from "../../../compaction/index.js";
13
10
  const MAX_ADMISSION_CAP_TOKENS = 50_000;
14
11
  const MIN_ADMISSION_CAP_TOKENS = 8192;
15
12
  const ADMISSION_CAP_FRACTION = 0.05;
16
13
  const HEAD_BUDGET_FRACTION = 0.6;
17
14
  const TAIL_BUDGET_FRACTION = 0.2;
18
- export const TOOL_ADMISSION_MARKER_PREFIX = "[tool result truncated:";
19
- /**
20
- * A whole line of the exact shape emitted by `buildMarker`. Anchored per line (`m` flag)
21
- * so the marker is found wherever it sits - including the middle of a head/tail excerpt -
22
- * while ordinary output that merely mentions the prefix mid-line is not matched.
23
- */
24
- const TOOL_ADMISSION_MARKER_LINE = /^\[tool result truncated: kept \d+ of ~\d+ tokens; full output at .+ - read it with the read tool if needed\]$/m;
25
- /** True when `text` already carries an admission marker line, at any position. */
26
- export function containsToolAdmissionMarker(text) {
27
- return TOOL_ADMISSION_MARKER_LINE.test(text);
28
- }
29
- /** Token budget a single tool result may occupy before it is spilled to disk. */
15
+ export const TOOL_ADMISSION_MARKER_PREFIX = "[tool result projected:";
16
+ /** Token budget a single tool result may occupy before it is projected. */
30
17
  export function resolveToolResultAdmissionCapTokens(contextWindow) {
31
18
  return Math.min(MAX_ADMISSION_CAP_TOKENS, Math.max(MIN_ADMISSION_CAP_TOKENS, Math.floor(ADMISSION_CAP_FRACTION * contextWindow)));
32
19
  }
33
20
  function estimateTextTokens(text) {
34
21
  return estimateTokens({ role: "user", content: text, timestamp: 0 });
35
22
  }
36
- export function buildAdmissionMarker(keptTokens, totalTokens, spillPath) {
37
- return `${TOOL_ADMISSION_MARKER_PREFIX} kept ${keptTokens} of ~${totalTokens} tokens; full output at ${spillPath} - read it with the read tool if needed]`;
38
- }
39
- export function estimateAdmissionMarkerTokens(totalTokens, spillPath) {
40
- return estimateTextTokens(buildAdmissionMarker(0, totalTokens, spillPath));
41
- }
42
- function spillToFile(spillDir, text) {
43
- mkdirSync(spillDir, { recursive: true });
44
- const digest = createHash("sha256").update(text).digest("hex").slice(0, 16);
45
- const spillPath = join(spillDir, `tool-result-${Number.parseInt(digest.slice(0, 10), 16)}-${digest.slice(10, 16)}.txt`);
46
- writeFileSync(spillPath, text, "utf-8");
47
- return spillPath;
48
- }
49
- function buildExcerpt(text, budgetChars, totalTokens, spillPath) {
50
- const headChars = Math.max(1, Math.floor(budgetChars * HEAD_BUDGET_FRACTION));
51
- const tailChars = Math.max(1, Math.floor(budgetChars * TAIL_BUDGET_FRACTION));
23
+ function buildExcerpt(text, budgetChars, totalTokens) {
24
+ const headChars = Math.floor(budgetChars * HEAD_BUDGET_FRACTION);
25
+ const tailChars = Math.floor(budgetChars * TAIL_BUDGET_FRACTION);
52
26
  const head = text.slice(0, headChars);
53
27
  const tail = text.slice(Math.max(headChars, text.length - tailChars));
54
28
  const keptTokens = estimateTextTokens(head) + estimateTextTokens(tail);
55
- return `${head}\n${buildAdmissionMarker(keptTokens, totalTokens, spillPath)}\n${tail}`;
29
+ return `${head}\n${TOOL_ADMISSION_MARKER_PREFIX} kept ${keptTokens} of ~${totalTokens} tokens]\n${tail}`;
56
30
  }
57
31
  /**
58
32
  * Cap a tool result at admission time. Under-cap text passes through untouched; over-cap
59
- * text is written to `spillDir` in full and replaced by a head/tail excerpt.
60
- *
61
- * Already-admitted text is returned unchanged: re-spilling an excerpt would persist the
62
- * excerpt (not the original output) and break the read-back pointer chain. This matters
63
- * when a model switch shrinks the context window below the cap the excerpt was built for.
33
+ * text becomes a deterministic, diskless head/tail projection. Re-evaluating projected
34
+ * text under a smaller model window is safe because admission never trusts marker text.
64
35
  */
65
- export function admitToolResult(input) {
66
- const { text, contextWindow, spillDir } = input;
67
- const capTokens = input.capTokens ?? resolveToolResultAdmissionCapTokens(contextWindow);
36
+ export function admitToolResultWithinBudget(text, budgetTokens) {
68
37
  const totalTokens = estimateTextTokens(text);
69
- if (totalTokens <= capTokens)
70
- return { text, spilled: false };
71
- let spillPath;
72
- try {
73
- spillPath = spillToFile(spillDir, text);
74
- }
75
- catch {
76
- // Admission is a safety boundary: if persistence fails, still cap model-visible text.
77
- }
78
- const pointer = spillPath ?? "(spill unavailable)";
38
+ if (totalTokens <= budgetTokens)
39
+ return { text, projected: false };
40
+ if (budgetTokens <= 0)
41
+ return { text: "", projected: true };
79
42
  const charsPerToken = text.length / Math.max(1, totalTokens);
80
- let budgetChars = Math.floor(capTokens * charsPerToken);
81
- let excerpt = buildExcerpt(text, budgetChars, totalTokens, pointer);
82
- while (estimateTextTokens(excerpt) > capTokens && budgetChars > 0) {
83
- budgetChars = Math.floor(budgetChars / 2);
84
- excerpt = budgetChars > 0 ? buildExcerpt(text, budgetChars, totalTokens, pointer) : "";
43
+ let budgetChars = Math.floor(budgetTokens * charsPerToken);
44
+ let excerpt = buildExcerpt(text, budgetChars, totalTokens);
45
+ while (estimateTextTokens(excerpt) > budgetTokens && budgetChars > 0) {
46
+ budgetChars = Math.floor(budgetChars * 0.8);
47
+ excerpt = buildExcerpt(text, budgetChars, totalTokens);
85
48
  }
86
- if (estimateTextTokens(excerpt) > capTokens || budgetChars === 0) {
87
- const marker = buildAdmissionMarker(0, totalTokens, pointer);
88
- excerpt = estimateTextTokens(marker) <= capTokens ? marker : "";
89
- }
90
- return { text: excerpt, spilled: true, ...(spillPath ? { spillPath } : {}) };
49
+ return estimateTextTokens(excerpt) <= budgetTokens
50
+ ? { text: excerpt, projected: true }
51
+ : { text: "", projected: true };
52
+ }
53
+ export function admitToolResult(input) {
54
+ return admitToolResultWithinBudget(input.text, resolveToolResultAdmissionCapTokens(input.contextWindow));
91
55
  }
92
56
  //# sourceMappingURL=tool-admission.js.map