@templatical/ai-kit 0.50.0 → 0.51.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.
package/README.md CHANGED
@@ -57,6 +57,8 @@ AI needs a loaded template: the AI assistant and other entry points appear after
57
57
 
58
58
  See [Bring your own AI backend](https://docs.templatical.com/guide/bring-your-own-ai) for a complete Fetch-API server, model adapters for the Vercel AI SDK, OpenAI and Anthropic, and the wire for other languages.
59
59
 
60
+ Model adapters: `@templatical/ai-kit/adapters/anthropic` (`createAnthropicAdapter({ client: new Anthropic(), model })`), `/adapters/openai` (`createOpenAiAdapter({ client: new OpenAI(), model })`) and `/adapters/vercel` (`createVercelAdapter({ model })`). Install the SDK of the one you import; they're optional peers.
61
+
60
62
  ## License
61
63
 
62
64
  MIT
@@ -0,0 +1,31 @@
1
+ import { t as AiModelAdapter } from "../model-xJvGTfLB.js";
2
+ import Anthropic from "@anthropic-ai/sdk";
3
+ //#region src/adapters/anthropic.d.ts
4
+ type StreamParams = Anthropic.MessageStreamParams;
5
+ /** The one method the adapter calls: `new Anthropic()` fits it. */
6
+ export interface AnthropicAdapterClient {
7
+ messages: {
8
+ stream(body: StreamParams, options?: {
9
+ signal?: AbortSignal | null;
10
+ }): AsyncIterable<Anthropic.RawMessageStreamEvent> & {
11
+ finalMessage(): Promise<Anthropic.Message>;
12
+ };
13
+ };
14
+ }
15
+ export interface AnthropicAdapterOptions {
16
+ /** A client you construct: `new Anthropic()`, or one with its own `baseURL`. */
17
+ client: AnthropicAdapterClient;
18
+ /** The model id. No default. */
19
+ model: string;
20
+ /** `max_tokens`. Default 16_000. */
21
+ maxTokens?: number;
22
+ /** Prompt caching: a breakpoint after the system prompt plus automatic caching. Default true. */
23
+ cache?: boolean;
24
+ /** Further request fields, e.g. `output_config` or `metadata`. */
25
+ params?: Omit<StreamParams, "model" | "system" | "messages" | "tools" | "max_tokens" | "stream" | "cache_control">;
26
+ }
27
+ /** An `AiModelAdapter` over the Anthropic Messages API. */
28
+ export declare function createAnthropicAdapter(options: AnthropicAdapterOptions): AiModelAdapter;
29
+ //#endregion
30
+ export {};
31
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1,139 @@
1
+ import { a as refused, i as providerError, n as cutOff, r as ownState, s as toolInput, t as blobBase64 } from "../shared-BuhccY6c.js";
2
+ //#region src/adapters/anthropic.ts
3
+ const ADAPTER = "anthropic";
4
+ const CUT_OFF = /* @__PURE__ */ new Set(["max_tokens", "model_context_window_exceeded"]);
5
+ const EPHEMERAL = { type: "ephemeral" };
6
+ function toTool(tool) {
7
+ return {
8
+ name: tool.name,
9
+ description: tool.description,
10
+ input_schema: tool.inputSchema
11
+ };
12
+ }
13
+ async function toBlock(attachment) {
14
+ if (attachment.kind === "image-url") return {
15
+ type: "image",
16
+ source: {
17
+ type: "url",
18
+ url: attachment.url
19
+ }
20
+ };
21
+ const data = await blobBase64(attachment.data);
22
+ return attachment.kind === "pdf" ? {
23
+ type: "document",
24
+ source: {
25
+ type: "base64",
26
+ media_type: "application/pdf",
27
+ data
28
+ }
29
+ } : {
30
+ type: "image",
31
+ source: {
32
+ type: "base64",
33
+ media_type: attachment.mediaType,
34
+ data
35
+ }
36
+ };
37
+ }
38
+ async function toMessages(messages) {
39
+ const out = [];
40
+ for (const message of messages) if (message.role === "tool") {
41
+ const result = {
42
+ type: "tool_result",
43
+ tool_use_id: message.toolCallId,
44
+ content: message.content
45
+ };
46
+ const last = out.at(-1);
47
+ if (last?.role === "user" && Array.isArray(last.content) && last.content.every((block) => block.type === "tool_result")) last.content.push(result);
48
+ else out.push({
49
+ role: "user",
50
+ content: [result]
51
+ });
52
+ } else if (message.role === "assistant") {
53
+ const content = ownState(message.state, ADAPTER) ?? [...message.content ? [{
54
+ type: "text",
55
+ text: message.content
56
+ }] : [], ...(message.toolCalls ?? []).map((call) => ({
57
+ type: "tool_use",
58
+ id: call.id,
59
+ name: call.name,
60
+ input: toolInput(call.input)
61
+ }))];
62
+ if (content.length > 0) out.push({
63
+ role: "assistant",
64
+ content
65
+ });
66
+ } else {
67
+ const files = await Promise.all((message.attachments ?? []).map(toBlock));
68
+ out.push({
69
+ role: "user",
70
+ content: [...files, {
71
+ type: "text",
72
+ text: message.content
73
+ }]
74
+ });
75
+ }
76
+ return out;
77
+ }
78
+ /** An `AiModelAdapter` over the Anthropic Messages API. */
79
+ function createAnthropicAdapter(options) {
80
+ const cache = options.cache ?? true;
81
+ return { async *stream({ system, messages, tools, signal }) {
82
+ const body = {
83
+ ...options.params,
84
+ model: options.model,
85
+ max_tokens: options.maxTokens ?? 16e3,
86
+ system: [{
87
+ type: "text",
88
+ text: system,
89
+ ...cache ? { cache_control: EPHEMERAL } : {}
90
+ }],
91
+ messages: await toMessages(messages),
92
+ ...tools.length > 0 ? { tools: tools.map(toTool) } : {},
93
+ ...cache ? { cache_control: EPHEMERAL } : {}
94
+ };
95
+ let final;
96
+ try {
97
+ const stream = options.client.messages.stream(body, { signal });
98
+ for await (const event of stream) {
99
+ signal?.throwIfAborted();
100
+ if (event.type === "content_block_delta" && event.delta.type === "text_delta") yield {
101
+ type: "text",
102
+ text: event.delta.text
103
+ };
104
+ }
105
+ final = await stream.finalMessage();
106
+ signal?.throwIfAborted();
107
+ } catch (error) {
108
+ throw providerError(error);
109
+ }
110
+ if (final.stop_reason === "refusal") throw refused();
111
+ const calls = final.content.filter((block) => block.type === "tool_use");
112
+ if (calls.length > 0 && final.stop_reason !== null && CUT_OFF.has(final.stop_reason)) throw cutOff();
113
+ yield {
114
+ type: "state",
115
+ state: {
116
+ adapter: ADAPTER,
117
+ content: final.content
118
+ }
119
+ };
120
+ for (const block of calls) yield {
121
+ type: "tool-call",
122
+ id: block.id,
123
+ name: block.name,
124
+ input: block.input
125
+ };
126
+ const usage = final.usage;
127
+ yield {
128
+ type: "usage",
129
+ inputTokens: usage.input_tokens,
130
+ outputTokens: usage.output_tokens,
131
+ ...usage.cache_read_input_tokens != null ? { cacheReadInputTokens: usage.cache_read_input_tokens } : {},
132
+ ...usage.cache_creation_input_tokens != null ? { cacheWriteInputTokens: usage.cache_creation_input_tokens } : {}
133
+ };
134
+ } };
135
+ }
136
+ //#endregion
137
+ export { createAnthropicAdapter };
138
+
139
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","names":[],"sources":["../../src/adapters/anthropic.ts"],"sourcesContent":["// @templatical/ai-kit/adapters/anthropic — the kit's model contract over the\n// Anthropic SDK's `messages.stream`. Types only from the SDK: you pass the client.\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport type {\n AiModelAdapter,\n AiModelAttachment,\n AiModelMessage,\n AiModelPart,\n} from \"../server/model\";\nimport type { AiToolDefinition } from \"../server/tool\";\nimport {\n blobBase64,\n cutOff,\n ownState,\n providerError,\n refused,\n toolInput,\n} from \"./shared\";\n\ntype StreamParams = Anthropic.MessageStreamParams;\n\n/** The one method the adapter calls: `new Anthropic()` fits it. */\nexport interface AnthropicAdapterClient {\n messages: {\n stream(\n body: StreamParams,\n options?: { signal?: AbortSignal | null },\n ): AsyncIterable<Anthropic.RawMessageStreamEvent> & {\n finalMessage(): Promise<Anthropic.Message>;\n };\n };\n}\n\nexport interface AnthropicAdapterOptions {\n /** A client you construct: `new Anthropic()`, or one with its own `baseURL`. */\n client: AnthropicAdapterClient;\n /** The model id. No default. */\n model: string;\n /** `max_tokens`. Default 16_000. */\n maxTokens?: number;\n /** Prompt caching: a breakpoint after the system prompt plus automatic caching. Default true. */\n cache?: boolean;\n /** Further request fields, e.g. `output_config` or `metadata`. */\n params?: Omit<\n StreamParams,\n | \"model\"\n | \"system\"\n | \"messages\"\n | \"tools\"\n | \"max_tokens\"\n | \"stream\"\n | \"cache_control\"\n >;\n}\n\nconst ADAPTER = \"anthropic\";\nconst CUT_OFF = new Set([\"max_tokens\", \"model_context_window_exceeded\"]);\nconst EPHEMERAL = { type: \"ephemeral\" } as const;\n\nfunction toTool(tool: AiToolDefinition): Anthropic.Tool {\n return {\n name: tool.name,\n description: tool.description,\n input_schema: tool.inputSchema as Anthropic.Tool.InputSchema,\n };\n}\n\nasync function toBlock(\n attachment: AiModelAttachment,\n): Promise<Anthropic.ContentBlockParam> {\n if (attachment.kind === \"image-url\")\n return { type: \"image\", source: { type: \"url\", url: attachment.url } };\n const data = await blobBase64(attachment.data);\n return attachment.kind === \"pdf\"\n ? {\n type: \"document\",\n source: { type: \"base64\", media_type: \"application/pdf\", data },\n }\n : {\n type: \"image\",\n source: {\n type: \"base64\",\n media_type:\n attachment.mediaType as Anthropic.Base64ImageSource[\"media_type\"],\n data,\n },\n };\n}\n\nasync function toMessages(\n messages: AiModelMessage[],\n): Promise<Anthropic.MessageParam[]> {\n const out: Anthropic.MessageParam[] = [];\n for (const message of messages) {\n if (message.role === \"tool\") {\n const result: Anthropic.ToolResultBlockParam = {\n type: \"tool_result\",\n tool_use_id: message.toolCallId,\n content: message.content,\n };\n const last = out.at(-1);\n // All results of one step go back in one user message.\n if (\n last?.role === \"user\" &&\n Array.isArray(last.content) &&\n last.content.every((block) => block.type === \"tool_result\")\n ) {\n last.content.push(result);\n } else {\n out.push({ role: \"user\", content: [result] });\n }\n } else if (message.role === \"assistant\") {\n const own = ownState<Anthropic.ContentBlockParam>(message.state, ADAPTER);\n const content = own ?? [\n ...(message.content\n ? [{ type: \"text\" as const, text: message.content }]\n : []),\n ...(message.toolCalls ?? []).map((call) => ({\n type: \"tool_use\" as const,\n id: call.id,\n name: call.name,\n input: toolInput(call.input),\n })),\n ];\n // An empty assistant turn would be refused; skipping it keeps the history valid.\n if (content.length > 0) out.push({ role: \"assistant\", content });\n } else {\n const files = await Promise.all((message.attachments ?? []).map(toBlock));\n out.push({\n role: \"user\",\n content: [...files, { type: \"text\", text: message.content }],\n });\n }\n }\n return out;\n}\n\n/** An `AiModelAdapter` over the Anthropic Messages API. */\nexport function createAnthropicAdapter(\n options: AnthropicAdapterOptions,\n): AiModelAdapter {\n const cache = options.cache ?? true;\n return {\n async *stream({\n system,\n messages,\n tools,\n signal,\n }): AsyncIterable<AiModelPart> {\n const body: StreamParams = {\n ...options.params,\n model: options.model,\n max_tokens: options.maxTokens ?? 16_000,\n system: [\n {\n type: \"text\",\n text: system,\n ...(cache ? { cache_control: EPHEMERAL } : {}),\n },\n ],\n messages: await toMessages(messages),\n ...(tools.length > 0 ? { tools: tools.map(toTool) } : {}),\n ...(cache ? { cache_control: EPHEMERAL } : {}),\n };\n let final: Anthropic.Message;\n try {\n const stream = options.client.messages.stream(body, { signal });\n for await (const event of stream) {\n signal?.throwIfAborted();\n if (\n event.type === \"content_block_delta\" &&\n event.delta.type === \"text_delta\"\n )\n yield { type: \"text\", text: event.delta.text };\n }\n final = await stream.finalMessage();\n signal?.throwIfAborted();\n } catch (error) {\n throw providerError(error);\n }\n if (final.stop_reason === \"refusal\") throw refused();\n const calls = final.content.filter(\n (block): block is Anthropic.ToolUseBlock => block.type === \"tool_use\",\n );\n if (\n calls.length > 0 &&\n final.stop_reason !== null &&\n CUT_OFF.has(final.stop_reason)\n )\n throw cutOff();\n yield {\n type: \"state\",\n state: { adapter: ADAPTER, content: final.content },\n };\n for (const block of calls) {\n yield {\n type: \"tool-call\",\n id: block.id,\n name: block.name,\n input: block.input as Record<string, unknown>,\n };\n }\n const usage = final.usage;\n yield {\n type: \"usage\",\n inputTokens: usage.input_tokens,\n outputTokens: usage.output_tokens,\n ...(usage.cache_read_input_tokens != null\n ? { cacheReadInputTokens: usage.cache_read_input_tokens }\n : {}),\n ...(usage.cache_creation_input_tokens != null\n ? { cacheWriteInputTokens: usage.cache_creation_input_tokens }\n : {}),\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { t as AiModelAdapter } from "../model-xJvGTfLB.js";
2
+ import OpenAI from "openai";
3
+ //#region src/adapters/openai.d.ts
4
+ type CreateParams = OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming;
5
+ /** The one method the adapter calls: `new OpenAI()` and `new AzureOpenAI()` fit it. */
6
+ export interface OpenAiAdapterClient {
7
+ chat: {
8
+ completions: {
9
+ create(body: CreateParams, options?: {
10
+ signal?: AbortSignal | null;
11
+ }): PromiseLike<AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>>;
12
+ };
13
+ };
14
+ }
15
+ export interface OpenAiAdapterOptions {
16
+ /** A client you construct: `new OpenAI()`, `new AzureOpenAI()`, or one with a `baseURL` for an OpenAI-compatible server. */
17
+ client: OpenAiAdapterClient;
18
+ /** The model id. No default. */
19
+ model: string;
20
+ /** `max_completion_tokens`. Default: the API's own. */
21
+ maxTokens?: number;
22
+ /** Further request fields, e.g. `reasoning_effort`. */
23
+ params?: Omit<CreateParams, "model" | "messages" | "tools" | "stream" | "stream_options" | "max_completion_tokens" | "max_tokens" | "n">;
24
+ }
25
+ /** An `AiModelAdapter` over OpenAI's Chat Completions API. */
26
+ export declare function createOpenAiAdapter(options: OpenAiAdapterOptions): AiModelAdapter;
27
+ //#endregion
28
+ export {};
29
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1,131 @@
1
+ import { a as refused, i as providerError, n as cutOff, o as toolArguments, t as blobBase64 } from "../shared-BuhccY6c.js";
2
+ //#region src/adapters/openai.ts
3
+ function toTool(tool) {
4
+ return {
5
+ type: "function",
6
+ function: {
7
+ name: tool.name,
8
+ description: tool.description,
9
+ parameters: tool.inputSchema
10
+ }
11
+ };
12
+ }
13
+ async function toPart(attachment, index) {
14
+ if (attachment.kind === "image-url") return {
15
+ type: "image_url",
16
+ image_url: { url: attachment.url }
17
+ };
18
+ const url = `data:${attachment.mediaType};base64,${await blobBase64(attachment.data)}`;
19
+ return attachment.kind === "pdf" ? {
20
+ type: "file",
21
+ file: {
22
+ filename: `attachment-${index + 1}.pdf`,
23
+ file_data: url
24
+ }
25
+ } : {
26
+ type: "image_url",
27
+ image_url: { url }
28
+ };
29
+ }
30
+ async function toMessage(message) {
31
+ if (message.role === "tool") return {
32
+ role: "tool",
33
+ tool_call_id: message.toolCallId,
34
+ content: message.content
35
+ };
36
+ if (message.role === "assistant") {
37
+ if (message.content.length === 0 && !message.toolCalls?.length) return null;
38
+ return {
39
+ role: "assistant",
40
+ content: message.content.length > 0 ? message.content : null,
41
+ ...message.toolCalls?.length ? { tool_calls: message.toolCalls.map((call) => ({
42
+ id: call.id,
43
+ type: "function",
44
+ function: {
45
+ name: call.name,
46
+ arguments: toolArguments(call.input)
47
+ }
48
+ })) } : {}
49
+ };
50
+ }
51
+ return {
52
+ role: "user",
53
+ content: [...await Promise.all((message.attachments ?? []).map(toPart)), {
54
+ type: "text",
55
+ text: message.content
56
+ }]
57
+ };
58
+ }
59
+ /** An `AiModelAdapter` over OpenAI's Chat Completions API. */
60
+ function createOpenAiAdapter(options) {
61
+ return { async *stream({ system, messages, tools, signal }) {
62
+ const body = {
63
+ ...options.params,
64
+ model: options.model,
65
+ stream: true,
66
+ stream_options: { include_usage: true },
67
+ ...options.maxTokens !== void 0 ? { max_completion_tokens: options.maxTokens } : {},
68
+ messages: [{
69
+ role: "system",
70
+ content: system
71
+ }, ...(await Promise.all(messages.map(toMessage))).filter((message) => message !== null)],
72
+ ...tools.length > 0 ? { tools: tools.map(toTool) } : {}
73
+ };
74
+ const calls = /* @__PURE__ */ new Map();
75
+ let finish = null;
76
+ let usage;
77
+ try {
78
+ const stream = await options.client.chat.completions.create(body, { signal });
79
+ for await (const chunk of stream) {
80
+ signal?.throwIfAborted();
81
+ const choice = chunk.choices.find((choice) => choice.index === 0);
82
+ const delta = choice?.delta;
83
+ if (delta?.refusal) throw refused();
84
+ if (delta?.content) yield {
85
+ type: "text",
86
+ text: delta.content
87
+ };
88
+ for (const call of delta?.tool_calls ?? []) {
89
+ const seen = calls.get(call.index) ?? {
90
+ id: "",
91
+ name: "",
92
+ args: ""
93
+ };
94
+ calls.set(call.index, {
95
+ id: call.id ?? seen.id,
96
+ name: call.function?.name ?? seen.name,
97
+ args: seen.args + (call.function?.arguments ?? "")
98
+ });
99
+ }
100
+ if (choice?.finish_reason) finish = choice.finish_reason;
101
+ if (chunk.usage) {
102
+ const cached = chunk.usage.prompt_tokens_details?.cached_tokens;
103
+ usage = {
104
+ inputTokens: chunk.usage.prompt_tokens - (cached ?? 0),
105
+ outputTokens: chunk.usage.completion_tokens,
106
+ ...cached !== void 0 ? { cacheReadInputTokens: cached } : {}
107
+ };
108
+ }
109
+ }
110
+ signal?.throwIfAborted();
111
+ } catch (error) {
112
+ throw providerError(error);
113
+ }
114
+ if (finish === "content_filter") throw refused();
115
+ if (finish === "length" && calls.size > 0) throw cutOff();
116
+ for (const [, call] of [...calls].sort(([a], [b]) => a - b)) yield {
117
+ type: "tool-call",
118
+ id: call.id,
119
+ name: call.name,
120
+ input: call.args
121
+ };
122
+ if (usage) yield {
123
+ type: "usage",
124
+ ...usage
125
+ };
126
+ } };
127
+ }
128
+ //#endregion
129
+ export { createOpenAiAdapter };
130
+
131
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","names":[],"sources":["../../src/adapters/openai.ts"],"sourcesContent":["// @templatical/ai-kit/adapters/openai — the kit's model contract over OpenAI's\n// Chat Completions API, which OpenAI-compatible servers implement too. Types\n// only from the SDK: you pass the client.\nimport type OpenAI from \"openai\";\nimport type {\n AiModelAdapter,\n AiModelAttachment,\n AiModelMessage,\n AiModelPart,\n AiUsage,\n} from \"../server/model\";\nimport type { AiToolDefinition } from \"../server/tool\";\nimport {\n blobBase64,\n cutOff,\n providerError,\n refused,\n toolArguments,\n} from \"./shared\";\n\ntype CreateParams = OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming;\ntype MessageParam = OpenAI.Chat.Completions.ChatCompletionMessageParam;\ntype ContentPart = OpenAI.Chat.Completions.ChatCompletionContentPart;\n\n/** The one method the adapter calls: `new OpenAI()` and `new AzureOpenAI()` fit it. */\nexport interface OpenAiAdapterClient {\n chat: {\n completions: {\n create(\n body: CreateParams,\n options?: { signal?: AbortSignal | null },\n ): PromiseLike<\n AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>\n >;\n };\n };\n}\n\nexport interface OpenAiAdapterOptions {\n /** A client you construct: `new OpenAI()`, `new AzureOpenAI()`, or one with a `baseURL` for an OpenAI-compatible server. */\n client: OpenAiAdapterClient;\n /** The model id. No default. */\n model: string;\n /** `max_completion_tokens`. Default: the API's own. */\n maxTokens?: number;\n /** Further request fields, e.g. `reasoning_effort`. */\n params?: Omit<\n CreateParams,\n | \"model\"\n | \"messages\"\n | \"tools\"\n | \"stream\"\n | \"stream_options\"\n | \"max_completion_tokens\"\n | \"max_tokens\"\n | \"n\"\n >;\n}\n\nfunction toTool(\n tool: AiToolDefinition,\n): OpenAI.Chat.Completions.ChatCompletionTool {\n return {\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.inputSchema,\n },\n };\n}\n\nasync function toPart(\n attachment: AiModelAttachment,\n index: number,\n): Promise<ContentPart> {\n if (attachment.kind === \"image-url\")\n return { type: \"image_url\", image_url: { url: attachment.url } };\n const url = `data:${attachment.mediaType};base64,${await blobBase64(attachment.data)}`;\n return attachment.kind === \"pdf\"\n ? {\n type: \"file\",\n file: { filename: `attachment-${index + 1}.pdf`, file_data: url },\n }\n : { type: \"image_url\", image_url: { url } };\n}\n\nasync function toMessage(\n message: AiModelMessage,\n): Promise<MessageParam | null> {\n if (message.role === \"tool\")\n return {\n role: \"tool\",\n tool_call_id: message.toolCallId,\n content: message.content,\n };\n if (message.role === \"assistant\") {\n if (message.content.length === 0 && !message.toolCalls?.length) return null;\n return {\n role: \"assistant\",\n content: message.content.length > 0 ? message.content : null,\n ...(message.toolCalls?.length\n ? {\n tool_calls: message.toolCalls.map((call) => ({\n id: call.id,\n type: \"function\" as const,\n function: {\n name: call.name,\n arguments: toolArguments(call.input),\n },\n })),\n }\n : {}),\n };\n }\n const files = await Promise.all((message.attachments ?? []).map(toPart));\n return {\n role: \"user\",\n content: [...files, { type: \"text\", text: message.content }],\n };\n}\n\n/** An `AiModelAdapter` over OpenAI's Chat Completions API. */\nexport function createOpenAiAdapter(\n options: OpenAiAdapterOptions,\n): AiModelAdapter {\n return {\n async *stream({\n system,\n messages,\n tools,\n signal,\n }): AsyncIterable<AiModelPart> {\n const body: CreateParams = {\n ...options.params,\n model: options.model,\n stream: true,\n stream_options: { include_usage: true },\n ...(options.maxTokens !== undefined\n ? { max_completion_tokens: options.maxTokens }\n : {}),\n messages: [\n { role: \"system\", content: system },\n ...(await Promise.all(messages.map(toMessage))).filter(\n (message) => message !== null,\n ),\n ],\n ...(tools.length > 0 ? { tools: tools.map(toTool) } : {}),\n };\n const calls = new Map<\n number,\n { id: string; name: string; args: string }\n >();\n let finish: string | null = null;\n let usage: AiUsage | undefined;\n try {\n const stream = await options.client.chat.completions.create(body, {\n signal,\n });\n for await (const chunk of stream) {\n signal?.throwIfAborted();\n const choice = chunk.choices.find((choice) => choice.index === 0);\n const delta = choice?.delta;\n if (delta?.refusal) throw refused();\n if (delta?.content) yield { type: \"text\", text: delta.content };\n for (const call of delta?.tool_calls ?? []) {\n const seen = calls.get(call.index) ?? {\n id: \"\",\n name: \"\",\n args: \"\",\n };\n calls.set(call.index, {\n id: call.id ?? seen.id,\n name: call.function?.name ?? seen.name,\n args: seen.args + (call.function?.arguments ?? \"\"),\n });\n }\n if (choice?.finish_reason) finish = choice.finish_reason;\n if (chunk.usage) {\n const cached = chunk.usage.prompt_tokens_details?.cached_tokens;\n usage = {\n inputTokens: chunk.usage.prompt_tokens - (cached ?? 0),\n outputTokens: chunk.usage.completion_tokens,\n ...(cached !== undefined ? { cacheReadInputTokens: cached } : {}),\n };\n }\n }\n signal?.throwIfAborted();\n } catch (error) {\n throw providerError(error);\n }\n if (finish === \"content_filter\") throw refused();\n if (finish === \"length\" && calls.size > 0) throw cutOff();\n for (const [, call] of [...calls].sort(([a], [b]) => a - b)) {\n yield {\n type: \"tool-call\",\n id: call.id,\n name: call.name,\n input: call.args,\n };\n }\n if (usage) yield { type: \"usage\", ...usage };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { t as AiModelAdapter } from "../model-xJvGTfLB.js";
2
+ import { LanguageModel, streamText } from "ai";
3
+ //#region src/adapters/vercel.d.ts
4
+ type StreamTextParams = Parameters<typeof streamText>[0];
5
+ export interface VercelAdapterOptions {
6
+ /** An AI SDK model, e.g. `anthropic('…')` from `@ai-sdk/anthropic`, or a gateway model id. */
7
+ model: LanguageModel;
8
+ /** `maxOutputTokens`. Default: the provider's own. */
9
+ maxTokens?: number;
10
+ /** Further `streamText` options, e.g. `providerOptions` or `maxRetries`. */
11
+ params?: Omit<StreamTextParams, "model" | "system" | "instructions" | "messages" | "prompt" | "tools" | "abortSignal" | "maxOutputTokens">;
12
+ }
13
+ /** An `AiModelAdapter` over the Vercel AI SDK. */
14
+ export declare function createVercelAdapter(options: VercelAdapterOptions): AiModelAdapter;
15
+ //#endregion
16
+ export {};
17
+ //# sourceMappingURL=vercel.d.ts.map
@@ -0,0 +1,137 @@
1
+ import { a as refused, i as providerError, n as cutOff, r as ownState, s as toolInput } from "../shared-BuhccY6c.js";
2
+ import { jsonSchema, streamText, tool } from "ai";
3
+ //#region src/adapters/vercel.ts
4
+ const ADAPTER = "vercel";
5
+ function toTools(tools) {
6
+ return Object.fromEntries(tools.map((definition) => [definition.name, tool({
7
+ description: definition.description,
8
+ inputSchema: jsonSchema(definition.inputSchema)
9
+ })]));
10
+ }
11
+ async function toFilePart(attachment) {
12
+ if (attachment.kind === "image-url") return {
13
+ type: "file",
14
+ data: new URL(attachment.url),
15
+ mediaType: "image"
16
+ };
17
+ return {
18
+ type: "file",
19
+ data: new Uint8Array(await attachment.data.arrayBuffer()),
20
+ mediaType: attachment.kind === "pdf" ? "application/pdf" : attachment.mediaType
21
+ };
22
+ }
23
+ async function toMessages(messages) {
24
+ const out = [];
25
+ for (const message of messages) if (message.role === "tool") {
26
+ const result = {
27
+ type: "tool-result",
28
+ toolCallId: message.toolCallId,
29
+ toolName: message.name,
30
+ output: {
31
+ type: "text",
32
+ value: message.content
33
+ }
34
+ };
35
+ const last = out.at(-1);
36
+ if (last?.role === "tool") last.content.push(result);
37
+ else out.push({
38
+ role: "tool",
39
+ content: [result]
40
+ });
41
+ } else if (message.role === "assistant") {
42
+ const content = ownState(message.state, ADAPTER) ?? [...message.content ? [{
43
+ type: "text",
44
+ text: message.content
45
+ }] : [], ...(message.toolCalls ?? []).map((call) => ({
46
+ type: "tool-call",
47
+ toolCallId: call.id,
48
+ toolName: call.name,
49
+ input: toolInput(call.input)
50
+ }))];
51
+ if (content.length > 0) out.push({
52
+ role: "assistant",
53
+ content
54
+ });
55
+ } else {
56
+ const files = await Promise.all((message.attachments ?? []).map(toFilePart));
57
+ out.push({
58
+ role: "user",
59
+ content: [...files, {
60
+ type: "text",
61
+ text: message.content
62
+ }]
63
+ });
64
+ }
65
+ return out;
66
+ }
67
+ /** An `AiModelAdapter` over the Vercel AI SDK. */
68
+ function createVercelAdapter(options) {
69
+ return { async *stream({ system, messages, tools, signal }) {
70
+ const calls = [];
71
+ let toolInputStarted = false;
72
+ let finishReason;
73
+ let usage;
74
+ let state;
75
+ try {
76
+ const result = streamText({
77
+ ...options.params,
78
+ model: options.model,
79
+ instructions: system,
80
+ messages: await toMessages(messages),
81
+ ...tools.length > 0 ? { tools: toTools(tools) } : {},
82
+ ...options.maxTokens !== void 0 ? { maxOutputTokens: options.maxTokens } : {},
83
+ abortSignal: signal
84
+ });
85
+ for await (const part of result.stream) {
86
+ signal?.throwIfAborted();
87
+ if (part.type === "text-delta") yield {
88
+ type: "text",
89
+ text: part.text
90
+ };
91
+ else if (part.type === "tool-input-start") toolInputStarted = true;
92
+ else if (part.type === "tool-call") calls.push({
93
+ id: part.toolCallId,
94
+ name: part.toolName,
95
+ input: part.input
96
+ });
97
+ else if (part.type === "finish") {
98
+ finishReason = part.finishReason;
99
+ usage = part.totalUsage;
100
+ } else if (part.type === "error") throw part.error;
101
+ else if (part.type === "abort") throw new Error(part.reason ?? "The AI SDK aborted the stream.");
102
+ }
103
+ signal?.throwIfAborted();
104
+ if (finishReason === "content-filter") throw refused();
105
+ if (finishReason === "length" && (toolInputStarted || calls.length > 0)) throw cutOff();
106
+ const assistant = (await result.responseMessages).find((message) => message.role === "assistant");
107
+ if (assistant && typeof assistant.content !== "string") state = assistant.content;
108
+ } catch (error) {
109
+ throw providerError(error);
110
+ }
111
+ if (state) yield {
112
+ type: "state",
113
+ state: {
114
+ adapter: ADAPTER,
115
+ content: state
116
+ }
117
+ };
118
+ for (const call of calls) yield {
119
+ type: "tool-call",
120
+ ...call
121
+ };
122
+ if (usage) {
123
+ const details = usage.inputTokenDetails;
124
+ yield {
125
+ type: "usage",
126
+ inputTokens: details?.noCacheTokens ?? (usage.inputTokens !== void 0 ? Math.max(0, usage.inputTokens - (details?.cacheReadTokens ?? 0) - (details?.cacheWriteTokens ?? 0)) : 0),
127
+ outputTokens: usage.outputTokens ?? 0,
128
+ ...details?.cacheReadTokens !== void 0 ? { cacheReadInputTokens: details.cacheReadTokens } : {},
129
+ ...details?.cacheWriteTokens !== void 0 ? { cacheWriteInputTokens: details.cacheWriteTokens } : {}
130
+ };
131
+ }
132
+ } };
133
+ }
134
+ //#endregion
135
+ export { createVercelAdapter };
136
+
137
+ //# sourceMappingURL=vercel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel.js","names":[],"sources":["../../src/adapters/vercel.ts"],"sourcesContent":["// @templatical/ai-kit/adapters/vercel — the kit's model contract over the\n// Vercel AI SDK's `streamText`, for any AI SDK provider model.\nimport {\n jsonSchema,\n streamText,\n tool,\n type AssistantModelMessage,\n type LanguageModel,\n type ModelMessage,\n type ToolSet,\n} from \"ai\";\nimport type {\n AiModelAdapter,\n AiModelAttachment,\n AiModelMessage,\n AiModelPart,\n AiModelToolCall,\n} from \"../server/model\";\nimport type { AiToolDefinition } from \"../server/tool\";\nimport { cutOff, ownState, providerError, refused, toolInput } from \"./shared\";\n\ntype StreamTextParams = Parameters<typeof streamText>[0];\ntype AssistantPart = Exclude<AssistantModelMessage[\"content\"], string>[number];\n\nexport interface VercelAdapterOptions {\n /** An AI SDK model, e.g. `anthropic('…')` from `@ai-sdk/anthropic`, or a gateway model id. */\n model: LanguageModel;\n /** `maxOutputTokens`. Default: the provider's own. */\n maxTokens?: number;\n /** Further `streamText` options, e.g. `providerOptions` or `maxRetries`. */\n params?: Omit<\n StreamTextParams,\n | \"model\"\n | \"system\"\n | \"instructions\"\n | \"messages\"\n | \"prompt\"\n | \"tools\"\n | \"abortSignal\"\n | \"maxOutputTokens\"\n >;\n}\n\nconst ADAPTER = \"vercel\";\n\nfunction toTools(tools: AiToolDefinition[]): ToolSet {\n return Object.fromEntries(\n tools.map((definition) => [\n definition.name,\n // No `execute`: the kit applies the call, so the SDK stops after one step.\n tool({\n description: definition.description,\n inputSchema: jsonSchema(definition.inputSchema),\n }),\n ]),\n );\n}\n\nasync function toFilePart(attachment: AiModelAttachment) {\n if (attachment.kind === \"image-url\")\n return {\n type: \"file\" as const,\n data: new URL(attachment.url),\n mediaType: \"image\",\n };\n const data = new Uint8Array(await attachment.data.arrayBuffer());\n return {\n type: \"file\" as const,\n data,\n mediaType:\n attachment.kind === \"pdf\" ? \"application/pdf\" : attachment.mediaType,\n };\n}\n\nasync function toMessages(messages: AiModelMessage[]): Promise<ModelMessage[]> {\n const out: ModelMessage[] = [];\n for (const message of messages) {\n if (message.role === \"tool\") {\n const result = {\n type: \"tool-result\" as const,\n toolCallId: message.toolCallId,\n toolName: message.name,\n output: { type: \"text\" as const, value: message.content },\n };\n const last = out.at(-1);\n // All results of one step go back in one tool message.\n if (last?.role === \"tool\") last.content.push(result);\n else out.push({ role: \"tool\", content: [result] });\n } else if (message.role === \"assistant\") {\n const content: AssistantPart[] = ownState<AssistantPart>(\n message.state,\n ADAPTER,\n ) ?? [\n ...(message.content\n ? [{ type: \"text\" as const, text: message.content }]\n : []),\n ...(message.toolCalls ?? []).map((call) => ({\n type: \"tool-call\" as const,\n toolCallId: call.id,\n toolName: call.name,\n input: toolInput(call.input),\n })),\n ];\n if (content.length > 0) out.push({ role: \"assistant\", content });\n } else {\n const files = await Promise.all(\n (message.attachments ?? []).map(toFilePart),\n );\n out.push({\n role: \"user\",\n content: [...files, { type: \"text\", text: message.content }],\n });\n }\n }\n return out;\n}\n\n/** An `AiModelAdapter` over the Vercel AI SDK. */\nexport function createVercelAdapter(\n options: VercelAdapterOptions,\n): AiModelAdapter {\n return {\n async *stream({\n system,\n messages,\n tools,\n signal,\n }): AsyncIterable<AiModelPart> {\n const calls: AiModelToolCall[] = [];\n let toolInputStarted = false;\n let finishReason: string | undefined;\n let usage:\n | {\n inputTokens?: number;\n outputTokens?: number;\n inputTokenDetails?: {\n noCacheTokens?: number;\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n };\n }\n | undefined;\n let state: AssistantPart[] | undefined;\n try {\n const result = streamText({\n ...options.params,\n model: options.model,\n instructions: system,\n messages: await toMessages(messages),\n ...(tools.length > 0 ? { tools: toTools(tools) } : {}),\n ...(options.maxTokens !== undefined\n ? { maxOutputTokens: options.maxTokens }\n : {}),\n abortSignal: signal,\n });\n for await (const part of result.stream) {\n signal?.throwIfAborted();\n if (part.type === \"text-delta\")\n yield { type: \"text\", text: part.text };\n else if (part.type === \"tool-input-start\") toolInputStarted = true;\n else if (part.type === \"tool-call\")\n calls.push({\n id: part.toolCallId,\n name: part.toolName,\n input: part.input as Record<string, unknown>,\n });\n else if (part.type === \"finish\") {\n finishReason = part.finishReason;\n usage = part.totalUsage;\n } else if (part.type === \"error\") throw part.error;\n // Our own signal is checked at the top of the loop, so this abort\n // came from the SDK itself, e.g. `params.timeout`.\n else if (part.type === \"abort\")\n throw new Error(part.reason ?? \"The AI SDK aborted the stream.\");\n }\n signal?.throwIfAborted();\n if (finishReason === \"content-filter\") throw refused();\n if (finishReason === \"length\" && (toolInputStarted || calls.length > 0))\n throw cutOff();\n const assistant = (await result.responseMessages).find(\n (message) => message.role === \"assistant\",\n );\n if (assistant && typeof assistant.content !== \"string\")\n state = assistant.content;\n } catch (error) {\n throw providerError(error);\n }\n if (state)\n yield { type: \"state\", state: { adapter: ADAPTER, content: state } };\n for (const call of calls) yield { type: \"tool-call\", ...call };\n if (usage) {\n const details = usage.inputTokenDetails;\n yield {\n type: \"usage\",\n inputTokens:\n details?.noCacheTokens ??\n (usage.inputTokens !== undefined\n ? Math.max(\n 0,\n usage.inputTokens -\n (details?.cacheReadTokens ?? 0) -\n (details?.cacheWriteTokens ?? 0),\n )\n : 0),\n outputTokens: usage.outputTokens ?? 0,\n ...(details?.cacheReadTokens !== undefined\n ? { cacheReadInputTokens: details.cacheReadTokens }\n : {}),\n ...(details?.cacheWriteTokens !== undefined\n ? { cacheWriteInputTokens: details.cacheWriteTokens }\n : {}),\n };\n }\n },\n };\n}\n"],"mappings":"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"}
@@ -471,6 +471,13 @@ function opsArgument(input) {
471
471
  return;
472
472
  }
473
473
  }
474
+ /** Adds a step's usage; a cache field appears once some step reports it. */
475
+ function addUsage(total, step) {
476
+ total.inputTokens += step.inputTokens;
477
+ total.outputTokens += step.outputTokens;
478
+ if (step.cacheReadInputTokens !== void 0) total.cacheReadInputTokens = (total.cacheReadInputTokens ?? 0) + step.cacheReadInputTokens;
479
+ if (step.cacheWriteInputTokens !== void 0) total.cacheWriteInputTokens = (total.cacheWriteInputTokens ?? 0) + step.cacheWriteInputTokens;
480
+ }
474
481
  /**
475
482
  * The reference editing loop (A1 §5): up to `maxSteps` model calls; text
476
483
  * streams as `text` events, each accepted `apply_operations` call as an
@@ -501,6 +508,7 @@ async function runEditingTurn(options) {
501
508
  for (let step = 0; step < maxSteps; step++) {
502
509
  signal?.throwIfAborted();
503
510
  let stepText = "";
511
+ let stepState;
504
512
  const calls = [];
505
513
  for await (const part of model.stream({
506
514
  system: options.system ?? editingInstructions,
@@ -521,15 +529,14 @@ async function runEditingTurn(options) {
521
529
  name: part.name,
522
530
  input: part.input
523
531
  });
524
- else {
525
- usage.inputTokens += part.inputTokens;
526
- usage.outputTokens += part.outputTokens;
527
- }
532
+ else if (part.type === "state") stepState = part.state;
533
+ else addUsage(usage, part);
528
534
  }
529
535
  messages.push({
530
536
  role: "assistant",
531
537
  content: stepText,
532
- ...calls.length > 0 ? { toolCalls: calls } : {}
538
+ ...calls.length > 0 ? { toolCalls: calls } : {},
539
+ ...stepState !== void 0 ? { state: stepState } : {}
533
540
  });
534
541
  if (calls.length === 0) break;
535
542
  for (const call of calls) {
@@ -646,4 +653,4 @@ function checkAltText(answer, { maxLength }) {
646
653
  //#endregion
647
654
  export { mergeTagTokens as C, FIX_RULE_GUIDANCE as E, flattenMergeTags as S, unknownMergeTags as T, buildEditingPrompt as _, checkRewrite as a, editingInstructions as b, createEditingSession as c, opRefusal as d, contentRuleRefusal as f, buildChatRequest as g, buildAltTextRequest as h, checkAltTextAnswer as i, applyOperationsTool as l, REWRITE_PRESET_INSTRUCTIONS as m, buildRewriteRepairPrompt as n, runEditingTurn as o, walkBlocks as p, checkAltText as r, EDITING_SESSION_LIMITS as s, buildAltTextRepairRequest as t, describeOp as u, buildFixRequest as v, mergeTagTokensIn as w, rewriteInstructions as x, buildRewritePrompt as y };
648
655
 
649
- //# sourceMappingURL=checks-Blbakkgc.js.map
656
+ //# sourceMappingURL=checks-DvLD3892.js.map