@graphorin/provider 0.5.0 → 0.6.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/CHANGELOG.md +56 -0
- package/README.md +14 -9
- package/dist/adapters/llamacpp-server.d.ts +1 -1
- package/dist/adapters/llamacpp-server.js +1 -1
- package/dist/adapters/llamacpp-server.js.map +1 -1
- package/dist/adapters/ollama.d.ts.map +1 -1
- package/dist/adapters/ollama.js +3 -2
- package/dist/adapters/ollama.js.map +1 -1
- package/dist/adapters/openai-compatible.d.ts +14 -1
- package/dist/adapters/openai-compatible.d.ts.map +1 -1
- package/dist/adapters/openai-compatible.js +2 -0
- package/dist/adapters/openai-compatible.js.map +1 -1
- package/dist/adapters/vercel-messages.js +245 -0
- package/dist/adapters/vercel-messages.js.map +1 -0
- package/dist/adapters/vercel.d.ts +10 -8
- package/dist/adapters/vercel.d.ts.map +1 -1
- package/dist/adapters/vercel.js +54 -14
- package/dist/adapters/vercel.js.map +1 -1
- package/dist/counters/anthropic-wire.js +149 -0
- package/dist/counters/anthropic-wire.js.map +1 -0
- package/dist/counters/anthropic.d.ts +6 -1
- package/dist/counters/anthropic.d.ts.map +1 -1
- package/dist/counters/anthropic.js +25 -4
- package/dist/counters/anthropic.js.map +1 -1
- package/dist/counters/bedrock.js.map +1 -1
- package/dist/counters/dispatcher.d.ts.map +1 -1
- package/dist/counters/dispatcher.js +6 -4
- package/dist/counters/dispatcher.js.map +1 -1
- package/dist/counters/google.js.map +1 -1
- package/dist/counters/js-tiktoken.js.map +1 -1
- package/dist/errors/errors.d.ts +39 -11
- package/dist/errors/errors.d.ts.map +1 -1
- package/dist/errors/errors.js +54 -11
- package/dist/errors/errors.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/internal/abort.js +2 -2
- package/dist/internal/abort.js.map +1 -1
- package/dist/internal/http.js +19 -4
- package/dist/internal/http.js.map +1 -1
- package/dist/internal/openai-shaped.js +8 -3
- package/dist/internal/openai-shaped.js.map +1 -1
- package/dist/middleware/compose.d.ts +6 -6
- package/dist/middleware/compose.js +7 -7
- package/dist/middleware/compose.js.map +1 -1
- package/dist/middleware/production-hook.js.map +1 -1
- package/dist/middleware/with-cost-limit.d.ts +1 -1
- package/dist/middleware/with-cost-limit.js.map +1 -1
- package/dist/middleware/with-cost-tracking.d.ts +17 -4
- package/dist/middleware/with-cost-tracking.d.ts.map +1 -1
- package/dist/middleware/with-cost-tracking.js +12 -2
- package/dist/middleware/with-cost-tracking.js.map +1 -1
- package/dist/middleware/with-fallback.js +4 -2
- package/dist/middleware/with-fallback.js.map +1 -1
- package/dist/middleware/with-rate-limit.d.ts +2 -2
- package/dist/middleware/with-rate-limit.js.map +1 -1
- package/dist/middleware/with-redaction.d.ts +1 -1
- package/dist/middleware/with-redaction.js.map +1 -1
- package/dist/middleware/with-retry.js +3 -1
- package/dist/middleware/with-retry.js.map +1 -1
- package/dist/middleware/with-tracing.js +27 -6
- package/dist/middleware/with-tracing.js.map +1 -1
- package/dist/model-tier/classify.d.ts +1 -1
- package/dist/model-tier/classify.js +13 -5
- package/dist/model-tier/classify.js.map +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/provider.js.map +1 -1
- package/dist/reasoning/apply-policy.js.map +1 -1
- package/dist/reasoning/classify-contract.js +14 -5
- package/dist/reasoning/classify-contract.js.map +1 -1
- package/dist/reasoning/retention.js.map +1 -1
- package/dist/tool-examples.d.ts +1 -1
- package/dist/tool-examples.js +1 -1
- package/dist/tool-examples.js.map +1 -1
- package/dist/trust/classify-local-provider.d.ts +1 -1
- package/dist/trust/classify-local-provider.js.map +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vercel-messages.js","names":["record: Record<string, unknown>","out: Array<Readonly<Record<string, unknown>>>","systemParts: string[]","parts: Array<Readonly<Record<string, unknown>>>","anthropic: Record<string, unknown>","buffer: string[]","cacheControl: Record<string, unknown>"],"sources":["../../src/adapters/vercel-messages.ts"],"sourcesContent":["/**\n * Conversion layer between Graphorin shapes and the Vercel AI SDK\n * call contract (core-provider-01).\n *\n * The AI SDK does NOT accept Graphorin messages or tool definitions\n * verbatim:\n *\n * - `tools` must be a `Record<string, Tool>` keyed by tool name whose\n * `inputSchema` is a zod / Standard Schema value or a `jsonSchema()`\n * wrapped `Schema` object. A `ToolDefinition[]` array advertises\n * tools under index keys (`\"0\"`, `\"1\"`, ...) with schemas the SDK\n * cannot consume.\n * - Assistant tool calls must be `{type: 'tool-call'}` *content parts*\n * and tool results `{role: 'tool', content: [{type: 'tool-result'}]}`\n * messages; Graphorin's separate `toolCalls` field and string-content\n * `ToolMessage` fail the SDK's `standardizePrompt` zod validation\n * (`AI_InvalidPromptError`) on the second step of any tool loop.\n *\n * Where the AI SDK renamed keys between major versions the converters\n * emit BOTH spellings (PS-6 dual-shape): `input` + `args` on tool-call\n * parts, `output` + `result` on tool-result parts, `mediaType` +\n * `mimeType` on binary parts, `inputSchema` + `parameters` on tools.\n * Both peers zod-strip the key they do not know.\n *\n * @internal\n */\n\nimport type { Message, MessageContent, ToolChoice, ToolDefinition } from '@graphorin/core';\n\n/**\n * Registered symbols used by the AI SDK's `asSchema()` detection. They\n * are created with `Symbol.for(...)` in the SDK, so a structurally\n * hand-rolled wrapper is indistinguishable from the SDK's own\n * `jsonSchema()` output - including across duplicated copies of the\n * SDK in a node_modules tree.\n */\nconst AI_SDK_SCHEMA_SYMBOL = Symbol.for('vercel.ai.schema');\nconst AI_SDK_VALIDATOR_SYMBOL = Symbol.for('vercel.ai.validator');\n\n/**\n * Wrap a plain JSON Schema record into the AI SDK `Schema` shape the\n * SDK's `asSchema()` accepts. Equivalent to importing `jsonSchema`\n * from `ai`, but hand-rolled so the converter works with fixture\n * runtimes and does not force the optional peer at module scope.\n *\n * @internal\n */\nexport function wrapAsAiSdkSchema(\n schema: Readonly<Record<string, unknown>>,\n): Readonly<Record<string, unknown>> {\n return {\n [AI_SDK_SCHEMA_SYMBOL]: true,\n [AI_SDK_VALIDATOR_SYMBOL]: true,\n _type: undefined,\n jsonSchema: schema,\n validate: undefined,\n } as Readonly<Record<string, unknown>>;\n}\n\n/**\n * Convert Graphorin `ToolDefinition[]` to the AI SDK's name-keyed tool\n * record.\n *\n * @internal\n */\nexport function toAiSdkTools(\n tools: ReadonlyArray<ToolDefinition>,\n): Readonly<Record<string, unknown>> {\n const record: Record<string, unknown> = {};\n for (const t of tools) {\n const schema = wrapAsAiSdkSchema(t.inputSchema);\n record[t.name] = {\n ...(t.description !== undefined ? { description: t.description } : {}),\n // v5+/v7 reads `inputSchema`; v4 reads `parameters` (PS-6).\n inputSchema: schema,\n parameters: schema,\n };\n }\n return record;\n}\n\n/**\n * Convert a Graphorin `ToolChoice` to the AI SDK's spelling. The\n * string modes are identical; the named-tool form differs\n * (`{tool: name}` vs `{type: 'tool', toolName: name}`).\n *\n * @internal\n */\nexport function toAiSdkToolChoice(choice: ToolChoice): unknown {\n if (choice === 'auto' || choice === 'required' || choice === 'none') return choice;\n return { type: 'tool', toolName: choice.tool };\n}\n\n/**\n * Result of {@link toAiSdkPrompt}: the converted message array plus the\n * hoisted system text (the SDK rejects `role: 'system'` entries inside\n * `messages` - system prompts ride the request-level option).\n *\n * @internal\n */\nexport interface AiSdkPrompt {\n readonly messages: ReadonlyArray<Readonly<Record<string, unknown>>>;\n readonly system?: string;\n}\n\n/**\n * Convert Graphorin messages to AI SDK `ModelMessage`s:\n *\n * - system-role messages are hoisted into `system` (v7 rejects them\n * inside the `messages` array outright),\n * - assistant `toolCalls` become `tool-call` content parts,\n * - `ToolMessage`s become `{role: 'tool'}` messages carrying a\n * `tool-result` part whose `toolName` is recovered from the paired\n * assistant call earlier in the transcript,\n * - binary content parts are renamed onto the SDK's part vocabulary\n * (`file` parts carry `data`; audio has no dedicated user part and\n * rides as a `file` part).\n *\n * @internal\n */\nexport function toAiSdkPrompt(messages: ReadonlyArray<Message>): AiSdkPrompt {\n const out: Array<Readonly<Record<string, unknown>>> = [];\n const systemParts: string[] = [];\n const toolNameById = new Map<string, string>();\n for (const msg of messages) {\n switch (msg.role) {\n case 'system':\n if (msg.content.length > 0) systemParts.push(msg.content);\n break;\n case 'user':\n out.push({\n role: 'user',\n content: typeof msg.content === 'string' ? msg.content : msg.content.flatMap(toUserPart),\n });\n break;\n case 'assistant': {\n for (const call of msg.toolCalls ?? []) {\n toolNameById.set(call.toolCallId, call.toolName);\n }\n const hasCalls = msg.toolCalls !== undefined && msg.toolCalls.length > 0;\n if (!hasCalls && typeof msg.content === 'string') {\n out.push({ role: 'assistant', content: msg.content });\n break;\n }\n const parts: Array<Readonly<Record<string, unknown>>> = [];\n if (typeof msg.content === 'string') {\n if (msg.content.length > 0) parts.push({ type: 'text', text: msg.content });\n } else {\n for (const part of msg.content) parts.push(...toAssistantPart(part));\n }\n for (const call of msg.toolCalls ?? []) {\n parts.push({\n type: 'tool-call',\n toolCallId: call.toolCallId,\n toolName: call.toolName,\n // v5+/v7 reads `input`; v4 reads `args` (PS-6).\n input: call.args,\n args: call.args,\n });\n }\n out.push({ role: 'assistant', content: parts });\n break;\n }\n case 'tool': {\n const text = flattenToText(msg.content);\n out.push({\n role: 'tool',\n content: [\n {\n type: 'tool-result',\n toolCallId: msg.toolCallId,\n toolName: toolNameById.get(msg.toolCallId) ?? 'unknown_tool',\n // v5+/v7 reads `output`; v4 reads `result` (PS-6).\n output: { type: 'text', value: text },\n result: text,\n },\n ],\n });\n break;\n }\n }\n }\n return {\n messages: out,\n ...(systemParts.length > 0 ? { system: systemParts.join('\\n\\n') } : {}),\n };\n}\n\nfunction toUserPart(part: MessageContent): ReadonlyArray<Readonly<Record<string, unknown>>> {\n switch (part.type) {\n case 'text':\n return [{ type: 'text', text: part.text }];\n case 'image':\n return [\n {\n type: 'image',\n image: part.image,\n // v5+/v7 reads `mediaType`; v4 reads `mimeType` (PS-6).\n ...(part.mimeType !== undefined\n ? { mediaType: part.mimeType, mimeType: part.mimeType }\n : {}),\n },\n ];\n case 'audio':\n return [filePart(part.audio, part.mimeType ?? 'application/octet-stream')];\n case 'file':\n return [filePart(part.file, part.mimeType, part.filename)];\n case 'reasoning':\n // Reasoning belongs to assistant turns; a stray part in user\n // content has no SDK representation. Drop it.\n return [];\n }\n}\n\nfunction toAssistantPart(part: MessageContent): ReadonlyArray<Readonly<Record<string, unknown>>> {\n switch (part.type) {\n case 'text':\n return part.text.length > 0 ? [{ type: 'text', text: part.text }] : [];\n case 'reasoning': {\n const meta = part.meta;\n const anthropic: Record<string, unknown> = {};\n if (meta?.provider === 'anthropic') {\n if (typeof meta.signature === 'string') anthropic.signature = meta.signature;\n if (typeof meta.data === 'string') anthropic.redactedData = meta.data;\n }\n return [\n {\n type: 'reasoning',\n text: part.text,\n ...(Object.keys(anthropic).length > 0 ? { providerOptions: { anthropic } } : {}),\n },\n ];\n }\n case 'image':\n return [filePart(part.image, part.mimeType ?? 'image/png')];\n case 'audio':\n return [filePart(part.audio, part.mimeType ?? 'application/octet-stream')];\n case 'file':\n return [filePart(part.file, part.mimeType, part.filename)];\n }\n}\n\nfunction filePart(\n data: Uint8Array | URL,\n mimeType: string,\n filename?: string,\n): Readonly<Record<string, unknown>> {\n return {\n type: 'file',\n data,\n // v5+/v7 reads `mediaType`; v4 reads `mimeType` (PS-6).\n mediaType: mimeType,\n mimeType,\n ...(filename !== undefined ? { filename } : {}),\n };\n}\n\nfunction flattenToText(content: string | ReadonlyArray<MessageContent>): string {\n if (typeof content === 'string') return content;\n const buffer: string[] = [];\n for (const part of content) {\n if (part.type === 'text' || part.type === 'reasoning') buffer.push(part.text);\n }\n return buffer.join('');\n}\n\n/**\n * Decorate the first and last converted conversation messages with an\n * Anthropic `cacheControl` provider option (core-provider-02).\n *\n * The first-message anchor makes tools + system + the stable prefix a\n * cache segment that is written once and read on every later step; the\n * last-message anchor caches the whole conversation so each step's write\n * becomes the next step's read. Non-Anthropic models ignore the option.\n * A single-message conversation gets one anchor.\n */\nexport function applyCacheAnchors(\n messages: ReadonlyArray<Readonly<Record<string, unknown>>>,\n ttl?: '5m' | '1h',\n): ReadonlyArray<Readonly<Record<string, unknown>>> {\n if (messages.length === 0) return messages;\n const cacheControl: Record<string, unknown> = {\n type: 'ephemeral',\n ...(ttl === '1h' ? { ttl: '1h' } : {}),\n };\n const decorate = (msg: Readonly<Record<string, unknown>>): Readonly<Record<string, unknown>> => {\n const existing =\n typeof msg.providerOptions === 'object' && msg.providerOptions !== null\n ? (msg.providerOptions as Record<string, unknown>)\n : {};\n const anthropic =\n typeof existing.anthropic === 'object' && existing.anthropic !== null\n ? (existing.anthropic as Record<string, unknown>)\n : {};\n return {\n ...msg,\n providerOptions: { ...existing, anthropic: { ...anthropic, cacheControl } },\n };\n };\n if (messages.length === 1) {\n const only = messages[0];\n return only === undefined ? messages : [decorate(only)];\n }\n const first = messages[0];\n const last = messages[messages.length - 1];\n if (first === undefined || last === undefined) return messages;\n return [decorate(first), ...messages.slice(1, -1), decorate(last)];\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Provider, ProviderCapabilities, Usage } from "@graphorin/core";
|
|
2
2
|
|
|
3
3
|
//#region src/adapters/vercel.d.ts
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ interface LanguageModelLike {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Loose chunk shape emitted by the AI SDK's `streamText`. The shape is
|
|
25
|
-
* intentionally permissive
|
|
25
|
+
* intentionally permissive - we accept anything that carries the
|
|
26
26
|
* fields we use and ignore the rest. This keeps the adapter tolerant
|
|
27
27
|
* of additive AI SDK schema changes.
|
|
28
28
|
*
|
|
@@ -44,7 +44,8 @@ interface AISDKChunk {
|
|
|
44
44
|
interface VercelRuntimeOverrides {
|
|
45
45
|
readonly streamText: (args: {
|
|
46
46
|
model: LanguageModelLike;
|
|
47
|
-
|
|
47
|
+
/** AI SDK `ModelMessage`-shaped values (converted, NOT Graphorin `Message`s). */
|
|
48
|
+
messages: ReadonlyArray<Readonly<Record<string, unknown>>>;
|
|
48
49
|
system?: string;
|
|
49
50
|
tools?: unknown;
|
|
50
51
|
toolChoice?: unknown;
|
|
@@ -57,7 +58,8 @@ interface VercelRuntimeOverrides {
|
|
|
57
58
|
};
|
|
58
59
|
readonly generateText: (args: {
|
|
59
60
|
model: LanguageModelLike;
|
|
60
|
-
|
|
61
|
+
/** AI SDK `ModelMessage`-shaped values (converted, NOT Graphorin `Message`s). */
|
|
62
|
+
messages: ReadonlyArray<Readonly<Record<string, unknown>>>;
|
|
61
63
|
system?: string;
|
|
62
64
|
tools?: unknown;
|
|
63
65
|
toolChoice?: unknown;
|
|
@@ -107,10 +109,10 @@ interface VercelAdapterOptions {
|
|
|
107
109
|
}
|
|
108
110
|
/**
|
|
109
111
|
* Wrap a Vercel AI SDK language-model value in a Graphorin
|
|
110
|
-
* {@link Provider}.
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* onto Graphorin `ProviderEvent`s.
|
|
112
|
+
* {@link Provider}. Outbound requests are converted onto the AI SDK
|
|
113
|
+
* call contract (name-keyed tools, `tool-call` / `tool-result` content
|
|
114
|
+
* parts - see `vercel-messages.ts`); the streaming chunks emitted by
|
|
115
|
+
* the AI SDK are translated back onto Graphorin `ProviderEvent`s.
|
|
114
116
|
*
|
|
115
117
|
* The adapter auto-detects the model's
|
|
116
118
|
* {@link import('@graphorin/core').ReasoningContract} from its
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel.d.ts","names":[],"sources":["../../src/adapters/vercel.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vercel.d.ts","names":[],"sources":["../../src/adapters/vercel.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAwIA;;;AAiB8B,UAnGb,iBAAA,CAmGa;EAAsB,SAAA,QAAA,EAAA,MAAA;EA8BpC,SAAA,OAAa,EAAA,MAAA;EACpB,SAAA,oBAAA,CAAA,EAAA,MAAA,GAAA,MAAA;EACE;;;AAoaX;;;;;;;;;;;;;;;;UA/gBiB,UAAA;;;;;;;;;UAUA,sBAAA;;WAEN;;cAEG,cAAc,SAAS;;;;;;kBAMnB;sBACI,SAAS;;yBAEN,cAAc;;;WAG5B;;cAEG,cAAc,SAAS;;;;;;kBAMnB;sBACI,SAAS;QACvB;;yBAEiB;;;;;qBAKJ,QAAQ;;;;;;gCAMG,SAAS;;;;;;;;UASxB,oBAAA;;;;;;;;;;;0BAWS,QAAQ;;;;;;8BAMJ;;;;;;;;;;;;;;;;;;iBA8Bd,aAAA,QACP,6BACE,uBACR;;;;;;;;iBAmaa,yBAAA,CAAA"}
|
package/dist/adapters/vercel.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ProviderHttpError, ProviderStreamParseError } from "../errors/errors.js";
|
|
2
2
|
import { applyReasoningPolicy } from "../reasoning/apply-policy.js";
|
|
3
3
|
import { resolveReasoningRetention } from "../reasoning/retention.js";
|
|
4
|
+
import { foldToolExamples } from "../tool-examples.js";
|
|
4
5
|
import { inferReasoningContract } from "../reasoning/classify-contract.js";
|
|
6
|
+
import { applyCacheAnchors, toAiSdkPrompt, toAiSdkToolChoice, toAiSdkTools } from "./vercel-messages.js";
|
|
5
7
|
|
|
6
8
|
//#region src/adapters/vercel.ts
|
|
7
9
|
const DEFAULT_CAPABILITIES = {
|
|
@@ -16,10 +18,10 @@ const DEFAULT_CAPABILITIES = {
|
|
|
16
18
|
};
|
|
17
19
|
/**
|
|
18
20
|
* Wrap a Vercel AI SDK language-model value in a Graphorin
|
|
19
|
-
* {@link Provider}.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* onto Graphorin `ProviderEvent`s.
|
|
21
|
+
* {@link Provider}. Outbound requests are converted onto the AI SDK
|
|
22
|
+
* call contract (name-keyed tools, `tool-call` / `tool-result` content
|
|
23
|
+
* parts - see `vercel-messages.ts`); the streaming chunks emitted by
|
|
24
|
+
* the AI SDK are translated back onto Graphorin `ProviderEvent`s.
|
|
23
25
|
*
|
|
24
26
|
* The adapter auto-detects the model's
|
|
25
27
|
* {@link import('@graphorin/core').ReasoningContract} from its
|
|
@@ -61,11 +63,13 @@ async function* streamFromVercel(model, providerName, capabilities, req, overrid
|
|
|
61
63
|
try {
|
|
62
64
|
stream = sdk.streamText(callArgs).fullStream;
|
|
63
65
|
} catch (cause) {
|
|
66
|
+
const headers = headersFromCause(cause);
|
|
64
67
|
throw new ProviderHttpError({
|
|
65
68
|
providerName,
|
|
66
69
|
status: statusFromCause(cause),
|
|
67
70
|
message: "streamText() failed before yielding any chunks",
|
|
68
|
-
cause
|
|
71
|
+
cause,
|
|
72
|
+
...headers !== void 0 ? { headers } : {}
|
|
69
73
|
});
|
|
70
74
|
}
|
|
71
75
|
yield {
|
|
@@ -166,11 +170,13 @@ async function generateFromVercel(model, providerName, capabilities, req, overri
|
|
|
166
170
|
try {
|
|
167
171
|
result = await sdk.generateText(callArgs);
|
|
168
172
|
} catch (cause) {
|
|
173
|
+
const headers = headersFromCause(cause);
|
|
169
174
|
throw new ProviderHttpError({
|
|
170
175
|
providerName,
|
|
171
176
|
status: statusFromCause(cause),
|
|
172
177
|
message: "generateText() rejected",
|
|
173
|
-
cause
|
|
178
|
+
cause,
|
|
179
|
+
...headers !== void 0 ? { headers } : {}
|
|
174
180
|
});
|
|
175
181
|
}
|
|
176
182
|
return {
|
|
@@ -202,12 +208,14 @@ function applyRequestPreflight(req, capabilities) {
|
|
|
202
208
|
};
|
|
203
209
|
}
|
|
204
210
|
function buildCallArgs(model, req) {
|
|
211
|
+
const prompt = toAiSdkPrompt(req.messages);
|
|
212
|
+
const system = [req.systemMessage, prompt.system].filter((s) => s !== void 0 && s.length > 0).join("\n\n");
|
|
205
213
|
return {
|
|
206
214
|
model,
|
|
207
|
-
messages: req.messages,
|
|
208
|
-
...
|
|
209
|
-
...req.tools !== void 0 ? { tools: req.tools } : {},
|
|
210
|
-
...req.toolChoice !== void 0 ? { toolChoice: req.toolChoice } : {},
|
|
215
|
+
messages: req.cachePolicy?.breakpoints === "auto" ? applyCacheAnchors(prompt.messages, req.cachePolicy.ttl) : prompt.messages,
|
|
216
|
+
...system.length > 0 ? { system } : {},
|
|
217
|
+
...req.tools !== void 0 && req.tools.length > 0 ? { tools: toAiSdkTools(foldToolExamples(req.tools)) } : {},
|
|
218
|
+
...req.toolChoice !== void 0 ? { toolChoice: toAiSdkToolChoice(req.toolChoice) } : {},
|
|
211
219
|
...req.temperature !== void 0 ? { temperature: req.temperature } : {},
|
|
212
220
|
...req.maxTokens !== void 0 ? {
|
|
213
221
|
maxTokens: req.maxTokens,
|
|
@@ -237,7 +245,7 @@ function normalizeToolCall(tc) {
|
|
|
237
245
|
* `APICallError` carries a numeric `statusCode`; surfacing it lets
|
|
238
246
|
* `withRetry` / `withFallback` see a genuine 429 / 5xx instead of the
|
|
239
247
|
* `status: 0` network-error placeholder. Returns `0` when no status is
|
|
240
|
-
* present (a true transport-level failure or an abort)
|
|
248
|
+
* present (a true transport-level failure or an abort) - `0` is itself
|
|
241
249
|
* retryable / fallback-eligible by default, while an abort is excluded by
|
|
242
250
|
* the predicates via the wrapped `cause`.
|
|
243
251
|
*/
|
|
@@ -249,6 +257,23 @@ function statusFromCause(cause) {
|
|
|
249
257
|
}
|
|
250
258
|
return 0;
|
|
251
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Lift backoff-relevant response headers from a rejected AI SDK call.
|
|
262
|
+
* `APICallError` carries `responseHeaders: Record<string, string>`;
|
|
263
|
+
* forwarding `retry-after` / `x-ratelimit-*` lets `withRetry` honour
|
|
264
|
+
* server-provided delays on 429s.
|
|
265
|
+
*/
|
|
266
|
+
function headersFromCause(cause) {
|
|
267
|
+
if (cause === null || typeof cause !== "object") return void 0;
|
|
268
|
+
const raw = cause.responseHeaders;
|
|
269
|
+
if (raw === null || typeof raw !== "object") return void 0;
|
|
270
|
+
const picked = {};
|
|
271
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
272
|
+
const lower = key.toLowerCase();
|
|
273
|
+
if ((lower === "retry-after" || lower.startsWith("x-ratelimit-")) && typeof value === "string") picked[lower] = value;
|
|
274
|
+
}
|
|
275
|
+
return Object.keys(picked).length > 0 ? picked : void 0;
|
|
276
|
+
}
|
|
252
277
|
function mapFinishReason(value) {
|
|
253
278
|
switch (value) {
|
|
254
279
|
case "stop":
|
|
@@ -265,14 +290,29 @@ function mapUsage(input) {
|
|
|
265
290
|
if (input === void 0 || input === null || typeof input !== "object") return void 0;
|
|
266
291
|
const u = input;
|
|
267
292
|
const promptTokens = u.promptTokens ?? u.inputTokens ?? 0;
|
|
268
|
-
const
|
|
293
|
+
const rawCompletion = u.completionTokens ?? u.outputTokens ?? 0;
|
|
294
|
+
const totalTokens = u.totalTokens ?? promptTokens + rawCompletion;
|
|
295
|
+
const cachedReadTokens = numberOrUndefined(u.inputTokenDetails?.cacheReadTokens);
|
|
296
|
+
const cacheWriteTokens = numberOrUndefined(u.inputTokenDetails?.cacheWriteTokens);
|
|
297
|
+
const detailReasoning = numberOrUndefined(u.outputTokenDetails?.reasoningTokens);
|
|
298
|
+
let completionTokens = rawCompletion;
|
|
299
|
+
let reasoningTokens = u.reasoningTokens;
|
|
300
|
+
if (reasoningTokens === void 0 && detailReasoning !== void 0 && detailReasoning > 0) {
|
|
301
|
+
reasoningTokens = detailReasoning;
|
|
302
|
+
completionTokens = Math.max(0, rawCompletion - detailReasoning);
|
|
303
|
+
}
|
|
269
304
|
return {
|
|
270
305
|
promptTokens,
|
|
271
306
|
completionTokens,
|
|
272
|
-
totalTokens
|
|
273
|
-
...
|
|
307
|
+
totalTokens,
|
|
308
|
+
...reasoningTokens !== void 0 ? { reasoningTokens } : {},
|
|
309
|
+
...cachedReadTokens !== void 0 && cachedReadTokens > 0 ? { cachedReadTokens } : {},
|
|
310
|
+
...cacheWriteTokens !== void 0 && cacheWriteTokens > 0 ? { cacheWriteTokens } : {}
|
|
274
311
|
};
|
|
275
312
|
}
|
|
313
|
+
function numberOrUndefined(value) {
|
|
314
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
315
|
+
}
|
|
276
316
|
let cachedRuntime = null;
|
|
277
317
|
async function loadRuntime(overrides) {
|
|
278
318
|
if (overrides !== void 0) return overrides;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel.js","names":["DEFAULT_CAPABILITIES: Omit<ProviderCapabilities, 'reasoningContract'>","capabilities: ProviderCapabilities","stream: AsyncIterable<AISDKChunk>","finalUsage: Usage | undefined","finishReason: FinishReason","result: Awaited<ReturnType<VercelRuntimeOverrides['generateText']>>","cachedRuntime: VercelRuntimeOverrides | null","mod: { streamText?: unknown; generateText?: unknown }"],"sources":["../../src/adapters/vercel.ts"],"sourcesContent":["/**\n * `vercelAdapter` — wraps a Vercel AI SDK `LanguageModel`-shaped value\n * into a Graphorin {@link Provider}. The adapter is the default cloud\n * path: it speaks the AI SDK's `streamText` / `generateText` API and\n * maps the resulting events onto the canonical\n * {@link import('@graphorin/core').ProviderEvent} discriminated union.\n *\n * The AI SDK is an **optional peer dependency** of `@graphorin/provider`.\n * Production callers leave `runtimeOverrides` unset and the adapter\n * dynamically imports the package on first use; test fixtures pass a\n * `runtimeOverrides` value to short-circuit the import and feed\n * fixture chunks directly. The overrides shape is intentionally\n * structural so users can supply hand-rolled stubs or any compatible\n * library.\n *\n * @packageDocumentation\n */\n\nimport type {\n FinishReason,\n Message,\n Provider,\n ProviderCapabilities,\n ProviderEvent,\n ProviderRequest,\n ProviderResponse,\n Usage,\n} from '@graphorin/core';\n\nimport { ProviderHttpError, ProviderStreamParseError } from '../errors/errors.js';\nimport { applyReasoningPolicy } from '../reasoning/apply-policy.js';\nimport { inferReasoningContract } from '../reasoning/classify-contract.js';\nimport { resolveReasoningRetention } from '../reasoning/retention.js';\n\n/**\n * Structural shape the adapter expects from the AI SDK language model\n * value. The real `LanguageModelV4` matches this shape. Re-declared\n * here so we do not pin a hard dependency on `@ai-sdk/provider`.\n *\n * @stable\n */\nexport interface LanguageModelLike {\n readonly provider: string;\n readonly modelId: string;\n readonly specificationVersion?: string | number;\n /**\n * Optional capability flags carried by the AI SDK model. The adapter\n * forwards them onto the canonical `ProviderCapabilities` shape;\n * missing values are filled in with conservative defaults.\n */\n readonly supportedToolCallTypes?: readonly string[];\n}\n\n/**\n * Loose chunk shape emitted by the AI SDK's `streamText`. The shape is\n * intentionally permissive — we accept anything that carries the\n * fields we use and ignore the rest. This keeps the adapter tolerant\n * of additive AI SDK schema changes.\n *\n * The fields we read are normalized in the adapter via narrow helper\n * functions, so we deliberately type each as `unknown` and gate\n * access behind `typeof` checks at runtime.\n *\n * @stable\n */\nexport interface AISDKChunk {\n readonly type: string;\n readonly [extra: string]: unknown;\n}\n\n/**\n * Subset of the AI SDK surface used by the adapter.\n *\n * @stable\n */\nexport interface VercelRuntimeOverrides {\n readonly streamText: (args: {\n model: LanguageModelLike;\n messages: ReadonlyArray<Message>;\n system?: string;\n tools?: unknown;\n toolChoice?: unknown;\n temperature?: number;\n maxTokens?: number;\n abortSignal?: AbortSignal;\n providerOptions?: Readonly<Record<string, unknown>>;\n }) => {\n readonly fullStream: AsyncIterable<AISDKChunk>;\n };\n readonly generateText: (args: {\n model: LanguageModelLike;\n messages: ReadonlyArray<Message>;\n system?: string;\n tools?: unknown;\n toolChoice?: unknown;\n temperature?: number;\n maxTokens?: number;\n abortSignal?: AbortSignal;\n providerOptions?: Readonly<Record<string, unknown>>;\n }) => Promise<{\n readonly text?: string;\n readonly toolCalls?: ReadonlyArray<{\n readonly toolCallId: string;\n readonly toolName: string;\n readonly args: unknown;\n }>;\n readonly usage?: Partial<Usage> & {\n readonly inputTokens?: number;\n readonly outputTokens?: number;\n readonly totalTokens?: number;\n };\n readonly finishReason?: string;\n readonly providerMetadata?: Readonly<Record<string, unknown>>;\n }>;\n}\n\n/**\n * Options accepted by {@link vercelAdapter}.\n *\n * @stable\n */\nexport interface VercelAdapterOptions {\n /**\n * Fully-qualified provider name, used for span / log labelling.\n * Defaults to `${model.provider}-${model.modelId}`.\n */\n readonly name?: string;\n /**\n * Capability declaration. The adapter merges these on top of a\n * conservative defaults table (`streaming: true`, `toolCalling: true`,\n * `multimodal: true`, …); supply explicit values to narrow them.\n */\n readonly capabilities?: Partial<ProviderCapabilities>;\n /**\n * Runtime override for the AI SDK functions. When unset, the adapter\n * lazily `await import('ai')` on first call. Test suites pass a\n * fixture-driven implementation directly.\n */\n readonly runtimeOverrides?: VercelRuntimeOverrides;\n}\n\nconst DEFAULT_CAPABILITIES: Omit<ProviderCapabilities, 'reasoningContract'> = {\n streaming: true,\n toolCalling: true,\n parallelToolCalls: true,\n multimodal: true,\n structuredOutput: true,\n reasoning: true,\n contextWindow: 200_000,\n maxOutput: 16_384,\n};\n\n/**\n * Wrap a Vercel AI SDK language-model value in a Graphorin\n * {@link Provider}. The adapter passes Graphorin `Message`s through\n * directly — both formats use the same role + content discriminated\n * shape — and translates the streaming chunks emitted by the AI SDK\n * onto Graphorin `ProviderEvent`s.\n *\n * The adapter auto-detects the model's\n * {@link import('@graphorin/core').ReasoningContract} from its\n * `modelId` (e.g. Anthropic Claude → `'round-trip-required'`,\n * OpenAI o1 / o3 → `'hidden'`, Gemini reasoning variants →\n * `'hidden'`, everything else → `'optional'`). Callers can override\n * the inferred value via `options.capabilities.reasoningContract`.\n *\n * @stable\n */\nexport function vercelAdapter(\n model: LanguageModelLike,\n options: VercelAdapterOptions = {},\n): Provider {\n const name = options.name ?? `${model.provider}-${model.modelId}`;\n const inferredContract = inferReasoningContract({\n modelId: model.modelId,\n provider: model.provider,\n });\n const capabilities: ProviderCapabilities = {\n ...DEFAULT_CAPABILITIES,\n reasoningContract: inferredContract,\n ...options.capabilities,\n };\n const runtime = options.runtimeOverrides;\n\n return {\n name,\n modelId: model.modelId,\n capabilities,\n stream(req) {\n return streamFromVercel(model, name, capabilities, req, runtime);\n },\n async generate(req) {\n return generateFromVercel(model, name, capabilities, req, runtime);\n },\n };\n}\n\nasync function* streamFromVercel(\n model: LanguageModelLike,\n providerName: string,\n capabilities: ProviderCapabilities,\n req: ProviderRequest,\n overrides: VercelRuntimeOverrides | undefined,\n): AsyncIterable<ProviderEvent> {\n const sdk = await loadRuntime(overrides);\n const callArgs = buildCallArgs(model, applyRequestPreflight(req, capabilities));\n let stream: AsyncIterable<AISDKChunk>;\n try {\n const result = sdk.streamText(callArgs);\n stream = result.fullStream;\n } catch (cause) {\n throw new ProviderHttpError({\n providerName,\n status: statusFromCause(cause),\n message: 'streamText() failed before yielding any chunks',\n cause,\n });\n }\n\n yield {\n type: 'stream-start',\n metadata: {\n providerName,\n modelId: model.modelId,\n },\n };\n\n let finalUsage: Usage | undefined;\n let finishReason: FinishReason = 'stop';\n\n for await (const chunk of stream) {\n if (req.signal?.aborted) {\n // PS-12: an aborted stream must report 'aborted', not the initial 'stop'\n // — mirrors the openai-shaped and ollama adapters.\n finishReason = 'aborted';\n break;\n }\n switch (chunk.type) {\n case 'text-delta': {\n const delta = pickString(chunk.textDelta) ?? pickString(chunk.text) ?? '';\n if (delta.length > 0) {\n yield { type: 'text-delta', delta };\n }\n break;\n }\n case 'reasoning':\n case 'reasoning-delta': {\n const delta =\n pickString(chunk.textDelta) ?? pickString(chunk.delta) ?? pickString(chunk.text) ?? '';\n if (delta.length > 0) {\n yield { type: 'reasoning-delta', delta };\n }\n break;\n }\n // PS-6 dual-shape: AI SDK v4 streams `tool-call-streaming-start` /\n // `tool-call-delta` keyed by `toolCallId`/`argsTextDelta`; v7 streams\n // `tool-input-start` / `tool-input-delta` keyed by `id`/`delta`.\n case 'tool-call-streaming-start':\n case 'tool-input-start': {\n const toolCallId = pickString(chunk.toolCallId) ?? pickString(chunk.id);\n const toolName = pickString(chunk.toolName);\n if (toolCallId !== undefined && toolName !== undefined) {\n yield {\n type: 'tool-call-start',\n toolCallId,\n toolName,\n };\n }\n break;\n }\n case 'tool-call-delta':\n case 'tool-input-delta': {\n const toolCallId = pickString(chunk.toolCallId) ?? pickString(chunk.id);\n const argsDelta =\n pickString(chunk.argsTextDelta) ??\n pickString(chunk.delta) ??\n pickString(chunk.inputTextDelta);\n if (toolCallId !== undefined && argsDelta !== undefined && argsDelta.length > 0) {\n yield {\n type: 'tool-call-input-delta',\n toolCallId,\n argsDelta,\n };\n }\n break;\n }\n case 'tool-call': {\n const toolCallId = pickString(chunk.toolCallId) ?? pickString(chunk.id);\n if (toolCallId !== undefined) {\n yield {\n type: 'tool-call-end',\n toolCallId,\n // v4 carries `args`; v7 carries `input`.\n finalArgs: chunk.args ?? chunk.input,\n };\n }\n break;\n }\n case 'finish': {\n finishReason = mapFinishReason(pickString(chunk.finishReason));\n // v4 carries `usage`; v7 carries `totalUsage` (zeroing the v4\n // read nulled cost tracking on streaming).\n finalUsage = mapUsage(chunk.totalUsage ?? chunk.usage);\n break;\n }\n case 'error': {\n const errorField = chunk.error;\n const message =\n typeof errorField === 'string'\n ? errorField\n : typeof errorField === 'object' && errorField !== null\n ? (pickString((errorField as { message?: unknown }).message) ?? 'unknown error')\n : 'unknown error';\n yield {\n type: 'error',\n error: { kind: 'unknown', message },\n };\n break;\n }\n default:\n // Unknown chunk types are forward-compatible no-ops; we keep\n // streaming so additive AI SDK upgrades do not break callers.\n break;\n }\n }\n\n yield {\n type: 'finish',\n finishReason,\n usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n };\n}\n\nasync function generateFromVercel(\n model: LanguageModelLike,\n providerName: string,\n capabilities: ProviderCapabilities,\n req: ProviderRequest,\n overrides: VercelRuntimeOverrides | undefined,\n): Promise<ProviderResponse> {\n const sdk = await loadRuntime(overrides);\n const callArgs = buildCallArgs(model, applyRequestPreflight(req, capabilities));\n let result: Awaited<ReturnType<VercelRuntimeOverrides['generateText']>>;\n try {\n result = await sdk.generateText(callArgs);\n } catch (cause) {\n throw new ProviderHttpError({\n providerName,\n status: statusFromCause(cause),\n message: 'generateText() rejected',\n cause,\n });\n }\n const usage = mapUsage(result.usage) ?? {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n };\n const finishReason = mapFinishReason(result.finishReason);\n const response: ProviderResponse = {\n usage,\n finishReason,\n ...(result.text !== undefined ? { text: result.text } : {}),\n ...(result.toolCalls !== undefined\n ? { toolCalls: result.toolCalls.map(normalizeToolCall) }\n : {}),\n ...(result.providerMetadata !== undefined ? { providerMetadata: result.providerMetadata } : {}),\n };\n return response;\n}\n\nfunction applyRequestPreflight(\n req: ProviderRequest,\n capabilities: ProviderCapabilities,\n): ProviderRequest {\n const retention = resolveReasoningRetention({\n ...(req.reasoningRetention !== undefined ? { requested: req.reasoningRetention } : {}),\n ...(capabilities.reasoningContract !== undefined\n ? { contract: capabilities.reasoningContract }\n : {}),\n });\n if (retention === 'pass-through-all') return req;\n const filtered = applyReasoningPolicy({ messages: req.messages, retention });\n if (filtered === req.messages) return req;\n return { ...req, messages: filtered };\n}\n\nfunction buildCallArgs(model: LanguageModelLike, req: ProviderRequest) {\n return {\n model,\n messages: req.messages,\n ...(req.systemMessage !== undefined ? { system: req.systemMessage } : {}),\n ...(req.tools !== undefined ? { tools: req.tools } : {}),\n ...(req.toolChoice !== undefined ? { toolChoice: req.toolChoice } : {}),\n ...(req.temperature !== undefined ? { temperature: req.temperature } : {}),\n // v4 reads `maxTokens`; v7 renamed it `maxOutputTokens` — send both\n // so the cap is honoured against either peer (PS-6).\n ...(req.maxTokens !== undefined\n ? { maxTokens: req.maxTokens, maxOutputTokens: req.maxTokens }\n : {}),\n ...(req.signal !== undefined ? { abortSignal: req.signal } : {}),\n ...(req.providerOptions !== undefined ? { providerOptions: req.providerOptions } : {}),\n } as const;\n}\n\nfunction pickString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Normalize a generate() tool call across peers: AI SDK v4 carries\n * `args`, v7 carries `input` (PS-6). The framework shape is `args`.\n */\nfunction normalizeToolCall(tc: unknown): { toolCallId: string; toolName: string; args: unknown } {\n const t = tc as {\n toolCallId?: unknown;\n id?: unknown;\n toolName?: unknown;\n args?: unknown;\n input?: unknown;\n };\n return {\n toolCallId: pickString(t.toolCallId) ?? pickString(t.id) ?? '',\n toolName: pickString(t.toolName) ?? '',\n args: t.args ?? t.input,\n };\n}\n\n/**\n * Lift a real HTTP status from a rejected AI SDK call (PS-2). The SDK's\n * `APICallError` carries a numeric `statusCode`; surfacing it lets\n * `withRetry` / `withFallback` see a genuine 429 / 5xx instead of the\n * `status: 0` network-error placeholder. Returns `0` when no status is\n * present (a true transport-level failure or an abort) — `0` is itself\n * retryable / fallback-eligible by default, while an abort is excluded by\n * the predicates via the wrapped `cause`.\n */\nfunction statusFromCause(cause: unknown): number {\n if (cause !== null && typeof cause === 'object') {\n const c = cause as { statusCode?: unknown; status?: unknown };\n if (typeof c.statusCode === 'number' && Number.isFinite(c.statusCode)) return c.statusCode;\n if (typeof c.status === 'number' && Number.isFinite(c.status)) return c.status;\n }\n return 0;\n}\n\nfunction mapFinishReason(value: string | undefined): FinishReason {\n switch (value) {\n case 'stop':\n case 'length':\n case 'tool-calls':\n case 'content-filter':\n case 'error':\n return value;\n case 'aborted':\n case 'cancelled':\n return 'aborted';\n default:\n return 'stop';\n }\n}\n\nfunction mapUsage(input: unknown): Usage | undefined {\n if (input === undefined || input === null || typeof input !== 'object') return undefined;\n const u = input as {\n promptTokens?: number;\n completionTokens?: number;\n inputTokens?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n totalTokens?: number;\n };\n const promptTokens = u.promptTokens ?? u.inputTokens ?? 0;\n const completionTokens = u.completionTokens ?? u.outputTokens ?? 0;\n const totalTokens = u.totalTokens ?? promptTokens + completionTokens;\n const usage: Usage = {\n promptTokens,\n completionTokens,\n totalTokens,\n ...(u.reasoningTokens !== undefined ? { reasoningTokens: u.reasoningTokens } : {}),\n };\n return usage;\n}\n\nlet cachedRuntime: VercelRuntimeOverrides | null = null;\n\nasync function loadRuntime(\n overrides: VercelRuntimeOverrides | undefined,\n): Promise<VercelRuntimeOverrides> {\n if (overrides !== undefined) return overrides;\n if (cachedRuntime !== null) return cachedRuntime;\n let mod: { streamText?: unknown; generateText?: unknown };\n try {\n mod = (await import('ai')) as { streamText?: unknown; generateText?: unknown };\n } catch (cause) {\n throw new ProviderStreamParseError(\n 'vercel',\n \"Failed to import the 'ai' peer dependency. Install it with `pnpm add ai` or pass a runtimeOverrides value.\",\n cause,\n );\n }\n if (typeof mod.streamText !== 'function' || typeof mod.generateText !== 'function') {\n throw new ProviderStreamParseError(\n 'vercel',\n \"The installed 'ai' package does not expose streamText / generateText functions.\",\n );\n }\n cachedRuntime = {\n streamText: mod.streamText as VercelRuntimeOverrides['streamText'],\n generateText: mod.generateText as VercelRuntimeOverrides['generateText'],\n };\n return cachedRuntime;\n}\n\n/**\n * Test-only hook that resets the cached AI SDK runtime. Provider tests\n * that mutate the cache (e.g. by injecting a mock then verifying the\n * default loader runs) call this between scenarios.\n *\n * @internal\n */\nexport function __resetVercelRuntimeCache(): void {\n cachedRuntime = null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vercel.js","names":["DEFAULT_CAPABILITIES: Omit<ProviderCapabilities, 'reasoningContract'>","capabilities: ProviderCapabilities","stream: AsyncIterable<AISDKChunk>","finalUsage: Usage | undefined","finishReason: FinishReason","result: Awaited<ReturnType<VercelRuntimeOverrides['generateText']>>","picked: Record<string, string>","cachedRuntime: VercelRuntimeOverrides | null","mod: { streamText?: unknown; generateText?: unknown }"],"sources":["../../src/adapters/vercel.ts"],"sourcesContent":["/**\n * `vercelAdapter` - wraps a Vercel AI SDK `LanguageModel`-shaped value\n * into a Graphorin {@link Provider}. The adapter is the default cloud\n * path: it speaks the AI SDK's `streamText` / `generateText` API and\n * maps the resulting events onto the canonical\n * {@link import('@graphorin/core').ProviderEvent} discriminated union.\n *\n * Outbound, the adapter converts Graphorin messages / tools onto the\n * AI SDK call contract (see `vercel-messages.ts`): tool definitions\n * become a name-keyed record with `jsonSchema()`-shaped input schemas,\n * assistant `toolCalls` become `tool-call` content parts, and\n * `ToolMessage`s become `tool-result` messages - the SDK zod-validates\n * all of these and rejects the raw Graphorin shapes.\n *\n * The AI SDK is an **optional peer dependency** of `@graphorin/provider`.\n * Production callers leave `runtimeOverrides` unset and the adapter\n * dynamically imports the package on first use; test fixtures pass a\n * `runtimeOverrides` value to short-circuit the import and feed\n * fixture chunks directly. The overrides shape is intentionally\n * structural so users can supply hand-rolled stubs or any compatible\n * library.\n *\n * @packageDocumentation\n */\n\nimport type {\n FinishReason,\n Provider,\n ProviderCapabilities,\n ProviderEvent,\n ProviderRequest,\n ProviderResponse,\n Usage,\n} from '@graphorin/core';\n\nimport { ProviderHttpError, ProviderStreamParseError } from '../errors/errors.js';\nimport { applyReasoningPolicy } from '../reasoning/apply-policy.js';\nimport { inferReasoningContract } from '../reasoning/classify-contract.js';\nimport { resolveReasoningRetention } from '../reasoning/retention.js';\nimport { foldToolExamples } from '../tool-examples.js';\nimport {\n applyCacheAnchors,\n toAiSdkPrompt,\n toAiSdkToolChoice,\n toAiSdkTools,\n} from './vercel-messages.js';\n\n/**\n * Structural shape the adapter expects from the AI SDK language model\n * value. The real `LanguageModelV4` matches this shape. Re-declared\n * here so we do not pin a hard dependency on `@ai-sdk/provider`.\n *\n * @stable\n */\nexport interface LanguageModelLike {\n readonly provider: string;\n readonly modelId: string;\n readonly specificationVersion?: string | number;\n /**\n * Optional capability flags carried by the AI SDK model. The adapter\n * forwards them onto the canonical `ProviderCapabilities` shape;\n * missing values are filled in with conservative defaults.\n */\n readonly supportedToolCallTypes?: readonly string[];\n}\n\n/**\n * Loose chunk shape emitted by the AI SDK's `streamText`. The shape is\n * intentionally permissive - we accept anything that carries the\n * fields we use and ignore the rest. This keeps the adapter tolerant\n * of additive AI SDK schema changes.\n *\n * The fields we read are normalized in the adapter via narrow helper\n * functions, so we deliberately type each as `unknown` and gate\n * access behind `typeof` checks at runtime.\n *\n * @stable\n */\nexport interface AISDKChunk {\n readonly type: string;\n readonly [extra: string]: unknown;\n}\n\n/**\n * Subset of the AI SDK surface used by the adapter.\n *\n * @stable\n */\nexport interface VercelRuntimeOverrides {\n readonly streamText: (args: {\n model: LanguageModelLike;\n /** AI SDK `ModelMessage`-shaped values (converted, NOT Graphorin `Message`s). */\n messages: ReadonlyArray<Readonly<Record<string, unknown>>>;\n system?: string;\n tools?: unknown;\n toolChoice?: unknown;\n temperature?: number;\n maxTokens?: number;\n abortSignal?: AbortSignal;\n providerOptions?: Readonly<Record<string, unknown>>;\n }) => {\n readonly fullStream: AsyncIterable<AISDKChunk>;\n };\n readonly generateText: (args: {\n model: LanguageModelLike;\n /** AI SDK `ModelMessage`-shaped values (converted, NOT Graphorin `Message`s). */\n messages: ReadonlyArray<Readonly<Record<string, unknown>>>;\n system?: string;\n tools?: unknown;\n toolChoice?: unknown;\n temperature?: number;\n maxTokens?: number;\n abortSignal?: AbortSignal;\n providerOptions?: Readonly<Record<string, unknown>>;\n }) => Promise<{\n readonly text?: string;\n readonly toolCalls?: ReadonlyArray<{\n readonly toolCallId: string;\n readonly toolName: string;\n readonly args: unknown;\n }>;\n readonly usage?: Partial<Usage> & {\n readonly inputTokens?: number;\n readonly outputTokens?: number;\n readonly totalTokens?: number;\n };\n readonly finishReason?: string;\n readonly providerMetadata?: Readonly<Record<string, unknown>>;\n }>;\n}\n\n/**\n * Options accepted by {@link vercelAdapter}.\n *\n * @stable\n */\nexport interface VercelAdapterOptions {\n /**\n * Fully-qualified provider name, used for span / log labelling.\n * Defaults to `${model.provider}-${model.modelId}`.\n */\n readonly name?: string;\n /**\n * Capability declaration. The adapter merges these on top of a\n * conservative defaults table (`streaming: true`, `toolCalling: true`,\n * `multimodal: true`, …); supply explicit values to narrow them.\n */\n readonly capabilities?: Partial<ProviderCapabilities>;\n /**\n * Runtime override for the AI SDK functions. When unset, the adapter\n * lazily `await import('ai')` on first call. Test suites pass a\n * fixture-driven implementation directly.\n */\n readonly runtimeOverrides?: VercelRuntimeOverrides;\n}\n\nconst DEFAULT_CAPABILITIES: Omit<ProviderCapabilities, 'reasoningContract'> = {\n streaming: true,\n toolCalling: true,\n parallelToolCalls: true,\n multimodal: true,\n structuredOutput: true,\n reasoning: true,\n contextWindow: 200_000,\n maxOutput: 16_384,\n};\n\n/**\n * Wrap a Vercel AI SDK language-model value in a Graphorin\n * {@link Provider}. Outbound requests are converted onto the AI SDK\n * call contract (name-keyed tools, `tool-call` / `tool-result` content\n * parts - see `vercel-messages.ts`); the streaming chunks emitted by\n * the AI SDK are translated back onto Graphorin `ProviderEvent`s.\n *\n * The adapter auto-detects the model's\n * {@link import('@graphorin/core').ReasoningContract} from its\n * `modelId` (e.g. Anthropic Claude → `'round-trip-required'`,\n * OpenAI o1 / o3 → `'hidden'`, Gemini reasoning variants →\n * `'hidden'`, everything else → `'optional'`). Callers can override\n * the inferred value via `options.capabilities.reasoningContract`.\n *\n * @stable\n */\nexport function vercelAdapter(\n model: LanguageModelLike,\n options: VercelAdapterOptions = {},\n): Provider {\n const name = options.name ?? `${model.provider}-${model.modelId}`;\n const inferredContract = inferReasoningContract({\n modelId: model.modelId,\n provider: model.provider,\n });\n const capabilities: ProviderCapabilities = {\n ...DEFAULT_CAPABILITIES,\n reasoningContract: inferredContract,\n ...options.capabilities,\n };\n const runtime = options.runtimeOverrides;\n\n return {\n name,\n modelId: model.modelId,\n capabilities,\n stream(req) {\n return streamFromVercel(model, name, capabilities, req, runtime);\n },\n async generate(req) {\n return generateFromVercel(model, name, capabilities, req, runtime);\n },\n };\n}\n\nasync function* streamFromVercel(\n model: LanguageModelLike,\n providerName: string,\n capabilities: ProviderCapabilities,\n req: ProviderRequest,\n overrides: VercelRuntimeOverrides | undefined,\n): AsyncIterable<ProviderEvent> {\n const sdk = await loadRuntime(overrides);\n const callArgs = buildCallArgs(model, applyRequestPreflight(req, capabilities));\n let stream: AsyncIterable<AISDKChunk>;\n try {\n const result = sdk.streamText(callArgs);\n stream = result.fullStream;\n } catch (cause) {\n const headers = headersFromCause(cause);\n throw new ProviderHttpError({\n providerName,\n status: statusFromCause(cause),\n message: 'streamText() failed before yielding any chunks',\n cause,\n ...(headers !== undefined ? { headers } : {}),\n });\n }\n\n yield {\n type: 'stream-start',\n metadata: {\n providerName,\n modelId: model.modelId,\n },\n };\n\n let finalUsage: Usage | undefined;\n let finishReason: FinishReason = 'stop';\n\n for await (const chunk of stream) {\n if (req.signal?.aborted) {\n // PS-12: an aborted stream must report 'aborted', not the initial 'stop'\n // - mirrors the openai-shaped and ollama adapters.\n finishReason = 'aborted';\n break;\n }\n switch (chunk.type) {\n case 'text-delta': {\n const delta = pickString(chunk.textDelta) ?? pickString(chunk.text) ?? '';\n if (delta.length > 0) {\n yield { type: 'text-delta', delta };\n }\n break;\n }\n case 'reasoning':\n case 'reasoning-delta': {\n const delta =\n pickString(chunk.textDelta) ?? pickString(chunk.delta) ?? pickString(chunk.text) ?? '';\n if (delta.length > 0) {\n yield { type: 'reasoning-delta', delta };\n }\n break;\n }\n // PS-6 dual-shape: AI SDK v4 streams `tool-call-streaming-start` /\n // `tool-call-delta` keyed by `toolCallId`/`argsTextDelta`; v7 streams\n // `tool-input-start` / `tool-input-delta` keyed by `id`/`delta`.\n case 'tool-call-streaming-start':\n case 'tool-input-start': {\n const toolCallId = pickString(chunk.toolCallId) ?? pickString(chunk.id);\n const toolName = pickString(chunk.toolName);\n if (toolCallId !== undefined && toolName !== undefined) {\n yield {\n type: 'tool-call-start',\n toolCallId,\n toolName,\n };\n }\n break;\n }\n case 'tool-call-delta':\n case 'tool-input-delta': {\n const toolCallId = pickString(chunk.toolCallId) ?? pickString(chunk.id);\n const argsDelta =\n pickString(chunk.argsTextDelta) ??\n pickString(chunk.delta) ??\n pickString(chunk.inputTextDelta);\n if (toolCallId !== undefined && argsDelta !== undefined && argsDelta.length > 0) {\n yield {\n type: 'tool-call-input-delta',\n toolCallId,\n argsDelta,\n };\n }\n break;\n }\n case 'tool-call': {\n const toolCallId = pickString(chunk.toolCallId) ?? pickString(chunk.id);\n if (toolCallId !== undefined) {\n yield {\n type: 'tool-call-end',\n toolCallId,\n // v4 carries `args`; v7 carries `input`.\n finalArgs: chunk.args ?? chunk.input,\n };\n }\n break;\n }\n case 'finish': {\n finishReason = mapFinishReason(pickString(chunk.finishReason));\n // v4 carries `usage`; v7 carries `totalUsage` (zeroing the v4\n // read nulled cost tracking on streaming).\n finalUsage = mapUsage(chunk.totalUsage ?? chunk.usage);\n break;\n }\n case 'error': {\n const errorField = chunk.error;\n const message =\n typeof errorField === 'string'\n ? errorField\n : typeof errorField === 'object' && errorField !== null\n ? (pickString((errorField as { message?: unknown }).message) ?? 'unknown error')\n : 'unknown error';\n yield {\n type: 'error',\n error: { kind: 'unknown', message },\n };\n break;\n }\n default:\n // Unknown chunk types are forward-compatible no-ops; we keep\n // streaming so additive AI SDK upgrades do not break callers.\n break;\n }\n }\n\n yield {\n type: 'finish',\n finishReason,\n usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n };\n}\n\nasync function generateFromVercel(\n model: LanguageModelLike,\n providerName: string,\n capabilities: ProviderCapabilities,\n req: ProviderRequest,\n overrides: VercelRuntimeOverrides | undefined,\n): Promise<ProviderResponse> {\n const sdk = await loadRuntime(overrides);\n const callArgs = buildCallArgs(model, applyRequestPreflight(req, capabilities));\n let result: Awaited<ReturnType<VercelRuntimeOverrides['generateText']>>;\n try {\n result = await sdk.generateText(callArgs);\n } catch (cause) {\n const headers = headersFromCause(cause);\n throw new ProviderHttpError({\n providerName,\n status: statusFromCause(cause),\n message: 'generateText() rejected',\n cause,\n ...(headers !== undefined ? { headers } : {}),\n });\n }\n const usage = mapUsage(result.usage) ?? {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n };\n const finishReason = mapFinishReason(result.finishReason);\n const response: ProviderResponse = {\n usage,\n finishReason,\n ...(result.text !== undefined ? { text: result.text } : {}),\n ...(result.toolCalls !== undefined\n ? { toolCalls: result.toolCalls.map(normalizeToolCall) }\n : {}),\n ...(result.providerMetadata !== undefined ? { providerMetadata: result.providerMetadata } : {}),\n };\n return response;\n}\n\nfunction applyRequestPreflight(\n req: ProviderRequest,\n capabilities: ProviderCapabilities,\n): ProviderRequest {\n const retention = resolveReasoningRetention({\n ...(req.reasoningRetention !== undefined ? { requested: req.reasoningRetention } : {}),\n ...(capabilities.reasoningContract !== undefined\n ? { contract: capabilities.reasoningContract }\n : {}),\n });\n if (retention === 'pass-through-all') return req;\n const filtered = applyReasoningPolicy({ messages: req.messages, retention });\n if (filtered === req.messages) return req;\n return { ...req, messages: filtered };\n}\n\nfunction buildCallArgs(model: LanguageModelLike, req: ProviderRequest) {\n const prompt = toAiSdkPrompt(req.messages);\n // System-role transcript messages are hoisted here - the SDK rejects\n // them inside `messages`. The request-level systemMessage leads.\n const system = [req.systemMessage, prompt.system]\n .filter((s): s is string => s !== undefined && s.length > 0)\n .join('\\n\\n');\n const messages =\n req.cachePolicy?.breakpoints === 'auto'\n ? applyCacheAnchors(prompt.messages, req.cachePolicy.ttl)\n : prompt.messages;\n return {\n model,\n messages,\n ...(system.length > 0 ? { system } : {}),\n // C2: fold worked examples in the adapter itself, so raw-adapter use\n // (no createProvider wrapper) still puts them in front of the model.\n // Idempotent: an upstream fold already dropped the structured field.\n ...(req.tools !== undefined && req.tools.length > 0\n ? { tools: toAiSdkTools(foldToolExamples(req.tools)) }\n : {}),\n ...(req.toolChoice !== undefined ? { toolChoice: toAiSdkToolChoice(req.toolChoice) } : {}),\n ...(req.temperature !== undefined ? { temperature: req.temperature } : {}),\n // v4 reads `maxTokens`; v7 renamed it `maxOutputTokens` - send both\n // so the cap is honoured against either peer (PS-6).\n ...(req.maxTokens !== undefined\n ? { maxTokens: req.maxTokens, maxOutputTokens: req.maxTokens }\n : {}),\n ...(req.signal !== undefined ? { abortSignal: req.signal } : {}),\n ...(req.providerOptions !== undefined ? { providerOptions: req.providerOptions } : {}),\n } as const;\n}\n\nfunction pickString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Normalize a generate() tool call across peers: AI SDK v4 carries\n * `args`, v7 carries `input` (PS-6). The framework shape is `args`.\n */\nfunction normalizeToolCall(tc: unknown): { toolCallId: string; toolName: string; args: unknown } {\n const t = tc as {\n toolCallId?: unknown;\n id?: unknown;\n toolName?: unknown;\n args?: unknown;\n input?: unknown;\n };\n return {\n toolCallId: pickString(t.toolCallId) ?? pickString(t.id) ?? '',\n toolName: pickString(t.toolName) ?? '',\n args: t.args ?? t.input,\n };\n}\n\n/**\n * Lift a real HTTP status from a rejected AI SDK call (PS-2). The SDK's\n * `APICallError` carries a numeric `statusCode`; surfacing it lets\n * `withRetry` / `withFallback` see a genuine 429 / 5xx instead of the\n * `status: 0` network-error placeholder. Returns `0` when no status is\n * present (a true transport-level failure or an abort) - `0` is itself\n * retryable / fallback-eligible by default, while an abort is excluded by\n * the predicates via the wrapped `cause`.\n */\nfunction statusFromCause(cause: unknown): number {\n if (cause !== null && typeof cause === 'object') {\n const c = cause as { statusCode?: unknown; status?: unknown };\n if (typeof c.statusCode === 'number' && Number.isFinite(c.statusCode)) return c.statusCode;\n if (typeof c.status === 'number' && Number.isFinite(c.status)) return c.status;\n }\n return 0;\n}\n\n/**\n * Lift backoff-relevant response headers from a rejected AI SDK call.\n * `APICallError` carries `responseHeaders: Record<string, string>`;\n * forwarding `retry-after` / `x-ratelimit-*` lets `withRetry` honour\n * server-provided delays on 429s.\n */\nfunction headersFromCause(cause: unknown): Readonly<Record<string, string>> | undefined {\n if (cause === null || typeof cause !== 'object') return undefined;\n const raw = (cause as { responseHeaders?: unknown }).responseHeaders;\n if (raw === null || typeof raw !== 'object') return undefined;\n const picked: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n const lower = key.toLowerCase();\n if (\n (lower === 'retry-after' || lower.startsWith('x-ratelimit-')) &&\n typeof value === 'string'\n ) {\n picked[lower] = value;\n }\n }\n return Object.keys(picked).length > 0 ? picked : undefined;\n}\n\nfunction mapFinishReason(value: string | undefined): FinishReason {\n switch (value) {\n case 'stop':\n case 'length':\n case 'tool-calls':\n case 'content-filter':\n case 'error':\n return value;\n case 'aborted':\n case 'cancelled':\n return 'aborted';\n default:\n return 'stop';\n }\n}\n\nfunction mapUsage(input: unknown): Usage | undefined {\n if (input === undefined || input === null || typeof input !== 'object') return undefined;\n const u = input as {\n promptTokens?: number;\n completionTokens?: number;\n inputTokens?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n totalTokens?: number;\n // v7 runtime-normalized detail splits (core-provider-02): inputTokens\n // is the TOTAL including cache reads/writes; outputTokens the TOTAL\n // including reasoning.\n inputTokenDetails?: {\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n noCacheTokens?: number;\n };\n outputTokenDetails?: { reasoningTokens?: number; textTokens?: number };\n };\n const promptTokens = u.promptTokens ?? u.inputTokens ?? 0;\n const rawCompletion = u.completionTokens ?? u.outputTokens ?? 0;\n const totalTokens = u.totalTokens ?? promptTokens + rawCompletion;\n const cachedReadTokens = numberOrUndefined(u.inputTokenDetails?.cacheReadTokens);\n const cacheWriteTokens = numberOrUndefined(u.inputTokenDetails?.cacheWriteTokens);\n // The v7 detail reports reasoning as a SUBSET of outputTokens; core Usage\n // declares reasoningTokens EXCLUSIVE of completionTokens, so split the\n // total (sum unchanged). The flat field (v4/v5 peers) is already exclusive.\n const detailReasoning = numberOrUndefined(u.outputTokenDetails?.reasoningTokens);\n let completionTokens = rawCompletion;\n let reasoningTokens = u.reasoningTokens;\n if (reasoningTokens === undefined && detailReasoning !== undefined && detailReasoning > 0) {\n reasoningTokens = detailReasoning;\n completionTokens = Math.max(0, rawCompletion - detailReasoning);\n }\n const usage: Usage = {\n promptTokens,\n completionTokens,\n totalTokens,\n ...(reasoningTokens !== undefined ? { reasoningTokens } : {}),\n ...(cachedReadTokens !== undefined && cachedReadTokens > 0 ? { cachedReadTokens } : {}),\n ...(cacheWriteTokens !== undefined && cacheWriteTokens > 0 ? { cacheWriteTokens } : {}),\n };\n return usage;\n}\n\nfunction numberOrUndefined(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nlet cachedRuntime: VercelRuntimeOverrides | null = null;\n\nasync function loadRuntime(\n overrides: VercelRuntimeOverrides | undefined,\n): Promise<VercelRuntimeOverrides> {\n if (overrides !== undefined) return overrides;\n if (cachedRuntime !== null) return cachedRuntime;\n let mod: { streamText?: unknown; generateText?: unknown };\n try {\n mod = (await import('ai')) as { streamText?: unknown; generateText?: unknown };\n } catch (cause) {\n throw new ProviderStreamParseError(\n 'vercel',\n \"Failed to import the 'ai' peer dependency. Install it with `pnpm add ai` or pass a runtimeOverrides value.\",\n cause,\n );\n }\n if (typeof mod.streamText !== 'function' || typeof mod.generateText !== 'function') {\n throw new ProviderStreamParseError(\n 'vercel',\n \"The installed 'ai' package does not expose streamText / generateText functions.\",\n );\n }\n cachedRuntime = {\n streamText: mod.streamText as VercelRuntimeOverrides['streamText'],\n generateText: mod.generateText as VercelRuntimeOverrides['generateText'],\n };\n return cachedRuntime;\n}\n\n/**\n * Test-only hook that resets the cached AI SDK runtime. Provider tests\n * that mutate the cache (e.g. by injecting a mock then verifying the\n * default loader runs) call this between scenarios.\n *\n * @internal\n */\nexport function __resetVercelRuntimeCache(): void {\n cachedRuntime = null;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
//#region src/counters/anthropic-wire.ts
|
|
2
|
+
/**
|
|
3
|
+
* Convert Graphorin messages to the Anthropic wire shape. Returns
|
|
4
|
+
* `null` when nothing countable remains (the caller should use its
|
|
5
|
+
* local fallback instead of paying an HTTP round trip for a 400).
|
|
6
|
+
*
|
|
7
|
+
* Consecutive same-role turns are merged and the transcript is forced
|
|
8
|
+
* to start with a user turn - both hard requirements of the API.
|
|
9
|
+
* Parts with no wire representation for counting purposes (audio,
|
|
10
|
+
* non-PDF files, unsigned reasoning) are skipped; the count is then a
|
|
11
|
+
* slight undercount rather than a guaranteed 400.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
function toAnthropicCountPayload(messages) {
|
|
16
|
+
const systemParts = [];
|
|
17
|
+
const turns = [];
|
|
18
|
+
const pushBlocks = (role, blocks) => {
|
|
19
|
+
if (blocks.length === 0) return;
|
|
20
|
+
const last = turns[turns.length - 1];
|
|
21
|
+
if (last !== void 0 && last.role === role) {
|
|
22
|
+
last.content.push(...blocks);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
turns.push({
|
|
26
|
+
role,
|
|
27
|
+
content: blocks
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
for (const msg of messages) switch (msg.role) {
|
|
31
|
+
case "system":
|
|
32
|
+
if (msg.content.length > 0) systemParts.push(msg.content);
|
|
33
|
+
break;
|
|
34
|
+
case "user":
|
|
35
|
+
pushBlocks("user", contentToBlocks(msg.content, "user"));
|
|
36
|
+
break;
|
|
37
|
+
case "assistant": {
|
|
38
|
+
const blocks = contentToBlocks(msg.content, "assistant");
|
|
39
|
+
for (const call of msg.toolCalls ?? []) blocks.push({
|
|
40
|
+
type: "tool_use",
|
|
41
|
+
id: call.toolCallId,
|
|
42
|
+
name: call.toolName,
|
|
43
|
+
input: toInputObject(call.args)
|
|
44
|
+
});
|
|
45
|
+
pushBlocks("assistant", blocks);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case "tool": {
|
|
49
|
+
const text = flattenToText(msg.content);
|
|
50
|
+
pushBlocks("user", [{
|
|
51
|
+
type: "tool_result",
|
|
52
|
+
tool_use_id: msg.toolCallId,
|
|
53
|
+
...text.length > 0 ? { content: [{
|
|
54
|
+
type: "text",
|
|
55
|
+
text
|
|
56
|
+
}] } : {}
|
|
57
|
+
}]);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (turns.length === 0 && systemParts.length === 0) return null;
|
|
62
|
+
if (turns.length === 0 || turns[0]?.role !== "user") turns.unshift({
|
|
63
|
+
role: "user",
|
|
64
|
+
content: [{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: "."
|
|
67
|
+
}]
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
...systemParts.length > 0 ? { system: systemParts.join("\n\n") } : {},
|
|
71
|
+
messages: turns
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function contentToBlocks(content, role) {
|
|
75
|
+
if (typeof content === "string") return content.length > 0 ? [{
|
|
76
|
+
type: "text",
|
|
77
|
+
text: content
|
|
78
|
+
}] : [];
|
|
79
|
+
const blocks = [];
|
|
80
|
+
for (const part of content) switch (part.type) {
|
|
81
|
+
case "text":
|
|
82
|
+
if (part.text.length > 0) blocks.push({
|
|
83
|
+
type: "text",
|
|
84
|
+
text: part.text
|
|
85
|
+
});
|
|
86
|
+
break;
|
|
87
|
+
case "image": {
|
|
88
|
+
const source = toImageSource(part.image, part.mimeType);
|
|
89
|
+
if (source !== null && role === "user") blocks.push({
|
|
90
|
+
type: "image",
|
|
91
|
+
source
|
|
92
|
+
});
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "file":
|
|
96
|
+
if (part.mimeType === "application/pdf" && part.file instanceof Uint8Array) blocks.push({
|
|
97
|
+
type: "document",
|
|
98
|
+
source: {
|
|
99
|
+
type: "base64",
|
|
100
|
+
media_type: "application/pdf",
|
|
101
|
+
data: toBase64(part.file)
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
case "reasoning":
|
|
106
|
+
if (role === "assistant" && part.meta?.provider === "anthropic" && typeof part.meta.signature === "string") blocks.push({
|
|
107
|
+
type: "thinking",
|
|
108
|
+
thinking: part.text,
|
|
109
|
+
signature: part.meta.signature
|
|
110
|
+
});
|
|
111
|
+
break;
|
|
112
|
+
case "audio": break;
|
|
113
|
+
}
|
|
114
|
+
return blocks;
|
|
115
|
+
}
|
|
116
|
+
function toImageSource(image, mimeType) {
|
|
117
|
+
if (image instanceof URL) return {
|
|
118
|
+
type: "url",
|
|
119
|
+
url: image.toString()
|
|
120
|
+
};
|
|
121
|
+
if (image instanceof Uint8Array) return {
|
|
122
|
+
type: "base64",
|
|
123
|
+
media_type: mimeType ?? "image/png",
|
|
124
|
+
data: toBase64(image)
|
|
125
|
+
};
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function toBase64(bytes) {
|
|
129
|
+
return Buffer.from(bytes).toString("base64");
|
|
130
|
+
}
|
|
131
|
+
function toInputObject(args) {
|
|
132
|
+
if (typeof args === "string") try {
|
|
133
|
+
const parsed = JSON.parse(args);
|
|
134
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
135
|
+
} catch {
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
return typeof args === "object" && args !== null ? args : {};
|
|
139
|
+
}
|
|
140
|
+
function flattenToText(content) {
|
|
141
|
+
if (typeof content === "string") return content;
|
|
142
|
+
const buffer = [];
|
|
143
|
+
for (const part of content) if (part.type === "text" || part.type === "reasoning") buffer.push(part.text);
|
|
144
|
+
return buffer.join("");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
export { toAnthropicCountPayload };
|
|
149
|
+
//# sourceMappingURL=anthropic-wire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-wire.js","names":["systemParts: string[]","turns: Array<{ role: 'user' | 'assistant'; content: Array<Record<string, unknown>> }>","blocks: Array<Record<string, unknown>>","parsed: unknown","buffer: string[]"],"sources":["../../src/counters/anthropic-wire.ts"],"sourcesContent":["/**\n * Conversion from Graphorin messages to the Anthropic Messages API\n * wire shape, used by `AnthropicAPICounter` (core-provider-04).\n *\n * The `count_tokens` endpoint accepts the same body as `/v1/messages`:\n * only `user` / `assistant` roles in `messages`, system prompts in the\n * top-level `system` field, assistant tool calls as `tool_use` blocks,\n * tool results as `tool_result` blocks inside a **user** turn, and\n * images as `source` blocks. Posting Graphorin-shaped messages 400s on\n * any transcript containing a system / tool message or `toolCalls`,\n * silently downgrading the \"native\" counter to tiktoken.\n *\n * @internal\n */\n\nimport type { Message, MessageContent } from '@graphorin/core';\n\n/** Anthropic-shaped count_tokens payload. */\nexport interface AnthropicCountPayload {\n readonly system?: string;\n readonly messages: ReadonlyArray<{\n readonly role: 'user' | 'assistant';\n readonly content: ReadonlyArray<Readonly<Record<string, unknown>>>;\n }>;\n}\n\n/**\n * Convert Graphorin messages to the Anthropic wire shape. Returns\n * `null` when nothing countable remains (the caller should use its\n * local fallback instead of paying an HTTP round trip for a 400).\n *\n * Consecutive same-role turns are merged and the transcript is forced\n * to start with a user turn - both hard requirements of the API.\n * Parts with no wire representation for counting purposes (audio,\n * non-PDF files, unsigned reasoning) are skipped; the count is then a\n * slight undercount rather than a guaranteed 400.\n *\n * @internal\n */\nexport function toAnthropicCountPayload(\n messages: ReadonlyArray<Message>,\n): AnthropicCountPayload | null {\n const systemParts: string[] = [];\n const turns: Array<{ role: 'user' | 'assistant'; content: Array<Record<string, unknown>> }> = [];\n\n const pushBlocks = (role: 'user' | 'assistant', blocks: Array<Record<string, unknown>>): void => {\n if (blocks.length === 0) return;\n const last = turns[turns.length - 1];\n if (last !== undefined && last.role === role) {\n last.content.push(...blocks);\n return;\n }\n turns.push({ role, content: blocks });\n };\n\n for (const msg of messages) {\n switch (msg.role) {\n case 'system':\n if (msg.content.length > 0) systemParts.push(msg.content);\n break;\n case 'user':\n pushBlocks('user', contentToBlocks(msg.content, 'user'));\n break;\n case 'assistant': {\n const blocks = contentToBlocks(msg.content, 'assistant');\n for (const call of msg.toolCalls ?? []) {\n blocks.push({\n type: 'tool_use',\n id: call.toolCallId,\n name: call.toolName,\n input: toInputObject(call.args),\n });\n }\n pushBlocks('assistant', blocks);\n break;\n }\n case 'tool': {\n // Tool results live in the next USER turn on the Anthropic wire.\n const text = flattenToText(msg.content);\n pushBlocks('user', [\n {\n type: 'tool_result',\n tool_use_id: msg.toolCallId,\n ...(text.length > 0 ? { content: [{ type: 'text', text }] } : {}),\n },\n ]);\n break;\n }\n }\n }\n\n if (turns.length === 0 && systemParts.length === 0) return null;\n // The API requires the first message to be a user turn.\n if (turns.length === 0 || turns[0]?.role !== 'user') {\n turns.unshift({ role: 'user', content: [{ type: 'text', text: '.' }] });\n }\n return {\n ...(systemParts.length > 0 ? { system: systemParts.join('\\n\\n') } : {}),\n messages: turns,\n };\n}\n\nfunction contentToBlocks(\n content: string | ReadonlyArray<MessageContent>,\n role: 'user' | 'assistant',\n): Array<Record<string, unknown>> {\n if (typeof content === 'string') {\n return content.length > 0 ? [{ type: 'text', text: content }] : [];\n }\n const blocks: Array<Record<string, unknown>> = [];\n for (const part of content) {\n switch (part.type) {\n case 'text':\n if (part.text.length > 0) blocks.push({ type: 'text', text: part.text });\n break;\n case 'image': {\n const source = toImageSource(part.image, part.mimeType);\n if (source !== null && role === 'user') blocks.push({ type: 'image', source });\n break;\n }\n case 'file': {\n // PDFs count as document blocks; other file kinds have no\n // countable wire form and are skipped.\n if (part.mimeType === 'application/pdf' && part.file instanceof Uint8Array) {\n blocks.push({\n type: 'document',\n source: {\n type: 'base64',\n media_type: 'application/pdf',\n data: toBase64(part.file),\n },\n });\n }\n break;\n }\n case 'reasoning': {\n // Only signed thinking blocks round-trip through the API;\n // unsigned reasoning would 400, so it is skipped (the default\n // retention policy strips it from outbound requests anyway).\n if (\n role === 'assistant' &&\n part.meta?.provider === 'anthropic' &&\n typeof part.meta.signature === 'string'\n ) {\n blocks.push({ type: 'thinking', thinking: part.text, signature: part.meta.signature });\n }\n break;\n }\n case 'audio':\n // No audio block on the Messages API; skip.\n break;\n }\n }\n return blocks;\n}\n\nfunction toImageSource(\n image: Uint8Array | URL,\n mimeType: string | undefined,\n): Record<string, unknown> | null {\n if (image instanceof URL) {\n return { type: 'url', url: image.toString() };\n }\n if (image instanceof Uint8Array) {\n return {\n type: 'base64',\n media_type: mimeType ?? 'image/png',\n data: toBase64(image),\n };\n }\n return null;\n}\n\nfunction toBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n\nfunction toInputObject(args: unknown): Record<string, unknown> {\n if (typeof args === 'string') {\n try {\n const parsed: unknown = JSON.parse(args);\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n return typeof args === 'object' && args !== null ? (args as Record<string, unknown>) : {};\n}\n\nfunction flattenToText(content: string | ReadonlyArray<MessageContent>): string {\n if (typeof content === 'string') return content;\n const buffer: string[] = [];\n for (const part of content) {\n if (part.type === 'text' || part.type === 'reasoning') buffer.push(part.text);\n }\n return buffer.join('');\n}\n"],"mappings":"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"}
|
|
@@ -14,11 +14,16 @@ interface AnthropicAPICounterOptions {
|
|
|
14
14
|
readonly fetchImpl?: typeof fetch;
|
|
15
15
|
/** Identifier carried on the produced counter. */
|
|
16
16
|
readonly id?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Log sink for the WARN emitted (once per counter) when the native
|
|
19
|
+
* call degrades to the tiktoken fallback. Defaults to `console.warn`.
|
|
20
|
+
*/
|
|
21
|
+
readonly logger?: (message: string, meta?: object) => void;
|
|
17
22
|
}
|
|
18
23
|
/**
|
|
19
24
|
* Counter that talks to `POST /v1/messages/count_tokens` when an API
|
|
20
25
|
* key is configured. Without an API key, the counter delegates to
|
|
21
|
-
* `JsTiktokenCounter('cl100k_base')`
|
|
26
|
+
* `JsTiktokenCounter('cl100k_base')` - the closest publicly-available
|
|
22
27
|
* proxy for Anthropic's tokenizer.
|
|
23
28
|
*
|
|
24
29
|
* @stable
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","names":[],"sources":["../../src/counters/anthropic.ts"],"sourcesContent":[],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","names":[],"sources":["../../src/counters/anthropic.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAqBiB,0BAAA;;;;8BAIa;;;;;;;;;;;;;;;;;cAkBjB,mBAAA,YAA+B;;;;uBAWrB;kBAWC,cAAc,WAAW;2BA+BhB"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { stripTrailingSlashes } from "../internal/url-utils.js";
|
|
2
|
+
import { toAnthropicCountPayload } from "./anthropic-wire.js";
|
|
2
3
|
import { JsTiktokenCounter } from "./js-tiktoken.js";
|
|
3
4
|
|
|
4
5
|
//#region src/counters/anthropic.ts
|
|
5
6
|
/**
|
|
6
7
|
* Counter that talks to `POST /v1/messages/count_tokens` when an API
|
|
7
8
|
* key is configured. Without an API key, the counter delegates to
|
|
8
|
-
* `JsTiktokenCounter('cl100k_base')`
|
|
9
|
+
* `JsTiktokenCounter('cl100k_base')` - the closest publicly-available
|
|
9
10
|
* proxy for Anthropic's tokenizer.
|
|
10
11
|
*
|
|
11
12
|
* @stable
|
|
@@ -18,6 +19,8 @@ var AnthropicAPICounter = class {
|
|
|
18
19
|
#baseUrl;
|
|
19
20
|
#fetchImpl;
|
|
20
21
|
#fallback;
|
|
22
|
+
#logger;
|
|
23
|
+
#fallbackWarned = false;
|
|
21
24
|
constructor(options) {
|
|
22
25
|
this.#modelId = options.modelId;
|
|
23
26
|
if (options.apiKey !== void 0) this.#apiKey = options.apiKey;
|
|
@@ -27,11 +30,14 @@ var AnthropicAPICounter = class {
|
|
|
27
30
|
encoding: "cl100k_base",
|
|
28
31
|
modelId: options.modelId
|
|
29
32
|
});
|
|
33
|
+
this.#logger = options.logger ?? ((message, meta) => console.warn(message, meta ?? ""));
|
|
30
34
|
this.id = options.id ?? `anthropic-native@${options.modelId}`;
|
|
31
35
|
this.version = `anthropic-native-${options.modelId}-v1`;
|
|
32
36
|
}
|
|
33
37
|
async count(messages) {
|
|
34
38
|
if (this.#apiKey === void 0) return this.#fallback.count(messages);
|
|
39
|
+
const payload = toAnthropicCountPayload(messages);
|
|
40
|
+
if (payload === null) return this.#fallback.count(messages);
|
|
35
41
|
try {
|
|
36
42
|
const resp = await this.#fetchImpl(`${this.#baseUrl}/v1/messages/count_tokens`, {
|
|
37
43
|
method: "POST",
|
|
@@ -42,20 +48,35 @@ var AnthropicAPICounter = class {
|
|
|
42
48
|
},
|
|
43
49
|
body: JSON.stringify({
|
|
44
50
|
model: this.#modelId,
|
|
45
|
-
|
|
51
|
+
...payload
|
|
46
52
|
})
|
|
47
53
|
});
|
|
48
|
-
if (!resp.ok)
|
|
54
|
+
if (!resp.ok) {
|
|
55
|
+
this.#warnFallback(`HTTP ${resp.status}`);
|
|
56
|
+
return this.#fallback.count(messages);
|
|
57
|
+
}
|
|
49
58
|
const json = await resp.json();
|
|
50
59
|
if (typeof json.input_tokens === "number") return json.input_tokens;
|
|
60
|
+
this.#warnFallback("response missing input_tokens");
|
|
51
61
|
return this.#fallback.count(messages);
|
|
52
|
-
} catch {
|
|
62
|
+
} catch (cause) {
|
|
63
|
+
this.#warnFallback(cause.message);
|
|
53
64
|
return this.#fallback.count(messages);
|
|
54
65
|
}
|
|
55
66
|
}
|
|
56
67
|
async countText(text) {
|
|
57
68
|
return this.#fallback.countText(text);
|
|
58
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* WARN once per counter when the native path degrades - a silent
|
|
72
|
+
* fallback to cl100k tiktoken undercounts Claude tokens by ~15-20%,
|
|
73
|
+
* so the degradation must be visible.
|
|
74
|
+
*/
|
|
75
|
+
#warnFallback(reason) {
|
|
76
|
+
if (this.#fallbackWarned) return;
|
|
77
|
+
this.#fallbackWarned = true;
|
|
78
|
+
this.#logger(`[graphorin/provider] AnthropicAPICounter(${this.#modelId}): native count_tokens failed (${reason}); falling back to cl100k tiktoken (undercounts Claude by ~15-20%). Further fallbacks are silent.`);
|
|
79
|
+
}
|
|
59
80
|
};
|
|
60
81
|
|
|
61
82
|
//#endregion
|