@arnilo/prism 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +139 -0
  4. package/dist/agents.d.ts +5 -0
  5. package/dist/agents.js +439 -0
  6. package/dist/cli-runner.d.ts +33 -0
  7. package/dist/cli-runner.js +167 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +10 -0
  10. package/dist/compaction.d.ts +9 -0
  11. package/dist/compaction.js +67 -0
  12. package/dist/config.d.ts +17 -0
  13. package/dist/config.js +69 -0
  14. package/dist/contracts.d.ts +670 -0
  15. package/dist/contracts.js +2 -0
  16. package/dist/contributions.d.ts +35 -0
  17. package/dist/contributions.js +47 -0
  18. package/dist/credentials.d.ts +22 -0
  19. package/dist/credentials.js +63 -0
  20. package/dist/extensions.d.ts +25 -0
  21. package/dist/extensions.js +131 -0
  22. package/dist/index.d.ts +47 -0
  23. package/dist/index.js +28 -0
  24. package/dist/input.d.ts +57 -0
  25. package/dist/input.js +225 -0
  26. package/dist/manifests.d.ts +28 -0
  27. package/dist/manifests.js +108 -0
  28. package/dist/middleware.d.ts +15 -0
  29. package/dist/middleware.js +49 -0
  30. package/dist/mock-provider.d.ts +6 -0
  31. package/dist/mock-provider.js +14 -0
  32. package/dist/models.d.ts +8 -0
  33. package/dist/models.js +25 -0
  34. package/dist/node/config.d.ts +9 -0
  35. package/dist/node/config.js +51 -0
  36. package/dist/node/session-store-jsonl.d.ts +17 -0
  37. package/dist/node/session-store-jsonl.js +134 -0
  38. package/dist/node/settings.d.ts +7 -0
  39. package/dist/node/settings.js +26 -0
  40. package/dist/node/trust.d.ts +13 -0
  41. package/dist/node/trust.js +56 -0
  42. package/dist/provider-events.d.ts +15 -0
  43. package/dist/provider-events.js +30 -0
  44. package/dist/provider-packages.d.ts +4 -0
  45. package/dist/provider-packages.js +12 -0
  46. package/dist/provider-request-policy.d.ts +9 -0
  47. package/dist/provider-request-policy.js +49 -0
  48. package/dist/providers/openai-compatible.d.ts +9 -0
  49. package/dist/providers/openai-compatible.js +197 -0
  50. package/dist/providers.d.ts +8 -0
  51. package/dist/providers.js +25 -0
  52. package/dist/redaction.d.ts +11 -0
  53. package/dist/redaction.js +68 -0
  54. package/dist/resources.d.ts +5 -0
  55. package/dist/resources.js +30 -0
  56. package/dist/retry.d.ts +11 -0
  57. package/dist/retry.js +48 -0
  58. package/dist/rpc.d.ts +18 -0
  59. package/dist/rpc.js +187 -0
  60. package/dist/security.d.ts +51 -0
  61. package/dist/security.js +60 -0
  62. package/dist/session-stores.d.ts +25 -0
  63. package/dist/session-stores.js +116 -0
  64. package/dist/settings.d.ts +3 -0
  65. package/dist/settings.js +26 -0
  66. package/dist/skills.d.ts +8 -0
  67. package/dist/skills.js +34 -0
  68. package/dist/system-prompts.d.ts +6 -0
  69. package/dist/system-prompts.js +47 -0
  70. package/dist/testing/provider-conformance.d.ts +36 -0
  71. package/dist/testing/provider-conformance.js +164 -0
  72. package/dist/tools.d.ts +25 -0
  73. package/dist/tools.js +109 -0
  74. package/docs/agent-session-runtime.md +167 -0
  75. package/docs/api-page-template.md +32 -0
  76. package/docs/cli-rpc.md +140 -0
  77. package/docs/compaction-and-retry.md +177 -0
  78. package/docs/compaction-llm.md +108 -0
  79. package/docs/compaction-observational-memory.md +123 -0
  80. package/docs/configuration-and-manifests.md +142 -0
  81. package/docs/context-and-skills.md +113 -0
  82. package/docs/contribution-registries.md +118 -0
  83. package/docs/credentials-and-redaction.md +122 -0
  84. package/docs/extensions.md +139 -0
  85. package/docs/index.md +56 -0
  86. package/docs/input-and-prompt-assembly.md +168 -0
  87. package/docs/middleware-hooks.md +123 -0
  88. package/docs/node-filesystem-config.md +85 -0
  89. package/docs/node-jsonl-session-store.md +81 -0
  90. package/docs/provider-conformance.md +109 -0
  91. package/docs/provider-layer.md +172 -0
  92. package/docs/provider-packages.md +171 -0
  93. package/docs/providers/kimi.md +110 -0
  94. package/docs/providers/openai-compatible.md +125 -0
  95. package/docs/providers/openai.md +131 -0
  96. package/docs/providers/opencode-go.md +103 -0
  97. package/docs/providers/openrouter.md +105 -0
  98. package/docs/providers/zai.md +108 -0
  99. package/docs/public-contracts.md +375 -0
  100. package/docs/release-and-install.md +141 -0
  101. package/docs/resource-loading.md +97 -0
  102. package/docs/session-stores-and-branching.md +119 -0
  103. package/docs/settings-auth-trust-security.md +73 -0
  104. package/docs/system-prompts.md +116 -0
  105. package/docs/tools.md +151 -0
  106. package/package.json +93 -0
@@ -0,0 +1,134 @@
1
+ import { mkdir, readFile, appendFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ export function createJsonlSessionStore(pathOrOptions) {
4
+ const options = typeof pathOrOptions === "string" ? { path: pathOrOptions, createDirectory: true } : pathOrOptions;
5
+ const path = options.path;
6
+ let appendChain = Promise.resolve();
7
+ return {
8
+ append(entry) {
9
+ appendChain = appendChain.then(async () => {
10
+ if (options.createDirectory !== false)
11
+ await mkdir(dirname(path), { recursive: true });
12
+ if (await findEntry(path, entry.id))
13
+ throw new Error(`Duplicate session entry id: ${entry.id}`);
14
+ await appendFile(path, `${JSON.stringify(entry)}\n`, "utf8");
15
+ });
16
+ return appendChain;
17
+ },
18
+ async list(sessionId) {
19
+ return (await readEntries(path)).filter((entry) => entry.sessionId === sessionId);
20
+ },
21
+ async get(id) {
22
+ return findEntry(path, id);
23
+ },
24
+ };
25
+ }
26
+ /** Read a JSONL session file and return both valid entries and per-line parse errors. */
27
+ export async function readJsonlSessionEntries(path) {
28
+ let text;
29
+ try {
30
+ text = await readFile(path, "utf8");
31
+ }
32
+ catch (error) {
33
+ if (isMissingFile(error))
34
+ return { entries: [], errors: [] };
35
+ throw new Error(`Failed to read session store ${path}: ${errorMessage(error)}`);
36
+ }
37
+ const entries = [];
38
+ const errors = [];
39
+ for (const [index, line] of text.split(/\r?\n/).filter(Boolean).entries()) {
40
+ const parsed = parseEntry(line, index + 1);
41
+ if (parsed.ok) {
42
+ entries.push(parsed.entry);
43
+ }
44
+ else {
45
+ errors.push(parsed.error);
46
+ }
47
+ }
48
+ return { entries, errors };
49
+ }
50
+ async function findEntry(path, id) {
51
+ return (await readEntries(path)).find((entry) => entry.id === id);
52
+ }
53
+ async function readEntries(path) {
54
+ return (await readJsonlSessionEntries(path)).entries;
55
+ }
56
+ function parseEntry(line, lineNumber) {
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(line);
60
+ }
61
+ catch (error) {
62
+ return { ok: false, error: { line: lineNumber, message: `Invalid JSON: ${errorMessage(error)}`, raw: line } };
63
+ }
64
+ return validateSessionEntry(parsed, lineNumber, line);
65
+ }
66
+ function validateSessionEntry(value, lineNumber, raw) {
67
+ if (!isBasicSessionEntry(value)) {
68
+ return { ok: false, error: { line: lineNumber, message: "Invalid session entry: expected object with id, sessionId, timestamp, and kind", raw } };
69
+ }
70
+ const entry = value;
71
+ if (entry.parentId !== undefined && typeof entry.parentId !== "string") {
72
+ return { ok: false, error: { line: lineNumber, message: "Invalid parentId: expected string", raw } };
73
+ }
74
+ switch (entry.kind) {
75
+ case "message":
76
+ if (!isMessage(entry.message))
77
+ return { ok: false, error: { line: lineNumber, message: "Invalid message entry: expected Message object", raw } };
78
+ break;
79
+ case "summary":
80
+ if (typeof entry.summary !== "string")
81
+ return { ok: false, error: { line: lineNumber, message: "Invalid summary entry: expected string summary", raw } };
82
+ break;
83
+ case "model_change":
84
+ if (!isModelConfig(entry.model))
85
+ return { ok: false, error: { line: lineNumber, message: "Invalid model_change entry: expected ModelConfig object", raw } };
86
+ break;
87
+ case "custom":
88
+ if (!isPlainObject(entry.data))
89
+ return { ok: false, error: { line: lineNumber, message: "Invalid custom entry: expected object data", raw } };
90
+ break;
91
+ case "compaction":
92
+ if (typeof entry.summary !== "string")
93
+ return { ok: false, error: { line: lineNumber, message: "Invalid compaction entry: expected string summary", raw } };
94
+ if (!isPlainObject(entry.data))
95
+ return { ok: false, error: { line: lineNumber, message: "Invalid compaction entry: expected object data", raw } };
96
+ break;
97
+ case "label":
98
+ if (typeof entry.label !== "string")
99
+ return { ok: false, error: { line: lineNumber, message: "Invalid label entry: expected string label", raw } };
100
+ break;
101
+ }
102
+ return { ok: true, entry };
103
+ }
104
+ function isBasicSessionEntry(value) {
105
+ if (!value || typeof value !== "object" || Array.isArray(value))
106
+ return false;
107
+ const entry = value;
108
+ return typeof entry.id === "string"
109
+ && typeof entry.sessionId === "string"
110
+ && typeof entry.timestamp === "string"
111
+ && typeof entry.kind === "string";
112
+ }
113
+ function isMessage(value) {
114
+ if (!value || typeof value !== "object" || Array.isArray(value))
115
+ return false;
116
+ const message = value;
117
+ return typeof message.role === "string" && Array.isArray(message.content);
118
+ }
119
+ function isModelConfig(value) {
120
+ if (!value || typeof value !== "object" || Array.isArray(value))
121
+ return false;
122
+ const model = value;
123
+ return typeof model.provider === "string" && typeof model.model === "string";
124
+ }
125
+ function isPlainObject(value) {
126
+ return !!value && typeof value === "object" && !Array.isArray(value);
127
+ }
128
+ function isMissingFile(error) {
129
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
130
+ }
131
+ function errorMessage(error) {
132
+ return error instanceof Error ? error.message : String(error);
133
+ }
134
+ //# sourceMappingURL=session-store-jsonl.js.map
@@ -0,0 +1,7 @@
1
+ import { type ConfigLayer } from "../config.js";
2
+ import { type NodeConfigFile } from "./config.js";
3
+ import type { SettingsProvider } from "../contracts.js";
4
+ export type NodeSettingsFile = NodeConfigFile;
5
+ export declare function defaultUserSettingsPath(appName?: string): string;
6
+ export declare function readSettingsFile(path: string): Promise<ConfigLayer["config"]>;
7
+ export declare function loadSettingsFiles(files: readonly NodeSettingsFile[]): Promise<SettingsProvider>;
@@ -0,0 +1,26 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { mergeConfigLayers } from "../config.js";
4
+ import { readConfigFile } from "./config.js";
5
+ import { createStaticSettingsProvider } from "../settings.js";
6
+ export function defaultUserSettingsPath(appName = "prism") {
7
+ return join(homedir(), ".config", appName, "settings.json");
8
+ }
9
+ export function readSettingsFile(path) {
10
+ return readConfigFile(path);
11
+ }
12
+ export async function loadSettingsFiles(files) {
13
+ const layers = [];
14
+ for (const file of files) {
15
+ try {
16
+ layers.push({ name: file.name, config: await readSettingsFile(file.path) });
17
+ }
18
+ catch (error) {
19
+ if (file.optional && error instanceof Error && error.message.includes("ENOENT"))
20
+ continue;
21
+ throw error;
22
+ }
23
+ }
24
+ return createStaticSettingsProvider(mergeConfigLayers(layers));
25
+ }
26
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1,13 @@
1
+ import type { TrustPolicy } from "../security.js";
2
+ export interface PathTrustPolicyOptions {
3
+ readonly trustedRoots: readonly string[];
4
+ }
5
+ export declare function isPathInside(root: string, target: string): boolean;
6
+ /** Async path-inside check that resolves symlinks on both root and target.
7
+ * Fails closed (returns false) if the root cannot be resolved, if realpath fails
8
+ * for another reason, or if the resolved target escapes the resolved root.
9
+ * If the target itself does not exist, its parent directory is resolved instead
10
+ * so that write-time trust checks can still validate the path.
11
+ */
12
+ export declare function isPathInsideReal(root: string, target: string): Promise<boolean>;
13
+ export declare function createPathTrustPolicy(options: PathTrustPolicyOptions): TrustPolicy;
@@ -0,0 +1,56 @@
1
+ import { isAbsolute, relative, resolve, dirname, basename, join } from "node:path";
2
+ import { realpath } from "node:fs/promises";
3
+ export function isPathInside(root, target) {
4
+ const from = resolve(root);
5
+ const to = resolve(target);
6
+ const rel = relative(from, to);
7
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
8
+ }
9
+ /** Async path-inside check that resolves symlinks on both root and target.
10
+ * Fails closed (returns false) if the root cannot be resolved, if realpath fails
11
+ * for another reason, or if the resolved target escapes the resolved root.
12
+ * If the target itself does not exist, its parent directory is resolved instead
13
+ * so that write-time trust checks can still validate the path.
14
+ */
15
+ export async function isPathInsideReal(root, target) {
16
+ let from;
17
+ try {
18
+ from = await realpath(root);
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ let to;
24
+ try {
25
+ to = await realpath(target);
26
+ }
27
+ catch (error) {
28
+ if (!isMissingFile(error))
29
+ return false;
30
+ const parent = dirname(target);
31
+ try {
32
+ const resolvedParent = await realpath(parent);
33
+ to = join(resolvedParent, basename(target));
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ return isPathInside(from, to);
40
+ }
41
+ export function createPathTrustPolicy(options) {
42
+ return {
43
+ async check(request) {
44
+ for (const root of options.trustedRoots) {
45
+ if (await isPathInsideReal(root, request.target)) {
46
+ return { trusted: true };
47
+ }
48
+ }
49
+ return { trusted: false, reason: `Path is outside trusted roots: ${request.target}` };
50
+ },
51
+ };
52
+ }
53
+ function isMissingFile(error) {
54
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
55
+ }
56
+ //# sourceMappingURL=trust.js.map
@@ -0,0 +1,15 @@
1
+ import type { ContentBlock, JsonObject, ProviderEvent, ToolCallContent, Usage } from "./contracts.js";
2
+ export declare function providerTextDelta(text: string): ProviderEvent;
3
+ export declare function providerThinkingDelta(text: string, signature?: string): ProviderEvent;
4
+ export declare function providerContentDelta(content: ContentBlock): ProviderEvent;
5
+ export declare function providerToolCall(call: ToolCallContent): ProviderEvent;
6
+ export declare function providerToolCallDelta(delta: {
7
+ readonly index: number;
8
+ readonly id?: string;
9
+ readonly name?: string;
10
+ readonly argumentsText?: string;
11
+ }): ProviderEvent;
12
+ export declare function providerUsage(usage: Usage): ProviderEvent;
13
+ export declare function providerDone(usage?: Usage): ProviderEvent;
14
+ export declare function providerError(error: unknown, secrets?: readonly (string | undefined)[]): ProviderEvent;
15
+ export declare function toolCallContent(id: string, name: string, args?: JsonObject): ToolCallContent;
@@ -0,0 +1,30 @@
1
+ import { errorToErrorInfo } from "./redaction.js";
2
+ export function providerTextDelta(text) {
3
+ return { type: "content_delta", content: { type: "text", text } };
4
+ }
5
+ export function providerThinkingDelta(text, signature) {
6
+ return { type: "content_delta", content: { type: "thinking", text, signature } };
7
+ }
8
+ export function providerContentDelta(content) {
9
+ return { type: "content_delta", content };
10
+ }
11
+ export function providerToolCall(call) {
12
+ return { type: "tool_call", call };
13
+ }
14
+ export function providerToolCallDelta(delta) {
15
+ return { type: "tool_call_delta", ...delta };
16
+ }
17
+ export function providerUsage(usage) {
18
+ return { type: "usage", usage };
19
+ }
20
+ export function providerDone(usage) {
21
+ return { type: "done", usage };
22
+ }
23
+ export function providerError(error, secrets = []) {
24
+ const info = errorToErrorInfo(error, secrets);
25
+ return { type: "error", error: info };
26
+ }
27
+ export function toolCallContent(id, name, args = {}) {
28
+ return { type: "tool_call", id, name, arguments: args };
29
+ }
30
+ //# sourceMappingURL=provider-events.js.map
@@ -0,0 +1,4 @@
1
+ import type { AuthMethod, ProviderPackage, SystemPromptContribution } from "./contracts.js";
2
+ export declare function defineProviderPackage(providerPackage: ProviderPackage): ProviderPackage;
3
+ export declare function authMethodKey(method: AuthMethod): string;
4
+ export declare function systemPromptContributionKey(contribution: SystemPromptContribution): string;
@@ -0,0 +1,12 @@
1
+ export function defineProviderPackage(providerPackage) {
2
+ if (!providerPackage.name.trim())
3
+ throw new Error("Provider package name is required");
4
+ return providerPackage;
5
+ }
6
+ export function authMethodKey(method) {
7
+ return `${method.provider}\0${method.name ?? method.kind}`;
8
+ }
9
+ export function systemPromptContributionKey(contribution) {
10
+ return contribution.id;
11
+ }
12
+ //# sourceMappingURL=provider-packages.js.map
@@ -0,0 +1,9 @@
1
+ import type { CacheRetention, ProviderRequest, ProviderRequestOptions, ProviderRequestPolicy, ProviderRequestPolicyResult } from "./contracts.js";
2
+ export interface SessionCachePolicyOptions {
3
+ readonly retention?: CacheRetention;
4
+ readonly cacheKey?: string;
5
+ }
6
+ export declare function createProviderRequestPolicyChain(policies: readonly ProviderRequestPolicy[]): ProviderRequestPolicy;
7
+ export declare function createSessionCachePolicy(options?: SessionCachePolicyOptions): ProviderRequestPolicy;
8
+ export declare function mergeProviderRequestOptions(base: ProviderRequestOptions | undefined, patch: ProviderRequestOptions | undefined): ProviderRequestOptions | undefined;
9
+ export declare function normalizeProviderRequestPolicyResult(result: ProviderRequest | ProviderRequestPolicyResult): ProviderRequestPolicyResult;
@@ -0,0 +1,49 @@
1
+ export function createProviderRequestPolicyChain(policies) {
2
+ return {
3
+ name: "provider-request-policy-chain",
4
+ async apply(context) {
5
+ let request = context.request;
6
+ const secrets = [];
7
+ for (const policy of policies) {
8
+ const result = await policy.apply({ ...context, request });
9
+ const normalized = normalizeProviderRequestPolicyResult(result);
10
+ request = normalized.request;
11
+ secrets.push(...(normalized.secrets ?? []));
12
+ }
13
+ return { request, secrets };
14
+ },
15
+ };
16
+ }
17
+ export function createSessionCachePolicy(options = {}) {
18
+ return {
19
+ name: "session-cache",
20
+ apply(context) {
21
+ const sessionId = context.request.options?.sessionId ?? context.sessionId;
22
+ return {
23
+ ...context.request,
24
+ options: mergeProviderRequestOptions(context.request.options, {
25
+ sessionId,
26
+ cacheKey: options.cacheKey ?? sessionId,
27
+ cacheRetention: options.retention ?? "short",
28
+ }),
29
+ };
30
+ },
31
+ };
32
+ }
33
+ export function mergeProviderRequestOptions(base, patch) {
34
+ if (!base)
35
+ return patch;
36
+ if (!patch)
37
+ return base;
38
+ return {
39
+ ...base,
40
+ ...patch,
41
+ headers: patch.headers || base.headers ? { ...base.headers, ...patch.headers } : undefined,
42
+ compat: patch.compat || base.compat ? { ...base.compat, ...patch.compat } : undefined,
43
+ extra: patch.extra || base.extra ? { ...base.extra, ...patch.extra } : undefined,
44
+ };
45
+ }
46
+ export function normalizeProviderRequestPolicyResult(result) {
47
+ return "request" in result ? result : { request: result };
48
+ }
49
+ //# sourceMappingURL=provider-request-policy.js.map
@@ -0,0 +1,9 @@
1
+ import type { AIProvider } from "../contracts.js";
2
+ import { type CredentialValueSource } from "../credentials.js";
3
+ export interface OpenAICompatibleProviderOptions {
4
+ readonly id?: string;
5
+ readonly baseUrl: string;
6
+ readonly apiKey?: CredentialValueSource;
7
+ readonly fetch?: typeof fetch;
8
+ }
9
+ export declare function createOpenAICompatibleProvider(options: OpenAICompatibleProviderOptions): AIProvider;
@@ -0,0 +1,197 @@
1
+ import { resolveCredentialValue } from "../credentials.js";
2
+ import { providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, toolCallContent, } from "../provider-events.js";
3
+ import { redactSecrets } from "../redaction.js";
4
+ export function createOpenAICompatibleProvider(options) {
5
+ const providerId = options.id ?? "openai-compatible";
6
+ return {
7
+ id: providerId,
8
+ async *generate(request) {
9
+ const apiKey = await resolveCredentialValue(options.apiKey, {
10
+ name: "apiKey",
11
+ provider: providerId,
12
+ });
13
+ const fetchImpl = options.fetch ?? fetch;
14
+ const secrets = [apiKey];
15
+ const tools = new Map();
16
+ try {
17
+ const response = await fetchImpl(`${options.baseUrl.replace(/\/$/, "")}/chat/completions`, {
18
+ method: "POST",
19
+ headers: {
20
+ "content-type": "application/json",
21
+ ...request.options?.headers,
22
+ ...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),
23
+ },
24
+ body: JSON.stringify(toOpenAIRequest(request)),
25
+ signal: request.signal,
26
+ });
27
+ if (!response.ok) {
28
+ yield providerError(new Error(`OpenAI-compatible request failed: ${response.status} ${await safeText(response)}`), secrets);
29
+ return;
30
+ }
31
+ if (!response.body) {
32
+ yield providerError(new Error("OpenAI-compatible response had no body"), secrets);
33
+ return;
34
+ }
35
+ for await (const data of readSseData(response.body)) {
36
+ if (data === "[DONE]")
37
+ break;
38
+ const parsed = JSON.parse(data);
39
+ const usage = toUsage(parsed.usage);
40
+ if (usage)
41
+ yield providerUsage(usage);
42
+ for (const choice of parsed.choices ?? []) {
43
+ const delta = choice.delta ?? {};
44
+ if (typeof delta.content === "string" && delta.content)
45
+ yield providerTextDelta(delta.content);
46
+ if (typeof delta.reasoning_content === "string" && delta.reasoning_content) {
47
+ yield providerThinkingDelta(delta.reasoning_content);
48
+ }
49
+ for (const tool of delta.tool_calls ?? []) {
50
+ const index = tool.index ?? 0;
51
+ const current = tools.get(index) ?? { argumentsText: "" };
52
+ current.id = tool.id ?? current.id;
53
+ current.name = tool.function?.name ?? current.name;
54
+ current.argumentsText += tool.function?.arguments ?? "";
55
+ tools.set(index, current);
56
+ yield providerToolCallDelta({
57
+ index,
58
+ id: tool.id,
59
+ name: tool.function?.name,
60
+ argumentsText: tool.function?.arguments,
61
+ });
62
+ }
63
+ }
64
+ }
65
+ for (const call of tools.values()) {
66
+ if (call.id && call.name)
67
+ yield providerToolCall(toolCallContent(call.id, call.name, parseArgs(call.argumentsText)));
68
+ }
69
+ yield providerDone();
70
+ }
71
+ catch (error) {
72
+ yield providerError(error, secrets);
73
+ }
74
+ },
75
+ };
76
+ }
77
+ function toOpenAIRequest(request) {
78
+ return {
79
+ model: request.model.model,
80
+ messages: request.messages.map((message) => toOpenAIMessage(message, request.model.capabilities ?? {})),
81
+ tools: request.tools?.map(toOpenAITool),
82
+ stream: true,
83
+ stream_options: { include_usage: true },
84
+ ...request.model.parameters,
85
+ };
86
+ }
87
+ function toOpenAIMessage(message, capabilities = {}) {
88
+ if (message.role === "tool") {
89
+ const result = message.content.find((part) => part.type === "tool_result");
90
+ return {
91
+ role: "tool",
92
+ tool_call_id: result?.toolCallId ?? "",
93
+ content: result ? JSON.stringify(result.result ?? result.error ?? null) : "",
94
+ };
95
+ }
96
+ if (message.role === "assistant") {
97
+ const toolCalls = message.content.filter((part) => part.type === "tool_call");
98
+ const textParts = message.content.filter((part) => part.type === "text" || part.type === "thinking");
99
+ if (toolCalls.length > 0) {
100
+ return {
101
+ role: "assistant",
102
+ content: textParts.map((part) => (part.type === "text" ? part.text : part.text)).join("\n") || null,
103
+ tool_calls: toolCalls.map((call) => ({
104
+ id: call.id,
105
+ type: "function",
106
+ function: { name: call.name, arguments: JSON.stringify(call.arguments) },
107
+ })),
108
+ };
109
+ }
110
+ }
111
+ const content = [];
112
+ for (const part of message.content) {
113
+ if (part.type === "text" || part.type === "thinking") {
114
+ content.push({ type: "text", text: part.text });
115
+ }
116
+ else if (part.type === "image") {
117
+ if (!capabilities.input?.includes("image")) {
118
+ throw new Error(`Provider ${message.role} message includes image but model does not declare image input capability`);
119
+ }
120
+ content.push(toOpenAIImage(part));
121
+ }
122
+ else if (part.type === "tool_call") {
123
+ throw new Error("Provider assistant tool_call blocks must be the only content on the message");
124
+ }
125
+ else if (part.type === "tool_result") {
126
+ throw new Error("Provider tool_result blocks must appear in role=tool messages");
127
+ }
128
+ }
129
+ if (content.length === 1 && content[0].type === "text") {
130
+ return { role: message.role, content: content[0].text };
131
+ }
132
+ return { role: message.role, content };
133
+ }
134
+ function toOpenAIImage(part) {
135
+ const url = part.url ?? (part.data ? `data:${part.mimeType ?? "image/png"};base64,${part.data}` : undefined);
136
+ if (!url)
137
+ throw new Error("Provider image block missing url or data");
138
+ return { type: "image_url", image_url: { url } };
139
+ }
140
+ function toOpenAITool(tool) {
141
+ return {
142
+ type: "function",
143
+ function: {
144
+ name: tool.name,
145
+ description: tool.description,
146
+ parameters: tool.parameters ?? { type: "object" },
147
+ },
148
+ };
149
+ }
150
+ async function* readSseData(body) {
151
+ const reader = body.getReader();
152
+ const decoder = new TextDecoder();
153
+ let buffer = "";
154
+ while (true) {
155
+ const { done, value } = await reader.read();
156
+ if (done)
157
+ break;
158
+ buffer += decoder.decode(value, { stream: true });
159
+ const lines = buffer.split(/\r?\n/);
160
+ buffer = lines.pop() ?? "";
161
+ for (const line of lines) {
162
+ if (line.startsWith("data: "))
163
+ yield line.slice(6).trim();
164
+ }
165
+ }
166
+ buffer += decoder.decode();
167
+ for (const line of buffer.split(/\r?\n/)) {
168
+ if (line.startsWith("data: "))
169
+ yield line.slice(6).trim();
170
+ }
171
+ }
172
+ async function safeText(response) {
173
+ try {
174
+ return redactSecrets(await response.text(), []);
175
+ }
176
+ catch {
177
+ return "";
178
+ }
179
+ }
180
+ function parseArgs(text) {
181
+ if (!text)
182
+ return {};
183
+ const parsed = JSON.parse(text);
184
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
185
+ }
186
+ function toUsage(usage) {
187
+ if (!usage)
188
+ return undefined;
189
+ return {
190
+ inputTokens: usage.prompt_tokens,
191
+ outputTokens: usage.completion_tokens,
192
+ totalTokens: usage.total_tokens,
193
+ cacheReadTokens: usage.prompt_tokens_details?.cached_tokens ?? usage.prompt_cache_hit_tokens,
194
+ cacheWriteTokens: usage.prompt_tokens_details?.cache_write_tokens,
195
+ };
196
+ }
197
+ //# sourceMappingURL=openai-compatible.js.map
@@ -0,0 +1,8 @@
1
+ import type { AIProvider, ModelConfig } from "./contracts.js";
2
+ export interface ProviderRegistry {
3
+ register(provider: AIProvider): void;
4
+ get(id: string): AIProvider | undefined;
5
+ resolve(model: Pick<ModelConfig, "provider"> | string): AIProvider;
6
+ list(): readonly AIProvider[];
7
+ }
8
+ export declare function createProviderRegistry(providers?: readonly AIProvider[]): ProviderRegistry;
@@ -0,0 +1,25 @@
1
+ export function createProviderRegistry(providers = []) {
2
+ const byId = new Map();
3
+ const registry = {
4
+ register(provider) {
5
+ byId.set(provider.id, provider);
6
+ },
7
+ get(id) {
8
+ return byId.get(id);
9
+ },
10
+ resolve(model) {
11
+ const id = typeof model === "string" ? model : model.provider;
12
+ const provider = byId.get(id);
13
+ if (!provider)
14
+ throw new Error(`Unknown provider: ${id}`);
15
+ return provider;
16
+ },
17
+ list() {
18
+ return [...byId.values()];
19
+ },
20
+ };
21
+ for (const provider of providers)
22
+ registry.register(provider);
23
+ return registry;
24
+ }
25
+ //# sourceMappingURL=providers.js.map
@@ -0,0 +1,11 @@
1
+ import type { AgentEvent, ErrorInfo, Message, ProviderRequest, SessionEntry } from "./contracts.js";
2
+ export interface SecretRedactor {
3
+ redact<T>(value: T): T;
4
+ }
5
+ export declare function createSecretRedactor(secrets: readonly (string | undefined)[]): SecretRedactor;
6
+ export declare function redactMessage(message: Message, redactor?: SecretRedactor): Message;
7
+ export declare function redactAgentEvent(event: AgentEvent, redactor?: SecretRedactor): AgentEvent;
8
+ export declare function redactSessionEntry(entry: SessionEntry, redactor?: SecretRedactor): SessionEntry;
9
+ export declare function redactProviderRequest(request: ProviderRequest, redactor?: SecretRedactor): ProviderRequest;
10
+ export declare function redactSecrets<T>(value: T, secrets: readonly (string | undefined)[]): T;
11
+ export declare function errorToErrorInfo(error: unknown, secrets?: readonly (string | undefined)[]): ErrorInfo;