@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.
- package/CHANGELOG.md +10 -0
- package/dist/agent-run-lifecycle.d.ts +2 -0
- package/dist/agent-run-lifecycle.js +7 -0
- package/dist/agent-run-state.d.ts +2 -0
- package/dist/agent-run-state.js +10 -0
- package/dist/agent-session.d.ts +7 -0
- package/dist/agent-session.js +25 -2
- package/dist/cache-telemetry.d.ts +58 -0
- package/dist/cache-telemetry.js +102 -0
- package/dist/cli-provider-add.d.ts +37 -0
- package/dist/cli-provider-add.js +293 -0
- package/dist/cli-runner.d.ts +5 -1
- package/dist/cli-runner.js +13 -1
- package/dist/contracts-run-state.d.ts +13 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/skill-load.d.ts +23 -0
- package/dist/skill-load.js +74 -0
- package/docs/acp.md +2 -2
- package/docs/agent-session-runtime.md +1 -1
- package/docs/cli-rpc.md +33 -0
- package/docs/coding-agent-tools.md +12 -7
- package/docs/coding-security.md +3 -0
- package/docs/context-and-skills.md +2 -2
- package/docs/document-reader.md +85 -0
- package/docs/index.md +8 -8
- package/docs/model-routing.md +45 -0
- package/docs/provider-caching.md +63 -0
- package/docs/provider-packages.md +2 -0
- package/docs/release-and-install.md +54 -4
- package/package.json +3 -2
- package/templates/provider/CHANGELOG.md.tmpl +5 -0
- package/templates/provider/README.md.tmpl +41 -0
- package/templates/provider/docs/providers/NAME.md.tmpl +61 -0
- package/templates/provider/package.json.tmpl +49 -0
- package/templates/provider/src/cache.ts.tmpl +20 -0
- package/templates/provider/src/index.ts.tmpl +33 -0
- package/templates/provider/src/models.ts.tmpl +16 -0
- package/templates/provider/src/provider.ts.tmpl +23 -0
- package/templates/provider/src/tests/provider.test.ts.tmpl +104 -0
- 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
|
+
}
|