@arnilo/prism 0.0.1

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 (106) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +139 -0
  4. package/dist/agents.d.ts +5 -0
  5. package/dist/agents.js +439 -0
  6. package/dist/cli-runner.d.ts +33 -0
  7. package/dist/cli-runner.js +167 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +10 -0
  10. package/dist/compaction.d.ts +9 -0
  11. package/dist/compaction.js +67 -0
  12. package/dist/config.d.ts +17 -0
  13. package/dist/config.js +69 -0
  14. package/dist/contracts.d.ts +670 -0
  15. package/dist/contracts.js +2 -0
  16. package/dist/contributions.d.ts +35 -0
  17. package/dist/contributions.js +47 -0
  18. package/dist/credentials.d.ts +22 -0
  19. package/dist/credentials.js +63 -0
  20. package/dist/extensions.d.ts +25 -0
  21. package/dist/extensions.js +131 -0
  22. package/dist/index.d.ts +47 -0
  23. package/dist/index.js +28 -0
  24. package/dist/input.d.ts +57 -0
  25. package/dist/input.js +225 -0
  26. package/dist/manifests.d.ts +28 -0
  27. package/dist/manifests.js +108 -0
  28. package/dist/middleware.d.ts +15 -0
  29. package/dist/middleware.js +49 -0
  30. package/dist/mock-provider.d.ts +6 -0
  31. package/dist/mock-provider.js +14 -0
  32. package/dist/models.d.ts +8 -0
  33. package/dist/models.js +25 -0
  34. package/dist/node/config.d.ts +9 -0
  35. package/dist/node/config.js +51 -0
  36. package/dist/node/session-store-jsonl.d.ts +17 -0
  37. package/dist/node/session-store-jsonl.js +134 -0
  38. package/dist/node/settings.d.ts +7 -0
  39. package/dist/node/settings.js +26 -0
  40. package/dist/node/trust.d.ts +13 -0
  41. package/dist/node/trust.js +56 -0
  42. package/dist/provider-events.d.ts +15 -0
  43. package/dist/provider-events.js +30 -0
  44. package/dist/provider-packages.d.ts +4 -0
  45. package/dist/provider-packages.js +12 -0
  46. package/dist/provider-request-policy.d.ts +9 -0
  47. package/dist/provider-request-policy.js +49 -0
  48. package/dist/providers/openai-compatible.d.ts +9 -0
  49. package/dist/providers/openai-compatible.js +197 -0
  50. package/dist/providers.d.ts +8 -0
  51. package/dist/providers.js +25 -0
  52. package/dist/redaction.d.ts +11 -0
  53. package/dist/redaction.js +68 -0
  54. package/dist/resources.d.ts +5 -0
  55. package/dist/resources.js +30 -0
  56. package/dist/retry.d.ts +11 -0
  57. package/dist/retry.js +48 -0
  58. package/dist/rpc.d.ts +18 -0
  59. package/dist/rpc.js +187 -0
  60. package/dist/security.d.ts +51 -0
  61. package/dist/security.js +60 -0
  62. package/dist/session-stores.d.ts +25 -0
  63. package/dist/session-stores.js +116 -0
  64. package/dist/settings.d.ts +3 -0
  65. package/dist/settings.js +26 -0
  66. package/dist/skills.d.ts +8 -0
  67. package/dist/skills.js +34 -0
  68. package/dist/system-prompts.d.ts +6 -0
  69. package/dist/system-prompts.js +47 -0
  70. package/dist/testing/provider-conformance.d.ts +36 -0
  71. package/dist/testing/provider-conformance.js +164 -0
  72. package/dist/tools.d.ts +25 -0
  73. package/dist/tools.js +109 -0
  74. package/docs/agent-session-runtime.md +167 -0
  75. package/docs/api-page-template.md +32 -0
  76. package/docs/cli-rpc.md +140 -0
  77. package/docs/compaction-and-retry.md +177 -0
  78. package/docs/compaction-llm.md +108 -0
  79. package/docs/compaction-observational-memory.md +123 -0
  80. package/docs/configuration-and-manifests.md +142 -0
  81. package/docs/context-and-skills.md +113 -0
  82. package/docs/contribution-registries.md +118 -0
  83. package/docs/credentials-and-redaction.md +122 -0
  84. package/docs/extensions.md +139 -0
  85. package/docs/index.md +56 -0
  86. package/docs/input-and-prompt-assembly.md +168 -0
  87. package/docs/middleware-hooks.md +123 -0
  88. package/docs/node-filesystem-config.md +85 -0
  89. package/docs/node-jsonl-session-store.md +81 -0
  90. package/docs/provider-conformance.md +109 -0
  91. package/docs/provider-layer.md +172 -0
  92. package/docs/provider-packages.md +171 -0
  93. package/docs/providers/kimi.md +110 -0
  94. package/docs/providers/openai-compatible.md +125 -0
  95. package/docs/providers/openai.md +131 -0
  96. package/docs/providers/opencode-go.md +103 -0
  97. package/docs/providers/openrouter.md +105 -0
  98. package/docs/providers/zai.md +108 -0
  99. package/docs/public-contracts.md +375 -0
  100. package/docs/release-and-install.md +141 -0
  101. package/docs/resource-loading.md +97 -0
  102. package/docs/session-stores-and-branching.md +119 -0
  103. package/docs/settings-auth-trust-security.md +73 -0
  104. package/docs/system-prompts.md +116 -0
  105. package/docs/tools.md +151 -0
  106. package/package.json +93 -0
package/dist/skills.js ADDED
@@ -0,0 +1,34 @@
1
+ export function createSkillRegistry(skills = []) {
2
+ const byName = new Map();
3
+ const registry = {
4
+ register(skill) {
5
+ byName.set(skill.name, skill);
6
+ },
7
+ get(name) {
8
+ return byName.get(name);
9
+ },
10
+ resolve(name) {
11
+ const skill = byName.get(name);
12
+ if (!skill)
13
+ throw new Error(`Unknown skill: ${name}`);
14
+ return skill;
15
+ },
16
+ list() {
17
+ return [...byName.values()];
18
+ },
19
+ };
20
+ for (const skill of skills)
21
+ registry.register(skill);
22
+ return registry;
23
+ }
24
+ export function resolveActiveSkills(options) {
25
+ const toolNames = new Set((options.tools ?? []).map((tool) => tool.name));
26
+ return (options.names ?? []).map((name) => {
27
+ const skill = options.registry.resolve(name);
28
+ const missingTool = skill.toolNames?.find((toolName) => !toolNames.has(toolName));
29
+ if (missingTool)
30
+ throw new Error(`Skill ${skill.name} requires inactive tool: ${missingTool}`);
31
+ return skill;
32
+ });
33
+ }
34
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1,6 @@
1
+ import type { SystemPromptConfig } from "./contracts.js";
2
+ export interface ComposeSystemPromptOptions {
3
+ readonly base?: string | readonly string[];
4
+ }
5
+ export declare function composeSystemPrompt(contributions?: SystemPromptConfig, options?: ComposeSystemPromptOptions): string | undefined;
6
+ export declare function mergeSystemPromptConfig(config: SystemPromptConfig | undefined, override: SystemPromptConfig | undefined): SystemPromptConfig;
@@ -0,0 +1,47 @@
1
+ const sourceRank = new Map([["package", 0], ["app", 1], ["user", 2], ["run", 3]]);
2
+ export function composeSystemPrompt(contributions = [], options = {}) {
3
+ const parts = baseParts(options.base);
4
+ if (contributions === false)
5
+ return joinPrompt(parts);
6
+ const layers = [...asContributions(contributions)]
7
+ .map((layer, index) => ({ layer, index }))
8
+ .sort((a, b) => rank(a.layer) - rank(b.layer) || a.index - b.index);
9
+ for (const { layer } of layers) {
10
+ if (layer.mode === "disable") {
11
+ parts.length = 0;
12
+ continue;
13
+ }
14
+ if (!layer.text)
15
+ continue;
16
+ if (layer.mode === "replace")
17
+ parts.splice(0, parts.length, layer.text);
18
+ else if (layer.mode === "prepend")
19
+ parts.unshift(layer.text);
20
+ else
21
+ parts.push(layer.text);
22
+ }
23
+ return joinPrompt(parts);
24
+ }
25
+ export function mergeSystemPromptConfig(config, override) {
26
+ if (override === false)
27
+ return config ? [] : [];
28
+ return [...asContributions(config), ...asContributions(override)];
29
+ }
30
+ function asContributions(value) {
31
+ if (value === undefined || value === false)
32
+ return [];
33
+ return isContributionArray(value) ? value : [value];
34
+ }
35
+ function isContributionArray(value) {
36
+ return Array.isArray(value);
37
+ }
38
+ function baseParts(base) {
39
+ return (typeof base === "string" ? [base] : base ?? []).filter((text) => text.length > 0);
40
+ }
41
+ function rank(layer) {
42
+ return sourceRank.get(layer.source ?? "") ?? 10;
43
+ }
44
+ function joinPrompt(parts) {
45
+ return parts.length ? parts.join("\n\n") : undefined;
46
+ }
47
+ //# sourceMappingURL=system-prompts.js.map
@@ -0,0 +1,36 @@
1
+ import type { AIProvider, ContentBlock, JsonObject, ProviderEvent, ProviderRequest, ToolCallContent, Usage } from "../contracts.js";
2
+ export interface ProviderStreamConformanceOptions {
3
+ readonly provider: AIProvider;
4
+ readonly request: ProviderRequest;
5
+ readonly expect?: {
6
+ readonly text?: string;
7
+ readonly usage?: Usage;
8
+ };
9
+ }
10
+ export interface ProviderAbortConformanceOptions {
11
+ readonly provider: AIProvider;
12
+ readonly request: Omit<ProviderRequest, "signal"> & {
13
+ readonly signal?: AbortSignal;
14
+ };
15
+ readonly reason?: unknown;
16
+ }
17
+ export interface ToolCallDeltaExpectation {
18
+ readonly index: number;
19
+ readonly id?: string;
20
+ readonly name?: string;
21
+ readonly arguments?: JsonObject;
22
+ }
23
+ export interface SerializedContentCoverageOptions {
24
+ readonly unsupported?: readonly ContentBlock["type"][];
25
+ }
26
+ export interface ProviderSecretLeakConformanceOptions {
27
+ readonly events: readonly ProviderEvent[];
28
+ readonly secrets: readonly string[];
29
+ }
30
+ export declare function collectProviderEvents(provider: AIProvider, request: ProviderRequest): Promise<readonly ProviderEvent[]>;
31
+ export declare function assertProviderStreamConforms(options: ProviderStreamConformanceOptions): Promise<readonly ProviderEvent[]>;
32
+ export declare function assertAbortIsObserved(options: ProviderAbortConformanceOptions): Promise<void>;
33
+ export declare function assertToolCallDeltasReconstruct(events: readonly ProviderEvent[], expected: readonly ToolCallDeltaExpectation[]): readonly ToolCallContent[];
34
+ export declare function assertSerializedRequestCoversContent(request: ProviderRequest, body: unknown, options?: SerializedContentCoverageOptions): void;
35
+ export declare function assertNoSecretLeak(events: readonly ProviderEvent[], secrets: readonly string[]): void;
36
+ export declare function assertUsageAccounting(events: readonly ProviderEvent[], expected: Usage): Usage;
@@ -0,0 +1,164 @@
1
+ export async function collectProviderEvents(provider, request) {
2
+ const events = [];
3
+ for await (const event of provider.generate(request))
4
+ events.push(event);
5
+ return events;
6
+ }
7
+ export async function assertProviderStreamConforms(options) {
8
+ const events = await collectProviderEvents(options.provider, options.request);
9
+ const terminal = events.at(-1);
10
+ if (!terminal || (terminal.type !== "done" && terminal.type !== "error"))
11
+ throw new Error("Provider stream must end with done or error");
12
+ if (events.slice(0, -1).some((event) => event.type === "done" || event.type === "error"))
13
+ throw new Error("Provider stream terminal event must be last");
14
+ if (options.expect?.text !== undefined && textFrom(events) !== options.expect.text)
15
+ throw new Error(`Provider text mismatch: expected ${JSON.stringify(options.expect.text)}`);
16
+ if (options.expect?.usage)
17
+ assertUsageAccounting(events, options.expect.usage);
18
+ return events;
19
+ }
20
+ export async function assertAbortIsObserved(options) {
21
+ const controller = new AbortController();
22
+ controller.abort(options.reason ?? new Error("aborted"));
23
+ let rejected = false;
24
+ try {
25
+ await collectProviderEvents(options.provider, { ...options.request, signal: controller.signal });
26
+ }
27
+ catch {
28
+ rejected = true;
29
+ }
30
+ if (!rejected)
31
+ throw new Error("Provider did not observe an already-aborted signal");
32
+ }
33
+ export function assertToolCallDeltasReconstruct(events, expected) {
34
+ const calls = reconstructToolCallDeltas(events);
35
+ for (const item of expected) {
36
+ const call = calls[item.index];
37
+ if (!call)
38
+ throw new Error(`Missing tool call at index ${item.index}`);
39
+ if (item.id !== undefined && call.id !== item.id)
40
+ throw new Error(`Tool call id mismatch at index ${item.index}`);
41
+ if (item.name !== undefined && call.name !== item.name)
42
+ throw new Error(`Tool call name mismatch at index ${item.index}`);
43
+ if (item.arguments !== undefined && JSON.stringify(call.arguments) !== JSON.stringify(item.arguments))
44
+ throw new Error(`Tool call arguments mismatch at index ${item.index}`);
45
+ }
46
+ return calls;
47
+ }
48
+ export function assertSerializedRequestCoversContent(request, body, options = {}) {
49
+ const unsupported = new Set(options.unsupported ?? []);
50
+ const bodyText = JSON.stringify(body);
51
+ for (const message of request.messages) {
52
+ for (const block of message.content) {
53
+ if (unsupported.has(block.type))
54
+ continue;
55
+ const canaries = contentBlockCanaries(block);
56
+ if (canaries.length === 0)
57
+ continue;
58
+ const missing = canaries.filter((canary) => !bodyText.includes(canary));
59
+ if (missing.length > 0) {
60
+ throw new Error(`Serialized request dropped ${block.type} content; missing canaries: ${JSON.stringify(missing)}`);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ export function assertNoSecretLeak(events, secrets) {
66
+ const eventText = JSON.stringify(events);
67
+ for (const secret of secrets) {
68
+ if (!secret)
69
+ continue;
70
+ if (eventText.includes(secret))
71
+ throw new Error(`Secret leaked into provider events: ${secret.slice(0, 8)}...`);
72
+ }
73
+ }
74
+ export function assertUsageAccounting(events, expected) {
75
+ const usage = [...events].reverse().find((event) => event.type === "done" && event.usage || event.type === "usage");
76
+ const actual = usage?.type === "usage" ? usage.usage : usage?.usage;
77
+ if (!actual)
78
+ throw new Error("Provider stream did not include usage");
79
+ for (const key of ["inputTokens", "outputTokens", "totalTokens", "cacheReadTokens", "cacheWriteTokens"]) {
80
+ if (expected[key] !== undefined && actual[key] !== expected[key])
81
+ throw new Error(`Usage ${key} mismatch: expected ${expected[key]}, got ${actual[key]}`);
82
+ }
83
+ return actual;
84
+ }
85
+ function reconstructToolCallDeltas(events) {
86
+ const partials = new Map();
87
+ for (const event of events) {
88
+ if (event.type !== "tool_call_delta")
89
+ continue;
90
+ const partial = partials.get(event.index) ?? { argumentsText: "" };
91
+ if (event.id !== undefined)
92
+ partial.id = event.id;
93
+ if (event.name !== undefined)
94
+ partial.name = event.name;
95
+ if (event.argumentsText !== undefined)
96
+ partial.argumentsText += event.argumentsText;
97
+ partials.set(event.index, partial);
98
+ }
99
+ return [...partials.entries()].sort(([a], [b]) => a - b).map(([index, partial]) => {
100
+ if (!partial.id || !partial.name)
101
+ throw new Error(`Incomplete tool call delta at index ${index}`);
102
+ return { type: "tool_call", id: partial.id, name: partial.name, arguments: parseArguments(partial.argumentsText, index) };
103
+ });
104
+ }
105
+ function parseArguments(text, index) {
106
+ try {
107
+ const value = text ? JSON.parse(text) : {};
108
+ if (!value || typeof value !== "object" || Array.isArray(value))
109
+ throw new Error("not object");
110
+ return value;
111
+ }
112
+ catch (error) {
113
+ throw new Error(`Invalid tool call arguments at index ${index}: ${error instanceof Error ? error.message : String(error)}`);
114
+ }
115
+ }
116
+ function contentBlockCanaries(block) {
117
+ switch (block.type) {
118
+ case "text":
119
+ return block.text ? [block.text] : [];
120
+ case "thinking":
121
+ return block.text ? [block.text] : [];
122
+ case "image":
123
+ return [block.url, block.data, block.mimeType].filter((value) => typeof value === "string" && value.length > 0);
124
+ case "tool_call":
125
+ return [block.id, block.name, ...jsonPrimitives(block.arguments)];
126
+ case "tool_result": {
127
+ const values = [block.toolCallId, block.name, ...jsonPrimitives(block.result), ...jsonPrimitives(block.error)];
128
+ return values.filter((value) => typeof value === "string" && value.length > 0);
129
+ }
130
+ default:
131
+ return [];
132
+ }
133
+ }
134
+ function jsonPrimitives(value) {
135
+ const primitives = [];
136
+ const seen = new Set();
137
+ function walk(current) {
138
+ if (seen.has(current))
139
+ return;
140
+ if (current && typeof current === "object") {
141
+ seen.add(current);
142
+ if (Array.isArray(current)) {
143
+ for (const item of current)
144
+ walk(item);
145
+ }
146
+ else {
147
+ for (const item of Object.values(current))
148
+ walk(item);
149
+ }
150
+ }
151
+ else if (typeof current === "string" && current.length > 0) {
152
+ primitives.push(current);
153
+ }
154
+ else if (typeof current === "number" || typeof current === "boolean") {
155
+ primitives.push(String(current));
156
+ }
157
+ }
158
+ walk(value);
159
+ return primitives;
160
+ }
161
+ function textFrom(events) {
162
+ return events.map((event) => event.type === "content_delta" && event.content.type === "text" ? event.content.text : "").join("");
163
+ }
164
+ //# sourceMappingURL=provider-conformance.js.map
@@ -0,0 +1,25 @@
1
+ import type { AgentEvent, ErrorInfo, JsonObject, ToolCallContent, ToolDefinition, ToolExecutionContext, ToolRegistry, ToolResult } from "./contracts.js";
2
+ import type { MiddlewareRegistry } from "./middleware.js";
3
+ import { type SecretRedactor } from "./redaction.js";
4
+ import { type PermissionPolicy } from "./security.js";
5
+ export interface ToolFilter {
6
+ readonly allow?: readonly string[];
7
+ readonly deny?: readonly string[];
8
+ }
9
+ export type ToolFilterInput = ToolFilter | readonly ToolFilter[];
10
+ export type ToolValidator = (tool: ToolDefinition, args: JsonObject, context: ToolExecutionContext) => void | string | ErrorInfo | Promise<void | string | ErrorInfo>;
11
+ export interface DispatchToolCallOptions {
12
+ readonly call: ToolCallContent;
13
+ readonly registry: ToolRegistry;
14
+ readonly context: ToolExecutionContext;
15
+ readonly filter?: ToolFilterInput;
16
+ readonly middleware?: MiddlewareRegistry;
17
+ readonly validate?: ToolValidator;
18
+ readonly emit?: (event: AgentEvent) => void | Promise<void>;
19
+ readonly secrets?: readonly (string | undefined)[];
20
+ readonly permission?: PermissionPolicy;
21
+ readonly redactor?: SecretRedactor;
22
+ }
23
+ export declare function createToolRegistry(tools?: readonly ToolDefinition[]): ToolRegistry;
24
+ export declare function filterTools(tools: readonly ToolDefinition[], filter?: ToolFilterInput): readonly ToolDefinition[];
25
+ export declare function dispatchToolCall(options: DispatchToolCallOptions): Promise<ToolResult>;
package/dist/tools.js ADDED
@@ -0,0 +1,109 @@
1
+ import { isJsonObject } from "./config.js";
2
+ import { errorToErrorInfo, redactSecrets } from "./redaction.js";
3
+ import { assertPermission } from "./security.js";
4
+ export function createToolRegistry(tools = []) {
5
+ const byName = new Map();
6
+ const registry = {
7
+ register(tool) {
8
+ byName.set(tool.name, tool);
9
+ },
10
+ get(name) {
11
+ return byName.get(name);
12
+ },
13
+ resolve(name) {
14
+ const tool = byName.get(name);
15
+ if (!tool)
16
+ throw new Error(`Unknown tool: ${name}`);
17
+ return tool;
18
+ },
19
+ list() {
20
+ return [...byName.values()];
21
+ },
22
+ };
23
+ for (const tool of tools)
24
+ registry.register(tool);
25
+ return registry;
26
+ }
27
+ export function filterTools(tools, filter) {
28
+ const filters = Array.isArray(filter) ? filter : filter ? [filter] : [];
29
+ const denied = new Set(filters.flatMap((item) => item.deny ?? []));
30
+ const allows = filters.map((item) => item.allow?.length ? new Set(item.allow) : undefined).filter((item) => Boolean(item));
31
+ return tools.filter((tool) => !denied.has(tool.name) && allows.every((allow) => allow.has(tool.name)));
32
+ }
33
+ export async function dispatchToolCall(options) {
34
+ const secrets = options.secrets ?? [];
35
+ const precheck = await checkCall(options.call, options);
36
+ if (precheck)
37
+ return precheck;
38
+ const mediatedCall = await (options.middleware?.run("tool_call", options.call) ?? options.call);
39
+ const tool = options.registry.get(mediatedCall.name);
40
+ const postcheck = await checkCall(mediatedCall, options);
41
+ if (postcheck)
42
+ return postcheck;
43
+ const context = {
44
+ ...options.context,
45
+ toolCallId: mediatedCall.id,
46
+ progress: async (progress, metadata) => {
47
+ await options.context.progress?.(progress, metadata);
48
+ await options.emit?.({
49
+ type: "tool_execution_progress",
50
+ sessionId: options.context.sessionId,
51
+ runId: options.context.runId,
52
+ toolCallId: mediatedCall.id,
53
+ name: mediatedCall.name,
54
+ progress,
55
+ metadata,
56
+ });
57
+ },
58
+ };
59
+ try {
60
+ await assertPermission(options.permission, { kind: "tool", action: "execute", target: mediatedCall.name, metadata: options.context.metadata });
61
+ }
62
+ catch (error) {
63
+ return blocked(mediatedCall, context, "permission_denied", errorToErrorInfo(error, secrets), options.emit);
64
+ }
65
+ const validation = await options.validate?.(tool, mediatedCall.arguments, context);
66
+ if (validation)
67
+ return blocked(mediatedCall, context, "validation_failed", toErrorInfo(validation, secrets), options.emit);
68
+ await options.emit?.({ type: "tool_execution_started", sessionId: context.sessionId, runId: context.runId, call: mediatedCall });
69
+ try {
70
+ const raw = await tool.execute(mediatedCall.arguments, context);
71
+ const mediatedResult = await (options.middleware?.run("tool_result", raw) ?? raw);
72
+ const result = options.redactor?.redact(mediatedResult) ?? mediatedResult;
73
+ await options.emit?.({ type: "tool_execution_finished", sessionId: context.sessionId, runId: context.runId, result });
74
+ return result;
75
+ }
76
+ catch (error) {
77
+ const info = errorToErrorInfo(error, secrets);
78
+ const result = { toolCallId: mediatedCall.id, name: mediatedCall.name, error: info };
79
+ await options.emit?.({ type: "tool_execution_error", sessionId: context.sessionId, runId: context.runId, call: mediatedCall, error: info });
80
+ return result;
81
+ }
82
+ }
83
+ async function checkCall(call, options) {
84
+ const context = options.context;
85
+ const tool = options.registry.get(call.name);
86
+ if (!tool)
87
+ return blocked(call, context, "unknown_tool", { message: `Unknown tool: ${call.name}` }, options.emit);
88
+ if (filterTools([tool], options.filter).length === 0)
89
+ return blocked(call, context, "tool_denied", { message: `Tool denied: ${call.name}` }, options.emit);
90
+ if (!isJsonObject(call.arguments))
91
+ return blocked(call, context, "invalid_arguments", { message: "Tool arguments must be a JSON object" }, options.emit);
92
+ return undefined;
93
+ }
94
+ async function blocked(call, context, reason, error, emit) {
95
+ await emit?.({
96
+ type: "tool_execution_blocked",
97
+ sessionId: context.sessionId,
98
+ runId: context.runId,
99
+ toolCallId: call.id,
100
+ name: call.name,
101
+ reason,
102
+ error,
103
+ });
104
+ return { toolCallId: call.id, name: call.name, error };
105
+ }
106
+ function toErrorInfo(value, secrets) {
107
+ return typeof value === "string" ? errorToErrorInfo(value, secrets) : redactSecrets(value, secrets);
108
+ }
109
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1,167 @@
1
+ # Agent/session runtime
2
+
3
+ ## What it does
4
+
5
+ The agent/session runtime adds the minimal shared SDK surface for running provider turns, dispatching complete host-owned tool calls, and subscribing to session events:
6
+
7
+ - `createAgent(config)`
8
+ - `createAgentSession(config)`
9
+ - `agent.createSession(config)`
10
+ - `session.run(input, options)`
11
+ - `session.prompt(input, options)`
12
+ - `session.compact(options?)`
13
+ - `session.subscribe()`
14
+ - `session.abort()`
15
+ - `session.entries()`
16
+ - `session.checkout(leafId?)`
17
+ - `session.fork(options?)`
18
+ - `session.clone(options?)`
19
+
20
+ The runtime streams provider text/tool-call content into `AgentEvent` values. Complete `tool_call` events are dispatched through the active host `ToolRegistry`, then returned as tool-result messages on the next provider turn. When a store is supplied, user, assistant, tool-result, and model-change entries are appended under the current branch leaf. Abort propagation and run exclusivity use native `AbortController`.
21
+
22
+ ## When to use it
23
+
24
+ Use this runtime when a host already has an explicit `AIProvider` and wants to run a prompt through Prism's default input/prompt assembly, optionally execute selected host tools, and observe normalized session events.
25
+
26
+ Do not use it as a CLI/RPC adapter, whole-run retry framework, vector memory engine, provider registry, credential resolver, or app-tool pack.
27
+
28
+ ## Inputs / request
29
+
30
+ ```ts
31
+ createAgent(config: AgentConfig): Agent
32
+ createAgentSession(config: AgentSessionConfig & { agent: Agent }): AgentSession
33
+ ```
34
+
35
+ `AgentConfig.provider` must contain the host-selected provider. Prism does not resolve providers from hidden globals.
36
+
37
+ `session.run(input, options)` accepts the existing Prism input shape:
38
+
39
+ ```ts
40
+ string | Message | readonly Message[]
41
+ ```
42
+
43
+ `AgentSessionConfig.store` overrides `AgentConfig.store`; otherwise the session gets a private memory store. `AgentSessionConfig.leafId` selects the branch leaf to resume from.
44
+
45
+ `RunOptions.model` can override the request model for a run. Model overrides append a `model_change` entry. `AgentConfig.providerOptions`/`RunOptions.providerOptions` supply generic provider request options. `AgentConfig.providerRequestPolicies`/`RunOptions.providerRequestPolicies` run before `AIProvider.generate()` and before `provider_request` middleware. `AgentConfig.systemPrompt` and `RunOptions.systemPrompt` add explicit layered system prompt contributions; `RunOptions.systemPrompt: false` disables configured prompt layers for that run while keeping `AgentConfig.instructions` as the base path. `RunOptions.compaction` can enable auto-compaction for that run or use `false` to disable configured auto-compaction. `RunOptions.retry` can enable provider-turn retry for that run or use `false` to disable configured retry. `RunOptions.metadata` is merged with agent/session metadata for assembly, provider requests, and tool contexts. `RunOptions.maxToolRounds` bounds repeated tool turns and defaults to `1`. `RunOptions.signal` is bridged into the per-run abort signal passed to assembly, providers, tools, auto-compaction, and retry backoff.
46
+
47
+ ## Outputs / response / events
48
+
49
+ `session.subscribe()` returns a live `AsyncIterable<AgentEvent>`. Subscribe before `run()` to observe that run's events.
50
+
51
+ For a text-only provider turn, the runtime emits:
52
+
53
+ 1. `agent_started`
54
+ 2. `turn_started`
55
+ 3. `message_started`
56
+ 4. `message_delta`
57
+ 5. `message_finished`
58
+ 6. `turn_finished`
59
+ 7. `agent_finished`
60
+
61
+ For complete tool calls, the runtime emits the assistant `tool_call` as `message_delta`, dispatches sequentially through `dispatchToolCall()`, emits tool execution events, appends a tool-result session entry, adds returned `ToolResult` values to the next provider turn, and stops when the provider returns no tool calls or `maxToolRounds` is reached. The next provider turn therefore receives the assistant `tool_call` followed by the matching role `tool` `tool_result` before any final assistant content.
62
+
63
+ `session.compact(options?)` runs the selected compaction strategy, appends one `kind: "compaction"` entry under the current leaf, updates the leaf, emits `compaction_started` and `compaction_finished`, and returns the appended `CompactionResult`. If `AgentConfig.compaction` or `RunOptions.compaction` includes `thresholdEntries`, auto-compaction checks once after input/model-change entries are appended and before provider input assembly; `RunOptions.compaction: false` skips that run's auto-compaction.
64
+
65
+ `entries()` returns the current branch entries. `checkout(leafId?)` moves the session to an existing leaf and rebuilds history. `fork()` returns a session on the same store/session id at the selected leaf without copying entries. `clone({ id })` copies the current branch to a new session id with new entry ids.
66
+
67
+ Missing providers fail closed: `run()` emits `error` and rejects before calling any provider. Provider `error` events emit session `error` and reject unless configured retry handles a transient provider-turn failure before output. Unknown tools fail closed through the tool harness and do not execute. Tool exceptions emit `tool_execution_error`, return an error `ToolResult`, and may still continue to the next provider turn.
68
+
69
+ Only one `run()` may be active per session. Concurrent `run()` calls emit `error` and reject immediately; Prism does not queue them. Manual `compact()` also rejects while a run is active.
70
+
71
+ `session.abort(reason)` aborts the active run. The abort signal is passed to input assembly, provider requests, and tool execution; if a tool/provider path aborts after a tool call, Prism does not start another provider turn.
72
+
73
+ ## Request/response example
74
+
75
+ ```json
76
+ {
77
+ "input": "Hi",
78
+ "events": ["agent_started", "turn_started", "message_delta", "agent_finished"],
79
+ "leafId": "entry_2"
80
+ }
81
+ ```
82
+
83
+ ## Implementation example
84
+
85
+ ```ts
86
+ import { createAgent, createMemorySessionStore, createMockProvider, providerDone, providerTextDelta, type ToolDefinition } from "@arnilo/prism";
87
+
88
+ const echo: ToolDefinition = {
89
+ name: "echo",
90
+ execute: (args, context) => ({ toolCallId: context.toolCallId, name: "echo", value: args }),
91
+ };
92
+
93
+ const store = createMemorySessionStore();
94
+ const agent = createAgent({
95
+ model: { provider: "mock", model: "demo" },
96
+ provider: createMockProvider([providerTextDelta("Hello"), providerDone()]),
97
+ tools: [echo],
98
+ store,
99
+ });
100
+
101
+ const session = agent.createSession({ id: "s1" });
102
+ const reader = (async () => {
103
+ for await (const event of session.subscribe()) console.log(event.type);
104
+ })();
105
+
106
+ await session.run("Hi", { maxToolRounds: 1, compaction: { thresholdEntries: 20, keepRecentEntries: 6 }, retry: { maxAttempts: 3, baseDelayMs: 50 } });
107
+ await session.compact({ keepRecentEntries: 4 });
108
+ const branch = await session.entries();
109
+ await session.checkout(branch.at(-1)?.id);
110
+ const clone = await session.clone({ id: "s2" });
111
+ await reader;
112
+ ```
113
+
114
+ ## Extension and configuration notes
115
+
116
+ The runtime calls `assembleProviderInput()` on every turn and uses only values supplied on `AgentConfig`: `instructions`, `systemPrompt`, `inputBuilder`, `promptBuilder`, `context`, selected `skills`, active `tools`, `middleware`, `resourceLoader`, metadata, `compaction`, `retry`, and `RunOptions.model`/`systemPrompt`/`compaction`/`retry`. Contributions remain inert until a host passes selected values into the agent config.
117
+
118
+ The runtime calls `middleware.run("compaction", { context, result })` after a compaction strategy returns and before appending the standard compaction entry. Middleware can adjust the result summary/data, but the runtime still owns store append ordering and branch parent ids.
119
+
120
+ Provider request policy application is one ordered in-memory pass per provider turn. Policies can patch `ProviderRequest.options` and return exact secret values for provider-error redaction. The runtime then calls `middleware.run("provider_request", request)` once before provider generation.
121
+
122
+ The runtime calls `middleware.run("retry", { context, decision })` after the retry policy decision and before emitting `retry_scheduled`. Middleware can stop retrying or adjust the delay. Retry wraps only the current provider turn, reuses the same assembled request, and never retries after assistant output has been emitted.
123
+
124
+ `createAgent()` is a thin wrapper over explicit config. It does not load `AgentConfig.extensions`, scan packages, resolve credentials, read settings, or consult hidden registries. External `AgentDefinition` implementations can call it from their own `create()` method:
125
+
126
+ ```ts
127
+ import { createAgent, createContributionRegistries } from "@arnilo/prism";
128
+
129
+ const contributions = createContributionRegistries();
130
+ contributions.agents.register("demo", {
131
+ name: "demo",
132
+ create: () => createAgent({ model, provider, context: [projectContext], tools: [echo] }),
133
+ });
134
+
135
+ const agent = await contributions.agents.resolve("demo").create();
136
+ await agent.createSession().run("Hi", { model: overrideModel });
137
+ ```
138
+
139
+ ## Security and performance notes
140
+
141
+ - No hidden provider, tool, credential, resource, settings, or extension globals are created.
142
+ - Unknown providers fail before provider streaming.
143
+ - Unknown, denied, or malformed tool calls fail closed through `dispatchToolCall()`.
144
+ - Abort uses native `AbortController`/`AbortSignal` only; no polling, queue, or dependency is added. Retry backoff uses native abort-aware timers only when configured.
145
+ - Concurrent runs fail fast instead of creating a scheduler.
146
+ - System prompt composition uses caller-supplied strings only; Prism does not discover `SYSTEM.md`, settings, manifests, packages, or prompt files.
147
+ - Provider request policies are in-memory only; they add no cache store, tokenizer, filesystem, network, or worker.
148
+ - Cache keys should be safe caller/session identifiers, not prompt text or credential values.
149
+ - Retry context contains session/run ids, attempt, redacted error info, optional metadata, and signal only; it excludes provider request messages/content, provider objects, credentials, credential resolvers, settings, and hidden metadata.
150
+ - Compaction context contains branch entries and explicit compaction options only; it does not include provider objects, provider requests, credential resolvers, resolved credentials, settings, or hidden metadata.
151
+ - Store entries contain explicit session data only; Prism does not store provider objects, credential resolvers, resolved credentials, full provider requests, settings, or hidden metadata.
152
+ - Runtime events contain messages/content only; do not put secrets in prompts, metadata, provider events, session entries, or docs examples.
153
+ - The event broadcaster is in-memory and live-only. It adds no dependency, timer, filesystem/network discovery, worker, or durable queue.
154
+
155
+ ## Related APIs
156
+
157
+ - [Public contracts](public-contracts.md): `Agent`, `AgentSession`, `RunOptions`, and `AgentEvent` contracts.
158
+ - [Provider layer](provider-layer.md): `AIProvider`, provider events, and `createMockProvider()`.
159
+ - [Input and prompt assembly](input-and-prompt-assembly.md): request assembly used by `session.run()`.
160
+ - [System prompts](system-prompts.md): layered prompt composition used before default input assembly.
161
+ - [Session stores and branching](session-stores-and-branching.md): `SessionStore`, memory store, branch helpers, and context rebuild.
162
+ - [Compaction and retry policies](compaction-and-retry.md): compaction strategy/config APIs used by `session.compact()` and auto-compaction, plus retry policy/config APIs.
163
+ - [Tools](tools.md): host-owned tool harness used by the bounded runtime tool loop.
164
+ - [Middleware hooks](middleware-hooks.md): hooks that configured assembly/runtime can run.
165
+ - [CLI/RPC](cli-rpc.md): terminal and JSONL adapters over this runtime.
166
+
167
+ `AgentConfig.redactor` and `RunOptions.redactor` redact exact known secret strings from provider requests, emitted events, and stored session entries. Redaction is opt-in and exact-match only.
@@ -0,0 +1,32 @@
1
+ # <API name>
2
+
3
+ ## What it does
4
+ <Small description of what the API does.>
5
+
6
+ ## When to use it
7
+ <When an app/package/extension should use this API.>
8
+
9
+ ## Inputs / request
10
+ <Field table or typed shape.>
11
+
12
+ ## Outputs / response / events
13
+ <Field table, return type, events, or side effects.>
14
+
15
+ ## Request/response example
16
+ ```json
17
+ <minimal example payload or config>
18
+ ```
19
+
20
+ ## Implementation example
21
+ ```ts
22
+ <minimal working TypeScript example>
23
+ ```
24
+
25
+ ## Extension and configuration notes
26
+ <How extensions/plugins/config can replace or contribute behavior.>
27
+
28
+ ## Security and performance notes
29
+ <Secrets, permissions, trust boundaries, resource use, latency, limits.>
30
+
31
+ ## Related APIs
32
+ - `<API or page>`: <relationship>