@arnilo/prism 0.0.15 → 0.0.17

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 (119) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/agent-definitions.js +2 -3
  3. package/dist/agent-loops.js +12 -7
  4. package/dist/agent-run-lifecycle.d.ts +1 -2
  5. package/dist/agent-run-lifecycle.js +1 -1
  6. package/dist/agent-run-state.js +29 -4
  7. package/dist/agents.d.ts +1 -1
  8. package/dist/agents.js +163 -61
  9. package/dist/cache-helpers.js +18 -9
  10. package/dist/checkpoints.d.ts +4 -0
  11. package/dist/checkpoints.js +17 -9
  12. package/dist/cli-init.js +3 -7
  13. package/dist/cli-runner.d.ts +2 -6
  14. package/dist/cli-runner.js +71 -33
  15. package/dist/compaction.js +5 -4
  16. package/dist/config.js +7 -4
  17. package/dist/content.js +26 -24
  18. package/dist/context-budget.js +18 -11
  19. package/dist/contracts.d.ts +16 -3
  20. package/dist/contracts.js +4 -1
  21. package/dist/contribution-parsing.js +6 -2
  22. package/dist/contributions.d.ts +2 -0
  23. package/dist/contributions.js +3 -0
  24. package/dist/conversations.js +2 -1
  25. package/dist/credentials.d.ts +8 -2
  26. package/dist/credentials.js +9 -3
  27. package/dist/event-multiplexer.js +18 -4
  28. package/dist/extensions.d.ts +7 -1
  29. package/dist/extensions.js +64 -6
  30. package/dist/feedback.js +12 -10
  31. package/dist/guardrails.d.ts +1 -1
  32. package/dist/guardrails.js +26 -17
  33. package/dist/identity.js +10 -2
  34. package/dist/index.d.ts +82 -83
  35. package/dist/index.js +42 -42
  36. package/dist/input.d.ts +2 -2
  37. package/dist/input.js +50 -27
  38. package/dist/instruction-injection.d.ts +1 -1
  39. package/dist/middleware.js +9 -1
  40. package/dist/models.d.ts +2 -0
  41. package/dist/models.js +3 -0
  42. package/dist/node/agent-definitions.js +16 -8
  43. package/dist/node/contribution-discovery.d.ts +1 -2
  44. package/dist/node/contribution-discovery.js +3 -3
  45. package/dist/node/session-store-jsonl.js +10 -7
  46. package/dist/node/settings.d.ts +1 -1
  47. package/dist/node/settings.js +1 -1
  48. package/dist/node/system-project-prompts.js +2 -4
  49. package/dist/node/trust.js +1 -1
  50. package/dist/persistence-lifecycle.js +1 -3
  51. package/dist/provider-events.js +3 -1
  52. package/dist/provider-request-policy.js +3 -4
  53. package/dist/providers/media.d.ts +1 -1
  54. package/dist/providers/openai-compatible.d.ts +42 -1
  55. package/dist/providers/openai-compatible.js +110 -49
  56. package/dist/providers/openai-primitives.js +7 -7
  57. package/dist/providers/transport.d.ts +6 -0
  58. package/dist/providers/transport.js +21 -0
  59. package/dist/providers.d.ts +2 -0
  60. package/dist/providers.js +3 -0
  61. package/dist/redaction.d.ts +1 -0
  62. package/dist/redaction.js +26 -9
  63. package/dist/resources.d.ts +2 -2
  64. package/dist/resources.js +2 -2
  65. package/dist/retry.d.ts +5 -0
  66. package/dist/retry.js +8 -1
  67. package/dist/rpc.js +42 -9
  68. package/dist/run-ledger.d.ts +6 -0
  69. package/dist/run-ledger.js +16 -13
  70. package/dist/run-limits.js +49 -10
  71. package/dist/secure-agent.js +1 -1
  72. package/dist/security.js +7 -2
  73. package/dist/session-stores.d.ts +1 -1
  74. package/dist/session-stores.js +28 -24
  75. package/dist/structured-output.js +2 -2
  76. package/dist/system-prompts.js +7 -2
  77. package/dist/testing/compaction-conformance.js +5 -1
  78. package/dist/testing/extension-conformance.js +15 -3
  79. package/dist/testing/feedback.d.ts +1 -3
  80. package/dist/testing/feedback.js +1 -1
  81. package/dist/testing/persistence-schema.js +206 -37
  82. package/dist/testing/provider-conformance.js +3 -3
  83. package/dist/testing/run-ledger-conformance.js +1 -1
  84. package/dist/testing/session-store-conformance.js +1 -1
  85. package/dist/testing/tool-conformance.js +30 -5
  86. package/dist/thinking.js +4 -1
  87. package/dist/tools.d.ts +2 -2
  88. package/dist/tools.js +24 -5
  89. package/docs/0.1.0-readiness.md +139 -0
  90. package/docs/agent-events.md +2 -1
  91. package/docs/agent-session-runtime.md +2 -2
  92. package/docs/cli-rpc.md +1 -5
  93. package/docs/coding-agent-tools.md +2 -0
  94. package/docs/compaction-and-retry.md +3 -1
  95. package/docs/contribution-registries.md +1 -0
  96. package/docs/credentials-and-redaction.md +1 -1
  97. package/docs/extensions.md +1 -1
  98. package/docs/guardrails.md +13 -2
  99. package/docs/index.md +4 -2
  100. package/docs/input-and-prompt-assembly.md +3 -3
  101. package/docs/middleware-hooks.md +2 -2
  102. package/docs/migration.md +40 -0
  103. package/docs/performance.md +33 -0
  104. package/docs/providers/openai-compatible.md +28 -1
  105. package/docs/public-contracts.md +2 -2
  106. package/docs/release-and-install.md +127 -17
  107. package/docs/session-stores.md +1 -1
  108. package/package.json +14 -6
  109. package/docs/review-coverage-2026-07-14.md +0 -260
  110. package/docs/review-coverage-2026-07-15.md +0 -193
  111. package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
  112. package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
  113. package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
  114. package/docs/review-coverage-2026-07-21-phase-5.md +0 -172
  115. package/docs/review-coverage-2026-07-22-phase-6.md +0 -209
  116. package/docs/review-coverage-2026-07-22-phase-7.md +0 -173
  117. package/docs/review-coverage-2026-07-23-phase-8.md +0 -245
  118. package/docs/review-coverage-2026-07-25-phase-9.md +0 -256
  119. package/docs/review-coverage-2026-07-26-phase-10.md +0 -132
@@ -1,5 +1,8 @@
1
1
  export function sanitizeCacheKey(value, maxLength) {
2
- const key = value?.replace(/[^A-Za-z0-9_.:-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, Math.max(0, maxLength));
2
+ const key = value
3
+ ?.replace(/[^A-Za-z0-9_.:-]+/g, "-")
4
+ .replace(/^-+|-+$/g, "")
5
+ .slice(0, Math.max(0, maxLength));
3
6
  return key || undefined;
4
7
  }
5
8
  export function mapCacheRetention(retention, model) {
@@ -37,7 +40,7 @@ export function cacheSavings(usage, model) {
37
40
  const cacheRead = model.cost?.cacheRead;
38
41
  if (read === undefined || input === undefined || cacheRead === undefined)
39
42
  return undefined;
40
- return read * Math.max(0, input - cacheRead) / costUnitDivisor(model.cost?.unit);
43
+ return (read * Math.max(0, input - cacheRead)) / costUnitDivisor(model.cost?.unit);
41
44
  }
42
45
  export function cacheUsageReport(usage, model) {
43
46
  if (!usage)
@@ -48,17 +51,23 @@ export function cacheUsageReport(usage, model) {
48
51
  cacheWriteTokens: usage.cacheWriteTokens ?? 0,
49
52
  hitRate: cacheHitRate(usage),
50
53
  estimatedSavings,
51
- currency: estimatedSavings === undefined ? usage.currency : model?.cost?.currency ?? usage.currency,
54
+ currency: estimatedSavings === undefined ? usage.currency : (model?.cost?.currency ?? usage.currency),
52
55
  };
53
56
  }
54
57
  function resolveBreakpoint(messages, breakpoint) {
55
58
  switch (breakpoint.location) {
56
- case "system_prompt": return messages.findIndex((message) => message.role === "system");
57
- case "tools": return messages.findIndex((message) => message.role === "tool" || message.content.some((block) => block.type === "tool_call" || block.type === "tool_result"));
58
- case "stable_context": return messages.findIndex((message) => message.role !== "system");
59
- case "last_stable_message": return messages.length > 1 ? messages.length - 2 : messages.length - 1;
60
- case "last_user_message": return findLastIndex(messages, (message) => message.role === "user");
61
- case "message_id": return messages.findIndex((message) => message.id === breakpoint.messageId);
59
+ case "system_prompt":
60
+ return messages.findIndex((message) => message.role === "system");
61
+ case "tools":
62
+ return messages.findIndex((message) => message.role === "tool" || message.content.some((block) => block.type === "tool_call" || block.type === "tool_result"));
63
+ case "stable_context":
64
+ return messages.findIndex((message) => message.role !== "system");
65
+ case "last_stable_message":
66
+ return messages.length > 1 ? messages.length - 2 : messages.length - 1;
67
+ case "last_user_message":
68
+ return findLastIndex(messages, (message) => message.role === "user");
69
+ case "message_id":
70
+ return messages.findIndex((message) => message.id === breakpoint.messageId);
62
71
  }
63
72
  }
64
73
  function findLastIndex(items, predicate) {
@@ -6,6 +6,10 @@ export declare class CheckpointConflictError extends Error {
6
6
  }
7
7
  export interface MemoryCheckpointStoreOptions {
8
8
  readonly maxPageSize?: number;
9
+ /** Capacity bound: least-recently-saved records are evicted past this count (default 10_000). */
10
+ readonly maxRecords?: number;
11
+ /** Per-record bound on the JSON-encoded value in bytes (default 1 MiB); oversized values are rejected. */
12
+ readonly maxValueBytes?: number;
9
13
  }
10
14
  /** In-process reference implementation of the generic checkpoint contract. */
11
15
  export declare function createMemoryCheckpointStore(options?: MemoryCheckpointStoreOptions): CheckpointStore;
@@ -6,10 +6,14 @@ export class CheckpointConflictError extends Error {
6
6
  this.name = "CheckpointConflictError";
7
7
  }
8
8
  }
9
+ const DEFAULT_MAX_CHECKPOINT_RECORDS = 10_000;
10
+ const DEFAULT_MAX_CHECKPOINT_VALUE_BYTES = 1024 * 1024;
9
11
  /** In-process reference implementation of the generic checkpoint contract. */
10
12
  export function createMemoryCheckpointStore(options = {}) {
11
13
  const records = new Map();
12
14
  const maxPageSize = Math.max(1, options.maxPageSize ?? 500);
15
+ const maxRecords = Math.max(1, options.maxRecords ?? DEFAULT_MAX_CHECKPOINT_RECORDS);
16
+ const maxValueBytes = Math.max(1, options.maxValueBytes ?? DEFAULT_MAX_CHECKPOINT_VALUE_BYTES);
13
17
  return {
14
18
  async saveCheckpoint(input) {
15
19
  throwIfAborted(input.signal);
@@ -32,6 +36,9 @@ export function createMemoryCheckpointStore(options = {}) {
32
36
  }
33
37
  const now = new Date().toISOString();
34
38
  const value = cloneJson(input.value, "Checkpoint value");
39
+ if (new TextEncoder().encode(JSON.stringify(value)).length > maxValueBytes) {
40
+ throw new RangeError(`Checkpoint value exceeds maxValueBytes (${maxValueBytes})`);
41
+ }
35
42
  const metadata = input.metadata === undefined ? undefined : cloneJson(input.metadata, "Checkpoint metadata");
36
43
  const record = {
37
44
  namespace: input.namespace,
@@ -45,7 +52,12 @@ export function createMemoryCheckpointStore(options = {}) {
45
52
  updatedAt: now,
46
53
  ...(metadata === undefined ? {} : { metadata }),
47
54
  };
55
+ // delete+set moves an updated record to the end, keeping Map order = least- to
56
+ // most-recently-saved, so capacity eviction is O(1) off the front.
57
+ records.delete(id);
48
58
  records.set(id, record);
59
+ while (records.size > maxRecords)
60
+ records.delete(records.keys().next().value);
49
61
  return record;
50
62
  },
51
63
  async loadCheckpoint(input) {
@@ -61,9 +73,7 @@ export function createMemoryCheckpointStore(options = {}) {
61
73
  throwIfAborted(query.signal);
62
74
  const offset = decodeCursor(query.cursor);
63
75
  const limit = Math.min(Math.max(1, query.limit ?? 100), maxPageSize);
64
- const categories = query.category === undefined
65
- ? undefined
66
- : new Set(Array.isArray(query.category) ? query.category : [query.category]);
76
+ const categories = query.category === undefined ? undefined : new Set(Array.isArray(query.category) ? query.category : [query.category]);
67
77
  const items = [...records.values()]
68
78
  .filter((record) => query.namespace === undefined || record.namespace === query.namespace)
69
79
  .filter((record) => query.keyPrefix === undefined || record.key.startsWith(query.keyPrefix))
@@ -104,14 +114,12 @@ function ownership(input) {
104
114
  };
105
115
  }
106
116
  function ownershipMatches(expected, actual) {
107
- return expected.tenantId === actual.tenantId
108
- && expected.accountId === actual.accountId
109
- && expected.userId === actual.userId;
117
+ return expected.tenantId === actual.tenantId && expected.accountId === actual.accountId && expected.userId === actual.userId;
110
118
  }
111
119
  function ownershipFilterMatches(expected, actual) {
112
- return (expected.tenantId === undefined || expected.tenantId === actual.tenantId)
113
- && (expected.accountId === undefined || expected.accountId === actual.accountId)
114
- && (expected.userId === undefined || expected.userId === actual.userId);
120
+ return ((expected.tenantId === undefined || expected.tenantId === actual.tenantId) &&
121
+ (expected.accountId === undefined || expected.accountId === actual.accountId) &&
122
+ (expected.userId === undefined || expected.userId === actual.userId));
115
123
  }
116
124
  function assertOwnership(expected, actual) {
117
125
  if (!ownershipMatches(expected, actual)) {
package/dist/cli-init.js CHANGED
@@ -1,5 +1,5 @@
1
- import { access, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
1
  import { constants as fsConstants, readFileSync } from "node:fs";
2
+ import { access, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  export class InitUsageError extends Error {
@@ -305,12 +305,8 @@ function buildTokens(input) {
305
305
  ? `2. Copy \`.env.example\` to \`.env\` and set \`${input.provider.envKey}\`.\n3. Run \`npm start\` for a live provider call.`
306
306
  : `2. Run \`npm start\` (mock provider; no network or credentials).`;
307
307
  const optionalDocs = [
308
- ...(input.withWorkflows
309
- ? ["- `src/workflows-example.ts` — tiny DAG example using `@arnilo/prism-workflows`."]
310
- : []),
311
- ...(input.withEvals
312
- ? ["- `src/evals-example.ts` — deterministic scorer example using `@arnilo/prism-evals`."]
313
- : []),
308
+ ...(input.withWorkflows ? ["- `src/workflows-example.ts` — tiny DAG example using `@arnilo/prism-workflows`."] : []),
309
+ ...(input.withEvals ? ["- `src/evals-example.ts` — deterministic scorer example using `@arnilo/prism-evals`."] : []),
314
310
  ].join("\n");
315
311
  return {
316
312
  __PROJECT_NAME__: input.projectName,
@@ -9,10 +9,6 @@ export interface CliOptions {
9
9
  readonly provider?: string;
10
10
  readonly model?: string;
11
11
  readonly session?: string;
12
- readonly config: readonly string[];
13
- readonly resources: readonly string[];
14
- readonly extensions: readonly string[];
15
- readonly tools: readonly string[];
16
12
  readonly system?: string;
17
13
  readonly context: readonly string[];
18
14
  readonly compact?: number;
@@ -46,7 +42,7 @@ export interface CliOptions {
46
42
  readonly agentsMdFile?: string;
47
43
  readonly systemMdFile?: string;
48
44
  /** Runtime-populated: AGENTS.md/SYSTEM.md layers loaded from disk (print/json modes only;
49
- * RPC is host-owned). Threaded into `AgentConfig.systemPrompt` by `defaultCreateSession`. */
45
+ * RPC is host-owned). Threaded into `AgentConfig.systemPrompt` by `defaultCreateSession`. */
50
46
  readonly systemPromptLayers: readonly SystemPromptContribution[];
51
47
  }
52
48
  export interface CliRuntime {
@@ -67,7 +63,7 @@ export interface CliRuntime {
67
63
  /** Working directory for relative `prism init` destinations (tests). */
68
64
  readonly cwd?: string;
69
65
  }
70
- export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\n prism init <dir> [--provider <name>] [--with-workflows] [--with-evals] [--force]\n\nOptions:\n -p, --prompt <text> Prompt to run in print/json mode\n --provider <name> Explicit provider id (mock is built in for smoke tests)\n --model <name> Explicit model name\n --session <id> Session id\n --config <path> Explicit config path (recorded, not auto-loaded)\n --resource <uri> Explicit resource URI (recorded, not auto-loaded)\n --extension <name> Explicit extension name (recorded, not auto-loaded)\n --tool <name> Explicit tool name (recorded, not auto-enabled)\n --system <text> System instructions\n --context <text> Context text\n --compact <entries> Auto-compaction threshold\n --max-tool-rounds <n> Maximum tool rounds\n --discover Enable workspace contribution discovery (opt-in)\n --discover-kinds <csv> Kinds to discover (default: skill; skill,tool,context,instructions)\n --no-discovery Disable discovery even if --discover is set\n --agents-config <path> App config root holding agents/<name>/AGENT.md bundles (opt-in)\n --no-agents-md Skip auto-loading <workspaceRoot>/AGENTS.md\n --no-system-md Skip auto-loading the global SYSTEM.md layer\n --agents-md-file <path> Read AGENTS.md from <path> instead (trust-gated, source: app)\n --system-md-file <path> Read SYSTEM.md from <path> instead (source: user)\n -h, --help Show this help\n";
66
+ export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\n prism init <dir> [--provider <name>] [--with-workflows] [--with-evals] [--force]\n\nOptions:\n -p, --prompt <text> Prompt to run in print/json mode\n --provider <name> Explicit provider id (mock is built in for smoke tests)\n --model <name> Explicit model name\n --session <id> Session id\n --system <text> System instructions\n --context <text> Context text\n --compact <entries> Auto-compaction threshold\n --max-tool-rounds <n> Maximum tool rounds\n --discover Enable workspace contribution discovery (opt-in)\n --discover-kinds <csv> Kinds to discover (default: skill; skill,tool,context,instructions)\n --no-discovery Disable discovery even if --discover is set\n --agents-config <path> App config root holding agents/<name>/AGENT.md bundles (opt-in)\n --no-agents-md Skip auto-loading <workspaceRoot>/AGENTS.md\n --no-system-md Skip auto-loading the global SYSTEM.md layer\n --agents-md-file <path> Read AGENTS.md from <path> instead (trust-gated, source: app)\n --system-md-file <path> Read SYSTEM.md from <path> instead (source: user)\n -h, --help Show this help\n";
71
67
  export declare function parseCliArgs(argv: readonly string[]): CliOptions;
72
68
  export declare function runCli(argv: readonly string[], runtime: CliRuntime): Promise<number>;
73
69
  export declare function runPromptMode(session: AgentSession, options: CliOptions, stdout: Writable, mode: "print" | "json"): Promise<void>;
@@ -1,16 +1,16 @@
1
- import process from "node:process";
2
- import { createAgent, createMockProvider, providerDone, providerTextDelta, resolveInstructionInjectors, createContributionRegistry } from "./index.js";
3
1
  import { readFile } from "node:fs/promises";
4
2
  import { basename, dirname } from "node:path";
3
+ import process from "node:process";
4
+ import { initUsage, runInitCommand } from "./cli-init.js";
5
5
  import { createContributionRegistries, registerDiscoveredContributions } from "./contributions.js";
6
- import { createSkillRegistry } from "./skills.js";
7
- import { discoverContributions } from "./node/contribution-discovery.js";
6
+ import { createAgent, createContributionRegistry, createMockProvider, providerDone, providerTextDelta, resolveInstructionInjectors, } from "./index.js";
8
7
  import { discoverAgentBundles } from "./node/agent-definitions.js";
8
+ import { discoverContributions } from "./node/contribution-discovery.js";
9
9
  import { registerDiscoveredInstructionInjectors } from "./node/instruction-injectors.js";
10
10
  import { loadSystemPromptFiles } from "./node/system-project-prompts.js";
11
11
  import { createPathTrustPolicy } from "./node/trust.js";
12
12
  import { runRpcServer } from "./rpc.js";
13
- import { initUsage, runInitCommand } from "./cli-init.js";
13
+ import { createSkillRegistry } from "./skills.js";
14
14
  export const usage = `Usage: prism [--mode print|json|rpc] [-p prompt] [options]
15
15
  prism init <dir> [--provider <name>] [--with-workflows] [--with-evals] [--force]
16
16
 
@@ -19,10 +19,6 @@ Options:
19
19
  --provider <name> Explicit provider id (mock is built in for smoke tests)
20
20
  --model <name> Explicit model name
21
21
  --session <id> Session id
22
- --config <path> Explicit config path (recorded, not auto-loaded)
23
- --resource <uri> Explicit resource URI (recorded, not auto-loaded)
24
- --extension <name> Explicit extension name (recorded, not auto-loaded)
25
- --tool <name> Explicit tool name (recorded, not auto-enabled)
26
22
  --system <text> System instructions
27
23
  --context <text> Context text
28
24
  --compact <entries> Auto-compaction threshold
@@ -37,8 +33,27 @@ Options:
37
33
  --system-md-file <path> Read SYSTEM.md from <path> instead (source: user)
38
34
  -h, --help Show this help
39
35
  `;
40
- const valueFlags = new Set(["--prompt", "--mode", "--provider", "--model", "--session", "--config", "--resource", "--extension", "--tool", "--system", "--context", "--compact", "--max-tool-rounds", "--discover-kinds", "--instruction", "--injector-file", "--agents-md-file", "--system-md-file", "--agents-config"]);
36
+ const valueFlags = new Set([
37
+ "--prompt",
38
+ "--mode",
39
+ "--provider",
40
+ "--model",
41
+ "--session",
42
+ "--system",
43
+ "--context",
44
+ "--compact",
45
+ "--max-tool-rounds",
46
+ "--discover-kinds",
47
+ "--instruction",
48
+ "--injector-file",
49
+ "--agents-md-file",
50
+ "--system-md-file",
51
+ "--agents-config",
52
+ ]);
41
53
  const boolFlags = new Set(["--discover", "--no-discovery", "--no-agents-md", "--no-system-md"]);
54
+ // Known-but-inert flags: parsed by earlier builds, never wired to any behavior.
55
+ // Rejected loudly (rather than silently ignored) until a CLI-harness plan wires them.
56
+ const unsupportedFlags = new Set(["--config", "--resource", "--extension", "--tool"]);
42
57
  const ALL_KINDS = ["skill", "tool", "context", "instructions"];
43
58
  export function parseCliArgs(argv) {
44
59
  let mode = "print";
@@ -58,10 +73,6 @@ export function parseCliArgs(argv) {
58
73
  let agentsMdFile;
59
74
  let systemMdFile;
60
75
  let agentsConfig;
61
- const config = [];
62
- const resources = [];
63
- const extensions = [];
64
- const tools = [];
65
76
  const context = [];
66
77
  const instructions = [];
67
78
  const injectorFiles = [];
@@ -72,6 +83,8 @@ export function parseCliArgs(argv) {
72
83
  continue;
73
84
  }
74
85
  const name = flag === "-p" ? "--prompt" : flag;
86
+ if (unsupportedFlags.has(name))
87
+ throw new CliUsageError(`${name} is not supported in this build`);
75
88
  if (boolFlags.has(name)) {
76
89
  switch (name) {
77
90
  case "--discover":
@@ -113,18 +126,6 @@ export function parseCliArgs(argv) {
113
126
  case "--session":
114
127
  session = value;
115
128
  break;
116
- case "--config":
117
- config.push(value);
118
- break;
119
- case "--resource":
120
- resources.push(value);
121
- break;
122
- case "--extension":
123
- extensions.push(value);
124
- break;
125
- case "--tool":
126
- tools.push(value);
127
- break;
128
129
  case "--system":
129
130
  system = value;
130
131
  break;
@@ -157,10 +158,39 @@ export function parseCliArgs(argv) {
157
158
  break;
158
159
  }
159
160
  }
160
- return { mode, prompt, provider, model, session, config, resources, extensions, tools, system, context, compact, maxToolRounds, help, discover, discoverKinds, noDiscovery, agentsConfig, discoveredSkills: [], discoveredInjectors: [], discoveredAgents: [], instructions, injectorFiles, resolvedInstructionInjectors: [], noAgentsMd, noSystemMd, agentsMdFile, systemMdFile, systemPromptLayers: [] };
161
+ return {
162
+ mode,
163
+ prompt,
164
+ provider,
165
+ model,
166
+ session,
167
+ system,
168
+ context,
169
+ compact,
170
+ maxToolRounds,
171
+ help,
172
+ discover,
173
+ discoverKinds,
174
+ noDiscovery,
175
+ agentsConfig,
176
+ discoveredSkills: [],
177
+ discoveredInjectors: [],
178
+ discoveredAgents: [],
179
+ instructions,
180
+ injectorFiles,
181
+ resolvedInstructionInjectors: [],
182
+ noAgentsMd,
183
+ noSystemMd,
184
+ agentsMdFile,
185
+ systemMdFile,
186
+ systemPromptLayers: [],
187
+ };
161
188
  }
162
189
  function parseKinds(csv, flag) {
163
- const kinds = csv.split(",").map((k) => k.trim()).filter(Boolean);
190
+ const kinds = csv
191
+ .split(",")
192
+ .map((k) => k.trim())
193
+ .filter(Boolean);
164
194
  if (kinds.length === 0)
165
195
  throw new CliUsageError(`Invalid value for ${flag}: ${csv}`);
166
196
  const invalid = kinds.filter((k) => !ALL_KINDS.includes(k));
@@ -193,7 +223,12 @@ export async function runCli(argv, runtime) {
193
223
  }
194
224
  try {
195
225
  if (options.mode === "rpc") {
196
- await runRpcServer({ stdin: runtime.stdin, stdout: runtime.stdout, createSession: (id) => (runtime.createSession ?? defaultCreateSession)({ ...options, session: id ?? options.session }), commands: runtime.commands });
226
+ await runRpcServer({
227
+ stdin: runtime.stdin,
228
+ stdout: runtime.stdout,
229
+ createSession: (id) => (runtime.createSession ?? defaultCreateSession)({ ...options, session: id ?? options.session }),
230
+ commands: runtime.commands,
231
+ });
197
232
  return 0;
198
233
  }
199
234
  if (!options.prompt)
@@ -248,7 +283,12 @@ export async function runCli(argv, runtime) {
248
283
  // the containment check (the user named it, so it's trusted by that act).
249
284
  const layers = await loadSystemPromptFiles({
250
285
  ...(options.noAgentsMd ? {} : { workspaceRoot, trust, ...(options.agentsMdFile ? { agentsMdPath: options.agentsMdFile } : {}) }),
251
- ...(options.noSystemMd ? {} : { ...(runtime.globalRoot !== undefined ? { globalRoot: runtime.globalRoot } : {}), ...(options.systemMdFile ? { systemMdPath: options.systemMdFile } : {}) }),
286
+ ...(options.noSystemMd
287
+ ? {}
288
+ : {
289
+ ...(runtime.globalRoot !== undefined ? { globalRoot: runtime.globalRoot } : {}),
290
+ ...(options.systemMdFile ? { systemMdPath: options.systemMdFile } : {}),
291
+ }),
252
292
  });
253
293
  options = { ...options, systemPromptLayers: layers };
254
294
  }
@@ -306,9 +346,7 @@ function runOptions(options) {
306
346
  ...(options.discover && !options.noDiscovery && options.discoveredSkills.length > 0
307
347
  ? { activeSkills: options.discoveredSkills.map((s) => s.name) }
308
348
  : {}),
309
- ...(options.resolvedInstructionInjectors.length > 0
310
- ? { instructionInjectors: options.resolvedInstructionInjectors }
311
- : {}),
349
+ ...(options.resolvedInstructionInjectors.length > 0 ? { instructionInjectors: options.resolvedInstructionInjectors } : {}),
312
350
  };
313
351
  }
314
352
  // ponytail: resolve --instruction names against discovered injectors (fail-closed) and load
@@ -27,10 +27,11 @@ export function isCompactionEntryData(value) {
27
27
  if (!value || typeof value !== "object" || Array.isArray(value))
28
28
  return false;
29
29
  const data = value;
30
- return (data.throughEntryId === undefined || typeof data.throughEntryId === "string")
31
- && (data.keepEntryIds === undefined || (Array.isArray(data.keepEntryIds) && data.keepEntryIds.every((item) => typeof item === "string")))
32
- && (data.strategy === undefined || typeof data.strategy === "string")
33
- && (data.trigger === undefined || typeof data.trigger === "string");
30
+ return ((data.throughEntryId === undefined || typeof data.throughEntryId === "string") &&
31
+ (data.keepEntryIds === undefined ||
32
+ (Array.isArray(data.keepEntryIds) && data.keepEntryIds.every((item) => typeof item === "string"))) &&
33
+ (data.strategy === undefined || typeof data.strategy === "string") &&
34
+ (data.trigger === undefined || typeof data.trigger === "string"));
34
35
  }
35
36
  function summarize(entries) {
36
37
  const lines = entries.flatMap((entry) => entryText(entry));
package/dist/config.js CHANGED
@@ -31,9 +31,10 @@ function mergeObjects(base, override, path = "config") {
31
31
  const childPath = `${path}.${key}`;
32
32
  assertSafeJsonKey(key, childPath);
33
33
  const current = result[key];
34
- result[key] = isPlainObject(current) && isPlainObject(value)
35
- ? mergeObjects(current, value, childPath)
36
- : cloneJsonValue(value, childPath);
34
+ result[key] =
35
+ isPlainObject(current) && isPlainObject(value)
36
+ ? mergeObjects(current, value, childPath)
37
+ : cloneJsonValue(value, childPath);
37
38
  }
38
39
  return result;
39
40
  }
@@ -77,7 +78,9 @@ function isSafeJsonValue(value) {
77
78
  }
78
79
  function assertSafeJsonValue(value, path) {
79
80
  if (Array.isArray(value)) {
80
- value.forEach((entry, index) => assertSafeJsonValue(entry, `${path}[${index}]`));
81
+ value.forEach((entry, index) => {
82
+ assertSafeJsonValue(entry, `${path}[${index}]`);
83
+ });
81
84
  return;
82
85
  }
83
86
  if (!isPlainObject(value))
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)
@@ -61,16 +61,19 @@ export function applyContextBudget(options) {
61
61
  const skills = [...(options.skills ?? [])].filter((skill) => skill.instructions);
62
62
  const tools = options.tools ? [...options.tools] : undefined;
63
63
  const omitted = [];
64
- const cost = () => measureAll(groups, context, skills, tools);
65
- while (overBudget(cost(), budget)) {
64
+ // Measure once, then subtract each dropped item's own estimate (dropNext computes it
65
+ // 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);
67
+ while (overBudget(kept, budget)) {
66
68
  const drop = dropNext(groups, context, skills, layout);
67
69
  if (!drop) {
68
70
  throw new ContextBudgetError();
69
71
  }
72
+ kept.tokens -= drop.tokenEstimate;
73
+ kept.bytes -= drop.byteLength;
70
74
  if (omitted.length < HARD_MAX_CONTEXT_BUDGET_OMISSIONS)
71
75
  omitted.push(drop);
72
76
  }
73
- const kept = cost();
74
77
  const reportOmissions = omitted.slice(0, DEFAULT_MAX_CONTEXT_BUDGET_OMISSIONS);
75
78
  return {
76
79
  groups,
@@ -197,13 +200,14 @@ function isContextBudgetReport(value) {
197
200
  if (!value || typeof value !== "object" || Array.isArray(value))
198
201
  return false;
199
202
  const report = value;
200
- return Array.isArray(report.omitted)
201
- && typeof report.keptTokens === "number"
202
- && typeof report.keptBytes === "number"
203
- && typeof report.truncated === "boolean";
203
+ return (Array.isArray(report.omitted) &&
204
+ typeof report.keptTokens === "number" &&
205
+ typeof report.keptBytes === "number" &&
206
+ typeof report.truncated === "boolean");
204
207
  }
205
208
  function messageText(message) {
206
- return message.content.map((part) => {
209
+ return message.content
210
+ .map((part) => {
207
211
  if (part.type === "text" || part.type === "thinking")
208
212
  return part.text;
209
213
  if (part.type === "tool_result")
@@ -221,15 +225,18 @@ function messageText(message) {
221
225
  if (part.type === "document")
222
226
  return part.transcript ?? part.name ?? part.url ?? part.resourceUri ?? part.mediaType ?? "[document]";
223
227
  return "[content]";
224
- }).join("\n");
228
+ })
229
+ .join("\n");
225
230
  }
226
231
  function contextBlockText(block) {
227
232
  if (typeof block.content === "string")
228
233
  return block.content;
229
- return block.content.map((part) => {
234
+ return block.content
235
+ .map((part) => {
230
236
  if (part.type === "text" || part.type === "thinking")
231
237
  return part.text;
232
238
  return "[content]";
233
- }).join("\n");
239
+ })
240
+ .join("\n");
234
241
  }
235
242
  //# sourceMappingURL=context-budget.js.map
@@ -1,11 +1,11 @@
1
- import type { AgentInput } from "./input.js";
1
+ import type { AudioContent, DocumentContent, FileContent } from "./content.js";
2
2
  import type { ContributionRegistries } from "./contributions.js";
3
+ import type { AgentInput } from "./input.js";
4
+ import type { ManifestContributionDeclaration } from "./manifests.js";
3
5
  import type { Middleware, MiddlewareHookName, MiddlewareRegistry } from "./middleware.js";
4
6
  import type { SecretRedactor } from "./redaction.js";
5
7
  import type { PermissionPolicy, TrustPolicy } from "./security.js";
6
- import type { ManifestContributionDeclaration } from "./manifests.js";
7
8
  import type { ToolValidator } from "./tools.js";
8
- import type { AudioContent, DocumentContent, FileContent } from "./content.js";
9
9
  export type JsonPrimitive = string | number | boolean | null;
10
10
  export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
11
11
  export interface JsonObject {
@@ -15,6 +15,9 @@ export interface ErrorInfo {
15
15
  readonly name?: string;
16
16
  readonly message: string;
17
17
  readonly code?: string | number;
18
+ /** Provider backpressure hint (e.g. from a `Retry-After` header); retry policies
19
+ * honor it capped at their own `maxDelayMs`. */
20
+ readonly retryAfterMs?: number;
18
21
  readonly cause?: unknown;
19
22
  }
20
23
  export type { AudioContent, DocumentContent, FileContent } from "./content.js";
@@ -749,6 +752,13 @@ export type AgentEvent = {
749
752
  readonly sessionId: string;
750
753
  readonly runId: string;
751
754
  readonly size: number;
755
+ } | {
756
+ /** A steered message was dropped by a terminal input guardrail; the run continues without it. */
757
+ readonly type: "steer_rejected";
758
+ readonly sessionId: string;
759
+ readonly runId: string;
760
+ readonly message: Message;
761
+ readonly record: GuardrailRecord;
752
762
  } | {
753
763
  readonly type: "event_subscriber_overflow";
754
764
  readonly sessionId: string;
@@ -937,6 +947,9 @@ export interface PromptBuildRequest {
937
947
  readonly context?: readonly ContextBlock[];
938
948
  readonly skills?: readonly Skill[];
939
949
  readonly tools?: readonly ToolDefinition[];
950
+ /** Model being prompted; lets builders adapt composition to declared capabilities
951
+ * (e.g. the default builder omits the `Available tools:` text for tool-capable models). */
952
+ readonly model?: ModelConfig;
940
953
  readonly metadata?: Readonly<Record<string, unknown>>;
941
954
  readonly signal?: AbortSignal;
942
955
  }
package/dist/contracts.js CHANGED
@@ -1,6 +1,9 @@
1
1
  export class AgentRunStateError extends Error {
2
2
  code = "ERR_PRISM_AGENT_RUN_STATE";
3
- constructor(message) { super(message); this.name = "AgentRunStateError"; }
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "AgentRunStateError";
6
+ }
4
7
  }
5
8
  export class AgentRunError extends Error {
6
9
  result;
@@ -50,7 +50,11 @@ function parseFrontBlock(lines) {
50
50
  out.set(key, items);
51
51
  }
52
52
  else if (value.startsWith("[") && value.endsWith("]")) {
53
- out.set(key, value.slice(1, -1).split(",").map((s) => s.trim()).filter((s) => s.length > 0));
53
+ out.set(key, value
54
+ .slice(1, -1)
55
+ .split(",")
56
+ .map((s) => s.trim())
57
+ .filter((s) => s.length > 0));
54
58
  }
55
59
  else {
56
60
  out.set(key, value);
@@ -63,7 +67,7 @@ function parseFrontBlock(lines) {
63
67
  // plain string split instead of `node:path`. Expects `.../<name>/SKILL.md`.
64
68
  function parentDirName(path) {
65
69
  const parts = path.split(/[/\\]+/).filter(Boolean);
66
- return parts.length >= 2 ? parts[parts.length - 2] : parts[parts.length - 1] ?? "";
70
+ return parts.length >= 2 ? parts[parts.length - 2] : (parts[parts.length - 1] ?? "");
67
71
  }
68
72
  const NAME_RE = /^[A-Za-z0-9 _-]+$/;
69
73
  /** Parse a `SKILL.md` into a {@link Skill}. Frontmatter keys map to `name`