@templatical/ai-kit 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/adapters/anthropic.d.ts +31 -0
- package/dist/adapters/anthropic.js +139 -0
- package/dist/adapters/anthropic.js.map +1 -0
- package/dist/adapters/openai.d.ts +29 -0
- package/dist/adapters/openai.js +131 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/adapters/vercel.d.ts +17 -0
- package/dist/adapters/vercel.js +137 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/{checks-Blbakkgc.js → checks-DvLD3892.js} +13 -6
- package/dist/{checks-Blbakkgc.js.map → checks-DvLD3892.js.map} +1 -1
- package/dist/client/index.js +3 -1
- package/dist/errors-DWshWx18.js +55 -0
- package/dist/errors-DWshWx18.js.map +1 -0
- package/dist/{http-provider-BoRkCd7H.js → http-provider-Cx2ZcLRB.js} +5 -54
- package/dist/http-provider-Cx2ZcLRB.js.map +1 -0
- package/dist/{model-BuHDCTlM.d.ts → model-xJvGTfLB.d.ts} +21 -11
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/shared-BuhccY6c.js +61 -0
- package/dist/shared-BuhccY6c.js.map +1 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +4 -3
- package/dist/testing/index.js.map +1 -1
- package/package.json +33 -2
- package/dist/http-provider-BoRkCd7H.js.map +0 -1
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
|
-
|
|
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-
|
|
656
|
+
//# sourceMappingURL=checks-DvLD3892.js.map
|