@arnilo/prism 0.0.19 → 0.0.20

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.20] - 2026-07-31
4
+
5
+ ### Added
6
+ - Progressive skill disclosure: `skillsDisclosure` (`"progressive"` default, `"eager"` opt-in), session `LoadedSkillSet`, `createLoadSkillTool` / `resolveSkillLoad` (`load_skill`), catalog/body byte caps.
7
+ - Runtime `activateAllSkills` migration opt-in when `AgentConfig.skills` is a `SkillRegistry` without per-run activation.
8
+ - Context budget: `ContextBlock.priority` ordering; skill body demotion (`skill_body` omission) before full drop.
9
+ - Optional `toolResultFold` host-gated projection for aged large tool results (session store untouched).
10
+
11
+ ### Changed
12
+ - Default runtime `SkillRegistry` activation is **empty** when neither `RunOptions.activeSkills` nor `RunOptions.skills` is set (was `SkillRegistry.list()`).
13
+ - Default skill prompt assembly is catalog-only (`name` + `description`); full `instructions` require eager mode or successful `load_skill`.
14
+
15
+ ### Breaking (minor, pre-1.0)
16
+ - Hosts relying on implicit activate-all registry behavior must pass `activateAllSkills: true` or set `activeSkills` / `skills` explicitly.
17
+ - Hosts expecting full skill bodies every turn must set `skillsDisclosure: "eager"` or register and use `load_skill`.
18
+
19
+ See [docs/migration.md](docs/migration.md) for the full 0.0.19 → 0.0.20 notes.
20
+
3
21
  ## [0.0.19] - 2026-07-30
4
22
 
5
23
  ### Added
package/dist/agents.js CHANGED
@@ -15,6 +15,8 @@ import { isFlushableRunLedger } from "./run-ledger.js";
15
15
  import { RunLimitError, RunLimitTracker, resolveRunLimits } from "./run-limits.js";
16
16
  import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext, } from "./session-stores.js";
17
17
  import { resolveActiveSkills } from "./skills.js";
18
+ import { createLoadedSkillSet, resolveSkillsDisclosure } from "./skill-disclosure.js";
19
+ import { resolveToolResultFold } from "./tool-result-fold.js";
18
20
  import { assertStructuredOutputRequestSupported, resolveRunProviderOptions } from "./structured-output.js";
19
21
  import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
20
22
  import { createToolRegistry, dispatchToolCall } from "./tools.js";
@@ -167,6 +169,7 @@ class RuntimeAgentSession {
167
169
  activeLimitOutputBuffer = false;
168
170
  activeDurable;
169
171
  activeLoopTurn = 1;
172
+ loadedSkills = createLoadedSkillSet();
170
173
  ledgerChain = Promise.resolve();
171
174
  ledgerFailure;
172
175
  snapshotGeneration = 0;
@@ -428,6 +431,9 @@ class RuntimeAgentSession {
428
431
  promptBuilder: this.agent.config.promptBuilder,
429
432
  contextProviders,
430
433
  skills: activeSkills,
434
+ skillsDisclosure: resolveSkillsDisclosure(options.skillsDisclosure, this.agent.config.skillsDisclosure),
435
+ toolResultFold: resolveToolResultFold(options.toolResultFold, this.agent.config.toolResultFold),
436
+ loadedSkills: this.loadedSkills,
431
437
  tools,
432
438
  resourceLoader: this.agent.config.resourceLoader,
433
439
  permission: this.agent.config.permission,
@@ -472,7 +478,12 @@ class RuntimeAgentSession {
472
478
  runId,
473
479
  toolCallId: call.id,
474
480
  signal: controller.signal,
475
- metadata,
481
+ metadata: {
482
+ ...metadata,
483
+ loadedSkills: this.loadedSkills,
484
+ activeTools: tools,
485
+ activeSkillNames: activeSkills.map((skill) => skill.name),
486
+ },
476
487
  identity: this.activeIdentity,
477
488
  },
478
489
  middleware: this.agent.config.middleware,
@@ -815,7 +826,11 @@ class RuntimeAgentSession {
815
826
  if (configured && typeof configured === "object" && "list" in configured) {
816
827
  if (options.activeSkills)
817
828
  return resolveActiveSkills({ registry: configured, names: options.activeSkills, tools });
818
- return configured.list();
829
+ if (options.skills !== undefined)
830
+ return options.skills;
831
+ if (options.activateAllSkills ?? this.agent.config.activateAllSkills)
832
+ return configured.list();
833
+ return [];
819
834
  }
820
835
  const arr = options.skills ?? (Array.isArray(configured) ? configured : []);
821
836
  return arr;
@@ -1,11 +1,12 @@
1
1
  import type { ContextBlock, InputAssemblyLayout, Message, ProviderRequest, Skill, ToolDefinition } from "./contracts.js";
2
+ import { type LoadedSkillSet, type SkillsDisclosure } from "./skill-disclosure.js";
2
3
  /** Assembler-time input budget. At least one max required when present. */
3
4
  export interface ContextBudget {
4
5
  readonly maxInputTokens?: number;
5
6
  readonly maxInputBytes?: number;
6
7
  readonly reportOmissions?: boolean;
7
8
  }
8
- export type ContextBudgetOmissionKind = "skills" | "context" | "history" | "tool_results" | "summaries" | "attachments" | "tools";
9
+ export type ContextBudgetOmissionKind = "skills" | "skill_body" | "context" | "history" | "tool_results" | "summaries" | "attachments" | "tools";
9
10
  export interface ContextBudgetOmission {
10
11
  readonly kind: ContextBudgetOmissionKind;
11
12
  readonly id?: string;
@@ -54,10 +55,13 @@ export declare function applyContextBudget(options: {
54
55
  readonly tools?: readonly ToolDefinition[];
55
56
  readonly budget: ContextBudget;
56
57
  readonly layout?: InputAssemblyLayout;
58
+ readonly skillsDisclosure?: SkillsDisclosure;
59
+ readonly loadedSkills?: LoadedSkillSet;
57
60
  }): {
58
61
  readonly groups: ContextBudgetMessageGroups;
59
62
  readonly context: readonly ContextBlock[];
60
63
  readonly skills: readonly Skill[];
61
64
  readonly tools: readonly ToolDefinition[] | undefined;
65
+ readonly demotedSkillBodies: readonly string[];
62
66
  readonly report: ContextBudgetReport;
63
67
  };
@@ -1,3 +1,4 @@
1
+ import { capSkillCatalog, selectSkillsForPrompt, skillHasRenderableBody, skillPromptText, } from "./skill-disclosure.js";
1
2
  export const CONTEXT_BUDGET_REPORT_METADATA_KEY = "contextBudgetReport";
2
3
  export const HARD_MAX_CONTEXT_BUDGET_TOKENS = 2_000_000;
3
4
  export const HARD_MAX_CONTEXT_BUDGET_BYTES = 32 * 1024 * 1024;
@@ -58,14 +59,16 @@ export function applyContextBudget(options) {
58
59
  toolResults: [...options.groups.toolResults],
59
60
  };
60
61
  const context = [...(options.context ?? [])];
61
- const skills = [...(options.skills ?? [])].filter((skill) => skill.instructions);
62
+ const skillContext = { disclosure: options.skillsDisclosure, loaded: options.loadedSkills };
63
+ const skills = [...capSkillCatalog(selectSkillsForPrompt(options.skills ?? [], skillContext))];
62
64
  const tools = options.tools ? [...options.tools] : undefined;
63
65
  const omitted = [];
66
+ const demotedBodies = new Set();
64
67
  // Measure once, then subtract each dropped item's own estimate (dropNext computes it
65
68
  // with the same estimators) — avoids an O(n²) re-scan of the full keep-set per drop.
66
- const kept = measureAll(groups, context, skills, tools);
69
+ const kept = measureAll(groups, context, skills, tools, skillContext, demotedBodies);
67
70
  while (overBudget(kept, budget)) {
68
- const drop = dropNext(groups, context, skills, layout);
71
+ const drop = dropNext(groups, context, skills, layout, skillContext, demotedBodies);
69
72
  if (!drop) {
70
73
  throw new ContextBudgetError();
71
74
  }
@@ -80,6 +83,7 @@ export function applyContextBudget(options) {
80
83
  context,
81
84
  skills,
82
85
  tools,
86
+ demotedSkillBodies: [...demotedBodies],
83
87
  report: {
84
88
  omitted: budget.reportOmissions ? reportOmissions : [],
85
89
  keptTokens: kept.tokens,
@@ -90,7 +94,7 @@ export function applyContextBudget(options) {
90
94
  },
91
95
  };
92
96
  }
93
- function dropNext(groups, context, skills, layout) {
97
+ function dropNext(groups, context, skills, layout, skillContext, demotedBodies) {
94
98
  // ponytail: drop droppable groups in layout order; within history, drop oldest first (shift).
95
99
  // cache_aware keeps attachments longer so stable prefix stays intact while budget still allows it.
96
100
  const order = layout === "cache_aware"
@@ -114,7 +118,8 @@ function dropNext(groups, context, skills, layout) {
114
118
  return omission("attachments", message.id, message);
115
119
  }
116
120
  if (kind === "context" && context.length > 0) {
117
- const block = context.pop();
121
+ const index = pickVictimIndex(context, (block) => block.priority ?? 0);
122
+ const block = context.splice(index, 1)[0];
118
123
  const text = `${block.title ? `${block.title}:\n` : "Context:\n"}${contextBlockText(block)}`;
119
124
  return {
120
125
  kind: "context",
@@ -124,8 +129,23 @@ function dropNext(groups, context, skills, layout) {
124
129
  };
125
130
  }
126
131
  if (kind === "skills" && skills.length > 0) {
127
- const skill = skills.pop();
128
- const text = `Skill ${skill.name}:\n${skill.instructions ?? ""}`;
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)) ?? "";
129
149
  return {
130
150
  kind: "skills",
131
151
  id: skill.name,
@@ -148,7 +168,8 @@ function toolResultId(message) {
148
168
  const block = message.content.find((part) => part.type === "tool_result");
149
169
  return block && block.type === "tool_result" ? block.toolCallId : undefined;
150
170
  }
151
- function measureAll(groups, context, skills, tools) {
171
+ function measureAll(groups, context, skills, tools, skillContext, demotedBodies) {
172
+ const renderContext = withDemoted(skillContext, demotedBodies);
152
173
  let tokens = 0;
153
174
  let bytes = 0;
154
175
  const addMessage = (message) => {
@@ -173,7 +194,7 @@ function measureAll(groups, context, skills, tools) {
173
194
  bytes += estimateTextBytes(text);
174
195
  }
175
196
  for (const skill of skills) {
176
- const text = `Skill ${skill.name}:\n${skill.instructions ?? ""}`;
197
+ const text = skillPromptText(skill, renderContext) ?? "";
177
198
  tokens += estimateTextTokens(text);
178
199
  bytes += estimateTextBytes(text);
179
200
  }
@@ -228,6 +249,31 @@ function messageText(message) {
228
249
  })
229
250
  .join("\n");
230
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
+ }
231
277
  function contextBlockText(block) {
232
278
  if (typeof block.content === "string")
233
279
  return block.content;
@@ -372,6 +372,12 @@ export interface RunOptions {
372
372
  readonly validate?: ToolValidator;
373
373
  readonly activeSkills?: readonly string[];
374
374
  readonly skills?: readonly Skill[];
375
+ /** Migration opt-in: activate every skill in a configured `SkillRegistry` when `activeSkills` / `skills` are unset. */
376
+ readonly activateAllSkills?: true;
377
+ /** Progressive: catalog (name+description) unless loaded; eager: full instructions every turn. Default progressive. */
378
+ readonly skillsDisclosure?: import("./skill-disclosure.js").SkillsDisclosure;
379
+ /** Opt-in projection-only fold for aged large tool results in provider view; store untouched. */
380
+ readonly toolResultFold?: import("./tool-result-fold.js").ToolResultFoldOptions;
375
381
  readonly instructionInjectors?: readonly InstructionInjector[];
376
382
  readonly inputLayout?: InputAssemblyLayout;
377
383
  readonly loop?: AgentLoopStrategy | AgentLoopOptions;
@@ -419,6 +425,12 @@ export interface AgentConfig {
419
425
  readonly tools?: ToolRegistry | readonly ToolDefinition[];
420
426
  readonly context?: readonly ContextProvider[];
421
427
  readonly skills?: SkillRegistry | readonly Skill[];
428
+ /** Migration opt-in: activate every registry skill by default when run options do not narrow activation. */
429
+ readonly activateAllSkills?: true;
430
+ /** Progressive: catalog (name+description) unless loaded; eager: full instructions every turn. Default progressive. */
431
+ readonly skillsDisclosure?: import("./skill-disclosure.js").SkillsDisclosure;
432
+ /** Opt-in projection-only fold for aged large tool results in provider view; store untouched. */
433
+ readonly toolResultFold?: import("./tool-result-fold.js").ToolResultFoldOptions;
422
434
  readonly inputBuilder?: InputBuilder;
423
435
  readonly promptBuilder?: PromptBuilder;
424
436
  readonly middleware?: MiddlewareRegistry;
@@ -946,6 +958,10 @@ export interface PromptBuildRequest {
946
958
  readonly messages: readonly Message[];
947
959
  readonly context?: readonly ContextBlock[];
948
960
  readonly skills?: readonly Skill[];
961
+ readonly skillsDisclosure?: import("./skill-disclosure.js").SkillsDisclosure;
962
+ readonly loadedSkills?: import("./skill-disclosure.js").LoadedSkillSet;
963
+ /** Skills demoted to catalog-only by context budget this turn. */
964
+ readonly demotedSkillBodies?: readonly string[];
949
965
  readonly tools?: readonly ToolDefinition[];
950
966
  /** Model being prompted; lets builders adapt composition to declared capabilities
951
967
  * (e.g. the default builder omits the `Available tools:` text for tool-capable models). */
package/dist/index.d.ts CHANGED
@@ -84,6 +84,12 @@ export { createMemorySessionStore, createSessionEntry, getSessionBranchEntries,
84
84
  export { createChainedSettingsProvider, createStaticSettingsProvider } from "./settings.js";
85
85
  export type { ResolveActiveSkillsOptions, SkillRegistryOptions } from "./skills.js";
86
86
  export { createSkillRegistry, resolveActiveSkills } from "./skills.js";
87
+ export { createLoadedSkillSet, DEFAULT_MAX_SKILL_CATALOG_ENTRIES, DEFAULT_MAX_SKILL_DESCRIPTION_BYTES, DEFAULT_MAX_SKILL_INSTRUCTION_BYTES, EMPTY_SKILL_DESCRIPTION, HARD_MAX_SKILL_CATALOG_ENTRIES, HARD_MAX_SKILL_DESCRIPTION_BYTES, HARD_MAX_SKILL_INSTRUCTION_BYTES, isSkillDisclosureError, resolveSkillsDisclosure, SkillDisclosureError, } from "./skill-disclosure.js";
88
+ export type { LoadedSkillSet, SkillRenderContext, SkillsDisclosure } from "./skill-disclosure.js";
89
+ export type { CreateLoadSkillToolOptions, ResolveSkillLoadOptions } from "./skill-load.js";
90
+ export { createLoadSkillTool, DEFAULT_LOAD_SKILL_TOOL_NAME, isSkillLoadError, MAX_LOAD_SKILL_RESULT_BYTES, resolveSkillLoad, SKILL_LOAD_ERROR_CODE, SkillLoadError, } from "./skill-load.js";
91
+ export { DEFAULT_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, DEFAULT_TOOL_RESULT_FOLD_MIN_AGE_TURNS, DEFAULT_TOOL_RESULT_FOLD_MIN_BYTES, foldedToolResultHeader, foldToolResultHistory, foldToolResults, formatFoldedToolResult, HARD_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, resolveToolResultFold, TOOL_RESULT_FOLD_TURN_METADATA_KEY, } from "./tool-result-fold.js";
92
+ export type { FoldToolResultsContext, ResolvedToolResultFoldOptions, ToolResultFoldInput, ToolResultFoldOptions, } from "./tool-result-fold.js";
87
93
  export { artifactStructuredOutputRequest, assertStructuredOutputRequestSupported, DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH, DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES, modelSupportsStructuredOutput, resolveRunProviderOptions, StructuredOutputError, validateStructuredOutputOptions, withoutStructuredOutput, } from "./structured-output.js";
88
94
  export type { ComposeSystemPromptOptions } from "./system-prompts.js";
89
95
  export { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
@@ -94,5 +100,5 @@ export { createToolParameterValidator, createToolRegistry, dispatchToolCall, fil
94
100
  export type { ResolvedUseCaseModel, ResolveUseCaseModelInput, UseCaseModelBinding, } from "./use-case-model.js";
95
101
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
96
102
  export declare const name = "prism";
97
- export declare const version = "0.0.19";
103
+ export declare const version = "0.0.20";
98
104
  export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
package/dist/index.js CHANGED
@@ -45,12 +45,15 @@ export { assertPermission, assertTrusted, checkPermission, createStaticPermissio
45
45
  export { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, listSessionBranches, rebuildSessionContext, } from "./session-stores.js";
46
46
  export { createChainedSettingsProvider, createStaticSettingsProvider } from "./settings.js";
47
47
  export { createSkillRegistry, resolveActiveSkills } from "./skills.js";
48
+ export { createLoadedSkillSet, DEFAULT_MAX_SKILL_CATALOG_ENTRIES, DEFAULT_MAX_SKILL_DESCRIPTION_BYTES, DEFAULT_MAX_SKILL_INSTRUCTION_BYTES, EMPTY_SKILL_DESCRIPTION, HARD_MAX_SKILL_CATALOG_ENTRIES, HARD_MAX_SKILL_DESCRIPTION_BYTES, HARD_MAX_SKILL_INSTRUCTION_BYTES, isSkillDisclosureError, resolveSkillsDisclosure, SkillDisclosureError, } from "./skill-disclosure.js";
49
+ export { createLoadSkillTool, DEFAULT_LOAD_SKILL_TOOL_NAME, isSkillLoadError, MAX_LOAD_SKILL_RESULT_BYTES, resolveSkillLoad, SKILL_LOAD_ERROR_CODE, SkillLoadError, } from "./skill-load.js";
50
+ export { DEFAULT_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, DEFAULT_TOOL_RESULT_FOLD_MIN_AGE_TURNS, DEFAULT_TOOL_RESULT_FOLD_MIN_BYTES, foldedToolResultHeader, foldToolResultHistory, foldToolResults, formatFoldedToolResult, HARD_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, resolveToolResultFold, TOOL_RESULT_FOLD_TURN_METADATA_KEY, } from "./tool-result-fold.js";
48
51
  export { artifactStructuredOutputRequest, assertStructuredOutputRequestSupported, DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH, DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES, modelSupportsStructuredOutput, resolveRunProviderOptions, StructuredOutputError, validateStructuredOutputOptions, withoutStructuredOutput, } from "./structured-output.js";
49
52
  export { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
50
53
  export { applyThinkingLevel, isThinkingLevel, normalizeThinkingLevel, THINKING_LEVELS, thinkingCompatFor, thinkingFamilyForModel, } from "./thinking.js";
51
54
  export { createToolParameterValidator, createToolRegistry, dispatchToolCall, filterTools } from "./tools.js";
52
55
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
53
56
  export const name = "prism";
54
- export const version = "0.0.19";
57
+ export const version = "0.0.20";
55
58
  export const description = "Agent harness for AI providers, agents, sessions, and tools.";
56
59
  //# sourceMappingURL=index.js.map
package/dist/input.d.ts CHANGED
@@ -2,6 +2,8 @@ import { type ContextBudget } from "./context-budget.js";
2
2
  import type { ContentBlock, ContextBlock, ContextProvider, ContextResolutionContext, InputBuildContext, InputBuilder, InstructionInjector, JsonObject, Message, ModelConfig, PromptBuilder, PromptBuildRequest, ProviderRequest, ProviderRequestOptions, ResourceLoader, Skill, ToolDefinition, ToolResult } from "./contracts.js";
3
3
  import type { MiddlewareRegistry } from "./middleware.js";
4
4
  import type { SecretRedactor } from "./redaction.js";
5
+ import { type LoadedSkillSet, type SkillsDisclosure } from "./skill-disclosure.js";
6
+ import { type ResolvedToolResultFoldOptions } from "./tool-result-fold.js";
5
7
  export type AgentInput = string | Message | readonly Message[];
6
8
  export interface PromptInstruction {
7
9
  readonly text: string;
@@ -53,10 +55,14 @@ export interface AssembleProviderInputOptions extends DefaultInputBuildContext {
53
55
  readonly contextProviders?: readonly ContextProvider[];
54
56
  readonly promptBuilder?: PromptBuilder;
55
57
  readonly skills?: readonly Skill[];
58
+ readonly skillsDisclosure?: SkillsDisclosure;
59
+ readonly loadedSkills?: LoadedSkillSet;
56
60
  readonly tools?: readonly ToolDefinition[];
57
61
  readonly providerOptions?: ProviderRequestOptions;
58
62
  /** Assembler-time eviction; does not delete session store history. */
59
63
  readonly contextBudget?: ContextBudget;
64
+ /** Resolved tool-result fold; projection-only, store untouched. */
65
+ readonly toolResultFold?: ResolvedToolResultFoldOptions;
60
66
  }
61
67
  export declare function createDefaultInputBuilder(): DefaultInputBuilder;
62
68
  export declare function resolveContextProviders(options: ResolveContextOptions): Promise<readonly ContextBlock[]>;
package/dist/input.js CHANGED
@@ -3,6 +3,8 @@ import { applyContextBudget, CONTEXT_BUDGET_REPORT_METADATA_KEY } from "./contex
3
3
  import { runInstructionInjectors } from "./instruction-injection.js";
4
4
  import { redactMessage } from "./redaction.js";
5
5
  import { loadTextResource } from "./resources.js";
6
+ import { skillMessages as buildSkillMessages } from "./skill-disclosure.js";
7
+ import { foldToolResultHistory, foldToolResults } from "./tool-result-fold.js";
6
8
  import { composeSystemPrompt } from "./system-prompts.js";
7
9
  export function createDefaultInputBuilder() {
8
10
  return {
@@ -39,7 +41,16 @@ export function createDefaultPromptBuilder() {
39
41
  // Tool-capable models receive schemas via request.tools; the text list only serves
40
42
  // text-only (or unknown-capability) models — duplicating it doubles tool tokens per turn.
41
43
  const tools = request.model?.capabilities?.tools === true ? undefined : request.tools;
42
- return [...contextMessages(request.context), ...skillMessages(request.skills), ...toolMessages(tools), ...request.messages];
44
+ return [
45
+ ...contextMessages(request.context),
46
+ ...buildSkillMessages(request.skills, {
47
+ disclosure: request.skillsDisclosure,
48
+ loaded: request.loadedSkills,
49
+ demotedBodies: request.demotedSkillBodies?.length ? new Set(request.demotedSkillBodies) : undefined,
50
+ }),
51
+ ...toolMessages(tools),
52
+ ...request.messages,
53
+ ];
43
54
  },
44
55
  };
45
56
  }
@@ -64,6 +75,18 @@ export async function assembleProviderInput(options) {
64
75
  // filter against loop-local turn. Instructions layer via composeSystemPrompt (no parallel prompt
65
76
  // code); contextBlocks merge via resolveContextProviders (middleware still runs).
66
77
  const turn = options.turn ?? 1;
78
+ const foldContext = {
79
+ sessionId: options.sessionId ?? "",
80
+ runId: options.runId ?? "",
81
+ turn,
82
+ signal: options.signal,
83
+ };
84
+ const foldedHistory = options.toolResultFold
85
+ ? await foldToolResultHistory(options.history ?? [], options.toolResultFold, foldContext)
86
+ : (options.history ?? []);
87
+ const foldedToolResults = options.toolResultFold && options.toolResults?.length
88
+ ? await foldToolResults(options.toolResults, options.toolResultFold, foldContext)
89
+ : options.toolResults;
67
90
  const injectorContribs = options.instructionInjectors?.length
68
91
  ? runInstructionInjectors(options.instructionInjectors, {
69
92
  sessionId: options.sessionId ?? "",
@@ -71,7 +94,7 @@ export async function assembleProviderInput(options) {
71
94
  turn,
72
95
  // Runtime history is already redacted; input is redacted here before injector code sees it.
73
96
  input: inputMessages(options.input).map((message) => redactMessage(message, options.redactor)),
74
- history: options.history ?? [],
97
+ history: foldedHistory,
75
98
  metadata: options.metadata ?? {},
76
99
  signal: options.signal ?? new AbortController().signal,
77
100
  })
@@ -79,12 +102,19 @@ export async function assembleProviderInput(options) {
79
102
  const systemInstructions = injectorContribs.instructions.length
80
103
  ? composeSystemPrompt(injectorContribs.instructions, { base: options.systemInstructions })
81
104
  : options.systemInstructions;
82
- const buildContext = { ...options, ...baseContext, systemInstructions };
105
+ const buildContext = {
106
+ ...options,
107
+ ...baseContext,
108
+ systemInstructions,
109
+ history: foldedHistory,
110
+ toolResults: foldedToolResults,
111
+ };
83
112
  const layout = buildContext.inputLayout ?? "cache_aware";
84
113
  let messages;
85
114
  let context;
86
115
  let skills = options.skills;
87
116
  let tools = options.tools;
117
+ let demotedSkillBodies;
88
118
  let budgetReport;
89
119
  if (options.contextBudget) {
90
120
  // ponytail: budget path always uses default groups so eviction kinds stay deterministic;
@@ -104,6 +134,8 @@ export async function assembleProviderInput(options) {
104
134
  tools,
105
135
  budget: options.contextBudget,
106
136
  layout,
137
+ skillsDisclosure: options.skillsDisclosure,
138
+ loadedSkills: options.loadedSkills,
107
139
  });
108
140
  messages = flattenInputGroups(applied.groups, layout);
109
141
  if (buildContext.middleware)
@@ -111,6 +143,7 @@ export async function assembleProviderInput(options) {
111
143
  context = applied.context;
112
144
  skills = applied.skills;
113
145
  tools = applied.tools;
146
+ demotedSkillBodies = applied.demotedSkillBodies;
114
147
  if (options.contextBudget.reportOmissions)
115
148
  budgetReport = applied.report;
116
149
  }
@@ -134,6 +167,9 @@ export async function assembleProviderInput(options) {
134
167
  messages,
135
168
  context,
136
169
  skills,
170
+ skillsDisclosure: options.skillsDisclosure,
171
+ loadedSkills: options.loadedSkills,
172
+ demotedSkillBodies,
137
173
  tools,
138
174
  metadata: options.metadata,
139
175
  signal: options.signal,
@@ -142,6 +178,9 @@ export async function assembleProviderInput(options) {
142
178
  messages,
143
179
  context,
144
180
  skills,
181
+ skillsDisclosure: options.skillsDisclosure,
182
+ loadedSkills: options.loadedSkills,
183
+ demotedSkillBodies,
145
184
  tools,
146
185
  metadata: options.metadata,
147
186
  signal: options.signal,
@@ -274,11 +313,6 @@ function textMessage(role, text, metadata) {
274
313
  function contextMessages(context) {
275
314
  return (context ?? []).map((block) => textMessage("system", `${block.title ? `${block.title}:\n` : "Context:\n"}${blockText(block)}`, block.metadata));
276
315
  }
277
- function skillMessages(skills) {
278
- return (skills ?? [])
279
- .filter((skill) => skill.instructions)
280
- .map((skill) => textMessage("system", `Skill ${skill.name}:\n${skill.instructions}`, skill.metadata));
281
- }
282
316
  function toolMessages(tools) {
283
317
  if (!tools?.length)
284
318
  return [];
@@ -0,0 +1,35 @@
1
+ import type { Message, Skill } from "./contracts.js";
2
+ export type SkillsDisclosure = "progressive" | "eager";
3
+ export declare const EMPTY_SKILL_DESCRIPTION: "(no description)";
4
+ export declare const DEFAULT_MAX_SKILL_CATALOG_ENTRIES = 64;
5
+ export declare const HARD_MAX_SKILL_CATALOG_ENTRIES = 256;
6
+ export declare const DEFAULT_MAX_SKILL_DESCRIPTION_BYTES = 512;
7
+ export declare const HARD_MAX_SKILL_DESCRIPTION_BYTES = 4096;
8
+ export declare const DEFAULT_MAX_SKILL_INSTRUCTION_BYTES = 32768;
9
+ export declare const HARD_MAX_SKILL_INSTRUCTION_BYTES = 262144;
10
+ export declare const SKILL_DISCLOSURE_ERROR_CODE: "skill_disclosure_exceeded";
11
+ export declare class SkillDisclosureError extends Error {
12
+ readonly code: "skill_disclosure_exceeded";
13
+ constructor(message: string);
14
+ }
15
+ export declare function isSkillDisclosureError(error: unknown): error is SkillDisclosureError;
16
+ export interface LoadedSkillSet {
17
+ has(name: string): boolean;
18
+ add(name: string): void;
19
+ list(): readonly string[];
20
+ clear(): void;
21
+ }
22
+ export declare function createLoadedSkillSet(): LoadedSkillSet;
23
+ export interface SkillRenderContext {
24
+ readonly disclosure?: SkillsDisclosure;
25
+ readonly loaded?: LoadedSkillSet;
26
+ /** Budget demotion: render catalog-only even when eager/loaded. */
27
+ readonly demotedBodies?: ReadonlySet<string>;
28
+ }
29
+ export declare function resolveSkillsDisclosure(run?: SkillsDisclosure, agent?: SkillsDisclosure): SkillsDisclosure;
30
+ export declare function capSkillCatalog(skills: readonly Skill[]): readonly Skill[];
31
+ /** Skills that should appear in prompt assembly for the given disclosure mode. */
32
+ export declare function selectSkillsForPrompt(skills: readonly Skill[], context?: SkillRenderContext): readonly Skill[];
33
+ export declare function skillHasRenderableBody(skill: Skill, context?: SkillRenderContext): boolean;
34
+ export declare function skillPromptText(skill: Skill, context?: SkillRenderContext): string | undefined;
35
+ export declare function skillMessages(skills: readonly Skill[] | undefined, context?: SkillRenderContext): Message[];
@@ -0,0 +1,101 @@
1
+ import { estimateTextBytes } from "./context-budget.js";
2
+ export const EMPTY_SKILL_DESCRIPTION = "(no description)";
3
+ export const DEFAULT_MAX_SKILL_CATALOG_ENTRIES = 64;
4
+ export const HARD_MAX_SKILL_CATALOG_ENTRIES = 256;
5
+ export const DEFAULT_MAX_SKILL_DESCRIPTION_BYTES = 512;
6
+ export const HARD_MAX_SKILL_DESCRIPTION_BYTES = 4_096;
7
+ export const DEFAULT_MAX_SKILL_INSTRUCTION_BYTES = 32_768;
8
+ export const HARD_MAX_SKILL_INSTRUCTION_BYTES = 262_144;
9
+ export const SKILL_DISCLOSURE_ERROR_CODE = "skill_disclosure_exceeded";
10
+ export class SkillDisclosureError extends Error {
11
+ code = SKILL_DISCLOSURE_ERROR_CODE;
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "SkillDisclosureError";
15
+ }
16
+ }
17
+ export function isSkillDisclosureError(error) {
18
+ return error instanceof Error && error.code === SKILL_DISCLOSURE_ERROR_CODE;
19
+ }
20
+ export function createLoadedSkillSet() {
21
+ const names = new Set();
22
+ return {
23
+ has(name) {
24
+ return names.has(name);
25
+ },
26
+ add(name) {
27
+ names.add(name);
28
+ },
29
+ list() {
30
+ return [...names];
31
+ },
32
+ clear() {
33
+ names.clear();
34
+ },
35
+ };
36
+ }
37
+ export function resolveSkillsDisclosure(run, agent) {
38
+ return run ?? agent ?? "progressive";
39
+ }
40
+ export function capSkillCatalog(skills) {
41
+ if (skills.length > HARD_MAX_SKILL_CATALOG_ENTRIES) {
42
+ throw new SkillDisclosureError(`Skill catalog exceeds hard cap (${HARD_MAX_SKILL_CATALOG_ENTRIES} entries)`);
43
+ }
44
+ return skills.slice(0, DEFAULT_MAX_SKILL_CATALOG_ENTRIES);
45
+ }
46
+ /** Skills that should appear in prompt assembly for the given disclosure mode. */
47
+ export function selectSkillsForPrompt(skills, context = {}) {
48
+ const disclosure = context.disclosure ?? "progressive";
49
+ if (disclosure === "eager")
50
+ return skills.filter((skill) => skill.instructions);
51
+ return skills;
52
+ }
53
+ export function skillHasRenderableBody(skill, context = {}) {
54
+ const disclosure = context.disclosure ?? "progressive";
55
+ const loaded = context.loaded?.has(skill.name) ?? false;
56
+ const demoted = context.demotedBodies?.has(skill.name) ?? false;
57
+ return !demoted && (disclosure === "eager" || loaded) && !!skill.instructions;
58
+ }
59
+ export function skillPromptText(skill, context = {}) {
60
+ const includeBody = skillHasRenderableBody(skill, context);
61
+ if (includeBody) {
62
+ if (!skill.instructions)
63
+ return undefined;
64
+ const bytes = estimateTextBytes(skill.instructions);
65
+ if (bytes > HARD_MAX_SKILL_INSTRUCTION_BYTES) {
66
+ throw new SkillDisclosureError(`Skill ${skill.name} instructions exceed hard cap (${HARD_MAX_SKILL_INSTRUCTION_BYTES} bytes)`);
67
+ }
68
+ return `Skill ${skill.name}:\n${skill.instructions}`;
69
+ }
70
+ let description = skill.description?.trim() || EMPTY_SKILL_DESCRIPTION;
71
+ const bytes = estimateTextBytes(description);
72
+ if (bytes > HARD_MAX_SKILL_DESCRIPTION_BYTES) {
73
+ throw new SkillDisclosureError(`Skill ${skill.name} description exceeds hard cap (${HARD_MAX_SKILL_DESCRIPTION_BYTES} bytes)`);
74
+ }
75
+ if (bytes > DEFAULT_MAX_SKILL_DESCRIPTION_BYTES) {
76
+ description = truncateUtf8Bytes(description, DEFAULT_MAX_SKILL_DESCRIPTION_BYTES);
77
+ }
78
+ return `Skill ${skill.name}: ${description}`;
79
+ }
80
+ export function skillMessages(skills, context = {}) {
81
+ return capSkillCatalog(selectSkillsForPrompt(skills ?? [], context))
82
+ .map((skill) => {
83
+ const text = skillPromptText(skill, context);
84
+ return text ? textMessage("system", text, skill.metadata) : undefined;
85
+ })
86
+ .filter((message) => message !== undefined);
87
+ }
88
+ function textMessage(role, text, metadata) {
89
+ return { role, content: [{ type: "text", text }], metadata };
90
+ }
91
+ function truncateUtf8Bytes(text, maxBytes) {
92
+ const encoded = new TextEncoder().encode(text);
93
+ if (encoded.length <= maxBytes)
94
+ return text;
95
+ const suffix = new TextEncoder().encode("…");
96
+ let end = Math.max(0, maxBytes - suffix.length);
97
+ while (end > 0 && (encoded[end] & 0xc0) === 0x80)
98
+ end--;
99
+ return new TextDecoder().decode(encoded.slice(0, end)) + "…";
100
+ }
101
+ //# sourceMappingURL=skill-disclosure.js.map
@@ -0,0 +1,25 @@
1
+ import type { Skill, SkillRegistry, ToolDefinition } from "./contracts.js";
2
+ import { type LoadedSkillSet } from "./skill-disclosure.js";
3
+ export declare const DEFAULT_LOAD_SKILL_TOOL_NAME: "load_skill";
4
+ export declare const SKILL_LOAD_ERROR_CODE: "skill_load_failed";
5
+ export declare const MAX_LOAD_SKILL_RESULT_BYTES = 512;
6
+ export declare class SkillLoadError extends Error {
7
+ readonly code: "skill_load_failed";
8
+ constructor(message: string);
9
+ }
10
+ export declare function isSkillLoadError(error: unknown): error is SkillLoadError;
11
+ export interface ResolveSkillLoadOptions {
12
+ readonly registry: SkillRegistry;
13
+ readonly name: string;
14
+ readonly tools?: readonly ToolDefinition[];
15
+ readonly loaded?: LoadedSkillSet;
16
+ readonly activeSkillNames?: readonly string[];
17
+ }
18
+ export declare function resolveSkillLoad(options: ResolveSkillLoadOptions): Skill;
19
+ export interface CreateLoadSkillToolOptions {
20
+ readonly registry: SkillRegistry;
21
+ readonly loaded?: LoadedSkillSet;
22
+ readonly tools?: readonly ToolDefinition[];
23
+ readonly name?: string;
24
+ }
25
+ export declare function createLoadSkillTool(options: CreateLoadSkillToolOptions): ToolDefinition;