@genkit-ai/vercel-ai 0.2.0-rc.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.
@@ -0,0 +1,247 @@
1
+ function mapRole(role) {
2
+ switch (role) {
3
+ case "assistant":
4
+ return "model";
5
+ case "user":
6
+ return "user";
7
+ case "system":
8
+ return "system";
9
+ default:
10
+ return "user";
11
+ }
12
+ }
13
+ function mapGenkitRole(role) {
14
+ switch (role) {
15
+ case "model":
16
+ return "assistant";
17
+ case "system":
18
+ return "system";
19
+ case "user":
20
+ default:
21
+ return "user";
22
+ }
23
+ }
24
+ function extractPartMetadata(part) {
25
+ const md = part?.metadata;
26
+ if (md && typeof md === "object" && !Array.isArray(md)) {
27
+ return md;
28
+ }
29
+ return void 0;
30
+ }
31
+ function mapUIPartToGenkit(part) {
32
+ const metadata = extractPartMetadata(part);
33
+ const withMetadata = (p) => metadata ? { ...p, metadata } : p;
34
+ if (part.type === "text") {
35
+ return [withMetadata({ text: part.text })];
36
+ }
37
+ if (part.type === "reasoning") {
38
+ const reasoning = part.text;
39
+ return typeof reasoning === "string" && reasoning !== "" ? [withMetadata({ reasoning })] : [];
40
+ }
41
+ if (part.type === "file") {
42
+ return [
43
+ withMetadata({
44
+ media: {
45
+ url: part.url,
46
+ contentType: part.mediaType
47
+ }
48
+ })
49
+ ];
50
+ }
51
+ if (part.type === "dynamic-tool" || part.type.startsWith("tool-")) {
52
+ const p = part;
53
+ const toolCallId = typeof p.toolCallId === "string" ? p.toolCallId : crypto.randomUUID();
54
+ const toolName = typeof p.toolName === "string" && p.toolName ? p.toolName : part.type.startsWith("tool-") ? part.type.slice("tool-".length) : "unknown";
55
+ const parts = [
56
+ withMetadata({
57
+ toolRequest: {
58
+ ref: toolCallId,
59
+ name: toolName,
60
+ input: p.input
61
+ }
62
+ })
63
+ ];
64
+ if (p.state === "output-available" && p.output !== void 0) {
65
+ parts.push(
66
+ withMetadata({
67
+ toolResponse: {
68
+ ref: toolCallId,
69
+ name: toolName,
70
+ output: p.output
71
+ }
72
+ })
73
+ );
74
+ }
75
+ return parts;
76
+ }
77
+ return [];
78
+ }
79
+ function mapUIMessageToGenkit(msg) {
80
+ const role = mapRole(msg.role);
81
+ const genkitParts = msg.parts.flatMap(mapUIPartToGenkit);
82
+ return { role, content: genkitParts };
83
+ }
84
+ function mapGenkitPartToUI(part, responsesByRef) {
85
+ const metadata = part.metadata && typeof part.metadata === "object" ? part.metadata : void 0;
86
+ const withMetadata = (p) => metadata ? { ...p, metadata } : p;
87
+ if (typeof part.text === "string") {
88
+ return [withMetadata({ type: "text", text: part.text })];
89
+ }
90
+ if (typeof part.reasoning === "string") {
91
+ return part.reasoning !== "" ? [withMetadata({ type: "reasoning", text: part.reasoning })] : [];
92
+ }
93
+ if (part.media) {
94
+ return [
95
+ withMetadata({
96
+ type: "file",
97
+ url: part.media.url,
98
+ mediaType: part.media.contentType
99
+ })
100
+ ];
101
+ }
102
+ if (part.toolRequest) {
103
+ const tr = part.toolRequest;
104
+ const toolCallId = tr.ref ?? crypto.randomUUID();
105
+ const base = {
106
+ type: `tool-${tr.name}`,
107
+ toolCallId,
108
+ input: tr.input
109
+ };
110
+ if (responsesByRef?.has(toolCallId)) {
111
+ return [
112
+ withMetadata({
113
+ ...base,
114
+ state: "output-available",
115
+ output: responsesByRef.get(toolCallId)
116
+ })
117
+ ];
118
+ }
119
+ return [withMetadata({ ...base, state: "input-available" })];
120
+ }
121
+ return [];
122
+ }
123
+ function mapGenkitMessageToUI(msg, responsesByRef, id) {
124
+ const parts = (msg.content ?? []).flatMap(
125
+ (part) => mapGenkitPartToUI(part, responsesByRef)
126
+ );
127
+ return {
128
+ id: id ?? `restored-${crypto.randomUUID()}`,
129
+ role: mapGenkitRole(msg.role),
130
+ parts
131
+ };
132
+ }
133
+ function messagesFromSnapshot(messages) {
134
+ const responsesByRef = /* @__PURE__ */ new Map();
135
+ for (const msg of messages) {
136
+ for (const part of msg.content ?? []) {
137
+ if (part.toolResponse?.ref) {
138
+ responsesByRef.set(part.toolResponse.ref, part.toolResponse.output);
139
+ }
140
+ }
141
+ }
142
+ const result = [];
143
+ let index = 0;
144
+ for (const msg of messages) {
145
+ if (msg.role === "tool") continue;
146
+ const ui = mapGenkitMessageToUI(msg, responsesByRef, `restored-${index}`);
147
+ index++;
148
+ if (ui.parts.length > 0) result.push(ui);
149
+ }
150
+ return result;
151
+ }
152
+ const RESTART_MARKER = "__genkitVercelAiRestart__";
153
+ function restartInterrupt(metadata) {
154
+ return metadata === void 0 ? { [RESTART_MARKER]: true } : { [RESTART_MARKER]: true, metadata };
155
+ }
156
+ function asRestartInterrupt(output) {
157
+ if (output && typeof output === "object" && output[RESTART_MARKER] === true) {
158
+ return output;
159
+ }
160
+ return void 0;
161
+ }
162
+ function isEmptyUserMessage(msg) {
163
+ return !msg.parts.some((part) => {
164
+ if (part.type === "text") {
165
+ return typeof part.text === "string" && part.text.trim() !== "";
166
+ }
167
+ if (part.type === "file") {
168
+ return true;
169
+ }
170
+ return false;
171
+ });
172
+ }
173
+ function resolvedToolsFromMessage(msg) {
174
+ const seen = /* @__PURE__ */ new Set();
175
+ const results = [];
176
+ for (const part of msg.parts) {
177
+ const p = part;
178
+ const isTool = part.type === "dynamic-tool" || part.type.startsWith("tool-");
179
+ if (isTool && typeof p.toolCallId === "string" && p.toolCallId && !seen.has(p.toolCallId) && p.state === "output-available" && p.output !== void 0) {
180
+ const toolName = typeof p.toolName === "string" && p.toolName ? p.toolName : part.type.startsWith("tool-") ? part.type.slice("tool-".length) : "unknown";
181
+ seen.add(p.toolCallId);
182
+ results.push({
183
+ toolCallId: p.toolCallId,
184
+ toolName,
185
+ input: p.input,
186
+ result: p.output
187
+ });
188
+ }
189
+ }
190
+ return results;
191
+ }
192
+ function extractResolvedToolResults(messages) {
193
+ const seen = /* @__PURE__ */ new Set();
194
+ const results = [];
195
+ for (let i = messages.length - 1; i >= 0; i--) {
196
+ const msg = messages[i];
197
+ if (msg.role !== "assistant") continue;
198
+ for (const tr of resolvedToolsFromMessage(msg)) {
199
+ if (seen.has(tr.toolCallId)) continue;
200
+ seen.add(tr.toolCallId);
201
+ results.push(tr);
202
+ }
203
+ }
204
+ return results;
205
+ }
206
+ function currentTurnResolvedTools(messages) {
207
+ let lastAssistantIdx = -1;
208
+ for (let i = messages.length - 1; i >= 0; i--) {
209
+ if (messages[i].role === "assistant") {
210
+ lastAssistantIdx = i;
211
+ break;
212
+ }
213
+ }
214
+ if (lastAssistantIdx === -1) return [];
215
+ for (let i = lastAssistantIdx + 1; i < messages.length; i++) {
216
+ if (messages[i].role === "user" && !isEmptyUserMessage(messages[i])) {
217
+ return [];
218
+ }
219
+ }
220
+ return resolvedToolsFromMessage(messages[lastAssistantIdx]);
221
+ }
222
+ function findLastUserMessage(messages) {
223
+ for (let i = messages.length - 1; i >= 0; i--) {
224
+ if (messages[i].role === "user") return messages[i];
225
+ }
226
+ return void 0;
227
+ }
228
+ function findLastNonEmptyUserMessage(messages) {
229
+ for (let i = messages.length - 1; i >= 0; i--) {
230
+ const msg = messages[i];
231
+ if (msg.role === "user" && !isEmptyUserMessage(msg)) return msg;
232
+ }
233
+ return void 0;
234
+ }
235
+ export {
236
+ asRestartInterrupt,
237
+ currentTurnResolvedTools,
238
+ extractResolvedToolResults,
239
+ findLastNonEmptyUserMessage,
240
+ findLastUserMessage,
241
+ mapGenkitMessageToUI,
242
+ mapUIMessageToGenkit,
243
+ mapUIPartToGenkit,
244
+ messagesFromSnapshot,
245
+ restartInterrupt
246
+ };
247
+ //# sourceMappingURL=mapping.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mapping.ts"],"sourcesContent":["/**\n * Copyright 2026 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { UIMessage } from 'ai';\nimport type { MessageData, Part } from 'genkit';\n\n// ---------------------------------------------------------------------------\n// UIMessage → Genkit\n// ---------------------------------------------------------------------------\n\n/**\n * Maps a Vercel AI SDK role to a Genkit role.\n */\nfunction mapRole(role: UIMessage['role']): MessageData['role'] {\n switch (role) {\n case 'assistant':\n return 'model';\n case 'user':\n return 'user';\n case 'system':\n return 'system';\n default:\n // Unknown roles fall back to 'user' so content is not silently dropped.\n return 'user';\n }\n}\n\n/**\n * Maps a Genkit role to a Vercel AI SDK role.\n */\nfunction mapGenkitRole(role: MessageData['role']): UIMessage['role'] {\n switch (role) {\n case 'model':\n return 'assistant';\n case 'system':\n return 'system';\n case 'user':\n default:\n return 'user';\n }\n}\n\n/**\n * Extracts a plain-object `metadata` value from an AI SDK UI part, if present.\n *\n * AI SDK part types don't all formally declare a `metadata` field, but the\n * transport (and middleware-aware UIs) may attach one. We read it defensively\n * so Genkit part metadata survives a UI → Genkit round-trip.\n */\nfunction extractPartMetadata(\n part: unknown\n): Record<string, unknown> | undefined {\n const md = (part as { metadata?: unknown } | null)?.metadata;\n if (md && typeof md === 'object' && !Array.isArray(md)) {\n return md as Record<string, unknown>;\n }\n return undefined;\n}\n\n/**\n * Maps a single Vercel AI SDK v6 UIMessagePart to zero or more Genkit Parts.\n *\n * Handles `text`, `file`, and tool parts (both `ToolUIPart` with\n * `type: 'tool-<name>'` and `DynamicToolUIPart` with `type: 'dynamic-tool'`).\n *\n * A tool part with state `output-available` produces both a `toolRequest`\n * and a `toolResponse` part (matching Genkit's conversation model).\n */\nexport function mapUIPartToGenkit(part: UIMessage['parts'][number]): Part[] {\n // Genkit part metadata that may have been attached to the UI part (e.g. by\n // a middleware-aware UI or a previous Genkit → UI mapping). Preserved so it\n // survives a UI → Genkit round-trip.\n const metadata = extractPartMetadata(part);\n const withMetadata = <T extends Part>(p: T): T =>\n metadata ? ({ ...p, metadata } as T) : p;\n\n if (part.type === 'text') {\n return [withMetadata({ text: part.text })];\n }\n\n if (part.type === 'reasoning') {\n // Map AI SDK reasoning parts to Genkit reasoning parts so the model's\n // thinking is preserved across turns (e.g. when replaying history).\n // Empty reasoning is dropped to avoid emitting stray, contentless\n // reasoning blocks (mirrors the streaming side in client.ts, which also\n // skips `reasoning === ''`).\n const reasoning = (part as { text?: unknown }).text;\n return typeof reasoning === 'string' && reasoning !== ''\n ? [withMetadata({ reasoning })]\n : [];\n }\n\n if (part.type === 'file') {\n return [\n withMetadata({\n media: {\n url: part.url,\n contentType: part.mediaType,\n },\n }),\n ];\n }\n\n // Handle v6 tool parts:\n // ToolUIPart: type: 'tool-<name>', toolCallId, state, input?, output?\n // DynamicToolUIPart: type: 'dynamic-tool', toolName, toolCallId, state, input?, output?\n //\n // Use runtime property checks via Record cast since TypeScript can't\n // narrow on prefix match for template literal types.\n if (part.type === 'dynamic-tool' || part.type.startsWith('tool-')) {\n const p = part as unknown as Record<string, unknown>;\n const toolCallId =\n typeof p.toolCallId === 'string' ? p.toolCallId : crypto.randomUUID();\n const toolName =\n typeof p.toolName === 'string' && p.toolName\n ? p.toolName\n : part.type.startsWith('tool-')\n ? part.type.slice('tool-'.length)\n : 'unknown';\n\n const parts: Part[] = [\n withMetadata({\n toolRequest: {\n ref: toolCallId,\n name: toolName,\n input: p.input,\n },\n }),\n ];\n\n if (p.state === 'output-available' && p.output !== undefined) {\n parts.push(\n withMetadata({\n toolResponse: {\n ref: toolCallId,\n name: toolName,\n output: p.output,\n },\n })\n );\n }\n\n return parts;\n }\n\n // Unsupported part types (source-*, step-start, data-*) are silently\n // skipped — they don't have a direct Genkit equivalent.\n return [];\n}\n\n/**\n * Maps a Vercel AI SDK UIMessage to a Genkit MessageData.\n */\nexport function mapUIMessageToGenkit(msg: UIMessage): MessageData {\n const role = mapRole(msg.role);\n const genkitParts: Part[] = msg.parts.flatMap(mapUIPartToGenkit);\n return { role, content: genkitParts };\n}\n\n// ---------------------------------------------------------------------------\n// Genkit → UIMessage (session restore)\n// ---------------------------------------------------------------------------\n\n/**\n * Maps a single Genkit {@link Part} to zero or more AI SDK UI parts.\n *\n * `toolResponse` outputs live in separate (`tool`-role) Genkit messages, so a\n * `toolRequest` is paired with its response via `responsesByRef` to produce a\n * single AI SDK tool part: `output-available` when the response is known, or\n * `input-available` when it is still pending (e.g. an unresolved interrupt).\n *\n * Standalone `toolResponse` parts are skipped here — they're merged into the\n * matching request's tool part instead of producing a separate one.\n */\nfunction mapGenkitPartToUI(\n part: Part,\n responsesByRef?: Map<string, unknown>\n): UIMessage['parts'] {\n // Preserve Genkit part metadata on the UI part so it survives a\n // Genkit → UI → Genkit round-trip (read back by extractPartMetadata).\n const metadata =\n part.metadata && typeof part.metadata === 'object'\n ? (part.metadata as Record<string, unknown>)\n : undefined;\n const withMetadata = (\n p: Record<string, unknown>\n ): UIMessage['parts'][number] =>\n (metadata ? { ...p, metadata } : p) as UIMessage['parts'][number];\n\n if (typeof part.text === 'string') {\n return [withMetadata({ type: 'text', text: part.text })];\n }\n\n if (typeof part.reasoning === 'string') {\n return part.reasoning !== ''\n ? [withMetadata({ type: 'reasoning', text: part.reasoning })]\n : [];\n }\n\n if (part.media) {\n return [\n withMetadata({\n type: 'file',\n url: part.media.url,\n mediaType: part.media.contentType,\n }),\n ];\n }\n\n if (part.toolRequest) {\n const tr = part.toolRequest;\n const toolCallId = tr.ref ?? crypto.randomUUID();\n const base: Record<string, unknown> = {\n type: `tool-${tr.name}`,\n toolCallId,\n input: tr.input,\n };\n if (responsesByRef?.has(toolCallId)) {\n return [\n withMetadata({\n ...base,\n state: 'output-available',\n output: responsesByRef.get(toolCallId),\n }),\n ];\n }\n return [withMetadata({ ...base, state: 'input-available' })];\n }\n\n // Standalone toolResponse (paired with a request above) or any other part\n // type without a direct UI equivalent is skipped.\n return [];\n}\n\n/**\n * Maps a single Genkit {@link MessageData} to an AI SDK {@link UIMessage}.\n *\n * Pass `responsesByRef` (built across the whole conversation) so each\n * `toolRequest` can be paired with the `toolResponse` that resolved it, even\n * though they live in different messages. When omitted, all tool parts are\n * emitted in the `input-available` state.\n */\nexport function mapGenkitMessageToUI(\n msg: MessageData,\n responsesByRef?: Map<string, unknown>,\n id?: string\n): UIMessage {\n const parts = (msg.content ?? []).flatMap((part) =>\n mapGenkitPartToUI(part, responsesByRef)\n );\n return {\n id: id ?? `restored-${crypto.randomUUID()}`,\n role: mapGenkitRole(msg.role),\n parts,\n };\n}\n\n/**\n * Converts the `messages` of a Genkit `SessionSnapshot` (an array of\n * {@link MessageData}) into AI SDK {@link UIMessage}s suitable for seeding\n * `useChat` (via its `messages` option or `setMessages`) to rehydrate a\n * previous conversation after a reload.\n *\n * Tool requests and their responses (which Genkit stores in separate\n * `model`/`tool` messages) are merged into single AI SDK tool parts, so the\n * UI renders each tool call as one element with both its input and output.\n * `tool`-role messages are therefore omitted from the result. Unresolved tool\n * requests (e.g. a pending interrupt) are emitted in the `input-available`\n * state.\n *\n * Conversation state is server-managed by `sessionId` (the `useChat` `id`), so\n * to resume the *next* turn you only need to reuse the same chat `id` — there\n * is no snapshot to restore on the client.\n *\n * @example\n * ```ts\n * const snapshot = await runFlow({ url: '/api/chat/weather/state', input: sessionId });\n * const messages = messagesFromSnapshot(snapshot.state.messages);\n * // pass `messages` to useChat({ id: sessionId, messages })\n * ```\n */\nexport function messagesFromSnapshot(messages: MessageData[]): UIMessage[] {\n // Collect every tool response by ref so a request in one message can be\n // paired with its response in another (Genkit stores them separately).\n const responsesByRef = new Map<string, unknown>();\n for (const msg of messages) {\n for (const part of msg.content ?? []) {\n if (part.toolResponse?.ref) {\n responsesByRef.set(part.toolResponse.ref, part.toolResponse.output);\n }\n }\n }\n\n const result: UIMessage[] = [];\n let index = 0;\n for (const msg of messages) {\n // tool-role messages only carry responses, which are merged into the\n // matching request's tool part — skip them.\n if (msg.role === 'tool') continue;\n const ui = mapGenkitMessageToUI(msg, responsesByRef, `restored-${index}`);\n index++;\n // Drop messages that produced no renderable parts.\n if (ui.parts.length > 0) result.push(ui);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Interrupt restart support\n// ---------------------------------------------------------------------------\n\n/**\n * Marker placed on a tool output to signal that the user chose to *restart*\n * (re-run) an interrupted tool rather than supply a final response.\n *\n * `Symbol.for` is intentionally avoided because tool outputs are serialized to\n * JSON before reaching the transport; a string marker survives that round-trip.\n */\nconst RESTART_MARKER = '__genkitVercelAiRestart__';\n\n/**\n * The serialized shape produced by {@link restartInterrupt}.\n */\nexport interface RestartInterruptOutput {\n [RESTART_MARKER]: true;\n /**\n * Optional metadata surfaced to the tool as its `resumed` argument when it\n * re-runs server-side. Defaults to `true` when omitted.\n */\n metadata?: unknown;\n}\n\n/**\n * Builds a tool output that instructs the Genkit agent to **restart** (re-run)\n * an interrupted tool instead of accepting a user-supplied response.\n *\n * Pass the result to the AI SDK's `addToolResult` as the tool `output`:\n *\n * ```tsx\n * addToolResult({\n * tool: 'userApproval',\n * toolCallId,\n * output: restartInterrupt({ note: 'user revised the request' }),\n * });\n * ```\n *\n * The transport detects this marker and emits a `resume.restart` entry (with\n * the original tool input, which the server requires to match exactly) instead\n * of a `resume.respond` entry. Any `metadata` is passed through to the tool as\n * its `resumed` value.\n */\nexport function restartInterrupt(metadata?: unknown): RestartInterruptOutput {\n return metadata === undefined\n ? { [RESTART_MARKER]: true }\n : { [RESTART_MARKER]: true, metadata };\n}\n\n/**\n * Returns the restart payload if `output` was produced by\n * {@link restartInterrupt}, otherwise `undefined`.\n */\nexport function asRestartInterrupt(\n output: unknown\n): RestartInterruptOutput | undefined {\n if (\n output &&\n typeof output === 'object' &&\n (output as Record<string, unknown>)[RESTART_MARKER] === true\n ) {\n return output as RestartInterruptOutput;\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * A resolved tool invocation extracted from the frontend message history.\n */\nexport interface ResolvedToolResult {\n toolCallId: string;\n toolName: string;\n /** The original tool input (needed to build a `resume.restart`). */\n input: unknown;\n /** The user-supplied output (or a {@link restartInterrupt} marker). */\n result: unknown;\n}\n\n/**\n * Returns true if a user message carries no meaningful content — i.e. it has\n * no non-empty text and no file parts. The AI SDK's `sendMessage({ text: '' })`\n * (used to nudge a resume) produces such a \"phantom\" user message, which must\n * not be treated as a real user turn.\n */\nfunction isEmptyUserMessage(msg: UIMessage): boolean {\n return !msg.parts.some((part) => {\n if (part.type === 'text') {\n return typeof part.text === 'string' && part.text.trim() !== '';\n }\n if (part.type === 'file') {\n return true;\n }\n return false;\n });\n}\n\n/**\n * Extracts the resolved tool invocations carried by a single UIMessage.\n *\n * Supports the v6 per-tool format (`type === 'tool-<toolName>'`) as well as\n * dynamic tools (`type === 'dynamic-tool'`, with an explicit `toolName`),\n * matching `state === 'output-available'`. De-duplicates by `toolCallId`.\n */\nfunction resolvedToolsFromMessage(msg: UIMessage): ResolvedToolResult[] {\n const seen = new Set<string>();\n const results: ResolvedToolResult[] = [];\n for (const part of msg.parts) {\n const p = part as unknown as Record<string, unknown>;\n const isTool =\n part.type === 'dynamic-tool' || part.type.startsWith('tool-');\n if (\n isTool &&\n typeof p.toolCallId === 'string' &&\n p.toolCallId &&\n !seen.has(p.toolCallId) &&\n p.state === 'output-available' &&\n p.output !== undefined\n ) {\n const toolName =\n typeof p.toolName === 'string' && p.toolName\n ? p.toolName\n : part.type.startsWith('tool-')\n ? part.type.slice('tool-'.length)\n : 'unknown';\n seen.add(p.toolCallId);\n results.push({\n toolCallId: p.toolCallId,\n toolName,\n input: p.input,\n result: p.output,\n });\n }\n }\n return results;\n}\n\n/**\n * Extracts resolved tool invocations from a UIMessage array.\n *\n * Supports the v6 per-tool format (`type === 'tool-<toolName>'`) as well as\n * dynamic tools (`type === 'dynamic-tool'`, with an explicit `toolName`),\n * matching `state === 'output-available'`.\n *\n * Scans **all** messages (most-recent first) and returns every resolved tool\n * result, de-duplicated by `toolCallId` (keeping the most recent).\n *\n * Prefer {@link currentTurnResolvedTools} for interrupt-resume detection — it\n * scopes the scan to the most recent turn so prior turns' (already-completed)\n * tool calls are not re-sent as a resume payload.\n */\nexport function extractResolvedToolResults(\n messages: UIMessage[]\n): ResolvedToolResult[] {\n const seen = new Set<string>();\n const results: ResolvedToolResult[] = [];\n\n // Walk backwards so the most recent resolution for a given toolCallId wins.\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n if (msg.role !== 'assistant') continue;\n for (const tr of resolvedToolsFromMessage(msg)) {\n if (seen.has(tr.toolCallId)) continue;\n seen.add(tr.toolCallId);\n results.push(tr);\n }\n }\n\n return results;\n}\n\n/**\n * Returns the resolved tool invocations that constitute an interrupt resume\n * for the *current* turn, or an empty array if this is a fresh user turn.\n *\n * An interrupt resume is identified by the last assistant message carrying\n * resolved tool outputs (e.g. supplied via `addToolResult`) with no *non-empty*\n * user message after it. A real user message following the assistant turn means\n * the user started a new turn, so no resume is performed.\n *\n * This deliberately scopes detection to the most recent turn: tool calls from\n * earlier turns are already part of the server-side session state and must not\n * be replayed as a resume payload.\n */\nexport function currentTurnResolvedTools(\n messages: UIMessage[]\n): ResolvedToolResult[] {\n // Locate the most recent assistant message.\n let lastAssistantIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'assistant') {\n lastAssistantIdx = i;\n break;\n }\n }\n if (lastAssistantIdx === -1) return [];\n\n // A non-empty user message after the assistant turn means a fresh user turn.\n for (let i = lastAssistantIdx + 1; i < messages.length; i++) {\n if (messages[i].role === 'user' && !isEmptyUserMessage(messages[i])) {\n return [];\n }\n }\n\n return resolvedToolsFromMessage(messages[lastAssistantIdx]);\n}\n\n/**\n * Finds the last user message in a UIMessage array.\n */\nexport function findLastUserMessage(\n messages: UIMessage[]\n): UIMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') return messages[i];\n }\n return undefined;\n}\n\n/**\n * Finds the last *non-empty* user message in a UIMessage array, skipping\n * phantom empty messages produced by `sendMessage({ text: '' })`.\n */\nexport function findLastNonEmptyUserMessage(\n messages: UIMessage[]\n): UIMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n if (msg.role === 'user' && !isEmptyUserMessage(msg)) return msg;\n }\n return undefined;\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@genkit-ai/vercel-ai",
3
+ "version": "0.2.0-rc.0",
4
+ "description": "A Genkit plugin providing a Vercel AI SDK ChatTransport for connecting useChat to Genkit Agents.",
5
+ "keywords": [
6
+ "genkit",
7
+ "genkit-plugin",
8
+ "vercel",
9
+ "vercel-ai",
10
+ "ai-sdk",
11
+ "useChat",
12
+ "ChatTransport",
13
+ "streaming",
14
+ "agents",
15
+ "ai",
16
+ "genai",
17
+ "generative-ai"
18
+ ],
19
+ "homepage": "https://github.com/genkit-ai/genkit/tree/main/js/plugins/vercel-ai#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/genkit-ai/genkit/issues"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/genkit-ai/genkit.git",
26
+ "directory": "js/plugins/vercel-ai"
27
+ },
28
+ "license": "Apache-2.0",
29
+ "author": "genkit",
30
+ "type": "commonjs",
31
+ "main": "./lib/index.js",
32
+ "types": "./lib/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "require": "./lib/index.js",
36
+ "import": "./lib/index.mjs",
37
+ "types": "./lib/index.d.ts",
38
+ "default": "./lib/index.js"
39
+ },
40
+ "./client": {
41
+ "require": "./lib/client.js",
42
+ "import": "./lib/client.mjs",
43
+ "types": "./lib/client.d.ts",
44
+ "default": "./lib/client.js"
45
+ }
46
+ },
47
+ "typesVersions": {
48
+ "*": {
49
+ "client": [
50
+ "lib/client"
51
+ ]
52
+ }
53
+ },
54
+ "files": [
55
+ "lib",
56
+ "README.md",
57
+ "LICENSE"
58
+ ],
59
+ "peerDependencies": {
60
+ "@ai-sdk/react": "^3.0.0",
61
+ "ai": "^6.0.0",
62
+ "genkit": "^1.39.0-rc.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "@ai-sdk/react": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "devDependencies": {
70
+ "@ai-sdk/react": "^3.0.0",
71
+ "ai": "^6.0.192",
72
+ "@types/express": "^4.17.21",
73
+ "@types/node": "^20.11.16",
74
+ "express": "^4.21.1",
75
+ "get-port": "^5.1.0",
76
+ "npm-run-all": "^4.1.5",
77
+ "rimraf": "^6.0.1",
78
+ "tsup": "^8.3.5",
79
+ "tsx": "^4.19.2",
80
+ "typescript": "^5.9.3",
81
+ "@genkit-ai/express": "1.39.0-rc.0"
82
+ },
83
+ "engines": {
84
+ "node": ">=20"
85
+ },
86
+ "scripts": {
87
+ "check": "tsc",
88
+ "compile": "tsup-node",
89
+ "build:clean": "rimraf ./lib",
90
+ "build": "npm-run-all build:clean check compile",
91
+ "build:watch": "tsup-node --watch",
92
+ "test": "tsx --test ./tests/*_test.ts"
93
+ }
94
+ }