@arnilo/prism 0.1.5 → 0.1.7

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 (41) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/agent-run-lifecycle.d.ts +2 -0
  3. package/dist/agent-run-lifecycle.js +7 -0
  4. package/dist/agent-run-state.d.ts +2 -0
  5. package/dist/agent-run-state.js +10 -0
  6. package/dist/agent-session.d.ts +7 -0
  7. package/dist/agent-session.js +25 -2
  8. package/dist/cache-telemetry.d.ts +58 -0
  9. package/dist/cache-telemetry.js +102 -0
  10. package/dist/cli-provider-add.d.ts +37 -0
  11. package/dist/cli-provider-add.js +293 -0
  12. package/dist/cli-runner.d.ts +5 -1
  13. package/dist/cli-runner.js +13 -1
  14. package/dist/contracts-run-state.d.ts +13 -0
  15. package/dist/index.d.ts +5 -3
  16. package/dist/index.js +3 -2
  17. package/dist/skill-load.d.ts +23 -0
  18. package/dist/skill-load.js +74 -0
  19. package/docs/acp.md +2 -2
  20. package/docs/agent-session-runtime.md +1 -1
  21. package/docs/cli-rpc.md +33 -0
  22. package/docs/coding-agent-tools.md +12 -7
  23. package/docs/coding-security.md +3 -0
  24. package/docs/context-and-skills.md +2 -2
  25. package/docs/document-reader.md +85 -0
  26. package/docs/index.md +8 -8
  27. package/docs/model-routing.md +45 -0
  28. package/docs/provider-caching.md +63 -0
  29. package/docs/provider-packages.md +2 -0
  30. package/docs/release-and-install.md +54 -4
  31. package/package.json +3 -2
  32. package/templates/provider/CHANGELOG.md.tmpl +5 -0
  33. package/templates/provider/README.md.tmpl +41 -0
  34. package/templates/provider/docs/providers/NAME.md.tmpl +61 -0
  35. package/templates/provider/package.json.tmpl +49 -0
  36. package/templates/provider/src/cache.ts.tmpl +20 -0
  37. package/templates/provider/src/index.ts.tmpl +33 -0
  38. package/templates/provider/src/models.ts.tmpl +16 -0
  39. package/templates/provider/src/provider.ts.tmpl +23 -0
  40. package/templates/provider/src/tests/provider.test.ts.tmpl +104 -0
  41. package/templates/provider/tsconfig.json.tmpl +16 -0
@@ -0,0 +1,41 @@
1
+ # __PACKAGE_NAME__
2
+
3
+ __PROVIDER_ID__ provider package for Prism (OpenAI-compatible Chat Completions).
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ npm install __PACKAGE_NAME__ @arnilo/prism
9
+ ```
10
+
11
+ Wire the provider and models into your Prism host. The package registers an
12
+ `api_key` auth method; hosts resolve the credential value — typically from the
13
+ `__ENV_KEY__` environment variable:
14
+
15
+ ```ts
16
+ import { createResolver } from "@arnilo/prism";
17
+ import { create__PROVIDER_PASCAL__ProviderPackage } from "__PACKAGE_NAME__";
18
+
19
+ const resolver = createResolver();
20
+ resolver.registerProviderPackage(
21
+ create__PROVIDER_PASCAL__ProviderPackage({
22
+ apiKey: () => process.env.__ENV_KEY__,
23
+ }),
24
+ );
25
+ ```
26
+
27
+ ## Models
28
+
29
+ Starter catalog in `src/models.ts` (`__MODEL_ID__`). Replace with
30
+ docs-verified model metadata (limits, costs, cache behavior) before publishing.
31
+
32
+ ## Conformance
33
+
34
+ `npm test` builds the package and runs the offline conformance suite wired to
35
+ `@arnilo/prism/testing/provider-conformance` (stream shape, tool-call delta
36
+ reconstruction, header ownership, secret-leak redaction, serialized content
37
+ coverage).
38
+
39
+ ## Docs
40
+
41
+ See `docs/providers/__PROVIDER_ID__.md`.
@@ -0,0 +1,61 @@
1
+ # __PROVIDER_ID__ provider
2
+
3
+ > Scaffold stub — replace with docs-verified provider documentation before publishing.
4
+
5
+ ## What it does
6
+
7
+ `__PACKAGE_NAME__` is an OpenAI-compatible provider package for Prism
8
+ (chat-completions style). It builds on `createOpenAICompatibleProvider` from
9
+ `@arnilo/prism/providers/openai-compatible`.
10
+
11
+ ## When to use it
12
+
13
+ Use it for OpenAI-compatible endpoints that follow the Chat Completions
14
+ convention. Skip it for providers with bespoke serialization or auth.
15
+
16
+ ## Inputs / request
17
+
18
+ - Base URL: `__BASE_URL__` (`--base-url` at scaffold time).
19
+ - Auth: `api_key` auth method; hosts resolve the credential value (e.g. from
20
+ `__ENV_KEY__`).
21
+
22
+ ## Outputs / response / events
23
+
24
+ Standard `ProviderEvent` stream: `content_delta`, `done` (with usage), or
25
+ `error`. Tool calls arrive as deltas and are reconstructed by the host.
26
+
27
+ ## Request/response example
28
+
29
+ ```ts
30
+ import { createResolver } from "@arnilo/prism";
31
+ import { create__PROVIDER_PASCAL__ProviderPackage } from "__PACKAGE_NAME__";
32
+
33
+ const resolver = createResolver();
34
+ resolver.registerProviderPackage(
35
+ create__PROVIDER_PASCAL__ProviderPackage({ apiKey: () => process.env.__ENV_KEY__ }),
36
+ );
37
+ ```
38
+
39
+ ## Implementation example
40
+
41
+ `src/provider.ts` calls `createOpenAICompatibleProvider` with the scaffolded
42
+ base URL, api key, and `doneUsage: true`. Model metadata lives in `src/models.ts`;
43
+ cache-hint mapping helpers live in `src/cache.ts`.
44
+
45
+ ## Extension and configuration notes
46
+
47
+ - Override `baseUrl`, `id`, `models`, `apiKey`, and `fetch` per provider
48
+ package/instance.
49
+ - Cache behavior is scaffolded as `cache: { kind: "implicit" }` — confirm
50
+ against the provider's actual caching before relying on it.
51
+
52
+ ## Security and performance notes
53
+
54
+ - API keys are host-resolved credentials; generated code stores no secrets.
55
+ - Cache keys are identifiers only, sanitized via shared core helpers.
56
+
57
+ ## Related APIs
58
+
59
+ - [OpenAI-compatible provider base](../../providers/openai-compatible.md)
60
+ - [Provider conformance](../../provider-conformance.md)
61
+ - [Provider layer](../../provider-layer.md)
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "version": "__PRISM_VERSION__",
4
+ "description": "__PROVIDER_ID__ provider package for Prism.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "!dist/__tests__",
17
+ "!dist/**/*.map",
18
+ "README.md",
19
+ "CHANGELOG.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "npm run build && node --test dist/__tests__/*.test.js",
25
+ "pack:dry-run": "npm pack --dry-run"
26
+ },
27
+ "peerDependencies": {
28
+ "@arnilo/prism": "__PRISM_VERSION__"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.0.0",
32
+ "typescript": "^5.7.0"
33
+ },
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "license": "MIT",
38
+ "keywords": [
39
+ "prism",
40
+ "provider",
41
+ "__PROVIDER_ID__",
42
+ "agent",
43
+ "llm"
44
+ ],
45
+ "sideEffects": false,
46
+ "publishConfig": {
47
+ "access": "public"
48
+ }
49
+ }
@@ -0,0 +1,20 @@
1
+ import type { ModelConfig, ProviderRequestOptions } from "@arnilo/prism";
2
+ import { mapCacheRetention, sanitizeCacheKey } from "@arnilo/prism";
3
+
4
+ /** OpenAI-compatible `prompt_cache_key` accepted length cap. */
5
+ export const __PROVIDER_UPPER___PROMPT_CACHE_KEY_MAX_LENGTH = 64;
6
+
7
+ export function __PROVIDER_ID__PromptCacheKey(options: ProviderRequestOptions | undefined): string | undefined {
8
+ // Sanitize + clamp via the shared core helper so cache keys cannot carry
9
+ // disallowed characters or exceed the provider limit. Cache keys are
10
+ // session/customer identifiers only, never credentials.
11
+ return sanitizeCacheKey(options?.cacheKey ?? options?.sessionId, __PROVIDER_UPPER___PROMPT_CACHE_KEY_MAX_LENGTH);
12
+ }
13
+
14
+ /** Retention mapping via the shared core helper; `"short"`/`"long"` only when the model supports it. */
15
+ export function __PROVIDER_ID__PromptCacheRetention(
16
+ retention: ProviderRequestOptions["cacheRetention"] | undefined,
17
+ model: ModelConfig,
18
+ ): "short" | "long" | undefined {
19
+ return mapCacheRetention(retention, model);
20
+ }
@@ -0,0 +1,33 @@
1
+ import { type CredentialValueSource, defineProviderPackage, type ModelConfig, type ProviderPackage } from "@arnilo/prism";
2
+ import { __PROVIDER_ID__Models } from "./models.js";
3
+ import { create__PROVIDER_PASCAL__Provider } from "./provider.js";
4
+
5
+ export interface __PROVIDER_PASCAL__ProviderPackageOptions {
6
+ readonly apiKey?: CredentialValueSource;
7
+ readonly fetch?: typeof fetch;
8
+ readonly baseUrl?: string;
9
+ readonly id?: string;
10
+ readonly models?: readonly ModelConfig[];
11
+ }
12
+
13
+ export function create__PROVIDER_PASCAL__ProviderPackage(options: __PROVIDER_PASCAL__ProviderPackageOptions = {}): ProviderPackage {
14
+ const providerId = options.id ?? "__PROVIDER_ID__";
15
+ return defineProviderPackage({
16
+ name: "__PACKAGE_NAME__",
17
+ description: "__PROVIDER_ID__ provider package for Prism.",
18
+ docs: { links: ["docs/providers/__PROVIDER_ID__.md"] },
19
+ setup(api) {
20
+ api.registerProvider(create__PROVIDER_PASCAL__Provider(options));
21
+ for (const model of options.models ?? __PROVIDER_ID__Models) api.registerModel({ ...model, provider: providerId });
22
+ api.registerAuthMethod({ kind: "api_key", provider: providerId, credentialName: "apiKey" });
23
+ },
24
+ });
25
+ }
26
+
27
+ export { __PROVIDER_ID__Models, type __PROVIDER_PASCAL__ModelConfig } from "./models.js";
28
+ export { create__PROVIDER_PASCAL__Provider, __PROVIDER_UPPER___DEFAULT_BASE_URL, type __PROVIDER_PASCAL__ProviderOptions } from "./provider.js";
29
+ export {
30
+ __PROVIDER_ID__PromptCacheKey,
31
+ __PROVIDER_ID__PromptCacheRetention,
32
+ __PROVIDER_UPPER___PROMPT_CACHE_KEY_MAX_LENGTH,
33
+ } from "./cache.js";
@@ -0,0 +1,16 @@
1
+ import type { ModelConfig } from "@arnilo/prism";
2
+
3
+ /** Starter catalog for __PROVIDER_ID__: replace with docs-verified model metadata. */
4
+ export interface __PROVIDER_PASCAL__ModelConfig extends Omit<ModelConfig, "provider"> {
5
+ readonly provider: "__PROVIDER_ID__";
6
+ }
7
+
8
+ export const __PROVIDER_ID__Models: readonly __PROVIDER_PASCAL__ModelConfig[] = [
9
+ {
10
+ provider: "__PROVIDER_ID__",
11
+ model: "__MODEL_ID__",
12
+ limits: { contextWindow: 128_000 },
13
+ cost: { input: 1, output: 2, cacheRead: 0.5, unit: "per_million_tokens" },
14
+ cache: { kind: "implicit" },
15
+ },
16
+ ];
@@ -0,0 +1,23 @@
1
+ import type { AIProvider, CredentialValueSource } from "@arnilo/prism";
2
+ import { createOpenAICompatibleProvider } from "@arnilo/prism/providers/openai-compatible";
3
+
4
+ /** Default Chat Completions base URL for __PROVIDER_ID__ (override per provider docs). */
5
+ export const __PROVIDER_UPPER___DEFAULT_BASE_URL = "__BASE_URL__";
6
+
7
+ export interface __PROVIDER_PASCAL__ProviderOptions {
8
+ readonly id?: string;
9
+ readonly baseUrl?: string;
10
+ readonly apiKey?: CredentialValueSource;
11
+ readonly fetch?: typeof fetch;
12
+ }
13
+
14
+ export function create__PROVIDER_PASCAL__Provider(options: __PROVIDER_PASCAL__ProviderOptions = {}): AIProvider {
15
+ return createOpenAICompatibleProvider({
16
+ id: options.id ?? "__PROVIDER_ID__",
17
+ baseUrl: (options.baseUrl ?? __PROVIDER_UPPER___DEFAULT_BASE_URL).replace(/\/+$/, ""),
18
+ apiKey: options.apiKey,
19
+ fetch: options.fetch,
20
+ doneUsage: true,
21
+ requestFailedPrefix: "__PROVIDER_PASCAL__ request failed",
22
+ });
23
+ }
@@ -0,0 +1,104 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import type { ProviderRequest } from "@arnilo/prism";
4
+ import {
5
+ assertNoSecretLeak,
6
+ assertProviderOwnedHeadersWin,
7
+ assertProviderStreamConforms,
8
+ assertSerializedRequestCoversContent,
9
+ assertToolCallDeltasReconstruct,
10
+ } from "@arnilo/prism/testing/provider-conformance";
11
+ import { create__PROVIDER_PASCAL__Provider } from "../index.js";
12
+ import { __PROVIDER_ID__Models } from "../models.js";
13
+
14
+ const request: ProviderRequest = {
15
+ model: __PROVIDER_ID__Models[0],
16
+ messages: [
17
+ { role: "system", content: [{ type: "text", text: "developer instructions" }] },
18
+ { role: "user", content: [{ type: "text", text: "hi" }] },
19
+ ],
20
+ tools: [{ name: "lookup", parameters: { type: "object" }, execute: () => ({ toolCallId: "call_1", name: "lookup", content: [] }) }],
21
+ };
22
+
23
+ const API_KEY = "fake-__PROVIDER_ID__-key";
24
+
25
+ describe("__PROVIDER_ID__ provider scaffold", () => {
26
+ it("streams text, usage, and done; owns its headers; leaks no secrets", async () => {
27
+ let captured: RequestInit | undefined;
28
+ const provider = create__PROVIDER_PASCAL__Provider({
29
+ apiKey: API_KEY,
30
+ fetch: (async (_input, init) => {
31
+ captured = init;
32
+ return ok(
33
+ sse([
34
+ { id: "chatcmpl-1", object: "chat.completion.chunk", choices: [{ index: 0, delta: { role: "assistant", content: "hi" } }] },
35
+ {
36
+ id: "chatcmpl-1",
37
+ object: "chat.completion.chunk",
38
+ choices: [{ index: 0, delta: {} }],
39
+ usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 },
40
+ },
41
+ ]),
42
+ );
43
+ }) as typeof fetch,
44
+ });
45
+ const events = await assertProviderStreamConforms({
46
+ provider,
47
+ request: {
48
+ ...request,
49
+ options: {
50
+ ...request.options,
51
+ headers: { authorization: "Bearer caller-key", "content-type": "text/plain", "x-caller": "kept" },
52
+ },
53
+ },
54
+ expect: { text: "hi", usage: { inputTokens: 5, outputTokens: 2, totalTokens: 7 } },
55
+ });
56
+ assertNoSecretLeak(events, [API_KEY]);
57
+ const headers = new Headers(captured?.headers);
58
+ assertProviderOwnedHeadersWin(headers, {
59
+ owned: { authorization: `Bearer ${API_KEY}`, "content-type": "application/json" },
60
+ caller: { authorization: "Bearer caller-key", "content-type": "text/plain", "x-caller": "kept" },
61
+ });
62
+ });
63
+
64
+ it("serializes request content and reconstructs tool-call deltas", async () => {
65
+ let body: unknown;
66
+ const provider = create__PROVIDER_PASCAL__Provider({
67
+ apiKey: API_KEY,
68
+ fetch: (async (_input, init) => {
69
+ body = JSON.parse(String(init?.body));
70
+ return ok(
71
+ sse([
72
+ {
73
+ id: "c1",
74
+ object: "chat.completion.chunk",
75
+ choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: "call_1", function: { name: "lookup", arguments: "" } }] } }],
76
+ },
77
+ {
78
+ id: "c1",
79
+ object: "chat.completion.chunk",
80
+ choices: [{ index: 0, delta: { tool_calls: [{ index: 0, function: { arguments: '{"q":"x"}' } }] } }],
81
+ },
82
+ ]),
83
+ );
84
+ }) as typeof fetch,
85
+ });
86
+ const events = await assertProviderStreamConforms({ provider, request });
87
+ assertToolCallDeltasReconstruct(events, [{ index: 0, id: "call_1", name: "lookup", arguments: { q: "x" } }]);
88
+ assertSerializedRequestCoversContent(request, body);
89
+ });
90
+ });
91
+
92
+ function ok(body: ReadableStream<Uint8Array>): Response {
93
+ return new Response(body, { status: 200 });
94
+ }
95
+
96
+ function sse(events: readonly object[]): ReadableStream<Uint8Array> {
97
+ const text = `${events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("")}data: [DONE]\n\n`;
98
+ return new ReadableStream({
99
+ start(controller) {
100
+ controller.enqueue(new TextEncoder().encode(text));
101
+ controller.close();
102
+ },
103
+ });
104
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "declaration": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["src"]
16
+ }