@arnilo/prism 0.4.0 → 0.5.0

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 (171) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +23 -20
  3. package/dist/agent-run-state.d.ts +1 -2
  4. package/dist/agent-run-state.js +0 -3
  5. package/dist/agent-session/session/assemble.d.ts +6 -0
  6. package/dist/agent-session/session/assemble.js +391 -0
  7. package/dist/agent-session/session/persist.d.ts +28 -0
  8. package/dist/agent-session/session/persist.js +166 -0
  9. package/dist/agent-session/session/provider-round.d.ts +6 -0
  10. package/dist/agent-session/session/provider-round.js +231 -0
  11. package/dist/agent-session/session/tool-round.d.ts +31 -0
  12. package/dist/agent-session/session/tool-round.js +473 -0
  13. package/dist/agent-session/session/types.d.ts +115 -0
  14. package/dist/agent-session/session/types.js +5 -0
  15. package/dist/agent-session/session.d.ts +49 -43
  16. package/dist/agent-session/session.js +11 -1177
  17. package/dist/capture.d.ts +63 -0
  18. package/dist/capture.js +67 -0
  19. package/dist/cli-init.d.ts +18 -2
  20. package/dist/cli-init.js +2 -7
  21. package/dist/cli-runner.d.ts +2 -2
  22. package/dist/cli-runner.js +45 -9
  23. package/dist/content.d.ts +3 -3
  24. package/dist/content.js +3 -1
  25. package/dist/contracts-core/agent.d.ts +2 -0
  26. package/dist/contracts-core/batch.d.ts +97 -0
  27. package/dist/contracts-core/batch.js +65 -0
  28. package/dist/contracts-core/content.d.ts +72 -1
  29. package/dist/contracts-core/embeddings.d.ts +30 -0
  30. package/dist/contracts-core/embeddings.js +17 -0
  31. package/dist/contracts-core/images.d.ts +60 -0
  32. package/dist/contracts-core/images.js +17 -0
  33. package/dist/contracts-core/moderation.d.ts +46 -0
  34. package/dist/contracts-core/moderation.js +34 -0
  35. package/dist/contracts-core/speech.d.ts +39 -0
  36. package/dist/contracts-core/speech.js +17 -0
  37. package/dist/contracts-core/transcription.d.ts +48 -0
  38. package/dist/contracts-core/transcription.js +17 -0
  39. package/dist/contracts-core/video.d.ts +61 -0
  40. package/dist/contracts-core/video.js +17 -0
  41. package/dist/contracts-core.d.ts +7 -0
  42. package/dist/contracts-core.js +7 -0
  43. package/dist/index.d.ts +5 -3
  44. package/dist/index.js +4 -3
  45. package/dist/node/agent-definitions.d.ts +1 -8
  46. package/dist/node/agent-definitions.js +0 -34
  47. package/dist/node/settings.d.ts +0 -1
  48. package/dist/node/settings.js +0 -5
  49. package/dist/pinned-fetch.js +29 -3
  50. package/dist/provider-events.js +3 -4
  51. package/dist/providers/media.d.ts +1 -2
  52. package/dist/providers/media.js +1 -4
  53. package/dist/rpc.d.ts +1 -1
  54. package/dist/rpc.js +4 -4
  55. package/dist/testing/provider-conformance.d.ts +114 -5
  56. package/dist/testing/provider-conformance.js +342 -0
  57. package/dist/testing/tool-effect-store-conformance.d.ts +0 -1
  58. package/dist/testing/tool-effect-store-conformance.js +0 -3
  59. package/dist/thinking.d.ts +48 -9
  60. package/dist/thinking.js +134 -8
  61. package/docs/0.1.0-readiness.md +3 -3
  62. package/docs/a2a.md +2 -2
  63. package/docs/acp.md +3 -3
  64. package/docs/ag-ui-adoption.md +1 -1
  65. package/docs/ag-ui.md +1 -2
  66. package/docs/agent-definitions.md +1 -1
  67. package/docs/agent-events.md +5 -5
  68. package/docs/agent-identity.md +13 -2
  69. package/docs/audit-export.md +3 -3
  70. package/docs/batch-jobs.md +120 -0
  71. package/docs/cli-rpc.md +20 -9
  72. package/docs/coding-agent-tools.md +19 -19
  73. package/docs/coding-review-and-diagnostics.md +2 -2
  74. package/docs/coding-security.md +4 -4
  75. package/docs/coding-workspaces.md +2 -2
  76. package/docs/computer-use-linux.md +13 -2
  77. package/docs/context-and-skills.md +1 -1
  78. package/docs/conversations.md +4 -4
  79. package/docs/credential-storage.md +11 -7
  80. package/docs/credentials-and-redaction.md +1 -1
  81. package/docs/data-classification.md +1 -1
  82. package/docs/database-persistence.md +4 -4
  83. package/docs/dev-inspector.md +6 -6
  84. package/docs/device-adapters.md +2 -2
  85. package/docs/diagrams.md +1 -1
  86. package/docs/document-reader.md +6 -6
  87. package/docs/documents.md +5 -4
  88. package/docs/embeddings.md +112 -0
  89. package/docs/enterprise-postgres-state.md +7 -7
  90. package/docs/evaluations.md +8 -8
  91. package/docs/extensions.md +3 -3
  92. package/docs/forge-integration.md +3 -3
  93. package/docs/graft.md +2 -2
  94. package/docs/guardrails.md +1 -1
  95. package/docs/host-security.md +15 -15
  96. package/docs/image-generation.md +129 -0
  97. package/docs/impeccable.md +5 -3
  98. package/docs/index.md +60 -33
  99. package/docs/indexed-code-search.md +2 -2
  100. package/docs/language-intelligence.md +4 -4
  101. package/docs/live-testing.md +126 -0
  102. package/docs/mcp-tools.md +43 -12
  103. package/docs/middleware-hooks.md +1 -1
  104. package/docs/migrate-to-0.4.md +3 -3
  105. package/docs/migrate-to-0.5.md +122 -0
  106. package/docs/migration.md +29 -1
  107. package/docs/model-registry.md +38 -0
  108. package/docs/model-routing.md +5 -5
  109. package/docs/moderation.md +117 -0
  110. package/docs/multi-agent-patterns.md +4 -4
  111. package/docs/multimodal-content.md +26 -2
  112. package/docs/obscura.md +2 -2
  113. package/docs/observability.md +32 -7
  114. package/docs/openapi-tools.md +13 -3
  115. package/docs/operations.md +11 -0
  116. package/docs/performance.md +7 -7
  117. package/docs/persistence-credentials-multimodality-primitives.md +6 -6
  118. package/docs/policy-and-audit.md +17 -7
  119. package/docs/ponytail.md +1 -1
  120. package/docs/postgres-persistence.md +5 -5
  121. package/docs/process-sessions.md +2 -2
  122. package/docs/prompt-registry.md +7 -7
  123. package/docs/provider-caching.md +4 -0
  124. package/docs/provider-conformance.md +23 -1
  125. package/docs/provider-packages.md +39 -3
  126. package/docs/provider-primitives.md +1 -1
  127. package/docs/provider-request-policies.md +1 -1
  128. package/docs/providers/ai-sdk.md +15 -3
  129. package/docs/providers/alibaba.md +5 -1
  130. package/docs/providers/anthropic.md +4 -0
  131. package/docs/providers/azure.md +17 -1
  132. package/docs/providers/bedrock.md +15 -0
  133. package/docs/providers/clinepass.md +4 -0
  134. package/docs/providers/commandcode.md +253 -0
  135. package/docs/providers/deepseek.md +4 -0
  136. package/docs/providers/google.md +4 -0
  137. package/docs/providers/hyper.md +284 -0
  138. package/docs/providers/kimi.md +4 -0
  139. package/docs/providers/neuralwatt.md +4 -0
  140. package/docs/providers/ollama.md +15 -0
  141. package/docs/providers/openai-compatible.md +4 -0
  142. package/docs/providers/openai.md +4 -0
  143. package/docs/providers/opencode-go.md +4 -0
  144. package/docs/providers/openrouter.md +5 -1
  145. package/docs/providers/vertex.md +16 -0
  146. package/docs/providers/xai.md +4 -0
  147. package/docs/providers/zai.md +4 -0
  148. package/docs/rag.md +26 -4
  149. package/docs/release-and-install.md +103 -46
  150. package/docs/resource-loading.md +1 -1
  151. package/docs/runs-and-usage.md +14 -2
  152. package/docs/server.md +5 -5
  153. package/docs/settings-auth-trust-security.md +7 -5
  154. package/docs/sheets.md +2 -2
  155. package/docs/speech.md +126 -0
  156. package/docs/sqlite-persistence.md +4 -4
  157. package/docs/supervisors.md +3 -3
  158. package/docs/thinking-and-reasoning.md +93 -60
  159. package/docs/tool-conformance.md +1 -1
  160. package/docs/tool-execution-primitives.md +8 -8
  161. package/docs/tools.md +4 -4
  162. package/docs/web-tools.md +1 -1
  163. package/docs/wiki.md +1 -1
  164. package/docs/work-artifacts-and-review.md +17 -6
  165. package/docs/work-connectors.md +4 -4
  166. package/docs/work-tools.md +5 -5
  167. package/docs/workflow-orchestration-primitives.md +11 -11
  168. package/docs/workflows.md +5 -5
  169. package/package.json +11 -8
  170. package/templates/init/providers.json +24 -8
  171. package/docs/antigravity-agent.md +0 -207
@@ -0,0 +1,63 @@
1
+ /** Provider request/response capture middleware (plan 062, review §7 P1):
2
+ * opt-in observation of already-normalized provider shapes — never raw HTTP.
3
+ *
4
+ * Request side rides the existing `provider_request` middleware hook; response
5
+ * side rides the existing subscriber-event seam (`provider_turn_finished`,
6
+ * which core already redacts). No new seam. Captured entries land in a capped
7
+ * FIFO ring buffer (`maxEvents`, default 100).
8
+ *
9
+ * Privacy policy `redact` controls content retention:
10
+ * - `"all"`: structure only — model id, counts, tool names, usage, latency.
11
+ * - `"secrets"` (default): also drops message content (the privacy default:
12
+ * captured buffers carry no conversation text unless the host opts in).
13
+ * - `"none"`: retains message content for replay debugging.
14
+ * Secret redaction is unconditional in every mode (replay-safe by
15
+ * construction): retained material passes through the same `redactSecrets`
16
+ * helper the logging seams use, so credentials never survive into a buffer
17
+ * that a host might persist or replay. Provider request/response options and
18
+ * headers are never captured at all — headers are where credentials ride.
19
+ */
20
+ import type { AgentEvent, ProviderRequest } from "./contracts.js";
21
+ import type { Middleware } from "./middleware.js";
22
+ import { type SecretRedactor } from "./redaction.js";
23
+ export type CaptureRedaction = "secrets" | "all" | "none";
24
+ export interface ProviderCapturePolicy {
25
+ /** Content-retention level; `"secrets"` (default) drops message content. */
26
+ readonly redact?: CaptureRedaction;
27
+ /** Ring-buffer capacity; oldest entries evict first. Default 100. */
28
+ readonly maxEvents?: number;
29
+ }
30
+ export interface ProviderCaptureOptions {
31
+ readonly policy?: ProviderCapturePolicy;
32
+ /** Secrets redacted from every retained field, matching the logging seams. */
33
+ readonly secrets?: readonly (string | undefined)[];
34
+ readonly redactor?: SecretRedactor;
35
+ readonly now?: () => number;
36
+ }
37
+ export interface ProviderCaptureEntry {
38
+ readonly kind: "request" | "response";
39
+ readonly at: string;
40
+ readonly redaction: CaptureRedaction;
41
+ readonly provider?: string;
42
+ readonly model?: string;
43
+ readonly messageCount?: number;
44
+ readonly toolNames?: readonly string[];
45
+ /** Message content — present only when the policy retains it (`"none"`), secrets redacted. */
46
+ readonly content?: unknown;
47
+ /** Response entries: normalized usage numbers (never sensitive). */
48
+ readonly usage?: unknown;
49
+ readonly latencyMs?: number;
50
+ readonly error?: unknown;
51
+ }
52
+ export interface ProviderCapture {
53
+ /** Register on the existing `provider_request` middleware hook. Passes the
54
+ * request through untouched and records one entry per round. */
55
+ middleware(): Middleware<ProviderRequest>;
56
+ /** Feed `provider_turn_finished` events from the session's existing
57
+ * subscriber loop (`session.subscribe()`) to record response entries. */
58
+ observeEvent(event: AgentEvent): void;
59
+ /** Ring-buffer snapshot, oldest first. */
60
+ events(): readonly ProviderCaptureEntry[];
61
+ clear(): void;
62
+ }
63
+ export declare function createProviderCapture(options?: ProviderCaptureOptions): ProviderCapture;
@@ -0,0 +1,67 @@
1
+ import { resolveRedactor } from "./redaction.js";
2
+ const DEFAULT_MAX_EVENTS = 100;
3
+ const HARD_MAX_EVENTS_CAP = 10_000;
4
+ function resolveMaxEvents(maxEvents) {
5
+ const value = maxEvents ?? DEFAULT_MAX_EVENTS;
6
+ if (!Number.isSafeInteger(value) || value < 1)
7
+ throw new Error("maxEvents must be a positive integer");
8
+ return Math.min(value, HARD_MAX_EVENTS_CAP);
9
+ }
10
+ export function createProviderCapture(options = {}) {
11
+ const redaction = options.policy?.redact ?? "secrets";
12
+ const maxEvents = resolveMaxEvents(options.policy?.maxEvents);
13
+ const now = options.now ?? Date.now;
14
+ const redactor = resolveRedactor(options.redactor, options.secrets);
15
+ const buffer = [];
16
+ const record = (entry) => {
17
+ buffer.push(entry);
18
+ if (buffer.length > maxEvents)
19
+ buffer.splice(0, buffer.length - maxEvents);
20
+ };
21
+ return {
22
+ middleware() {
23
+ return (request, next) => {
24
+ const entry = {
25
+ kind: "request",
26
+ at: new Date(now()).toISOString(),
27
+ redaction,
28
+ provider: request.model.provider,
29
+ model: request.model.model,
30
+ messageCount: request.messages.length,
31
+ toolNames: request.tools?.map((tool) => tool.name),
32
+ ...(redaction === "none" && request.messages.length > 0
33
+ ? { content: redactor ? redactor.redact(request.messages) : request.messages }
34
+ : {}),
35
+ };
36
+ record(entry);
37
+ return next(request);
38
+ };
39
+ },
40
+ observeEvent(event) {
41
+ if (event.type !== "provider_turn_finished")
42
+ return;
43
+ const metadata = (event.metadata ?? {});
44
+ record({
45
+ kind: "response",
46
+ at: new Date(now()).toISOString(),
47
+ redaction,
48
+ provider: typeof metadata.providerId === "string" ? metadata.providerId : undefined,
49
+ model: typeof metadata.model?.model === "string"
50
+ ? metadata.model.model
51
+ : undefined,
52
+ usage: event.usage,
53
+ latencyMs: typeof metadata.latencyMs === "number" ? metadata.latencyMs : undefined,
54
+ // Core already redacts emitted errors; redact again so replay safety holds even
55
+ // when a host feeds events captured before redaction.
56
+ error: event.error && redactor ? redactor.redact(event.error) : event.error,
57
+ });
58
+ },
59
+ events() {
60
+ return [...buffer];
61
+ },
62
+ clear() {
63
+ buffer.length = 0;
64
+ },
65
+ };
66
+ }
67
+ //# sourceMappingURL=capture.js.map
@@ -39,11 +39,27 @@ export interface TemplateInfo {
39
39
  readonly version?: string;
40
40
  readonly path: string;
41
41
  }
42
+ interface ProviderSpec {
43
+ readonly id: string;
44
+ readonly packageName?: string;
45
+ /** Module specifier the CLI's real-provider mode dynamically imports (e.g. `@arnilo/prism-providers/openai`). */
46
+ readonly factoryModule?: string;
47
+ /** Named export on `factoryModule` that builds the provider. */
48
+ readonly factoryExport?: string;
49
+ readonly envKey?: string;
50
+ readonly envPlaceholder?: string;
51
+ readonly modelProvider: string;
52
+ readonly modelName: string;
53
+ readonly imports: string;
54
+ readonly providerExpression: string;
55
+ readonly modelExpression: string;
56
+ }
57
+ /** Provider catalog shared by `prism init` templates and the CLI's real-provider mode. */
58
+ export declare function loadProvidersCatalog(templatesRoot?: string): ReadonlyMap<string, ProviderSpec>;
42
59
  /** Provider ids supported by `prism init --provider`. Loaded from templates data. */
43
60
  export declare function listInitProviders(templatesRoot?: string): readonly string[];
44
61
  /** Template list discovered from the templates gallery. */
45
62
  export declare function listInitTemplates(galleryRoot?: string): readonly TemplateInfo[];
46
- export declare function isInitTemplate(value: string, galleryRoot?: string): boolean;
47
63
  export declare function getInitUsage(templatesRoot?: string, galleryRoot?: string): string;
48
64
  export declare const initUsage: string;
49
65
  export declare function parseInitArgs(argv: readonly string[], templatesRoot?: string, galleryRoot?: string): InitOptions;
@@ -51,4 +67,4 @@ export declare function runInitCommand(argv: readonly string[], runtime: InitRun
51
67
  export declare function createInitProject(options: InitOptions, runtime?: InitRuntime): Promise<InitResult>;
52
68
  export declare function defaultTemplatesRoot(): string;
53
69
  export declare function defaultGalleryRoot(): string;
54
- export declare function isInitProvider(value: string, templatesRoot?: string): value is InitProvider;
70
+ export {};
package/dist/cli-init.js CHANGED
@@ -9,7 +9,8 @@ let cachedProviderIds;
9
9
  function providersCatalogPath(templatesRoot) {
10
10
  return join(templatesRoot, "providers.json");
11
11
  }
12
- function loadProvidersCatalog(templatesRoot = defaultTemplatesRoot()) {
12
+ /** Provider catalog shared by `prism init` templates and the CLI's real-provider mode. */
13
+ export function loadProvidersCatalog(templatesRoot = defaultTemplatesRoot()) {
13
14
  if (cachedCatalog && templatesRoot === defaultTemplatesRoot())
14
15
  return cachedCatalog;
15
16
  const raw = JSON.parse(readFileSync(providersCatalogPath(templatesRoot), "utf8"));
@@ -85,9 +86,6 @@ export function listInitTemplates(galleryRoot = defaultGalleryRoot()) {
85
86
  ]);
86
87
  }
87
88
  }
88
- export function isInitTemplate(value, galleryRoot) {
89
- return listInitTemplates(galleryRoot).some((t) => t.name === value);
90
- }
91
89
  export function getInitUsage(templatesRoot, galleryRoot) {
92
90
  const providers = listInitProviders(templatesRoot).join("|");
93
91
  const templates = listInitTemplates(galleryRoot)
@@ -388,9 +386,6 @@ function buildTokensForTemplate(input) {
388
386
  __PROVIDER_README_NOTE__: "",
389
387
  };
390
388
  }
391
- export function isInitProvider(value, templatesRoot) {
392
- return listInitProviders(templatesRoot).includes(value);
393
- }
394
389
  function resolveInitDirectory(input, cwd) {
395
390
  const trimmed = input.trim();
396
391
  if (!trimmed)
@@ -49,7 +49,7 @@ export interface CliRuntime {
49
49
  readonly stdin: Readable;
50
50
  readonly stdout: Writable;
51
51
  readonly stderr: Writable;
52
- readonly createSession?: (options: CliOptions) => AgentSession;
52
+ readonly createSession?: (options: CliOptions) => AgentSession | Promise<AgentSession>;
53
53
  readonly commands?: RpcSessionFactory["commands"];
54
54
  /** Workspace root for `--discover`. Defaults to `process.cwd()`. */
55
55
  readonly workspaceRoot?: string;
@@ -71,7 +71,7 @@ export interface CliRuntime {
71
71
  /** Test injection for `prism dev`: overrides `@arnilo/prism-dev` resolution. */
72
72
  readonly loadDevCli?: () => Promise<unknown>;
73
73
  }
74
- export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\n prism init <dir> [--template <name>] [--list-templates] [--provider <name>] [--with-workflows] [--with-evals] [--force]\n prism providers add <name> [--base-url <url>] [--env-key <name>] [--model <id>] [--force]\n prism dev [--port <n>] [--host <addr>] (loopback inspector; delegates into @arnilo/prism-dev)\n\n\nOptions:\n -p, --prompt <text> Prompt to run in print/json mode\n --provider <name> Explicit provider id (mock is built in for smoke tests)\n --model <name> Explicit model name\n --session <id> Session id\n --system <text> System instructions\n --context <text> Context text\n --compact <entries> Auto-compaction threshold\n --max-tool-rounds <n> Maximum tool rounds\n --discover Enable workspace contribution discovery (opt-in)\n --discover-kinds <csv> Kinds to discover (default: skill; skill,tool,context,instructions)\n --no-discovery Disable discovery even if --discover is set\n --agents-config <path> App config root holding agents/<name>/AGENT.md bundles (opt-in)\n --no-agents-md Skip auto-loading <workspaceRoot>/AGENTS.md\n --no-system-md Skip auto-loading the global SYSTEM.md layer\n --agents-md-file <path> Read AGENTS.md from <path> instead (trust-gated, source: app)\n --system-md-file <path> Read SYSTEM.md from <path> instead (source: user)\n -h, --help Show this help\n";
74
+ export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\n prism init <dir> [--template <name>] [--list-templates] [--provider <name>] [--with-workflows] [--with-evals] [--force]\n prism providers add <name> [--base-url <url>] [--env-key <name>] [--model <id>] [--force]\n prism dev [--port <n>] [--host <addr>] (loopback inspector; delegates into @arnilo/prism-dev)\n\n\nOptions:\n -p, --prompt <text> Prompt to run in print/json mode\n --provider <name> Provider id from the init provider catalog ('mock' is built in;\n real providers need their @arnilo/prism-providers package +\n credential env var)\n --model <name> Explicit model name\n --session <id> Session id\n --system <text> System instructions\n --context <text> Context text\n --compact <entries> Auto-compaction threshold\n --max-tool-rounds <n> Maximum tool rounds\n --discover Enable workspace contribution discovery (opt-in)\n --discover-kinds <csv> Kinds to discover (default: skill; skill,tool,context,instructions)\n --no-discovery Disable discovery even if --discover is set\n --agents-config <path> App config root holding agents/<name>/AGENT.md bundles (opt-in)\n --no-agents-md Skip auto-loading <workspaceRoot>/AGENTS.md\n --no-system-md Skip auto-loading the global SYSTEM.md layer\n --agents-md-file <path> Read AGENTS.md from <path> instead (trust-gated, source: app)\n --system-md-file <path> Read SYSTEM.md from <path> instead (source: user)\n -h, --help Show this help\n";
75
75
  export declare function parseCliArgs(argv: readonly string[]): CliOptions;
76
76
  export declare function runCli(argv: readonly string[], runtime: CliRuntime): Promise<number>;
77
77
  export declare function runPromptMode(session: AgentSession, options: CliOptions, stdout: Writable, mode: "print" | "json"): Promise<void>;
@@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
2
2
  import { basename, dirname } from "node:path";
3
3
  import process from "node:process";
4
4
  import { runPrismDevSubcommand } from "./cli-dev.js";
5
- import { initUsage, runInitCommand } from "./cli-init.js";
5
+ import { initUsage, loadProvidersCatalog, runInitCommand } from "./cli-init.js";
6
6
  import { providerAddUsage, runProviderAddCommand } from "./cli-provider-add.js";
7
7
  import { createContributionRegistries, registerDiscoveredContributions } from "./contributions.js";
8
8
  import { createAgent, createContributionRegistry, createMockProvider, providerDone, providerTextDelta, resolveInstructionInjectors, } from "./index.js";
@@ -21,7 +21,9 @@ export const usage = `Usage: prism [--mode print|json|rpc] [-p prompt] [options]
21
21
 
22
22
  Options:
23
23
  -p, --prompt <text> Prompt to run in print/json mode
24
- --provider <name> Explicit provider id (mock is built in for smoke tests)
24
+ --provider <name> Provider id from the init provider catalog ('mock' is built in;
25
+ real providers need their @arnilo/prism-providers package +
26
+ credential env var)
25
27
  --model <name> Explicit model name
26
28
  --session <id> Session id
27
29
  --system <text> System instructions
@@ -315,7 +317,7 @@ export async function runCli(argv, runtime) {
315
317
  });
316
318
  options = { ...options, systemPromptLayers: layers };
317
319
  }
318
- const session = (runtime.createSession ?? defaultCreateSession)(options);
320
+ const session = await (runtime.createSession ?? defaultCreateSession)(options);
319
321
  await runPromptMode(session, options, runtime.stdout, mode);
320
322
  return 0;
321
323
  }
@@ -346,13 +348,47 @@ export async function runPromptMode(session, options, stdout, mode) {
346
348
  }
347
349
  export class CliUsageError extends Error {
348
350
  }
349
- function defaultCreateSession(options) {
350
- if (options.provider !== "mock")
351
- throw new CliUsageError("No provider configured. Pass --provider mock for a smoke test or embed Prism with an explicit provider.");
352
- const model = { provider: "mock", model: options.model ?? "mock" };
351
+ /** Build the agent session for the CLI. Real providers resolve through the init
352
+ * provider catalog (`templates/init/providers.json`): the catalog names the
353
+ * factory module/export and credential env var, the CLI imports it from the
354
+ * consumer's node_modules — same catalog `prism init` scaffolds from, so the
355
+ * CLI and the scaffold never disagree about how a provider is built. */
356
+ async function defaultCreateSession(options) {
357
+ const providerId = options.provider;
358
+ if (!providerId) {
359
+ throw new CliUsageError("No provider configured. Pass --provider <id> (see the init provider catalog, or 'mock') or embed Prism with an explicit provider.");
360
+ }
361
+ if (providerId === "mock")
362
+ return mockSession(options);
363
+ const spec = loadProvidersCatalog().get(providerId);
364
+ if (!spec?.factoryModule || !spec.factoryExport) {
365
+ throw new CliUsageError(`Unknown provider "${providerId}". Supported: ${["mock", ...loadProvidersCatalog().keys()].join(", ")} — or embed Prism with an explicit provider.`);
366
+ }
367
+ const envKey = spec.envKey ?? "";
368
+ if (!process.env[envKey])
369
+ throw new CliUsageError(`Provider "${providerId}" requires ${envKey} in the environment.`);
370
+ let factory;
371
+ try {
372
+ factory = (await import(spec.factoryModule))[spec.factoryExport];
373
+ }
374
+ catch {
375
+ throw new CliUsageError(`Provider "${providerId}" needs the ${spec.packageName} package installed (import ${spec.factoryModule} failed).`);
376
+ }
377
+ if (typeof factory !== "function") {
378
+ throw new CliUsageError(`Provider "${providerId}": ${spec.factoryModule} does not export ${spec.factoryExport}.`);
379
+ }
380
+ const providerInstance = factory({ apiKey: () => process.env[envKey] });
381
+ const modelConfig = { provider: spec.modelProvider, model: options.model ?? spec.modelName };
382
+ return agentSession({ ...options, providerInstance, modelConfig });
383
+ }
384
+ function mockSession(options) {
385
+ const modelConfig = { provider: "mock", model: options.model ?? "mock" };
386
+ return agentSession({ ...options, providerInstance: createMockProvider([providerTextDelta("Hello"), providerDone()]), modelConfig });
387
+ }
388
+ function agentSession(options) {
353
389
  return createAgent({
354
- model,
355
- provider: createMockProvider([providerTextDelta("Hello"), providerDone()]),
390
+ model: options.modelConfig,
391
+ provider: options.providerInstance,
356
392
  instructions: options.system,
357
393
  // ponytail: Phase 31 — file layers compose with `instructions` (base) via the existing
358
394
  // composeSystemPrompt pipeline; rank order (user<package<app<run) is enforced inside.
package/dist/content.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { ContentBlock, ImageContent, Message, ModelConfig, ResourceLoadContext, ResourceLoader } from "./contracts.js";
1
+ import type { ContentBlock, ImageContent, Message, ModelConfig, ResourceLoadContext, ResourceLoader, VideoContent } from "./contracts.js";
2
2
  /** Known model input capability tags for `ModelCapabilities.input`. */
3
- export declare const MODEL_INPUT_CAPABILITIES: readonly ["text", "image", "audio", "file", "document"];
3
+ export declare const MODEL_INPUT_CAPABILITIES: readonly ["text", "image", "audio", "file", "document", "video"];
4
4
  export type ModelInputCapability = (typeof MODEL_INPUT_CAPABILITIES)[number];
5
5
  /** Default per-item media byte ceiling (10 MB; aligns with coding-agent image bounds). */
6
6
  export declare const DEFAULT_MAX_MEDIA_ITEM_BYTES = 10000000;
@@ -45,7 +45,7 @@ export interface DocumentContent {
45
45
  readonly transcript?: string;
46
46
  readonly metadata?: Readonly<Record<string, unknown>>;
47
47
  }
48
- export type MediaContentBlock = AudioContent | FileContent | DocumentContent | ImageContent;
48
+ export type MediaContentBlock = AudioContent | FileContent | DocumentContent | ImageContent | VideoContent;
49
49
  export interface MediaContentBounds {
50
50
  readonly maxItemBytes?: number;
51
51
  readonly maxRequestBytes?: number;
package/dist/content.js CHANGED
@@ -3,7 +3,7 @@ import { isIP } from "node:net";
3
3
  import { pinnedFetch } from "./pinned-fetch.js";
4
4
  import { assertPermission } from "./security.js";
5
5
  /** Known model input capability tags for `ModelCapabilities.input`. */
6
- export const MODEL_INPUT_CAPABILITIES = ["text", "image", "audio", "file", "document"];
6
+ export const MODEL_INPUT_CAPABILITIES = ["text", "image", "audio", "file", "document", "video"];
7
7
  /** Default per-item media byte ceiling (10 MB; aligns with coding-agent image bounds). */
8
8
  export const DEFAULT_MAX_MEDIA_ITEM_BYTES = 10_000_000;
9
9
  /** Default total media byte budget per request assembly. */
@@ -44,6 +44,8 @@ export function contentBlockInputModality(block) {
44
44
  return "file";
45
45
  case "document":
46
46
  return "document";
47
+ case "video":
48
+ return "video";
47
49
  default:
48
50
  return undefined;
49
51
  }
@@ -77,6 +77,8 @@ export interface AgentConfig {
77
77
  readonly systemPrompt?: SystemPromptConfig;
78
78
  readonly redactor?: SecretRedactor;
79
79
  readonly runLedger?: RunLedger;
80
+ /** Optional host-supplied pricing adapter: turn usage without a provider-reported cost is priced through it; absent or stale quotes degrade to usage-only. */
81
+ readonly costCatalog?: import("./content.js").CostCatalog;
80
82
  /** Optional durable recovery store. */
81
83
  readonly effectStore?: ToolEffectStore;
82
84
  readonly ownership?: OwnershipScope;
@@ -0,0 +1,97 @@
1
+ /** Provider-neutral async batch-jobs contract (plan 061 Task 7).
2
+ *
3
+ * Standalone contract — deliberately not coupled to the orchestration saga seam
4
+ * in v1 (plan decision). Job ids are opaque strings; request payloads are
5
+ * provider-native `JsonObject` bodies and inherit the provider's request caps;
6
+ * polling is a plain exported utility (`pollBatch`), never loop-integrated into
7
+ * core.
8
+ */
9
+ import type { JsonObject, ModelCapabilities, ModelConfig } from "./content.js";
10
+ /** Neutral job state union. Adapters map provider states onto these:
11
+ * `queued` (submitted, not yet running), `cancelling`, and the three terminal
12
+ * states `completed` / `failed` / `cancelled` / `expired`. */
13
+ export type BatchJobState = "queued" | "running" | "cancelling" | "completed" | "failed" | "cancelled" | "expired";
14
+ export declare const BATCH_TERMINAL_STATES: readonly BatchJobState[];
15
+ export declare function isBatchJobTerminal(job: Pick<BatchJob, "state">): boolean;
16
+ export type BatchJobsErrorCode = "empty_requests" | "too_many_requests" | "unsupported_model" | "job_not_found" | "invalid_cursor" | "request_failed" | "response_malformed" | "unsupported_operation" | "job_failed" | "job_cancelled" | "job_expired";
17
+ export declare class BatchJobsError extends Error {
18
+ readonly code: BatchJobsErrorCode;
19
+ constructor(code: BatchJobsErrorCode, message: string);
20
+ }
21
+ export declare function modelSupportsBatchJobs(capabilities?: ModelCapabilities): boolean;
22
+ export declare function assertBatchJobsSupported(model: ModelConfig): void;
23
+ /** One batched request: provider-native body plus an optional caller-assigned
24
+ * correlation id echoed on results. Bodies are opaque to the contract. */
25
+ export interface BatchRequestItem {
26
+ readonly customId?: string;
27
+ readonly body: JsonObject;
28
+ }
29
+ export interface BatchSubmitRequest {
30
+ readonly model: string;
31
+ readonly requests: readonly BatchRequestItem[];
32
+ /** Provider-native routing metadata (weights, priorities) — opaque passthrough. */
33
+ readonly metadata?: JsonObject;
34
+ readonly signal?: AbortSignal;
35
+ }
36
+ export interface BatchJob {
37
+ /** Opaque provider job id — contract never parses or scopes it. */
38
+ readonly id: string;
39
+ readonly state: BatchJobState;
40
+ readonly requestCounts?: {
41
+ readonly total: number;
42
+ readonly completed: number;
43
+ readonly failed: number;
44
+ };
45
+ readonly createdAt?: string;
46
+ readonly completedAt?: string;
47
+ readonly expiresAt?: string;
48
+ /** Provider-native job fields, unmodified, for host-side audits. */
49
+ readonly raw?: JsonObject;
50
+ /** Terminal failure detail when `state` is `failed`. */
51
+ readonly error?: {
52
+ readonly code?: string;
53
+ readonly message?: string;
54
+ };
55
+ }
56
+ export interface BatchResultItem {
57
+ readonly customId: string;
58
+ /** Provider-native per-request response (status + payload), when it succeeded. */
59
+ readonly response?: JsonObject;
60
+ /** Per-request failure detail (the job itself may still be `completed`). */
61
+ readonly error?: {
62
+ readonly code?: string;
63
+ readonly message?: string;
64
+ };
65
+ /** Provider-native raw line for audits. */
66
+ readonly raw?: JsonObject;
67
+ }
68
+ export interface BatchResultsPage {
69
+ readonly job?: BatchJob;
70
+ readonly items: readonly BatchResultItem[];
71
+ /** Opaque continuation token; `null`/`undefined` when the page is last. */
72
+ readonly nextCursor?: string | null;
73
+ }
74
+ export interface BatchResultsOptions {
75
+ readonly cursor?: string | null;
76
+ readonly pageSize?: number;
77
+ readonly signal?: AbortSignal;
78
+ }
79
+ export interface BatchJobsProvider {
80
+ readonly id: string;
81
+ submit(request: BatchSubmitRequest): Promise<BatchJob>;
82
+ status(jobId: string): Promise<BatchJob>;
83
+ cancel(jobId: string): Promise<BatchJob>;
84
+ results(jobId: string, options?: BatchResultsOptions): Promise<BatchResultsPage>;
85
+ }
86
+ export interface PollBatchOptions {
87
+ readonly intervalMs?: number;
88
+ /** Backoff multiplier applied per poll; caps at `maxIntervalMs`. */
89
+ readonly backoffMultiplier?: number;
90
+ readonly maxIntervalMs?: number;
91
+ readonly maxAttempts?: number;
92
+ readonly signal?: AbortSignal;
93
+ }
94
+ /** Poll a job until it reaches a terminal state. Plain utility — core never
95
+ * calls it. Terminal failure states surface typed `BatchJobsError`s
96
+ * (`job_failed` / `job_cancelled` / `job_expired`); only `completed` resolves. */
97
+ export declare function pollBatch(provider: BatchJobsProvider, jobId: string, options?: PollBatchOptions): Promise<BatchJob>;
@@ -0,0 +1,65 @@
1
+ export const BATCH_TERMINAL_STATES = ["completed", "failed", "cancelled", "expired"];
2
+ export function isBatchJobTerminal(job) {
3
+ return BATCH_TERMINAL_STATES.includes(job.state);
4
+ }
5
+ export class BatchJobsError extends Error {
6
+ code;
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.name = "BatchJobsError";
10
+ this.code = code;
11
+ }
12
+ }
13
+ export function modelSupportsBatchJobs(capabilities) {
14
+ return capabilities?.batchJobs === true;
15
+ }
16
+ export function assertBatchJobsSupported(model) {
17
+ if (!modelSupportsBatchJobs(model.capabilities)) {
18
+ throw new BatchJobsError("unsupported_model", `Model ${model.provider}/${model.model} does not declare the batchJobs capability`);
19
+ }
20
+ }
21
+ /** Poll a job until it reaches a terminal state. Plain utility — core never
22
+ * calls it. Terminal failure states surface typed `BatchJobsError`s
23
+ * (`job_failed` / `job_cancelled` / `job_expired`); only `completed` resolves. */
24
+ export async function pollBatch(provider, jobId, options = {}) {
25
+ const intervalMs = options.intervalMs ?? 30_000;
26
+ const multiplier = options.backoffMultiplier ?? 1;
27
+ const maxIntervalMs = options.maxIntervalMs ?? intervalMs * 16;
28
+ const maxAttempts = options.maxAttempts ?? Number.POSITIVE_INFINITY;
29
+ let delay = intervalMs;
30
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
31
+ if (options.signal?.aborted)
32
+ throw options.signal.reason ?? new Error("pollBatch aborted");
33
+ let job;
34
+ try {
35
+ job = await provider.status(jobId);
36
+ }
37
+ catch (error) {
38
+ if (error instanceof BatchJobsError && error.code === "job_not_found") {
39
+ throw new BatchJobsError("job_not_found", `pollBatch: job ${jobId} not found`);
40
+ }
41
+ throw error;
42
+ }
43
+ if (isBatchJobTerminal(job)) {
44
+ if (job.state === "completed")
45
+ return job;
46
+ if (job.state === "cancelled")
47
+ throw new BatchJobsError("job_cancelled", `Batch job ${jobId} was cancelled`);
48
+ if (job.state === "expired")
49
+ throw new BatchJobsError("job_expired", `Batch job ${jobId} expired before completion`);
50
+ throw new BatchJobsError("job_failed", `Batch job ${jobId} failed: ${job.error?.message ?? "provider reported failure"}`);
51
+ }
52
+ if (attempt === maxAttempts)
53
+ break;
54
+ await new Promise((resolve) => {
55
+ const timer = setTimeout(resolve, delay);
56
+ options.signal?.addEventListener("abort", () => {
57
+ clearTimeout(timer);
58
+ resolve();
59
+ }, { once: true });
60
+ });
61
+ delay = Math.min(delay * multiplier, maxIntervalMs);
62
+ }
63
+ throw new BatchJobsError("request_failed", `Batch job ${jobId} did not reach a terminal state within ${maxAttempts} polls`);
64
+ }
65
+ //# sourceMappingURL=batch.js.map
@@ -16,7 +16,7 @@ export interface ErrorInfo {
16
16
  readonly retryAfterMs?: number;
17
17
  readonly cause?: unknown;
18
18
  }
19
- export type ContentBlock = TextContent | ImageContent | AudioContent | FileContent | DocumentContent | ThinkingContent | ToolCallDeltaContent | ToolCallContent | ToolResultContent;
19
+ export type ContentBlock = TextContent | ImageContent | AudioContent | FileContent | DocumentContent | VideoContent | ThinkingContent | ToolCallDeltaContent | ToolCallContent | ToolResultContent;
20
20
  export interface TextContent {
21
21
  readonly type: "text";
22
22
  readonly text: string;
@@ -30,6 +30,19 @@ export interface ImageContent {
30
30
  readonly name?: string;
31
31
  readonly metadata?: Readonly<Record<string, unknown>>;
32
32
  }
33
+ export interface VideoContent {
34
+ readonly type: "video";
35
+ readonly mediaType?: string;
36
+ readonly name?: string;
37
+ /** Base64-encoded video bytes. */
38
+ readonly data?: string;
39
+ readonly url?: string;
40
+ readonly resourceUri?: string;
41
+ readonly durationMs?: number;
42
+ /** Frame-sampling hint for providers that downsample (e.g. Qwen-VL defaults to 2.0). */
43
+ readonly fps?: number;
44
+ readonly metadata?: Readonly<Record<string, unknown>>;
45
+ }
33
46
  export interface ThinkingContent {
34
47
  readonly type: "thinking";
35
48
  readonly text: string;
@@ -92,6 +105,22 @@ export interface ModelCapabilities {
92
105
  readonly streaming?: boolean;
93
106
  /** Native JSON-schema structured output support for this model. */
94
107
  readonly structuredOutput?: boolean | "json_schema";
108
+ /** Provider-neutral embeddings generation support (plan 061). */
109
+ readonly embeddings?: boolean;
110
+ /** Portable thinking/reasoning effort levels this model declares, ascending ladder order (`none` < `minimal` < `low` < `medium` < `high` < `xhigh` < `max`). Advisory legality metadata for hosts; absent means forward-compat passthrough (phase 65). */
111
+ readonly thinkingLevels?: readonly string[];
112
+ /** Provider-neutral speech synthesis support (plan 061). */
113
+ readonly speech?: boolean;
114
+ /** Provider-neutral speech transcription support (plan 061). */
115
+ readonly transcription?: boolean;
116
+ /** Provider-neutral image generation/editing support (plan 061). */
117
+ readonly imageGeneration?: boolean;
118
+ /** Provider-neutral video generation support (plan 061). */
119
+ readonly videoGeneration?: boolean;
120
+ /** Provider-neutral moderation classification support (plan 061). */
121
+ readonly moderation?: boolean;
122
+ /** Provider-neutral async batch-jobs support (plan 061). */
123
+ readonly batchJobs?: boolean;
95
124
  }
96
125
  export interface ModelLimits {
97
126
  readonly contextWindow?: number;
@@ -105,6 +134,36 @@ export interface ModelCost {
105
134
  readonly currency?: string;
106
135
  readonly unit?: string;
107
136
  }
137
+ /** Normalized model-list/capability discovery provenance (plan 062): where a
138
+ * listing came from, when, and how long hosts may cache it. */
139
+ export interface ModelDiscoveryProvenance {
140
+ readonly provider: string;
141
+ /** ISO-8601 timestamp of the fetch (or catalog snapshot) moment. */
142
+ readonly fetchedAt: string;
143
+ /** `"api"` = live provider listing; `"catalog"` = host/registry snapshot, no network. */
144
+ readonly source: "api" | "catalog";
145
+ /** Cache-TTL guidance in milliseconds; hosts may serve the result from cache this long. */
146
+ readonly ttlMs?: number;
147
+ }
148
+ export interface ModelDiscoveryOptions {
149
+ /** Cache window in ms; within TTL a cached result is returned without network.
150
+ * `0` forces a refresh. Defaults to the adapter's configured TTL. */
151
+ readonly ttlMs?: number;
152
+ readonly signal?: AbortSignal;
153
+ }
154
+ /** Normalized `listModels()` result: the existing `ModelConfig` contract verbatim
155
+ * (id = `model`, context window = `limits`, capabilities = `capabilities`, pricing
156
+ * hint = `cost`) plus provenance. No new model shape ships. */
157
+ export interface ModelDiscoveryResult {
158
+ readonly models: readonly ModelConfig[];
159
+ readonly provenance: ModelDiscoveryProvenance;
160
+ }
161
+ /** Model-list/capability discovery seam (plan 062). Adapter implementations
162
+ * normalize provider listings to `ModelConfig` and cache per provider within
163
+ * the configured TTL; hosts merge their own catalog overrides on top. */
164
+ export interface ModelDiscovery {
165
+ listModels(options?: ModelDiscoveryOptions): Promise<ModelDiscoveryResult>;
166
+ }
108
167
  export interface Usage {
109
168
  readonly inputTokens?: number;
110
169
  readonly outputTokens?: number;
@@ -114,3 +173,15 @@ export interface Usage {
114
173
  readonly cost?: number;
115
174
  readonly currency?: string;
116
175
  }
176
+ /**
177
+ * Host-supplied pricing adapter (plan 062): quotes cost rates per model id. Core
178
+ * ships no pricing tables — when no catalog is configured, usage is reported
179
+ * without cost fields. Quotes follow the repo-wide `per_million_tokens` unit
180
+ * convention (see {@link ModelCost}); a stale or unknown model must resolve to
181
+ * `undefined` (degrades to usage-only), never throw.
182
+ */
183
+ export interface CostCatalog {
184
+ get(modelId: string, options?: {
185
+ readonly signal?: AbortSignal;
186
+ }): Promise<ModelCost | undefined>;
187
+ }
@@ -0,0 +1,30 @@
1
+ /** Provider-neutral embeddings contract (plan 061 Task 2).
2
+ * One-shot batch shape: the request carries the abort signal, results report the
3
+ * shared `Usage`. Adapters enforce provider batch caps with typed errors — they
4
+ * never silently auto-chunk; callers chunk (e.g. memory's `embedBatched`).
5
+ * ponytail: per-item token caps are server-enforced; the local cap is batch count. */
6
+ import type { ModelCapabilities, ModelConfig, Usage } from "./content.js";
7
+ export interface EmbeddingsRequest {
8
+ readonly model: string;
9
+ readonly inputs: readonly string[];
10
+ /** Output dimensions override; only for models that support reduced dimensions. */
11
+ readonly dimensions?: number;
12
+ readonly signal?: AbortSignal;
13
+ }
14
+ export interface EmbeddingsResult {
15
+ /** Vectors in input order; `vectors[i]` corresponds to `inputs[i]`. */
16
+ readonly vectors: readonly (readonly number[])[];
17
+ readonly usage: Usage;
18
+ readonly dimensions: number;
19
+ }
20
+ export interface EmbeddingsProvider {
21
+ readonly id: string;
22
+ embedMany(request: EmbeddingsRequest): Promise<EmbeddingsResult>;
23
+ }
24
+ export type EmbeddingsErrorCode = "empty_input" | "batch_too_large" | "request_failed" | "response_malformed" | "unsupported_model";
25
+ export declare class EmbeddingsError extends Error {
26
+ readonly code: EmbeddingsErrorCode;
27
+ constructor(code: EmbeddingsErrorCode, message: string);
28
+ }
29
+ export declare function modelSupportsEmbeddings(capabilities?: ModelCapabilities): boolean;
30
+ export declare function assertEmbeddingsSupported(model: ModelConfig): void;