@arnilo/prism 0.0.3 → 0.0.5

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 (131) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +62 -26
  3. package/dist/agent-loops.d.ts +8 -1
  4. package/dist/agent-loops.js +57 -11
  5. package/dist/agents.js +212 -32
  6. package/dist/checkpoints.d.ts +11 -0
  7. package/dist/checkpoints.js +144 -0
  8. package/dist/cli-init.d.ts +41 -0
  9. package/dist/cli-init.js +390 -0
  10. package/dist/cli-runner.d.ts +7 -1
  11. package/dist/cli-runner.js +13 -1
  12. package/dist/compaction.js +9 -1
  13. package/dist/content.d.ts +121 -0
  14. package/dist/content.js +538 -0
  15. package/dist/contracts.d.ts +236 -11
  16. package/dist/contracts.js +8 -0
  17. package/dist/event-multiplexer.d.ts +23 -0
  18. package/dist/event-multiplexer.js +136 -0
  19. package/dist/execution-policy.d.ts +28 -0
  20. package/dist/execution-policy.js +24 -0
  21. package/dist/feedback.d.ts +48 -0
  22. package/dist/feedback.js +230 -0
  23. package/dist/index.d.ts +20 -6
  24. package/dist/index.js +13 -5
  25. package/dist/input.js +11 -1
  26. package/dist/leases.d.ts +8 -0
  27. package/dist/leases.js +111 -0
  28. package/dist/node/agent-definitions.js +3 -5
  29. package/dist/node/config.d.ts +1 -0
  30. package/dist/node/config.js +5 -3
  31. package/dist/node/contribution-discovery.js +5 -8
  32. package/dist/node/session-store-jsonl.js +8 -5
  33. package/dist/node/settings.js +2 -2
  34. package/dist/node/trust.js +2 -4
  35. package/dist/observability.d.ts +3 -0
  36. package/dist/observability.js +18 -0
  37. package/dist/providers/media.d.ts +44 -0
  38. package/dist/providers/media.js +126 -0
  39. package/dist/providers/openai-compatible.js +18 -119
  40. package/dist/providers/openai-primitives.d.ts +9 -0
  41. package/dist/providers/openai-primitives.js +129 -0
  42. package/dist/providers/transport.d.ts +40 -0
  43. package/dist/providers/transport.js +221 -0
  44. package/dist/redaction.js +40 -13
  45. package/dist/resources.d.ts +5 -0
  46. package/dist/resources.js +4 -0
  47. package/dist/structured-output.d.ts +11 -0
  48. package/dist/structured-output.js +59 -0
  49. package/dist/testing/feedback.d.ts +6 -0
  50. package/dist/testing/feedback.js +37 -0
  51. package/dist/testing/persistence-schema.d.ts +102 -0
  52. package/dist/testing/persistence-schema.js +487 -0
  53. package/dist/testing/provider-conformance.js +10 -1
  54. package/dist/testing/run-ledger-conformance.d.ts +33 -0
  55. package/dist/testing/run-ledger-conformance.js +178 -0
  56. package/dist/testing/session-store-conformance.d.ts +16 -0
  57. package/dist/testing/session-store-conformance.js +73 -0
  58. package/dist/tools.d.ts +17 -0
  59. package/dist/tools.js +29 -2
  60. package/docs/a2a.md +73 -0
  61. package/docs/agent-events.md +17 -10
  62. package/docs/agent-loops.md +11 -5
  63. package/docs/agent-session-runtime.md +15 -16
  64. package/docs/cli-rpc.md +36 -5
  65. package/docs/coding-agent-tools.md +43 -9
  66. package/docs/coding-security.md +88 -0
  67. package/docs/compaction-observational-memory.md +2 -0
  68. package/docs/context-and-skills.md +1 -0
  69. package/docs/credential-storage.md +177 -0
  70. package/docs/credentials-and-redaction.md +4 -3
  71. package/docs/database-persistence.md +52 -7
  72. package/docs/evaluations.md +122 -0
  73. package/docs/extensions.md +2 -2
  74. package/docs/host-security.md +33 -2
  75. package/docs/index.md +46 -18
  76. package/docs/input-and-prompt-assembly.md +6 -5
  77. package/docs/mcp-tools.md +184 -0
  78. package/docs/middleware-hooks.md +2 -0
  79. package/docs/migration.md +51 -28
  80. package/docs/model-registry.md +5 -3
  81. package/docs/multimodal-content.md +156 -0
  82. package/docs/observability.md +171 -0
  83. package/docs/performance.md +249 -1
  84. package/docs/persistence-credentials-multimodality-primitives.md +303 -0
  85. package/docs/postgres-persistence.md +143 -0
  86. package/docs/provider-conformance.md +18 -0
  87. package/docs/provider-layer.md +1 -1
  88. package/docs/provider-packages.md +2 -0
  89. package/docs/provider-primitives.md +281 -0
  90. package/docs/providers/ai-sdk.md +113 -0
  91. package/docs/providers/kimi.md +1 -0
  92. package/docs/providers/neuralwatt.md +1 -0
  93. package/docs/providers/openai-compatible.md +2 -1
  94. package/docs/providers/openai.md +8 -1
  95. package/docs/providers/opencode-go.md +1 -0
  96. package/docs/providers/openrouter.md +1 -0
  97. package/docs/providers/zai.md +1 -0
  98. package/docs/public-contracts.md +13 -5
  99. package/docs/rag.md +113 -0
  100. package/docs/release-and-install.md +237 -30
  101. package/docs/resource-loading.md +14 -4
  102. package/docs/review-coverage-2026-07-14.md +260 -0
  103. package/docs/review-coverage-2026-07-15.md +193 -0
  104. package/docs/run-ledger-conformance.md +96 -0
  105. package/docs/runs-and-usage.md +43 -4
  106. package/docs/server.md +139 -0
  107. package/docs/session-store-conformance.md +16 -0
  108. package/docs/session-stores-and-branching.md +1 -0
  109. package/docs/settings-auth-trust-security.md +6 -5
  110. package/docs/sqlite-persistence.md +123 -0
  111. package/docs/structured-output.md +9 -0
  112. package/docs/supervisors.md +71 -0
  113. package/docs/tool-conformance.md +1 -0
  114. package/docs/tool-execution-primitives.md +374 -0
  115. package/docs/tools.md +39 -1
  116. package/docs/workflow-orchestration-primitives.md +581 -0
  117. package/docs/workflow-tui-primitives.md +5 -0
  118. package/docs/workflows.md +293 -0
  119. package/docs/working-and-semantic-memory.md +169 -0
  120. package/package.json +43 -5
  121. package/templates/init/README.md.tmpl +28 -0
  122. package/templates/init/env.example.tmpl +1 -0
  123. package/templates/init/gitignore.tmpl +11 -0
  124. package/templates/init/optional/evals-example.ts.tmpl +17 -0
  125. package/templates/init/optional/workflows-example.ts.tmpl +27 -0
  126. package/templates/init/package.json.tmpl +22 -0
  127. package/templates/init/providers.json +76 -0
  128. package/templates/init/src/agent.ts.tmpl +10 -0
  129. package/templates/init/src/index.ts.tmpl +12 -0
  130. package/templates/init/src/tests/agent.test.ts.tmpl +24 -0
  131. package/templates/init/tsconfig.json.tmpl +15 -0
@@ -0,0 +1,390 @@
1
+ import { access, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
+ import { constants as fsConstants, readFileSync } from "node:fs";
3
+ import { dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ export class InitUsageError extends Error {
6
+ }
7
+ let cachedCatalog;
8
+ let cachedProviderIds;
9
+ function providersCatalogPath(templatesRoot) {
10
+ return join(templatesRoot, "providers.json");
11
+ }
12
+ function loadProvidersCatalog(templatesRoot = defaultTemplatesRoot()) {
13
+ if (cachedCatalog && templatesRoot === defaultTemplatesRoot())
14
+ return cachedCatalog;
15
+ const raw = JSON.parse(readFileSync(providersCatalogPath(templatesRoot), "utf8"));
16
+ const map = new Map();
17
+ for (const [id, spec] of Object.entries(raw)) {
18
+ if (!id || typeof spec !== "object" || spec === null) {
19
+ throw new Error(`Invalid init provider catalog entry: ${id}`);
20
+ }
21
+ map.set(id, { ...spec, id });
22
+ }
23
+ if (map.size === 0)
24
+ throw new Error("Init provider catalog is empty");
25
+ if (templatesRoot === defaultTemplatesRoot()) {
26
+ cachedCatalog = map;
27
+ cachedProviderIds = Object.freeze([...map.keys()]);
28
+ }
29
+ return map;
30
+ }
31
+ /** Provider ids supported by `prism init --provider`. Loaded from templates data. */
32
+ export function listInitProviders(templatesRoot) {
33
+ if (!templatesRoot && cachedProviderIds)
34
+ return cachedProviderIds;
35
+ return Object.freeze([...loadProvidersCatalog(templatesRoot).keys()]);
36
+ }
37
+ /** @deprecated Prefer listInitProviders(); retained for tests that import the name. */
38
+ export const INIT_PROVIDERS = listInitProviders();
39
+ export function getInitUsage(templatesRoot) {
40
+ const providers = listInitProviders(templatesRoot).join("|");
41
+ return `Usage: prism init <dir> [options]
42
+
43
+ Create a minimal TypeScript Prism project with one explicit provider and an offline mock test.
44
+
45
+ Arguments:
46
+ <dir> Destination directory (created if missing)
47
+
48
+ Options:
49
+ --provider <name> Provider id (${providers}). Default: mock
50
+ --with-workflows Add optional workflows dependency and example
51
+ --with-evals Add optional evals dependency and example
52
+ --force Overwrite existing generated files
53
+ -h, --help Show this help
54
+
55
+ Examples:
56
+ prism init my-agent
57
+ prism init my-agent --provider openai
58
+ prism init my-agent --provider openai --with-workflows
59
+ `;
60
+ }
61
+ export const initUsage = getInitUsage();
62
+ export function parseInitArgs(argv, templatesRoot) {
63
+ let directory;
64
+ let provider = "mock";
65
+ let withWorkflows = false;
66
+ let withEvals = false;
67
+ let force = false;
68
+ let help = false;
69
+ const providers = listInitProviders(templatesRoot);
70
+ for (let i = 0; i < argv.length; i += 1) {
71
+ const arg = argv[i];
72
+ if (arg === "-h" || arg === "--help") {
73
+ help = true;
74
+ continue;
75
+ }
76
+ if (arg === "--force") {
77
+ force = true;
78
+ continue;
79
+ }
80
+ if (arg === "--with-workflows") {
81
+ withWorkflows = true;
82
+ continue;
83
+ }
84
+ if (arg === "--with-evals") {
85
+ withEvals = true;
86
+ continue;
87
+ }
88
+ if (arg === "--provider") {
89
+ const value = argv[i + 1];
90
+ if (value === undefined || value.startsWith("-")) {
91
+ throw new InitUsageError("Missing value for --provider");
92
+ }
93
+ i += 1;
94
+ if (!providers.includes(value)) {
95
+ throw new InitUsageError(`Unknown provider: ${value}. Expected one of ${providers.join(", ")}`);
96
+ }
97
+ provider = value;
98
+ continue;
99
+ }
100
+ if (arg.startsWith("-")) {
101
+ throw new InitUsageError(`Unknown flag: ${arg}`);
102
+ }
103
+ if (directory !== undefined) {
104
+ throw new InitUsageError(`Unexpected argument: ${arg}`);
105
+ }
106
+ directory = arg;
107
+ }
108
+ if (!help && directory === undefined) {
109
+ throw new InitUsageError("Missing destination directory");
110
+ }
111
+ return {
112
+ directory: directory ?? ".",
113
+ provider,
114
+ withWorkflows,
115
+ withEvals,
116
+ force,
117
+ help,
118
+ };
119
+ }
120
+ export async function runInitCommand(argv, runtime) {
121
+ let options;
122
+ try {
123
+ options = parseInitArgs(argv, runtime.templatesRoot);
124
+ }
125
+ catch (error) {
126
+ write(runtime.stderr, `${error instanceof Error ? error.message : String(error)}\n${getInitUsage(runtime.templatesRoot)}`);
127
+ return 2;
128
+ }
129
+ if (options.help) {
130
+ write(runtime.stdout, getInitUsage(runtime.templatesRoot));
131
+ return 0;
132
+ }
133
+ try {
134
+ const result = await createInitProject(options, runtime);
135
+ write(runtime.stdout, [
136
+ `Created Prism project in ${result.targetDir}`,
137
+ ` provider: ${result.provider}`,
138
+ ` files: ${result.writtenFiles.length}`,
139
+ ` bytes: ${result.totalBytes}`,
140
+ ...(result.withWorkflows ? [" optional: workflows"] : []),
141
+ ...(result.withEvals ? [" optional: evals"] : []),
142
+ "",
143
+ "Next:",
144
+ ` cd ${shellQuote(displayPath(result.targetDir, runtime.cwd ?? process.cwd()))}`,
145
+ " npm install",
146
+ " npm test",
147
+ "",
148
+ ].join("\n"));
149
+ return 0;
150
+ }
151
+ catch (error) {
152
+ const message = error instanceof Error ? error.message : String(error);
153
+ if (error instanceof InitUsageError) {
154
+ write(runtime.stderr, `${message}\n${getInitUsage(runtime.templatesRoot)}`);
155
+ return 2;
156
+ }
157
+ write(runtime.stderr, `${message}\n`);
158
+ return 1;
159
+ }
160
+ }
161
+ export async function createInitProject(options, runtime = {
162
+ stdout: process.stdout,
163
+ stderr: process.stderr,
164
+ }) {
165
+ const cwd = runtime.cwd ?? process.cwd();
166
+ const targetDir = resolveInitDirectory(options.directory, cwd);
167
+ const templatesRoot = runtime.templatesRoot ?? defaultTemplatesRoot();
168
+ const version = runtime.packageVersion ?? (await readPackageVersion());
169
+ const catalog = loadProvidersCatalog(templatesRoot);
170
+ const provider = catalog.get(options.provider);
171
+ if (!provider) {
172
+ throw new InitUsageError(`Unknown provider: ${options.provider}. Expected one of ${[...catalog.keys()].join(", ")}`);
173
+ }
174
+ const projectName = sanitizePackageName(basenameSafe(targetDir));
175
+ await assertDestinationWritable(targetDir, options.force);
176
+ const tokens = buildTokens({
177
+ projectName,
178
+ version,
179
+ provider,
180
+ withWorkflows: options.withWorkflows,
181
+ withEvals: options.withEvals,
182
+ });
183
+ const planned = await planFiles({
184
+ templatesRoot,
185
+ withWorkflows: options.withWorkflows,
186
+ withEvals: options.withEvals,
187
+ });
188
+ if (!options.force) {
189
+ for (const file of planned) {
190
+ const dest = join(targetDir, file.relativePath);
191
+ if (await exists(dest)) {
192
+ throw new InitUsageError(`Refusing to overwrite existing file: ${file.relativePath} (pass --force to overwrite)`);
193
+ }
194
+ }
195
+ }
196
+ const writtenFiles = [];
197
+ let totalBytes = 0;
198
+ for (const file of planned) {
199
+ const dest = join(targetDir, file.relativePath);
200
+ assertPathInside(targetDir, dest);
201
+ const raw = await readFile(file.sourcePath, "utf8");
202
+ const content = applyTokens(raw, tokens);
203
+ await mkdir(dirname(dest), { recursive: true });
204
+ await writeFile(dest, content, "utf8");
205
+ writtenFiles.push(file.relativePath);
206
+ totalBytes += Buffer.byteLength(content, "utf8");
207
+ }
208
+ return {
209
+ targetDir,
210
+ writtenFiles,
211
+ provider: options.provider,
212
+ withWorkflows: options.withWorkflows,
213
+ withEvals: options.withEvals,
214
+ totalBytes,
215
+ };
216
+ }
217
+ export function defaultTemplatesRoot() {
218
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "templates", "init");
219
+ }
220
+ export function isInitProvider(value, templatesRoot) {
221
+ return listInitProviders(templatesRoot).includes(value);
222
+ }
223
+ function resolveInitDirectory(input, cwd) {
224
+ const trimmed = input.trim();
225
+ if (!trimmed)
226
+ throw new InitUsageError("Missing destination directory");
227
+ if (trimmed.includes("\0"))
228
+ throw new InitUsageError("Invalid destination directory");
229
+ // Reject Windows drive-relative oddities and keep the path normal.
230
+ const normalizedInput = normalize(trimmed);
231
+ if (normalizedInput.split(sep).includes("..") && !isAbsolute(normalizedInput)) {
232
+ // Allow intentional relative parents, but still resolve and validate after join.
233
+ }
234
+ const resolved = resolve(cwd, normalizedInput);
235
+ if (resolved === sep || /^[A-Za-z]:[\\/]?$/.test(resolved)) {
236
+ throw new InitUsageError(`Refusing to scaffold into filesystem root: ${resolved}`);
237
+ }
238
+ return resolved;
239
+ }
240
+ async function assertDestinationWritable(targetDir, force) {
241
+ if (!(await exists(targetDir))) {
242
+ await mkdir(targetDir, { recursive: true });
243
+ return;
244
+ }
245
+ const entries = await readdir(targetDir);
246
+ if (entries.length > 0 && !force) {
247
+ throw new InitUsageError(`Destination is not empty: ${targetDir} (pass --force to overwrite generated files)`);
248
+ }
249
+ }
250
+ async function planFiles(options) {
251
+ const root = options.templatesRoot;
252
+ const files = [
253
+ { relativePath: "package.json", sourcePath: join(root, "package.json.tmpl") },
254
+ { relativePath: "tsconfig.json", sourcePath: join(root, "tsconfig.json.tmpl") },
255
+ { relativePath: ".gitignore", sourcePath: join(root, "gitignore.tmpl") },
256
+ { relativePath: ".env.example", sourcePath: join(root, "env.example.tmpl") },
257
+ { relativePath: "README.md", sourcePath: join(root, "README.md.tmpl") },
258
+ { relativePath: "src/agent.ts", sourcePath: join(root, "src/agent.ts.tmpl") },
259
+ { relativePath: "src/index.ts", sourcePath: join(root, "src/index.ts.tmpl") },
260
+ { relativePath: "src/__tests__/agent.test.ts", sourcePath: join(root, "src/tests/agent.test.ts.tmpl") },
261
+ ];
262
+ if (options.withWorkflows) {
263
+ files.push({
264
+ relativePath: "src/workflows-example.ts",
265
+ sourcePath: join(root, "optional/workflows-example.ts.tmpl"),
266
+ });
267
+ }
268
+ if (options.withEvals) {
269
+ files.push({
270
+ relativePath: "src/evals-example.ts",
271
+ sourcePath: join(root, "optional/evals-example.ts.tmpl"),
272
+ });
273
+ }
274
+ for (const file of files) {
275
+ try {
276
+ await access(file.sourcePath, fsConstants.R_OK);
277
+ }
278
+ catch {
279
+ throw new Error(`Missing init template: ${file.sourcePath}`);
280
+ }
281
+ }
282
+ return files;
283
+ }
284
+ function buildTokens(input) {
285
+ const dependencies = {
286
+ "@arnilo/prism": input.version,
287
+ };
288
+ if (input.provider.packageName) {
289
+ dependencies[input.provider.packageName] = input.version;
290
+ }
291
+ if (input.withWorkflows) {
292
+ dependencies["@arnilo/prism-workflows"] = input.version;
293
+ }
294
+ if (input.withEvals) {
295
+ dependencies["@arnilo/prism-evals"] = input.version;
296
+ }
297
+ const dependencyLines = Object.entries(dependencies)
298
+ .sort(([a], [b]) => a.localeCompare(b))
299
+ .map(([name, ver]) => ` ${JSON.stringify(name)}: ${JSON.stringify(ver)}`)
300
+ .join(",\n");
301
+ const envBlock = input.provider.envKey
302
+ ? `# Placeholder only — never commit real secrets.\n${input.provider.envKey}=${input.provider.envPlaceholder ?? ""}\n`
303
+ : `# No API key required for the mock provider.\n# Switch providers with: prism init <dir> --provider openai --force\n`;
304
+ const nextStepsLive = input.provider.envKey
305
+ ? `2. Copy \`.env.example\` to \`.env\` and set \`${input.provider.envKey}\`.\n3. Run \`npm start\` for a live provider call.`
306
+ : `2. Run \`npm start\` (mock provider; no network or credentials).`;
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
+ : []),
314
+ ].join("\n");
315
+ return {
316
+ __PROJECT_NAME__: input.projectName,
317
+ __PRISM_VERSION__: input.version,
318
+ __PROVIDER_ID__: input.provider.id,
319
+ __PROVIDER_PACKAGE__: input.provider.packageName ?? "",
320
+ __DEPENDENCIES__: dependencyLines,
321
+ __PROVIDER_IMPORTS__: input.provider.imports,
322
+ __PROVIDER_EXPRESSION__: input.provider.providerExpression,
323
+ __MODEL_EXPRESSION__: input.provider.modelExpression,
324
+ __ENV_EXAMPLE__: envBlock,
325
+ __NEXT_STEPS_LIVE__: nextStepsLive,
326
+ __OPTIONAL_DOCS__: optionalDocs.length > 0 ? `${optionalDocs}\n` : "",
327
+ __PROVIDER_README_NOTE__: input.provider.packageName
328
+ ? `Selected provider package: \`${input.provider.packageName}\` (model \`${input.provider.modelName}\`).`
329
+ : "Selected provider: built-in `mock` (offline demos and tests).",
330
+ };
331
+ }
332
+ function applyTokens(template, tokens) {
333
+ let out = template;
334
+ for (const [token, value] of Object.entries(tokens)) {
335
+ out = out.split(token).join(value);
336
+ }
337
+ if (/__[A-Z0-9_]+__/.test(out)) {
338
+ const leftover = out.match(/__[A-Z0-9_]+__/g) ?? [];
339
+ throw new Error(`Unresolved init template tokens: ${Array.from(new Set(leftover)).join(", ")}`);
340
+ }
341
+ return out;
342
+ }
343
+ async function readPackageVersion() {
344
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
345
+ const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
346
+ if (!pkg.version)
347
+ throw new Error(`Missing version in ${pkgPath}`);
348
+ return pkg.version;
349
+ }
350
+ function assertPathInside(root, candidate) {
351
+ const rel = relative(root, candidate);
352
+ if (rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)))
353
+ return;
354
+ throw new InitUsageError(`Refusing to write outside destination: ${candidate}`);
355
+ }
356
+ async function exists(path) {
357
+ try {
358
+ await access(path, fsConstants.F_OK);
359
+ return true;
360
+ }
361
+ catch {
362
+ return false;
363
+ }
364
+ }
365
+ function sanitizePackageName(name) {
366
+ const cleaned = name
367
+ .toLowerCase()
368
+ .replace(/[^a-z0-9._-]+/g, "-")
369
+ .replace(/^-+|-+$/g, "");
370
+ return cleaned.length > 0 ? cleaned : "prism-agent";
371
+ }
372
+ function basenameSafe(path) {
373
+ const parts = path.split(/[\\/]/).filter(Boolean);
374
+ return parts[parts.length - 1] ?? "prism-agent";
375
+ }
376
+ function displayPath(targetDir, cwd) {
377
+ const rel = relative(cwd, targetDir);
378
+ if (rel && !rel.startsWith("..") && !isAbsolute(rel))
379
+ return rel || ".";
380
+ return targetDir;
381
+ }
382
+ function shellQuote(value) {
383
+ if (/^[A-Za-z0-9_./:@-]+$/.test(value))
384
+ return value;
385
+ return `'${value.replace(/'/g, `'\\''`)}'`;
386
+ }
387
+ function write(stream, text) {
388
+ stream.write(text);
389
+ }
390
+ //# sourceMappingURL=cli-init.js.map
@@ -60,8 +60,14 @@ export interface CliRuntime {
60
60
  /** Optional global root for SYSTEM.md auto-load (host-controlled). No default;
61
61
  * the CLI does not auto-touch the user's home directory. */
62
62
  readonly globalRoot?: string;
63
+ /** Override init template root (tests). */
64
+ readonly initTemplatesRoot?: string;
65
+ /** Override version stamped by `prism init` (tests). */
66
+ readonly initPackageVersion?: string;
67
+ /** Working directory for relative `prism init` destinations (tests). */
68
+ readonly cwd?: string;
63
69
  }
64
- export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\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";
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";
65
71
  export declare function parseCliArgs(argv: readonly string[]): CliOptions;
66
72
  export declare function runCli(argv: readonly string[], runtime: CliRuntime): Promise<number>;
67
73
  export declare function runPromptMode(session: AgentSession, options: CliOptions, stdout: Writable, mode: "print" | "json"): Promise<void>;
@@ -10,7 +10,9 @@ import { registerDiscoveredInstructionInjectors } from "./node/instruction-injec
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
14
  export const usage = `Usage: prism [--mode print|json|rpc] [-p prompt] [options]
15
+ prism init <dir> [--provider <name>] [--with-workflows] [--with-evals] [--force]
14
16
 
15
17
  Options:
16
18
  -p, --prompt <text> Prompt to run in print/json mode
@@ -167,6 +169,16 @@ function parseKinds(csv, flag) {
167
169
  return kinds;
168
170
  }
169
171
  export async function runCli(argv, runtime) {
172
+ if (argv[0] === "init") {
173
+ const initRuntime = {
174
+ stdout: runtime.stdout,
175
+ stderr: runtime.stderr,
176
+ ...(runtime.initTemplatesRoot !== undefined ? { templatesRoot: runtime.initTemplatesRoot } : {}),
177
+ ...(runtime.initPackageVersion !== undefined ? { packageVersion: runtime.initPackageVersion } : {}),
178
+ ...(runtime.cwd !== undefined ? { cwd: runtime.cwd } : {}),
179
+ };
180
+ return runInitCommand(argv.slice(1), initRuntime);
181
+ }
170
182
  let options;
171
183
  try {
172
184
  options = parseCliArgs(argv);
@@ -176,7 +188,7 @@ export async function runCli(argv, runtime) {
176
188
  return 2;
177
189
  }
178
190
  if (options.help) {
179
- write(runtime.stdout, usage);
191
+ write(runtime.stdout, `${usage}\n${initUsage}`);
180
192
  return 0;
181
193
  }
182
194
  try {
@@ -59,7 +59,15 @@ function blockText(block) {
59
59
  return `[tool_call_delta ${block.name ?? block.index}]`;
60
60
  if (block.type === "tool_result")
61
61
  return `[tool_result ${block.name}]`;
62
- return "[image]";
62
+ if (block.type === "image")
63
+ return "[image]";
64
+ if (block.type === "audio")
65
+ return "[audio]";
66
+ if (block.type === "file")
67
+ return "[file]";
68
+ if (block.type === "document")
69
+ return "[document]";
70
+ return "[content]";
63
71
  }
64
72
  function truncate(text, max) {
65
73
  if (max === 0 || text.length <= max)
@@ -0,0 +1,121 @@
1
+ import type { ContentBlock, ImageContent, Message, ModelConfig, ResourceLoadContext, ResourceLoader } from "./contracts.js";
2
+ /** Known model input capability tags for `ModelCapabilities.input`. */
3
+ export declare const MODEL_INPUT_CAPABILITIES: readonly ["text", "image", "audio", "file", "document"];
4
+ export type ModelInputCapability = (typeof MODEL_INPUT_CAPABILITIES)[number];
5
+ /** Default per-item media byte ceiling (10 MB; aligns with coding-agent image bounds). */
6
+ export declare const DEFAULT_MAX_MEDIA_ITEM_BYTES = 10000000;
7
+ /** Default total media byte budget per request assembly. */
8
+ export declare const DEFAULT_MAX_MEDIA_REQUEST_BYTES: number;
9
+ /** Default decoded audio duration ceiling. */
10
+ export declare const DEFAULT_MAX_AUDIO_DURATION_MS: number;
11
+ /** Default URL fetch timeout for media resolution. */
12
+ export declare const DEFAULT_MEDIA_FETCH_TIMEOUT_MS = 30000;
13
+ /** Default maximum media-bearing blocks per request assembly. */
14
+ export declare const DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST = 32;
15
+ export interface AudioContent {
16
+ readonly type: "audio";
17
+ readonly mediaType: string;
18
+ readonly name?: string;
19
+ /** Base64-encoded audio bytes. */
20
+ readonly data?: string;
21
+ readonly url?: string;
22
+ readonly resourceUri?: string;
23
+ readonly durationMs?: number;
24
+ readonly transcript?: string;
25
+ readonly metadata?: Readonly<Record<string, unknown>>;
26
+ }
27
+ export interface FileContent {
28
+ readonly type: "file";
29
+ readonly mediaType: string;
30
+ readonly name?: string;
31
+ /** Base64-encoded file bytes. */
32
+ readonly data?: string;
33
+ readonly url?: string;
34
+ readonly resourceUri?: string;
35
+ readonly metadata?: Readonly<Record<string, unknown>>;
36
+ }
37
+ export interface DocumentContent {
38
+ readonly type: "document";
39
+ readonly mediaType: string;
40
+ readonly name?: string;
41
+ /** Base64-encoded document bytes. */
42
+ readonly data?: string;
43
+ readonly url?: string;
44
+ readonly resourceUri?: string;
45
+ readonly transcript?: string;
46
+ readonly metadata?: Readonly<Record<string, unknown>>;
47
+ }
48
+ export type MediaContentBlock = AudioContent | FileContent | DocumentContent | ImageContent;
49
+ export interface MediaContentBounds {
50
+ readonly maxItemBytes?: number;
51
+ readonly maxRequestBytes?: number;
52
+ readonly maxItems?: number;
53
+ readonly maxAudioDurationMs?: number;
54
+ readonly fetchTimeoutMs?: number;
55
+ }
56
+ export interface SsrfPolicy {
57
+ /** When true (default), deny private/link-local/metadata hostnames and IPs. */
58
+ readonly denyPrivateHosts?: boolean;
59
+ /** Optional hostname allow-list. When set, only listed hosts are permitted. */
60
+ readonly allowedHostnames?: readonly string[];
61
+ }
62
+ export interface MediaMimePolicy {
63
+ /** Reject when magic bytes disagree with declared media type. Default `true`. */
64
+ readonly strictMagicValidation?: boolean;
65
+ }
66
+ export interface MediaHostAddress {
67
+ readonly address: string;
68
+ readonly family: 4 | 6;
69
+ }
70
+ export type MediaHostnameResolver = (hostname: string, signal: AbortSignal) => Promise<readonly MediaHostAddress[]>;
71
+ export interface MediaUrlRequest {
72
+ readonly url: URL;
73
+ readonly address: MediaHostAddress;
74
+ readonly maxBytes: number;
75
+ readonly signal: AbortSignal;
76
+ }
77
+ export type MediaUrlRequester = (request: MediaUrlRequest) => Promise<Uint8Array>;
78
+ export interface ResolveMediaContentOptions {
79
+ readonly bounds?: MediaContentBounds;
80
+ readonly ssrf?: SsrfPolicy;
81
+ readonly mime?: MediaMimePolicy;
82
+ readonly loader?: ResourceLoader;
83
+ readonly loadContext?: ResourceLoadContext;
84
+ /** Trusted custom transport; the host owns DNS resolution and rebinding protection. */
85
+ readonly fetch?: typeof globalThis.fetch;
86
+ /** Test/custom resolver used only with the default or `requestUrl` pinned transport. */
87
+ readonly resolveHostname?: MediaHostnameResolver;
88
+ /** Trusted custom transport that must connect to `request.address`, not resolve `request.url` again. */
89
+ readonly requestUrl?: MediaUrlRequester;
90
+ readonly signal?: AbortSignal;
91
+ }
92
+ export interface ResolvedMediaContent {
93
+ readonly mediaType: string;
94
+ readonly bytes: Uint8Array;
95
+ readonly name?: string;
96
+ readonly durationMs?: number;
97
+ readonly transcript?: string;
98
+ readonly metadata?: Readonly<Record<string, unknown>>;
99
+ }
100
+ export declare class UnsupportedModalityError extends Error {
101
+ readonly modality: ModelInputCapability;
102
+ readonly provider: string;
103
+ readonly model: string;
104
+ constructor(modality: ModelInputCapability, model: ModelConfig);
105
+ }
106
+ export declare class MediaContentError extends Error {
107
+ readonly code: "ambiguous_source" | "missing_source" | "item_too_large" | "request_too_large" | "too_many_items" | "audio_too_long" | "invalid_base64" | "ssrf_denied" | "fetch_failed" | "fetch_timeout" | "resource_required" | "mime_mismatch" | "unsupported_url_scheme";
108
+ constructor(code: MediaContentError["code"], message: string);
109
+ }
110
+ export declare function contentBlockInputModality(block: ContentBlock): ModelInputCapability | undefined;
111
+ export declare function collectMessageContentBlocks(messages: readonly Message[]): ContentBlock[];
112
+ export declare function assertModelSupportsContentBlocks(model: ModelConfig, blocks: readonly ContentBlock[]): void;
113
+ export declare function assertMessagesSupportModelCapabilities(model: ModelConfig, messages: readonly Message[]): void;
114
+ export declare function assertMediaBlocksWithinBounds(blocks: readonly MediaContentBlock[], bounds?: MediaContentBounds): void;
115
+ export declare function assertSsrfAllowedUrl(url: string, policy?: SsrfPolicy): void;
116
+ export declare function sniffMediaMimeType(bytes: Uint8Array): string | undefined;
117
+ export declare function assertDeclaredMediaTypeMatches(declaredMediaType: string, bytes: Uint8Array, policy?: MediaMimePolicy): void;
118
+ export declare function resolveMediaContentBlock(block: MediaContentBlock, options?: ResolveMediaContentOptions): Promise<ResolvedMediaContent>;
119
+ /** Resolve and validate one complete media request before provider serialization/upload. */
120
+ export declare function resolveMediaContentBlocks(blocks: readonly MediaContentBlock[], options?: ResolveMediaContentOptions): Promise<readonly ResolvedMediaContent[]>;
121
+ export declare function loadBoundedBinaryResource(loader: ResourceLoader, uri: string, context: ResourceLoadContext | undefined, maxBytes: number, signal?: AbortSignal): Promise<Uint8Array>;