@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
@@ -0,0 +1,68 @@
1
+ const REDACTED = "[REDACTED]";
2
+ export function createSecretRedactor(secrets) {
3
+ return { redact: (value) => redactSecrets(value, secrets) };
4
+ }
5
+ export function redactMessage(message, redactor) {
6
+ return redactor?.redact(message) ?? message;
7
+ }
8
+ export function redactAgentEvent(event, redactor) {
9
+ return redactor?.redact(event) ?? event;
10
+ }
11
+ export function redactSessionEntry(entry, redactor) {
12
+ return redactor?.redact(entry) ?? entry;
13
+ }
14
+ export function redactProviderRequest(request, redactor) {
15
+ return redactor?.redact(request) ?? request;
16
+ }
17
+ export function redactSecrets(value, secrets) {
18
+ const needles = secrets.filter((secret) => Boolean(secret));
19
+ if (needles.length === 0)
20
+ return value;
21
+ const redactString = (text) => needles.reduce((current, secret) => current.split(secret).join(REDACTED), text);
22
+ // ponytail: WeakSet cycle guard + leaf passthrough for Date/RegExp/ArrayBuffer/typed
23
+ // arrays. Map/Set are normalized to plain object/array so output stays JSON-shaped.
24
+ // Upgrade path: if callers need original Map/Set preserved, thread a mode flag.
25
+ const redact = (input, seen = new WeakSet()) => {
26
+ if (typeof input === "string")
27
+ return redactString(input);
28
+ if (input === null || typeof input !== "object")
29
+ return input;
30
+ if (input instanceof Date || input instanceof RegExp)
31
+ return input;
32
+ if (ArrayBuffer.isView(input) || input instanceof ArrayBuffer)
33
+ return input;
34
+ if (seen.has(input))
35
+ return "[Circular]";
36
+ seen.add(input);
37
+ if (Array.isArray(input))
38
+ return input.map((item) => redact(item, seen));
39
+ if (input instanceof Map)
40
+ return Object.fromEntries([...input].map(([key, item]) => [key, redact(item, seen)]));
41
+ if (input instanceof Set)
42
+ return [...input].map((item) => redact(item, seen));
43
+ return Object.fromEntries(Object.entries(input).map(([key, item]) => [key, redact(item, seen)]));
44
+ };
45
+ return redact(value);
46
+ }
47
+ export function errorToErrorInfo(error, secrets = []) {
48
+ const code = readErrorCode(error);
49
+ if (error instanceof Error) {
50
+ return {
51
+ name: error.name,
52
+ message: redactSecrets(error.message, secrets),
53
+ code,
54
+ cause: error.cause ? redactSecrets(String(error.cause), secrets) : undefined,
55
+ };
56
+ }
57
+ if (error && typeof error === "object" && "message" in error) {
58
+ return { message: redactSecrets(String(error.message), secrets), code };
59
+ }
60
+ return { message: redactSecrets(String(error), secrets), code };
61
+ }
62
+ function readErrorCode(error) {
63
+ if (!error || typeof error !== "object" || !("code" in error))
64
+ return undefined;
65
+ const code = error.code;
66
+ return typeof code === "string" || typeof code === "number" ? code : undefined;
67
+ }
68
+ //# sourceMappingURL=redaction.js.map
@@ -0,0 +1,5 @@
1
+ import { type PrismManifest } from "./manifests.js";
2
+ import type { JsonObject, ResourceLoader, ResourceLoadContext } from "./contracts.js";
3
+ export declare function loadTextResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext): Promise<string>;
4
+ export declare function loadJsonResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext): Promise<JsonObject>;
5
+ export declare function loadManifestResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext): Promise<PrismManifest>;
@@ -0,0 +1,30 @@
1
+ import { parsePrismManifest } from "./manifests.js";
2
+ import { assertJsonObject } from "./config.js";
3
+ import { assertPermission } from "./security.js";
4
+ export async function loadTextResource(loader, uri, context) {
5
+ await assertPermission(context?.permission, { kind: "resource", action: "load", target: uri, metadata: context?.metadata });
6
+ const resource = await loader.load(uri, context);
7
+ if (resource.text !== undefined)
8
+ return resource.text;
9
+ if (resource.data !== undefined)
10
+ return new TextDecoder().decode(resource.data);
11
+ throw new Error(`Resource ${uri} has no text or data`);
12
+ }
13
+ export async function loadJsonResource(loader, uri, context) {
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(await loadTextResource(loader, uri, context));
17
+ }
18
+ catch (error) {
19
+ throw new Error(`Invalid JSON resource ${uri}: ${errorMessage(error)}`);
20
+ }
21
+ assertJsonObject(parsed, `resource ${uri}`);
22
+ return parsed;
23
+ }
24
+ export async function loadManifestResource(loader, uri, context) {
25
+ return parsePrismManifest(await loadJsonResource(loader, uri, context));
26
+ }
27
+ function errorMessage(error) {
28
+ return error instanceof Error ? error.message : String(error);
29
+ }
30
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1,11 @@
1
+ import type { ErrorInfo, RetryDecision, RetryPolicy } from "./contracts.js";
2
+ export interface DefaultRetryPolicyOptions {
3
+ readonly name?: string;
4
+ readonly maxAttempts?: number;
5
+ readonly baseDelayMs?: number;
6
+ readonly maxDelayMs?: number;
7
+ readonly transientCodes?: readonly (string | number)[];
8
+ }
9
+ export declare function createDefaultRetryPolicy(options?: DefaultRetryPolicyOptions): RetryPolicy;
10
+ export declare function isTransientErrorInfo(error: ErrorInfo, transientCodes?: ReadonlySet<string | number>): boolean;
11
+ export declare function waitForRetry(decision: RetryDecision, signal?: AbortSignal): Promise<void>;
package/dist/retry.js ADDED
@@ -0,0 +1,48 @@
1
+ import { setTimeout as sleep } from "node:timers/promises";
2
+ const TRANSIENT_CODES = new Set([
3
+ "ETIMEDOUT",
4
+ "ECONNRESET",
5
+ "ECONNREFUSED",
6
+ "EAI_AGAIN",
7
+ "ENOTFOUND",
8
+ "timeout",
9
+ "rate_limit",
10
+ "overloaded",
11
+ "temporarily_unavailable",
12
+ 408,
13
+ 409,
14
+ 425,
15
+ 429,
16
+ 500,
17
+ 502,
18
+ 503,
19
+ 504,
20
+ ]);
21
+ export function createDefaultRetryPolicy(options = {}) {
22
+ const maxAttempts = Math.max(1, options.maxAttempts ?? 3);
23
+ const baseDelayMs = Math.max(0, options.baseDelayMs ?? 100);
24
+ const maxDelayMs = Math.max(baseDelayMs, options.maxDelayMs ?? 1000);
25
+ const transientCodes = new Set([...TRANSIENT_CODES, ...(options.transientCodes ?? [])]);
26
+ return {
27
+ name: options.name ?? "default-retry",
28
+ decide(context) {
29
+ if (context.signal?.aborted || context.attempt >= maxAttempts || !isTransientErrorInfo(context.error, transientCodes))
30
+ return { retry: false };
31
+ return { retry: true, delayMs: Math.min(maxDelayMs, baseDelayMs * 2 ** Math.max(0, context.attempt - 1)) };
32
+ },
33
+ };
34
+ }
35
+ export function isTransientErrorInfo(error, transientCodes = TRANSIENT_CODES) {
36
+ if (error.name === "AbortError" || /aborted|abort/i.test(error.message))
37
+ return false;
38
+ if (error.code !== undefined && transientCodes.has(error.code))
39
+ return true;
40
+ return /timeout|temporar|rate.?limit|overload|unavailable/i.test(error.message);
41
+ }
42
+ export async function waitForRetry(decision, signal) {
43
+ const delayMs = Math.max(0, decision.delayMs ?? 0);
44
+ if (delayMs === 0)
45
+ return;
46
+ await sleep(delayMs, undefined, { signal, ref: false });
47
+ }
48
+ //# sourceMappingURL=retry.js.map
package/dist/rpc.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ import type { AgentSession, CommandDefinition } from "./contracts.js";
3
+ export type RpcCommandName = "prompt" | "steer" | "followUp" | "abort" | "state" | "messages" | "setModel" | "compact" | "switchSession" | "forkSession" | "cloneSession" | "command";
4
+ export interface RpcRequest {
5
+ readonly id: string | number;
6
+ readonly command: RpcCommandName;
7
+ readonly params?: Record<string, unknown>;
8
+ }
9
+ export interface RpcSessionFactory {
10
+ createSession(id?: string): AgentSession;
11
+ readonly commands?: readonly CommandDefinition[];
12
+ }
13
+ export interface RpcServerOptions extends RpcSessionFactory {
14
+ readonly stdin: Readable;
15
+ readonly stdout: Writable;
16
+ }
17
+ export declare function runRpcServer(options: RpcServerOptions): Promise<void>;
18
+ export declare function parseRpcRequest(value: unknown): RpcRequest;
package/dist/rpc.js ADDED
@@ -0,0 +1,187 @@
1
+ import { createInterface } from "node:readline";
2
+ import { errorToErrorInfo } from "./redaction.js";
3
+ export async function runRpcServer(options) {
4
+ const first = options.createSession();
5
+ const state = {
6
+ current: first,
7
+ sessions: new Map([[first.id, first]]),
8
+ commands: new Map((options.commands ?? []).map((command) => [command.name, command])),
9
+ createSession: options.createSession,
10
+ };
11
+ const activeRuns = new Map();
12
+ const lines = createInterface({ input: options.stdin, crlfDelay: Infinity });
13
+ for await (const line of lines) {
14
+ if (!line.trim())
15
+ continue;
16
+ let raw;
17
+ let request;
18
+ try {
19
+ raw = JSON.parse(line);
20
+ request = parseRpcRequest(raw);
21
+ }
22
+ catch (error) {
23
+ write(options.stdout, { id: readRequestId(raw), ok: false, error: errorToErrorInfo(error) });
24
+ continue;
25
+ }
26
+ try {
27
+ await handleRequest(request, state, options.stdout, activeRuns);
28
+ }
29
+ catch (error) {
30
+ write(options.stdout, { id: request.id, ok: false, error: errorToErrorInfo(error) });
31
+ }
32
+ }
33
+ // Await any active runs before returning so callers do not lose final envelopes.
34
+ await Promise.allSettled([...activeRuns.values()].map((run) => run.promise));
35
+ }
36
+ export function parseRpcRequest(value) {
37
+ if (!isObject(value))
38
+ throw new Error("RPC request must be an object");
39
+ const { id, command, params } = value;
40
+ if (typeof id !== "string" && typeof id !== "number")
41
+ throw new Error("RPC request id must be a string or number");
42
+ if (!isCommand(command))
43
+ throw new Error(`Unknown RPC command: ${String(command)}`);
44
+ if (params !== undefined && !isObject(params))
45
+ throw new Error("RPC params must be an object");
46
+ return { id, command, params: params };
47
+ }
48
+ async function handleRequest(request, state, stdout, activeRuns) {
49
+ switch (request.command) {
50
+ case "prompt":
51
+ case "followUp": {
52
+ const input = stringParam(request.params, "input") ?? stringParam(request.params, "prompt");
53
+ if (!input)
54
+ throw new Error(`${request.command} requires params.input`);
55
+ const session = state.current;
56
+ const existing = activeRuns.get(session.id);
57
+ if (existing) {
58
+ write(stdout, { id: request.id, ok: false, error: errorToErrorInfo(new Error(`Session ${session.id} already has an active run for request ${existing.requestId}`)) });
59
+ return;
60
+ }
61
+ const events = pumpEvents(session, stdout, request.id);
62
+ const promise = (async () => {
63
+ try {
64
+ await session.run(input, runOptions(state, request.params));
65
+ }
66
+ finally {
67
+ await events;
68
+ }
69
+ write(stdout, { id: request.id, ok: true, result: { sessionId: session.id } });
70
+ })().catch((error) => {
71
+ write(stdout, { id: request.id, ok: false, error: errorToErrorInfo(error) });
72
+ }).finally(() => {
73
+ activeRuns.delete(session.id);
74
+ });
75
+ activeRuns.set(session.id, { requestId: request.id, promise });
76
+ break;
77
+ }
78
+ case "steer":
79
+ throw new Error("steer is not supported by the current AgentSession runtime");
80
+ case "abort":
81
+ state.current.abort(stringParam(request.params, "reason"));
82
+ write(stdout, { id: request.id, ok: true, result: { sessionId: state.current.id } });
83
+ break;
84
+ case "state":
85
+ write(stdout, { id: request.id, ok: true, result: { sessionId: state.current.id, sessions: [...state.sessions.keys()], model: state.model } });
86
+ break;
87
+ case "messages":
88
+ write(stdout, { id: request.id, ok: true, result: { sessionId: state.current.id, entries: await state.current.entries() } });
89
+ break;
90
+ case "setModel":
91
+ state.model = modelParam(request.params);
92
+ write(stdout, { id: request.id, ok: true, result: { model: state.model } });
93
+ break;
94
+ case "compact":
95
+ write(stdout, { id: request.id, ok: true, result: await state.current.compact() });
96
+ break;
97
+ case "switchSession": {
98
+ const id = stringParam(request.params, "sessionId") ?? stringParam(request.params, "id");
99
+ if (!id)
100
+ throw new Error("switchSession requires params.sessionId");
101
+ const session = state.sessions.get(id) ?? makeSession(state, id);
102
+ state.current = session;
103
+ write(stdout, { id: request.id, ok: true, result: { sessionId: session.id } });
104
+ break;
105
+ }
106
+ case "forkSession": {
107
+ const session = state.current.fork({ leafId: stringParam(request.params, "leafId") });
108
+ state.sessions.set(session.id, session);
109
+ state.current = session;
110
+ write(stdout, { id: request.id, ok: true, result: { sessionId: session.id } });
111
+ break;
112
+ }
113
+ case "cloneSession": {
114
+ const session = await state.current.clone({ id: stringParam(request.params, "id"), leafId: stringParam(request.params, "leafId") });
115
+ state.sessions.set(session.id, session);
116
+ state.current = session;
117
+ write(stdout, { id: request.id, ok: true, result: { sessionId: session.id } });
118
+ break;
119
+ }
120
+ case "command": {
121
+ const name = stringParam(request.params, "name");
122
+ if (!name)
123
+ throw new Error("command requires params.name");
124
+ const command = state.commands.get(name);
125
+ if (!command)
126
+ throw new Error(`Unknown command: ${name}`);
127
+ const args = objectParam(request.params, "args") ?? {};
128
+ const result = await command.execute(args, { sessionId: state.current.id });
129
+ write(stdout, { id: request.id, ok: true, result });
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ function pumpEvents(session, stdout, requestId) {
135
+ return (async () => {
136
+ for await (const event of session.subscribe())
137
+ write(stdout, eventEnvelope(event, requestId));
138
+ })();
139
+ }
140
+ function eventEnvelope(event, requestId) {
141
+ const sessionId = "sessionId" in event ? event.sessionId : undefined;
142
+ const runId = "runId" in event ? event.runId : undefined;
143
+ return { type: "event", id: requestId, sessionId, runId, event };
144
+ }
145
+ function makeSession(state, id) {
146
+ const session = state.createSession(id);
147
+ state.sessions.set(session.id, session);
148
+ return session;
149
+ }
150
+ function runOptions(state, params) {
151
+ return { model: modelParam(params) ?? state.model, maxToolRounds: numberParam(params, "maxToolRounds") };
152
+ }
153
+ function modelParam(params) {
154
+ if (!params)
155
+ return undefined;
156
+ const model = params.model;
157
+ if (isObject(model) && typeof model.provider === "string" && typeof model.model === "string")
158
+ return model;
159
+ if (typeof model === "string")
160
+ return { provider: stringParam(params, "provider") ?? "rpc", model };
161
+ return undefined;
162
+ }
163
+ function stringParam(params, key) {
164
+ const value = params?.[key];
165
+ return typeof value === "string" ? value : undefined;
166
+ }
167
+ function numberParam(params, key) {
168
+ const value = params?.[key];
169
+ return typeof value === "number" ? value : undefined;
170
+ }
171
+ function objectParam(params, key) {
172
+ const value = params?.[key];
173
+ return isObject(value) ? value : undefined;
174
+ }
175
+ function isObject(value) {
176
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
177
+ }
178
+ function isCommand(value) {
179
+ return typeof value === "string" && ["prompt", "steer", "followUp", "abort", "state", "messages", "setModel", "compact", "switchSession", "forkSession", "cloneSession", "command"].includes(value);
180
+ }
181
+ function readRequestId(value) {
182
+ return isObject(value) && (typeof value.id === "string" || typeof value.id === "number") ? value.id : null;
183
+ }
184
+ function write(stdout, value) {
185
+ stdout.write(`${JSON.stringify(value)}\n`);
186
+ }
187
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1,51 @@
1
+ import type { ErrorInfo } from "./contracts.js";
2
+ export interface TrustRequest {
3
+ readonly kind: "project" | "resource" | "extension" | string;
4
+ readonly target: string;
5
+ readonly capability?: string;
6
+ readonly metadata?: Readonly<Record<string, unknown>>;
7
+ }
8
+ export interface TrustDecision {
9
+ readonly trusted: boolean;
10
+ readonly reason?: string;
11
+ readonly metadata?: Readonly<Record<string, unknown>>;
12
+ }
13
+ export interface TrustPolicy {
14
+ check(request: TrustRequest): TrustDecision | Promise<TrustDecision>;
15
+ }
16
+ export interface PermissionRequest {
17
+ readonly kind: "tool" | "extension" | "resource" | string;
18
+ readonly action: string;
19
+ readonly target: string;
20
+ readonly metadata?: Readonly<Record<string, unknown>>;
21
+ }
22
+ export interface PermissionDecision {
23
+ readonly allowed: boolean;
24
+ readonly reason?: string;
25
+ readonly metadata?: Readonly<Record<string, unknown>>;
26
+ }
27
+ export interface PermissionPolicy {
28
+ check(request: PermissionRequest): PermissionDecision | Promise<PermissionDecision>;
29
+ }
30
+ export declare class TrustDeniedError extends Error {
31
+ readonly request: TrustRequest;
32
+ readonly decision: TrustDecision;
33
+ readonly code = "ERR_PRISM_UNTRUSTED";
34
+ constructor(request: TrustRequest, decision: TrustDecision);
35
+ }
36
+ export declare class PermissionDeniedError extends Error {
37
+ readonly request: PermissionRequest;
38
+ readonly decision: PermissionDecision;
39
+ readonly code = "ERR_PRISM_PERMISSION_DENIED";
40
+ constructor(request: PermissionRequest, decision: PermissionDecision);
41
+ }
42
+ export declare function isTrusted(policy: TrustPolicy | undefined, request: TrustRequest): Promise<boolean>;
43
+ export declare function assertTrusted(policy: TrustPolicy | undefined, request: TrustRequest): Promise<void>;
44
+ export declare function createStaticTrustPolicy(decision: boolean | TrustDecision): TrustPolicy;
45
+ export declare function checkPermission(policy: PermissionPolicy | undefined, request: PermissionRequest): Promise<PermissionDecision>;
46
+ export declare function assertPermission(policy: PermissionPolicy | undefined, request: PermissionRequest): Promise<void>;
47
+ export declare function createStaticPermissionPolicy(options: {
48
+ readonly allow?: readonly string[];
49
+ readonly deny?: readonly string[];
50
+ } | boolean): PermissionPolicy;
51
+ export declare function denialToErrorInfo(error: unknown): ErrorInfo;
@@ -0,0 +1,60 @@
1
+ export class TrustDeniedError extends Error {
2
+ request;
3
+ decision;
4
+ code = "ERR_PRISM_UNTRUSTED";
5
+ constructor(request, decision) {
6
+ super(decision.reason ?? `Untrusted ${request.kind}: ${request.target}`);
7
+ this.request = request;
8
+ this.decision = decision;
9
+ this.name = "TrustDeniedError";
10
+ }
11
+ }
12
+ export class PermissionDeniedError extends Error {
13
+ request;
14
+ decision;
15
+ code = "ERR_PRISM_PERMISSION_DENIED";
16
+ constructor(request, decision) {
17
+ super(decision.reason ?? `Permission denied for ${request.kind}:${request.target}:${request.action}`);
18
+ this.request = request;
19
+ this.decision = decision;
20
+ this.name = "PermissionDeniedError";
21
+ }
22
+ }
23
+ export async function isTrusted(policy, request) {
24
+ return policy ? (await policy.check(request)).trusted : true;
25
+ }
26
+ export async function assertTrusted(policy, request) {
27
+ const decision = policy ? await policy.check(request) : { trusted: true };
28
+ if (!decision.trusted)
29
+ throw new TrustDeniedError(request, decision);
30
+ }
31
+ export function createStaticTrustPolicy(decision) {
32
+ const resolved = typeof decision === "boolean" ? { trusted: decision } : decision;
33
+ return { check: () => resolved };
34
+ }
35
+ export async function checkPermission(policy, request) {
36
+ return policy ? policy.check(request) : { allowed: true };
37
+ }
38
+ export async function assertPermission(policy, request) {
39
+ const decision = await checkPermission(policy, request);
40
+ if (!decision.allowed)
41
+ throw new PermissionDeniedError(request, decision);
42
+ }
43
+ export function createStaticPermissionPolicy(options) {
44
+ if (typeof options === "boolean")
45
+ return { check: () => ({ allowed: options }) };
46
+ const allow = new Set(options.allow ?? []);
47
+ const deny = new Set(options.deny ?? []);
48
+ return {
49
+ check(request) {
50
+ const key = `${request.kind}:${request.target}:${request.action}`;
51
+ if (deny.has(key))
52
+ return { allowed: false, reason: `Permission denied: ${key}` };
53
+ return { allowed: allow.size === 0 || allow.has(key), reason: allow.size && !allow.has(key) ? `Permission not allowed: ${key}` : undefined };
54
+ },
55
+ };
56
+ }
57
+ export function denialToErrorInfo(error) {
58
+ return error instanceof Error ? { name: error.name, message: error.message, code: error.code } : { message: String(error) };
59
+ }
60
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1,25 @@
1
+ import type { Message, SessionEntry, SessionStore } from "./contracts.js";
2
+ export interface CreateSessionEntryOptions extends Omit<SessionEntry, "id" | "timestamp"> {
3
+ readonly id?: string;
4
+ readonly timestamp?: string;
5
+ readonly createId?: () => string;
6
+ readonly now?: () => Date;
7
+ }
8
+ export interface SessionBranchOptions {
9
+ readonly leafId?: string;
10
+ }
11
+ export interface SessionBranch {
12
+ readonly leafId: string;
13
+ readonly entries: readonly SessionEntry[];
14
+ }
15
+ export interface SessionContextSnapshot {
16
+ readonly leafId?: string;
17
+ readonly entries: readonly SessionEntry[];
18
+ readonly messages: readonly Message[];
19
+ readonly summaries: readonly string[];
20
+ }
21
+ export declare function createSessionEntry(options: CreateSessionEntryOptions): SessionEntry;
22
+ export declare function getSessionBranchEntries(entries: readonly SessionEntry[], options?: SessionBranchOptions): readonly SessionEntry[];
23
+ export declare function listSessionBranches(entries: readonly SessionEntry[]): readonly SessionBranch[];
24
+ export declare function rebuildSessionContext(entries: readonly SessionEntry[], options?: SessionBranchOptions): SessionContextSnapshot;
25
+ export declare function createMemorySessionStore(initialEntries?: readonly SessionEntry[]): SessionStore;
@@ -0,0 +1,116 @@
1
+ export function createSessionEntry(options) {
2
+ const { createId, now, ...entry } = options;
3
+ return {
4
+ ...entry,
5
+ id: entry.id ?? createId?.() ?? randomId("entry"),
6
+ timestamp: entry.timestamp ?? (now?.() ?? new Date()).toISOString(),
7
+ };
8
+ }
9
+ export function getSessionBranchEntries(entries, options = {}) {
10
+ const index = indexEntries(entries);
11
+ const leafId = options.leafId ?? entries.at(-1)?.id;
12
+ if (!leafId)
13
+ return [];
14
+ if (!index.byId.has(leafId))
15
+ throw new Error(`Unknown session leaf: ${leafId}`);
16
+ const branch = [];
17
+ for (let id = leafId; id;) {
18
+ const entry = index.byId.get(id);
19
+ if (!entry)
20
+ throw new Error(`Missing session parent: ${id}`);
21
+ branch.push(entry);
22
+ id = entry.parentId;
23
+ }
24
+ return branch.reverse().map(cloneEntry);
25
+ }
26
+ export function listSessionBranches(entries) {
27
+ const index = indexEntries(entries);
28
+ return entries
29
+ .filter((entry) => !index.parentIds.has(entry.id))
30
+ .map((entry) => ({ leafId: entry.id, entries: getSessionBranchEntries(entries, { leafId: entry.id }) }));
31
+ }
32
+ export function rebuildSessionContext(entries, options = {}) {
33
+ const branch = getSessionBranchEntries(entries, options);
34
+ const compaction = [...branch].reverse().find((entry) => entry.kind === "compaction" && entry.summary && isCompactionEntryData(entry.data));
35
+ if (!compaction || !isCompactionEntryData(compaction.data)) {
36
+ return {
37
+ leafId: branch.at(-1)?.id,
38
+ entries: branch,
39
+ messages: branch.flatMap((entry) => entry.kind === "message" && entry.message ? [entry.message] : []),
40
+ summaries: branch.flatMap((entry) => entry.kind === "summary" && entry.summary ? [entry.summary] : []),
41
+ };
42
+ }
43
+ const keepIds = new Set(compaction.data.keepEntryIds ?? []);
44
+ let afterThrough = !compaction.data.throughEntryId;
45
+ const messages = [];
46
+ const summaries = [compaction.summary];
47
+ for (const entry of branch) {
48
+ if (entry.id === compaction.data.throughEntryId) {
49
+ afterThrough = true;
50
+ continue;
51
+ }
52
+ if (entry.id === compaction.id)
53
+ continue;
54
+ if (entry.kind === "message" && entry.message && (afterThrough || keepIds.has(entry.id)))
55
+ messages.push(cloneEntry(entry.message));
56
+ if (entry.kind === "summary" && entry.summary && afterThrough)
57
+ summaries.push(entry.summary);
58
+ }
59
+ return { leafId: branch.at(-1)?.id, entries: branch, messages, summaries };
60
+ }
61
+ export function createMemorySessionStore(initialEntries = []) {
62
+ const byId = new Map();
63
+ const bySession = new Map();
64
+ for (const entry of initialEntries)
65
+ add(entry);
66
+ return {
67
+ async append(entry) {
68
+ add(entry);
69
+ },
70
+ async list(sessionId) {
71
+ return (bySession.get(sessionId) ?? []).map(cloneEntry);
72
+ },
73
+ async get(id) {
74
+ const entry = byId.get(id);
75
+ return entry ? cloneEntry(entry) : undefined;
76
+ },
77
+ };
78
+ function add(entry) {
79
+ if (byId.has(entry.id))
80
+ throw new Error(`Duplicate session entry id: ${entry.id}`);
81
+ byId.set(entry.id, entry);
82
+ const entries = bySession.get(entry.sessionId) ?? [];
83
+ entries.push(entry);
84
+ bySession.set(entry.sessionId, entries);
85
+ }
86
+ }
87
+ function cloneEntry(entry) {
88
+ return structuredClone(entry);
89
+ }
90
+ function isCompactionEntryData(value) {
91
+ if (!value || typeof value !== "object" || Array.isArray(value))
92
+ return false;
93
+ const data = value;
94
+ return (data.throughEntryId === undefined || typeof data.throughEntryId === "string")
95
+ && (data.keepEntryIds === undefined || (Array.isArray(data.keepEntryIds) && data.keepEntryIds.every((item) => typeof item === "string")));
96
+ }
97
+ function indexEntries(entries) {
98
+ const byId = new Map();
99
+ const parentIds = new Set();
100
+ for (const entry of entries) {
101
+ if (byId.has(entry.id))
102
+ throw new Error(`Duplicate session entry id: ${entry.id}`);
103
+ byId.set(entry.id, entry);
104
+ if (entry.parentId)
105
+ parentIds.add(entry.parentId);
106
+ }
107
+ for (const parentId of parentIds) {
108
+ if (!byId.has(parentId))
109
+ throw new Error(`Missing session parent: ${parentId}`);
110
+ }
111
+ return { byId, parentIds };
112
+ }
113
+ function randomId(prefix) {
114
+ return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
115
+ }
116
+ //# sourceMappingURL=session-stores.js.map
@@ -0,0 +1,3 @@
1
+ import type { JsonObject, SettingsProvider } from "./contracts.js";
2
+ export declare function createStaticSettingsProvider(settings: JsonObject): SettingsProvider;
3
+ export declare function createChainedSettingsProvider(providers: readonly SettingsProvider[]): SettingsProvider;
@@ -0,0 +1,26 @@
1
+ import { assertJsonObject } from "./config.js";
2
+ export function createStaticSettingsProvider(settings) {
3
+ assertJsonObject(settings, "settings");
4
+ return {
5
+ get(key) {
6
+ return key.split(".").reduce((value, part) => {
7
+ if (!value || typeof value !== "object" || Array.isArray(value))
8
+ return undefined;
9
+ return value[part];
10
+ }, settings);
11
+ },
12
+ };
13
+ }
14
+ export function createChainedSettingsProvider(providers) {
15
+ return {
16
+ async get(key) {
17
+ for (const provider of providers) {
18
+ const value = await provider.get(key);
19
+ if (value !== undefined)
20
+ return value;
21
+ }
22
+ return undefined;
23
+ },
24
+ };
25
+ }
26
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1,8 @@
1
+ import type { Skill, SkillRegistry, ToolDefinition } from "./contracts.js";
2
+ export interface ResolveActiveSkillsOptions {
3
+ readonly registry: SkillRegistry;
4
+ readonly names?: readonly string[];
5
+ readonly tools?: readonly ToolDefinition[];
6
+ }
7
+ export declare function createSkillRegistry(skills?: readonly Skill[]): SkillRegistry;
8
+ export declare function resolveActiveSkills(options: ResolveActiveSkillsOptions): readonly Skill[];