@arnilo/prism 0.0.96 → 0.1.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.
Files changed (203) hide show
  1. package/CHANGELOG.md +285 -2
  2. package/README.md +17 -3
  3. package/dist/agent-definitions.js +2 -3
  4. package/dist/agent-event-source.d.ts +11 -0
  5. package/dist/agent-event-source.js +512 -0
  6. package/dist/agent-loops.d.ts +5 -0
  7. package/dist/agent-loops.js +99 -14
  8. package/dist/agent-run-lifecycle.d.ts +5 -2
  9. package/dist/agent-run-lifecycle.js +18 -2
  10. package/dist/agent-run-state.d.ts +27 -1
  11. package/dist/agent-run-state.js +113 -7
  12. package/dist/agents.d.ts +3 -1
  13. package/dist/agents.js +1255 -129
  14. package/dist/artifacts.d.ts +132 -0
  15. package/dist/artifacts.js +44 -0
  16. package/dist/cache-helpers.js +18 -9
  17. package/dist/checkpoints.d.ts +4 -0
  18. package/dist/checkpoints.js +17 -9
  19. package/dist/cli-init.js +3 -7
  20. package/dist/cli-runner.d.ts +2 -6
  21. package/dist/cli-runner.js +71 -33
  22. package/dist/compaction.js +5 -4
  23. package/dist/config.js +7 -4
  24. package/dist/content.js +26 -24
  25. package/dist/context-budget.d.ts +67 -0
  26. package/dist/context-budget.js +288 -0
  27. package/dist/contracts.d.ts +590 -8
  28. package/dist/contracts.js +142 -1
  29. package/dist/contribution-parsing.js +6 -2
  30. package/dist/contributions.d.ts +2 -0
  31. package/dist/contributions.js +3 -0
  32. package/dist/conversations.d.ts +50 -0
  33. package/dist/conversations.js +98 -0
  34. package/dist/credentials.d.ts +22 -2
  35. package/dist/credentials.js +18 -3
  36. package/dist/devices.d.ts +94 -0
  37. package/dist/devices.js +138 -0
  38. package/dist/event-multiplexer.js +18 -4
  39. package/dist/extensions.d.ts +18 -1
  40. package/dist/extensions.js +79 -6
  41. package/dist/feedback.js +12 -10
  42. package/dist/guardrails.d.ts +1 -1
  43. package/dist/guardrails.js +26 -17
  44. package/dist/identity.d.ts +92 -0
  45. package/dist/identity.js +265 -0
  46. package/dist/index.d.ts +94 -72
  47. package/dist/index.js +48 -36
  48. package/dist/input.d.ts +10 -1
  49. package/dist/input.js +152 -52
  50. package/dist/instruction-injection.d.ts +1 -1
  51. package/dist/middleware.js +9 -1
  52. package/dist/models.d.ts +2 -0
  53. package/dist/models.js +3 -0
  54. package/dist/node/agent-definitions.js +16 -8
  55. package/dist/node/contribution-discovery.d.ts +1 -2
  56. package/dist/node/contribution-discovery.js +3 -3
  57. package/dist/node/session-store-jsonl.js +13 -7
  58. package/dist/node/settings.d.ts +1 -1
  59. package/dist/node/settings.js +1 -1
  60. package/dist/node/system-project-prompts.js +2 -4
  61. package/dist/node/trust.js +1 -1
  62. package/dist/persistence-lifecycle.d.ts +103 -0
  63. package/dist/persistence-lifecycle.js +202 -0
  64. package/dist/provider-events.d.ts +1 -0
  65. package/dist/provider-events.js +6 -1
  66. package/dist/provider-request-policy.js +3 -4
  67. package/dist/providers/media.d.ts +1 -1
  68. package/dist/providers/openai-compatible.d.ts +46 -1
  69. package/dist/providers/openai-compatible.js +123 -53
  70. package/dist/providers/openai-primitives.js +10 -7
  71. package/dist/providers/transport.d.ts +6 -0
  72. package/dist/providers/transport.js +21 -0
  73. package/dist/providers.d.ts +2 -0
  74. package/dist/providers.js +3 -0
  75. package/dist/redaction.d.ts +1 -0
  76. package/dist/redaction.js +26 -9
  77. package/dist/resources.d.ts +2 -2
  78. package/dist/resources.js +2 -2
  79. package/dist/retry.d.ts +5 -0
  80. package/dist/retry.js +8 -1
  81. package/dist/rpc.js +55 -11
  82. package/dist/run-ledger.d.ts +6 -0
  83. package/dist/run-ledger.js +16 -13
  84. package/dist/run-limits.js +49 -10
  85. package/dist/secure-agent.js +8 -2
  86. package/dist/security.js +7 -2
  87. package/dist/session-stores.d.ts +7 -2
  88. package/dist/session-stores.js +195 -21
  89. package/dist/skill-disclosure.d.ts +35 -0
  90. package/dist/skill-disclosure.js +101 -0
  91. package/dist/skill-load.d.ts +25 -0
  92. package/dist/skill-load.js +112 -0
  93. package/dist/structured-output.d.ts +5 -1
  94. package/dist/structured-output.js +20 -2
  95. package/dist/system-prompts.js +7 -2
  96. package/dist/testing/agent-event-source-conformance.d.ts +4 -0
  97. package/dist/testing/agent-event-source-conformance.js +54 -0
  98. package/dist/testing/compaction-conformance.js +5 -1
  99. package/dist/testing/extension-conformance.js +15 -3
  100. package/dist/testing/feedback.d.ts +1 -3
  101. package/dist/testing/feedback.js +1 -1
  102. package/dist/testing/persistence-schema.d.ts +2 -2
  103. package/dist/testing/persistence-schema.js +280 -35
  104. package/dist/testing/provider-conformance.js +3 -3
  105. package/dist/testing/run-ledger-conformance.js +1 -1
  106. package/dist/testing/session-store-conformance.d.ts +6 -0
  107. package/dist/testing/session-store-conformance.js +37 -2
  108. package/dist/testing/tool-conformance.js +30 -5
  109. package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
  110. package/dist/testing/tool-effect-store-conformance.js +85 -0
  111. package/dist/thinking.js +4 -1
  112. package/dist/tool-effects.d.ts +15 -0
  113. package/dist/tool-effects.js +352 -0
  114. package/dist/tool-result-fold.d.ts +40 -0
  115. package/dist/tool-result-fold.js +176 -0
  116. package/dist/tools.d.ts +8 -3
  117. package/dist/tools.js +248 -13
  118. package/docs/0.1.0-readiness.md +202 -0
  119. package/docs/a2a.md +33 -2
  120. package/docs/acp.md +126 -0
  121. package/docs/ag-ui-adoption.md +77 -0
  122. package/docs/ag-ui.md +225 -0
  123. package/docs/agent-events.md +34 -3
  124. package/docs/agent-identity.md +144 -0
  125. package/docs/agent-loops.md +17 -2
  126. package/docs/agent-session-runtime.md +21 -4
  127. package/docs/browser-automation.md +5 -0
  128. package/docs/caveman.md +129 -0
  129. package/docs/cli-rpc.md +3 -6
  130. package/docs/coding-agent-tools.md +229 -25
  131. package/docs/coding-security.md +77 -11
  132. package/docs/compaction-and-retry.md +5 -2
  133. package/docs/compaction-llm.md +20 -1
  134. package/docs/compaction-observational-memory.md +52 -8
  135. package/docs/context-and-skills.md +94 -7
  136. package/docs/contribution-registries.md +1 -0
  137. package/docs/conversations.md +135 -0
  138. package/docs/credential-storage.md +34 -1
  139. package/docs/credentials-and-redaction.md +11 -1
  140. package/docs/database-persistence.md +27 -7
  141. package/docs/device-adapters.md +97 -0
  142. package/docs/enterprise-postgres-state.md +178 -0
  143. package/docs/evaluations.md +14 -1
  144. package/docs/extensions.md +4 -1
  145. package/docs/forge-integration.md +113 -0
  146. package/docs/guardrails.md +16 -2
  147. package/docs/host-security.md +35 -4
  148. package/docs/index.md +69 -37
  149. package/docs/input-and-prompt-assembly.md +8 -7
  150. package/docs/language-intelligence.md +162 -0
  151. package/docs/mcp-tools.md +62 -5
  152. package/docs/middleware-hooks.md +2 -2
  153. package/docs/migration.md +423 -2
  154. package/docs/model-routing.md +111 -0
  155. package/docs/multimodal-content.md +8 -5
  156. package/docs/node-jsonl-session-store.md +1 -1
  157. package/docs/observability.md +2 -0
  158. package/docs/openapi-tools.md +56 -0
  159. package/docs/performance.md +282 -0
  160. package/docs/policy-and-audit.md +171 -0
  161. package/docs/ponytail.md +127 -0
  162. package/docs/postgres-persistence.md +8 -4
  163. package/docs/process-sessions.md +147 -0
  164. package/docs/provider-caching.md +13 -1
  165. package/docs/provider-conformance.md +29 -5
  166. package/docs/provider-packages.md +43 -2
  167. package/docs/provider-request-policies.md +2 -0
  168. package/docs/providers/ai-sdk.md +24 -7
  169. package/docs/providers/alibaba.md +179 -0
  170. package/docs/providers/anthropic.md +93 -0
  171. package/docs/providers/azure.md +74 -0
  172. package/docs/providers/bedrock.md +72 -0
  173. package/docs/providers/google.md +89 -0
  174. package/docs/providers/ollama.md +166 -0
  175. package/docs/providers/openai-compatible.md +31 -2
  176. package/docs/providers/openai.md +24 -5
  177. package/docs/providers/openrouter.md +2 -0
  178. package/docs/providers/vertex.md +71 -0
  179. package/docs/public-contracts.md +61 -4
  180. package/docs/rag.md +41 -12
  181. package/docs/release-and-install.md +323 -206
  182. package/docs/resource-loading.md +3 -0
  183. package/docs/runs-and-usage.md +3 -0
  184. package/docs/server.md +44 -6
  185. package/docs/session-store-conformance.md +2 -0
  186. package/docs/session-stores.md +41 -2
  187. package/docs/sqlite-persistence.md +11 -3
  188. package/docs/structured-output.md +7 -1
  189. package/docs/supervisors.md +8 -0
  190. package/docs/tool-effects.md +95 -0
  191. package/docs/tools.md +5 -0
  192. package/docs/work-artifacts-and-review.md +102 -0
  193. package/docs/work-connectors.md +32 -0
  194. package/docs/work-tools.md +137 -0
  195. package/docs/workflows.md +6 -0
  196. package/docs/working-and-semantic-memory.md +40 -7
  197. package/package.json +30 -8
  198. package/templates/init/providers.json +22 -0
  199. package/docs/review-coverage-2026-07-14.md +0 -260
  200. package/docs/review-coverage-2026-07-15.md +0 -193
  201. package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
  202. package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
  203. package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
package/dist/content.js CHANGED
@@ -112,12 +112,12 @@ export function assertSsrfAllowedUrl(url, policy = {}) {
112
112
  }
113
113
  if (policy.denyPrivateHosts === false)
114
114
  return;
115
- if (hostname === "localhost"
116
- || hostname.endsWith(".localhost")
117
- || hostname.endsWith(".local")
118
- || hostname === "metadata"
119
- || hostname === "metadata.google.internal"
120
- || hostname === "instance-data") {
115
+ if (hostname === "localhost" ||
116
+ hostname.endsWith(".localhost") ||
117
+ hostname.endsWith(".local") ||
118
+ hostname === "metadata" ||
119
+ hostname === "metadata.google.internal" ||
120
+ hostname === "instance-data") {
121
121
  throw new MediaContentError("ssrf_denied", `Media URL host ${hostname} is not allowed`);
122
122
  }
123
123
  if (isBlockedIp(hostname)) {
@@ -306,9 +306,7 @@ async function fetchBoundedMediaUrl(url, options) {
306
306
  assertSsrfAllowedUrl(url, options.ssrf);
307
307
  const timeoutController = new AbortController();
308
308
  const timeout = setTimeout(() => timeoutController.abort(), options.timeoutMs);
309
- const signal = options.signal
310
- ? AbortSignal.any([options.signal, timeoutController.signal])
311
- : timeoutController.signal;
309
+ const signal = options.signal ? AbortSignal.any([options.signal, timeoutController.signal]) : timeoutController.signal;
312
310
  try {
313
311
  if (options.fetch)
314
312
  return await readFetchResponse(await options.fetch(url, { signal, redirect: "error" }), options.maxBytes);
@@ -372,7 +370,13 @@ async function raceAbort(promise, signal) {
372
370
  reject(signal.reason);
373
371
  };
374
372
  signal.addEventListener("abort", onAbort, { once: true });
375
- promise.then((value) => { cleanup(); resolve(value); }, (error) => { cleanup(); reject(error); });
373
+ promise.then((value) => {
374
+ cleanup();
375
+ resolve(value);
376
+ }, (error) => {
377
+ cleanup();
378
+ reject(error);
379
+ });
376
380
  });
377
381
  }
378
382
  async function readFetchResponse(response, maxBytes) {
@@ -479,22 +483,22 @@ function isBlockedIp(hostname) {
479
483
  }
480
484
  function isBlockedIpv4(address) {
481
485
  const [a, b] = address.split(".").map(Number);
482
- return a === 0
483
- || a === 10
484
- || a === 127
485
- || (a === 100 && b >= 64 && b <= 127)
486
- || (a === 169 && b === 254)
487
- || (a === 172 && b >= 16 && b <= 31)
488
- || (a === 192 && (b === 0 || b === 168))
489
- || (a === 198 && (b === 18 || b === 19 || b === 51))
490
- || (a === 203 && b === 0)
491
- || a >= 224;
486
+ return (a === 0 ||
487
+ a === 10 ||
488
+ a === 127 ||
489
+ (a === 100 && b >= 64 && b <= 127) ||
490
+ (a === 169 && b === 254) ||
491
+ (a === 172 && b >= 16 && b <= 31) ||
492
+ (a === 192 && (b === 0 || b === 168)) ||
493
+ (a === 198 && (b === 18 || b === 19 || b === 51)) ||
494
+ (a === 203 && b === 0) ||
495
+ a >= 224);
492
496
  }
493
497
  function isBlockedIpv6(address) {
494
498
  const words = parseIpv6Words(address);
495
499
  if (!words)
496
500
  return true;
497
- if (words.every((word) => word === 0) || words.slice(0, 7).every((word) => word === 0) && words[7] === 1)
501
+ if (words.every((word) => word === 0) || (words.slice(0, 7).every((word) => word === 0) && words[7] === 1))
498
502
  return true;
499
503
  if ((words[0] & 0xfe00) === 0xfc00)
500
504
  return true;
@@ -517,9 +521,7 @@ function parseIpv6Words(address) {
517
521
  if (missing < 0 || (parts.length === 1 && missing !== 0))
518
522
  return undefined;
519
523
  const words = [...left, ...Array.from({ length: missing }, () => "0"), ...right].map((part) => Number.parseInt(part, 16));
520
- return words.length === 8 && words.every((word) => Number.isInteger(word) && word >= 0 && word <= 0xffff)
521
- ? words
522
- : undefined;
524
+ return words.length === 8 && words.every((word) => Number.isInteger(word) && word >= 0 && word <= 0xffff) ? words : undefined;
523
525
  }
524
526
  function startsWith(bytes, prefix) {
525
527
  if (bytes.length < prefix.length)
@@ -0,0 +1,67 @@
1
+ import type { ContextBlock, InputAssemblyLayout, Message, ProviderRequest, Skill, ToolDefinition } from "./contracts.js";
2
+ import { type LoadedSkillSet, type SkillsDisclosure } from "./skill-disclosure.js";
3
+ /** Assembler-time input budget. At least one max required when present. */
4
+ export interface ContextBudget {
5
+ readonly maxInputTokens?: number;
6
+ readonly maxInputBytes?: number;
7
+ readonly reportOmissions?: boolean;
8
+ }
9
+ export type ContextBudgetOmissionKind = "skills" | "skill_body" | "context" | "history" | "tool_results" | "summaries" | "attachments" | "tools";
10
+ export interface ContextBudgetOmission {
11
+ readonly kind: ContextBudgetOmissionKind;
12
+ readonly id?: string;
13
+ readonly tokenEstimate: number;
14
+ readonly byteLength: number;
15
+ }
16
+ export interface ContextBudgetReport {
17
+ readonly omitted: readonly ContextBudgetOmission[];
18
+ readonly keptTokens: number;
19
+ readonly keptBytes: number;
20
+ readonly maxInputTokens?: number;
21
+ readonly maxInputBytes?: number;
22
+ readonly truncated: boolean;
23
+ }
24
+ export interface ContextBudgetMessageGroups {
25
+ readonly instructions: readonly Message[];
26
+ readonly summaries: readonly Message[];
27
+ readonly history: readonly Message[];
28
+ readonly input: readonly Message[];
29
+ readonly attachments: readonly Message[];
30
+ readonly toolResults: readonly Message[];
31
+ }
32
+ export declare const CONTEXT_BUDGET_REPORT_METADATA_KEY: "contextBudgetReport";
33
+ export declare const HARD_MAX_CONTEXT_BUDGET_TOKENS = 2000000;
34
+ export declare const HARD_MAX_CONTEXT_BUDGET_BYTES: number;
35
+ export declare const DEFAULT_MAX_CONTEXT_BUDGET_OMISSIONS = 256;
36
+ export declare const HARD_MAX_CONTEXT_BUDGET_OMISSIONS = 1024;
37
+ export declare const CONTEXT_BUDGET_ERROR_CODE: "context_budget_exceeded";
38
+ export declare class ContextBudgetError extends Error {
39
+ readonly code: "context_budget_exceeded";
40
+ constructor(message?: string);
41
+ }
42
+ export declare function isContextBudgetError(error: unknown): error is ContextBudgetError;
43
+ /** UTF-16 code units / 4. Estimate only — not billing. */
44
+ export declare function estimateTextTokens(text: string): number;
45
+ export declare function estimateTextBytes(text: string): number;
46
+ export declare function estimateMessageTokens(message: Message): number;
47
+ export declare function estimateMessageBytes(message: Message): number;
48
+ export declare function estimateAssemblyTokens(messages: readonly Message[]): number;
49
+ export declare function resolveContextBudget(budget: ContextBudget): Required<Pick<ContextBudget, "reportOmissions">> & ContextBudget;
50
+ export declare function getContextBudgetReport(request: ProviderRequest): ContextBudgetReport | undefined;
51
+ export declare function applyContextBudget(options: {
52
+ readonly groups: ContextBudgetMessageGroups;
53
+ readonly context?: readonly ContextBlock[];
54
+ readonly skills?: readonly Skill[];
55
+ readonly tools?: readonly ToolDefinition[];
56
+ readonly budget: ContextBudget;
57
+ readonly layout?: InputAssemblyLayout;
58
+ readonly skillsDisclosure?: SkillsDisclosure;
59
+ readonly loadedSkills?: LoadedSkillSet;
60
+ }): {
61
+ readonly groups: ContextBudgetMessageGroups;
62
+ readonly context: readonly ContextBlock[];
63
+ readonly skills: readonly Skill[];
64
+ readonly tools: readonly ToolDefinition[] | undefined;
65
+ readonly demotedSkillBodies: readonly string[];
66
+ readonly report: ContextBudgetReport;
67
+ };
@@ -0,0 +1,288 @@
1
+ import { capSkillCatalog, selectSkillsForPrompt, skillHasRenderableBody, skillPromptText, } from "./skill-disclosure.js";
2
+ export const CONTEXT_BUDGET_REPORT_METADATA_KEY = "contextBudgetReport";
3
+ export const HARD_MAX_CONTEXT_BUDGET_TOKENS = 2_000_000;
4
+ export const HARD_MAX_CONTEXT_BUDGET_BYTES = 32 * 1024 * 1024;
5
+ export const DEFAULT_MAX_CONTEXT_BUDGET_OMISSIONS = 256;
6
+ export const HARD_MAX_CONTEXT_BUDGET_OMISSIONS = 1_024;
7
+ export const CONTEXT_BUDGET_ERROR_CODE = "context_budget_exceeded";
8
+ export class ContextBudgetError extends Error {
9
+ code = CONTEXT_BUDGET_ERROR_CODE;
10
+ constructor(message = "context budget exceeded: mandatory prefix cannot fit") {
11
+ super(message);
12
+ this.name = "ContextBudgetError";
13
+ }
14
+ }
15
+ export function isContextBudgetError(error) {
16
+ return error instanceof Error && error.code === CONTEXT_BUDGET_ERROR_CODE;
17
+ }
18
+ /** UTF-16 code units / 4. Estimate only — not billing. */
19
+ export function estimateTextTokens(text) {
20
+ return Math.ceil(text.length / 4);
21
+ }
22
+ export function estimateTextBytes(text) {
23
+ return new TextEncoder().encode(text).byteLength;
24
+ }
25
+ export function estimateMessageTokens(message) {
26
+ return estimateTextTokens(messageText(message));
27
+ }
28
+ export function estimateMessageBytes(message) {
29
+ return estimateTextBytes(messageText(message));
30
+ }
31
+ export function estimateAssemblyTokens(messages) {
32
+ return messages.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
33
+ }
34
+ export function resolveContextBudget(budget) {
35
+ const hasTokens = budget.maxInputTokens !== undefined;
36
+ const hasBytes = budget.maxInputBytes !== undefined;
37
+ if (!hasTokens && !hasBytes) {
38
+ throw new TypeError("contextBudget requires maxInputTokens and/or maxInputBytes");
39
+ }
40
+ if (hasTokens)
41
+ assertPositiveCap(budget.maxInputTokens, "maxInputTokens", HARD_MAX_CONTEXT_BUDGET_TOKENS);
42
+ if (hasBytes)
43
+ assertPositiveCap(budget.maxInputBytes, "maxInputBytes", HARD_MAX_CONTEXT_BUDGET_BYTES);
44
+ return { ...budget, reportOmissions: budget.reportOmissions === true };
45
+ }
46
+ export function getContextBudgetReport(request) {
47
+ const value = request.metadata?.[CONTEXT_BUDGET_REPORT_METADATA_KEY];
48
+ return isContextBudgetReport(value) ? value : undefined;
49
+ }
50
+ export function applyContextBudget(options) {
51
+ const budget = resolveContextBudget(options.budget);
52
+ const layout = options.layout ?? "cache_aware";
53
+ const groups = {
54
+ instructions: [...options.groups.instructions],
55
+ summaries: [...options.groups.summaries],
56
+ history: [...options.groups.history],
57
+ input: [...options.groups.input],
58
+ attachments: [...options.groups.attachments],
59
+ toolResults: [...options.groups.toolResults],
60
+ };
61
+ const context = [...(options.context ?? [])];
62
+ const skillContext = { disclosure: options.skillsDisclosure, loaded: options.loadedSkills };
63
+ const skills = [...capSkillCatalog(selectSkillsForPrompt(options.skills ?? [], skillContext))];
64
+ const tools = options.tools ? [...options.tools] : undefined;
65
+ const omitted = [];
66
+ const demotedBodies = new Set();
67
+ // Measure once, then subtract each dropped item's own estimate (dropNext computes it
68
+ // with the same estimators) — avoids an O(n²) re-scan of the full keep-set per drop.
69
+ const kept = measureAll(groups, context, skills, tools, skillContext, demotedBodies);
70
+ while (overBudget(kept, budget)) {
71
+ const drop = dropNext(groups, context, skills, layout, skillContext, demotedBodies);
72
+ if (!drop) {
73
+ throw new ContextBudgetError();
74
+ }
75
+ kept.tokens -= drop.tokenEstimate;
76
+ kept.bytes -= drop.byteLength;
77
+ if (omitted.length < HARD_MAX_CONTEXT_BUDGET_OMISSIONS)
78
+ omitted.push(drop);
79
+ }
80
+ const reportOmissions = omitted.slice(0, DEFAULT_MAX_CONTEXT_BUDGET_OMISSIONS);
81
+ return {
82
+ groups,
83
+ context,
84
+ skills,
85
+ tools,
86
+ demotedSkillBodies: [...demotedBodies],
87
+ report: {
88
+ omitted: budget.reportOmissions ? reportOmissions : [],
89
+ keptTokens: kept.tokens,
90
+ keptBytes: kept.bytes,
91
+ maxInputTokens: budget.maxInputTokens,
92
+ maxInputBytes: budget.maxInputBytes,
93
+ truncated: omitted.length > 0,
94
+ },
95
+ };
96
+ }
97
+ function dropNext(groups, context, skills, layout, skillContext, demotedBodies) {
98
+ // ponytail: drop droppable groups in layout order; within history, drop oldest first (shift).
99
+ // cache_aware keeps attachments longer so stable prefix stays intact while budget still allows it.
100
+ const order = layout === "cache_aware"
101
+ ? ["tool_results", "history", "summaries", "context", "skills", "attachments"]
102
+ : ["tool_results", "history", "summaries", "attachments", "context", "skills"];
103
+ for (const kind of order) {
104
+ if (kind === "tool_results" && groups.toolResults.length > 0) {
105
+ const message = groups.toolResults.pop();
106
+ return omission("tool_results", message.id ?? toolResultId(message), message);
107
+ }
108
+ if (kind === "history" && groups.history.length > 0) {
109
+ const message = groups.history.shift();
110
+ return omission("history", message.id, message);
111
+ }
112
+ if (kind === "summaries" && groups.summaries.length > 0) {
113
+ const message = groups.summaries.pop();
114
+ return omission("summaries", message.id, message);
115
+ }
116
+ if (kind === "attachments" && groups.attachments.length > 0) {
117
+ const message = groups.attachments.pop();
118
+ return omission("attachments", message.id, message);
119
+ }
120
+ if (kind === "context" && context.length > 0) {
121
+ const index = pickVictimIndex(context, (block) => block.priority ?? 0);
122
+ const block = context.splice(index, 1)[0];
123
+ const text = `${block.title ? `${block.title}:\n` : "Context:\n"}${contextBlockText(block)}`;
124
+ return {
125
+ kind: "context",
126
+ id: block.id ?? block.title,
127
+ tokenEstimate: estimateTextTokens(text),
128
+ byteLength: estimateTextBytes(text),
129
+ };
130
+ }
131
+ if (kind === "skills" && skills.length > 0) {
132
+ const renderContext = withDemoted(skillContext, demotedBodies);
133
+ const demoteIndex = findSkillBodyIndex(skills, renderContext);
134
+ if (demoteIndex !== undefined) {
135
+ const skill = skills[demoteIndex];
136
+ const beforeText = skillPromptText(skill, renderContext) ?? "";
137
+ demotedBodies.add(skill.name);
138
+ const afterText = skillPromptText(skill, withDemoted(skillContext, demotedBodies)) ?? "";
139
+ return {
140
+ kind: "skill_body",
141
+ id: skill.name,
142
+ tokenEstimate: estimateTextTokens(beforeText) - estimateTextTokens(afterText),
143
+ byteLength: estimateTextBytes(beforeText) - estimateTextBytes(afterText),
144
+ };
145
+ }
146
+ const index = pickVictimIndex(skills, () => 0);
147
+ const skill = skills.splice(index, 1)[0];
148
+ const text = skillPromptText(skill, withDemoted(skillContext, demotedBodies)) ?? "";
149
+ return {
150
+ kind: "skills",
151
+ id: skill.name,
152
+ tokenEstimate: estimateTextTokens(text),
153
+ byteLength: estimateTextBytes(text),
154
+ };
155
+ }
156
+ }
157
+ return undefined;
158
+ }
159
+ function omission(kind, id, message) {
160
+ return {
161
+ kind,
162
+ id,
163
+ tokenEstimate: estimateMessageTokens(message),
164
+ byteLength: estimateMessageBytes(message),
165
+ };
166
+ }
167
+ function toolResultId(message) {
168
+ const block = message.content.find((part) => part.type === "tool_result");
169
+ return block && block.type === "tool_result" ? block.toolCallId : undefined;
170
+ }
171
+ function measureAll(groups, context, skills, tools, skillContext, demotedBodies) {
172
+ const renderContext = withDemoted(skillContext, demotedBodies);
173
+ let tokens = 0;
174
+ let bytes = 0;
175
+ const addMessage = (message) => {
176
+ tokens += estimateMessageTokens(message);
177
+ bytes += estimateMessageBytes(message);
178
+ };
179
+ for (const message of groups.instructions)
180
+ addMessage(message);
181
+ for (const message of groups.summaries)
182
+ addMessage(message);
183
+ for (const message of groups.history)
184
+ addMessage(message);
185
+ for (const message of groups.input)
186
+ addMessage(message);
187
+ for (const message of groups.attachments)
188
+ addMessage(message);
189
+ for (const message of groups.toolResults)
190
+ addMessage(message);
191
+ for (const block of context) {
192
+ const text = `${block.title ? `${block.title}:\n` : "Context:\n"}${contextBlockText(block)}`;
193
+ tokens += estimateTextTokens(text);
194
+ bytes += estimateTextBytes(text);
195
+ }
196
+ for (const skill of skills) {
197
+ const text = skillPromptText(skill, renderContext) ?? "";
198
+ tokens += estimateTextTokens(text);
199
+ bytes += estimateTextBytes(text);
200
+ }
201
+ if (tools?.length) {
202
+ const text = `Available tools:\n${tools.map((tool) => `- ${tool.name}${tool.description ? `: ${tool.description}` : ""}`).join("\n")}`;
203
+ tokens += estimateTextTokens(text);
204
+ bytes += estimateTextBytes(text);
205
+ }
206
+ return { tokens, bytes };
207
+ }
208
+ function overBudget(cost, budget) {
209
+ if (budget.maxInputTokens !== undefined && cost.tokens > budget.maxInputTokens)
210
+ return true;
211
+ if (budget.maxInputBytes !== undefined && cost.bytes > budget.maxInputBytes)
212
+ return true;
213
+ return false;
214
+ }
215
+ function assertPositiveCap(value, name, hardMax) {
216
+ if (!Number.isSafeInteger(value) || value < 1 || value > hardMax) {
217
+ throw new TypeError(`contextBudget.${name} must be a safe integer from 1 to ${hardMax}`);
218
+ }
219
+ }
220
+ function isContextBudgetReport(value) {
221
+ if (!value || typeof value !== "object" || Array.isArray(value))
222
+ return false;
223
+ const report = value;
224
+ return (Array.isArray(report.omitted) &&
225
+ typeof report.keptTokens === "number" &&
226
+ typeof report.keptBytes === "number" &&
227
+ typeof report.truncated === "boolean");
228
+ }
229
+ function messageText(message) {
230
+ return message.content
231
+ .map((part) => {
232
+ if (part.type === "text" || part.type === "thinking")
233
+ return part.text;
234
+ if (part.type === "tool_result")
235
+ return JSON.stringify(part.result ?? part.error ?? null);
236
+ if (part.type === "tool_call")
237
+ return `${part.name}(${JSON.stringify(part.arguments)})`;
238
+ if (part.type === "tool_call_delta")
239
+ return `${part.name ?? "tool"}(${part.argumentsText ?? ""})`;
240
+ if (part.type === "image")
241
+ return part.url ?? part.resourceUri ?? part.mimeType ?? "[image]";
242
+ if (part.type === "audio")
243
+ return part.transcript ?? part.name ?? part.url ?? part.resourceUri ?? part.mediaType ?? "[audio]";
244
+ if (part.type === "file")
245
+ return part.name ?? part.url ?? part.resourceUri ?? part.mediaType ?? "[file]";
246
+ if (part.type === "document")
247
+ return part.transcript ?? part.name ?? part.url ?? part.resourceUri ?? part.mediaType ?? "[document]";
248
+ return "[content]";
249
+ })
250
+ .join("\n");
251
+ }
252
+ /** Lowest priority first; equal priority → LIFO (highest index). */
253
+ function pickVictimIndex(items, priorityOf) {
254
+ let best = 0;
255
+ for (let i = 1; i < items.length; i++) {
256
+ const pi = priorityOf(items[i]);
257
+ const pb = priorityOf(items[best]);
258
+ if (pi < pb || (pi === pb && i > best))
259
+ best = i;
260
+ }
261
+ return best;
262
+ }
263
+ function withDemoted(context, demotedBodies) {
264
+ return demotedBodies.size > 0 ? { ...context, demotedBodies } : context;
265
+ }
266
+ /** Index of skill with renderable body to demote; LIFO among ties (all skills default priority 0). */
267
+ function findSkillBodyIndex(skills, renderContext) {
268
+ let best;
269
+ for (let i = 0; i < skills.length; i++) {
270
+ if (!skillHasRenderableBody(skills[i], renderContext))
271
+ continue;
272
+ if (best === undefined || i > best)
273
+ best = i;
274
+ }
275
+ return best;
276
+ }
277
+ function contextBlockText(block) {
278
+ if (typeof block.content === "string")
279
+ return block.content;
280
+ return block.content
281
+ .map((part) => {
282
+ if (part.type === "text" || part.type === "thinking")
283
+ return part.text;
284
+ return "[content]";
285
+ })
286
+ .join("\n");
287
+ }
288
+ //# sourceMappingURL=context-budget.js.map