@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.
- package/CHANGELOG.md +285 -2
- package/README.md +17 -3
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-event-source.d.ts +11 -0
- package/dist/agent-event-source.js +512 -0
- package/dist/agent-loops.d.ts +5 -0
- package/dist/agent-loops.js +99 -14
- package/dist/agent-run-lifecycle.d.ts +5 -2
- package/dist/agent-run-lifecycle.js +18 -2
- package/dist/agent-run-state.d.ts +27 -1
- package/dist/agent-run-state.js +113 -7
- package/dist/agents.d.ts +3 -1
- package/dist/agents.js +1255 -129
- package/dist/artifacts.d.ts +132 -0
- package/dist/artifacts.js +44 -0
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +17 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +2 -6
- package/dist/cli-runner.js +71 -33
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.d.ts +67 -0
- package/dist/context-budget.js +288 -0
- package/dist/contracts.d.ts +590 -8
- package/dist/contracts.js +142 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/conversations.d.ts +50 -0
- package/dist/conversations.js +98 -0
- package/dist/credentials.d.ts +22 -2
- package/dist/credentials.js +18 -3
- package/dist/devices.d.ts +94 -0
- package/dist/devices.js +138 -0
- package/dist/event-multiplexer.js +18 -4
- package/dist/extensions.d.ts +18 -1
- package/dist/extensions.js +79 -6
- package/dist/feedback.js +12 -10
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +26 -17
- package/dist/identity.d.ts +92 -0
- package/dist/identity.js +265 -0
- package/dist/index.d.ts +94 -72
- package/dist/index.js +48 -36
- package/dist/input.d.ts +10 -1
- package/dist/input.js +152 -52
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +13 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.d.ts +103 -0
- package/dist/persistence-lifecycle.js +202 -0
- package/dist/provider-events.d.ts +1 -0
- package/dist/provider-events.js +6 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.d.ts +46 -1
- package/dist/providers/openai-compatible.js +123 -53
- package/dist/providers/openai-primitives.js +10 -7
- package/dist/providers/transport.d.ts +6 -0
- package/dist/providers/transport.js +21 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/redaction.d.ts +1 -0
- package/dist/redaction.js +26 -9
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/rpc.js +55 -11
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +16 -13
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +8 -2
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +7 -2
- package/dist/session-stores.js +195 -21
- package/dist/skill-disclosure.d.ts +35 -0
- package/dist/skill-disclosure.js +101 -0
- package/dist/skill-load.d.ts +25 -0
- package/dist/skill-load.js +112 -0
- package/dist/structured-output.d.ts +5 -1
- package/dist/structured-output.js +20 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/agent-event-source-conformance.d.ts +4 -0
- package/dist/testing/agent-event-source-conformance.js +54 -0
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +280 -35
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.d.ts +6 -0
- package/dist/testing/session-store-conformance.js +37 -2
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
- package/dist/testing/tool-effect-store-conformance.js +85 -0
- package/dist/thinking.js +4 -1
- package/dist/tool-effects.d.ts +15 -0
- package/dist/tool-effects.js +352 -0
- package/dist/tool-result-fold.d.ts +40 -0
- package/dist/tool-result-fold.js +176 -0
- package/dist/tools.d.ts +8 -3
- package/dist/tools.js +248 -13
- package/docs/0.1.0-readiness.md +202 -0
- package/docs/a2a.md +33 -2
- package/docs/acp.md +126 -0
- package/docs/ag-ui-adoption.md +77 -0
- package/docs/ag-ui.md +225 -0
- package/docs/agent-events.md +34 -3
- package/docs/agent-identity.md +144 -0
- package/docs/agent-loops.md +17 -2
- package/docs/agent-session-runtime.md +21 -4
- package/docs/browser-automation.md +5 -0
- package/docs/caveman.md +129 -0
- package/docs/cli-rpc.md +3 -6
- package/docs/coding-agent-tools.md +229 -25
- package/docs/coding-security.md +77 -11
- package/docs/compaction-and-retry.md +5 -2
- package/docs/compaction-llm.md +20 -1
- package/docs/compaction-observational-memory.md +52 -8
- package/docs/context-and-skills.md +94 -7
- package/docs/contribution-registries.md +1 -0
- package/docs/conversations.md +135 -0
- package/docs/credential-storage.md +34 -1
- package/docs/credentials-and-redaction.md +11 -1
- package/docs/database-persistence.md +27 -7
- package/docs/device-adapters.md +97 -0
- package/docs/enterprise-postgres-state.md +178 -0
- package/docs/evaluations.md +14 -1
- package/docs/extensions.md +4 -1
- package/docs/forge-integration.md +113 -0
- package/docs/guardrails.md +16 -2
- package/docs/host-security.md +35 -4
- package/docs/index.md +69 -37
- package/docs/input-and-prompt-assembly.md +8 -7
- package/docs/language-intelligence.md +162 -0
- package/docs/mcp-tools.md +62 -5
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +423 -2
- package/docs/model-routing.md +111 -0
- package/docs/multimodal-content.md +8 -5
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/observability.md +2 -0
- package/docs/openapi-tools.md +56 -0
- package/docs/performance.md +282 -0
- package/docs/policy-and-audit.md +171 -0
- package/docs/ponytail.md +127 -0
- package/docs/postgres-persistence.md +8 -4
- package/docs/process-sessions.md +147 -0
- package/docs/provider-caching.md +13 -1
- package/docs/provider-conformance.md +29 -5
- package/docs/provider-packages.md +43 -2
- package/docs/provider-request-policies.md +2 -0
- package/docs/providers/ai-sdk.md +24 -7
- package/docs/providers/alibaba.md +179 -0
- package/docs/providers/anthropic.md +93 -0
- package/docs/providers/azure.md +74 -0
- package/docs/providers/bedrock.md +72 -0
- package/docs/providers/google.md +89 -0
- package/docs/providers/ollama.md +166 -0
- package/docs/providers/openai-compatible.md +31 -2
- package/docs/providers/openai.md +24 -5
- package/docs/providers/openrouter.md +2 -0
- package/docs/providers/vertex.md +71 -0
- package/docs/public-contracts.md +61 -4
- package/docs/rag.md +41 -12
- package/docs/release-and-install.md +323 -206
- package/docs/resource-loading.md +3 -0
- package/docs/runs-and-usage.md +3 -0
- package/docs/server.md +44 -6
- package/docs/session-store-conformance.md +2 -0
- package/docs/session-stores.md +41 -2
- package/docs/sqlite-persistence.md +11 -3
- package/docs/structured-output.md +7 -1
- package/docs/supervisors.md +8 -0
- package/docs/tool-effects.md +95 -0
- package/docs/tools.md +5 -0
- package/docs/work-artifacts-and-review.md +102 -0
- package/docs/work-connectors.md +32 -0
- package/docs/work-tools.md +137 -0
- package/docs/workflows.md +6 -0
- package/docs/working-and-semantic-memory.md +40 -7
- package/package.json +30 -8
- package/templates/init/providers.json +22 -0
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- 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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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) => {
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|