@ai-sdk-tool/parser 1.0.0 → 1.0.2

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,16 @@
1
+ import * as ai from 'ai';
2
+ import { LanguageModelV1Middleware } from 'ai';
3
+
4
+ declare const defaultTemplate: (tools: string) => string;
5
+ declare function createToolMiddleware({ toolCallTag, toolCallEndTag, toolResponseTag, toolResponseEndTag, toolSystemPromptTemplate, }: {
6
+ toolCallTag?: string;
7
+ toolCallEndTag?: string;
8
+ toolResponseTag?: string;
9
+ toolResponseEndTag?: string;
10
+ toolSystemPromptTemplate?: (tools: string) => string;
11
+ }): LanguageModelV1Middleware;
12
+
13
+ declare const gemmaToolMiddleware: ai.LanguageModelV1Middleware;
14
+ declare const hermesToolMiddleware: ai.LanguageModelV1Middleware;
15
+
16
+ export { createToolMiddleware, defaultTemplate, gemmaToolMiddleware, hermesToolMiddleware };
@@ -0,0 +1,16 @@
1
+ import * as ai from 'ai';
2
+ import { LanguageModelV1Middleware } from 'ai';
3
+
4
+ declare const defaultTemplate: (tools: string) => string;
5
+ declare function createToolMiddleware({ toolCallTag, toolCallEndTag, toolResponseTag, toolResponseEndTag, toolSystemPromptTemplate, }: {
6
+ toolCallTag?: string;
7
+ toolCallEndTag?: string;
8
+ toolResponseTag?: string;
9
+ toolResponseEndTag?: string;
10
+ toolSystemPromptTemplate?: (tools: string) => string;
11
+ }): LanguageModelV1Middleware;
12
+
13
+ declare const gemmaToolMiddleware: ai.LanguageModelV1Middleware;
14
+ declare const hermesToolMiddleware: ai.LanguageModelV1Middleware;
15
+
16
+ export { createToolMiddleware, defaultTemplate, gemmaToolMiddleware, hermesToolMiddleware };
package/dist/index.js ADDED
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ createToolMiddleware: () => createToolMiddleware,
34
+ defaultTemplate: () => defaultTemplate,
35
+ gemmaToolMiddleware: () => gemmaToolMiddleware,
36
+ hermesToolMiddleware: () => hermesToolMiddleware
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/tool-call-middleware.ts
41
+ var import_ai = require("ai");
42
+ var RJSON = __toESM(require("relaxed-json"));
43
+
44
+ // src/utils.ts
45
+ function getPotentialStartIndex(text, searchedText) {
46
+ if (searchedText.length === 0) {
47
+ return null;
48
+ }
49
+ const directIndex = text.indexOf(searchedText);
50
+ if (directIndex !== -1) {
51
+ return directIndex;
52
+ }
53
+ for (let i = text.length - 1; i >= 0; i--) {
54
+ const suffix = text.substring(i);
55
+ if (searchedText.startsWith(suffix)) {
56
+ return i;
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ // src/tool-call-middleware.ts
63
+ var defaultTemplate = (tools) => `You are a function calling AI model.
64
+ You are provided with function signatures within <tools></tools> XML tags.
65
+ You may call one or more functions to assist with the user query.
66
+ Don't make assumptions about what values to plug into functions.
67
+ Here are the available tools: <tools>${tools}</tools>
68
+ Use the following pydantic model json schema for each tool call you will make: {'title': 'FunctionCall', 'type': 'object', 'properties': {'arguments': {'title': 'Arguments', 'type': 'object'}, 'name': {'title': 'Name', 'type': 'string'}}, 'required': ['arguments', 'name']}
69
+ For each function call return a json object with function name and arguments within <tool_call></tool_call> XML tags as follows:
70
+ <tool_call>
71
+ {'arguments': <args-dict>, 'name': <function-name>}
72
+ </tool_call>`;
73
+ function createToolMiddleware({
74
+ toolCallTag = "<tool_call>",
75
+ toolCallEndTag = "</tool_call>",
76
+ toolResponseTag = "<tool_response>",
77
+ toolResponseEndTag = "</tool_response>",
78
+ toolSystemPromptTemplate = defaultTemplate
79
+ }) {
80
+ return {
81
+ middlewareVersion: "v1",
82
+ wrapStream: async ({ doStream }) => {
83
+ const { stream, ...rest } = await doStream();
84
+ let isFirstToolCall = true;
85
+ let isFirstText = true;
86
+ let afterSwitch = false;
87
+ let isToolCall = false;
88
+ let buffer = "";
89
+ let toolCallIndex = -1;
90
+ let toolCallBuffer = [];
91
+ const transformStream = new TransformStream({
92
+ transform(chunk, controller) {
93
+ if (chunk.type === "finish") {
94
+ if (toolCallBuffer.length > 0) {
95
+ toolCallBuffer.forEach((toolCall) => {
96
+ try {
97
+ const parsedToolCall = RJSON.parse(toolCall);
98
+ controller.enqueue({
99
+ type: "tool-call",
100
+ toolCallType: "function",
101
+ toolCallId: (0, import_ai.generateId)(),
102
+ toolName: parsedToolCall.name,
103
+ args: JSON.stringify(parsedToolCall.arguments)
104
+ });
105
+ } catch (e) {
106
+ console.error(`Error parsing tool call: ${toolCall}`, e);
107
+ controller.enqueue({
108
+ type: "text-delta",
109
+ textDelta: `Failed to parse tool call: ${e}`
110
+ });
111
+ }
112
+ });
113
+ }
114
+ controller.enqueue(chunk);
115
+ return;
116
+ } else if (chunk.type !== "text-delta") {
117
+ controller.enqueue(chunk);
118
+ return;
119
+ }
120
+ buffer += chunk.textDelta;
121
+ function publish(text) {
122
+ if (text.length > 0) {
123
+ const prefix = afterSwitch && (isToolCall ? !isFirstToolCall : !isFirstText) ? "\n" : "";
124
+ if (isToolCall) {
125
+ if (!toolCallBuffer[toolCallIndex]) {
126
+ toolCallBuffer[toolCallIndex] = "";
127
+ }
128
+ toolCallBuffer[toolCallIndex] += text;
129
+ } else {
130
+ controller.enqueue({
131
+ type: "text-delta",
132
+ textDelta: prefix + text
133
+ });
134
+ }
135
+ afterSwitch = false;
136
+ if (isToolCall) {
137
+ isFirstToolCall = false;
138
+ } else {
139
+ isFirstText = false;
140
+ }
141
+ }
142
+ }
143
+ do {
144
+ const nextTag = isToolCall ? toolCallEndTag : toolCallTag;
145
+ const startIndex = getPotentialStartIndex(buffer, nextTag);
146
+ if (startIndex == null) {
147
+ publish(buffer);
148
+ buffer = "";
149
+ break;
150
+ }
151
+ publish(buffer.slice(0, startIndex));
152
+ const foundFullMatch = startIndex + nextTag.length <= buffer.length;
153
+ if (foundFullMatch) {
154
+ buffer = buffer.slice(startIndex + nextTag.length);
155
+ toolCallIndex++;
156
+ isToolCall = !isToolCall;
157
+ afterSwitch = true;
158
+ } else {
159
+ buffer = buffer.slice(startIndex);
160
+ break;
161
+ }
162
+ } while (true);
163
+ }
164
+ });
165
+ return {
166
+ stream: stream.pipeThrough(transformStream),
167
+ ...rest
168
+ };
169
+ },
170
+ wrapGenerate: async ({ doGenerate }) => {
171
+ var _a;
172
+ const result = await doGenerate();
173
+ if (!((_a = result.text) == null ? void 0 : _a.includes(toolCallTag))) {
174
+ return result;
175
+ }
176
+ const toolCallRegex = new RegExp(
177
+ `${toolCallTag}(.*?)(?:${toolCallEndTag}|$)`,
178
+ "gs"
179
+ );
180
+ const matches = [...result.text.matchAll(toolCallRegex)];
181
+ const function_call_tuples = matches.map((match) => match[1] || match[2]);
182
+ return {
183
+ ...result,
184
+ // TODO: Return the remaining value after extracting the tool call tag.
185
+ text: "",
186
+ toolCalls: function_call_tuples.map((toolCall) => {
187
+ const parsedToolCall = RJSON.parse(toolCall);
188
+ const toolName = parsedToolCall.name;
189
+ const args = parsedToolCall.arguments;
190
+ return {
191
+ toolCallType: "function",
192
+ toolCallId: (0, import_ai.generateId)(),
193
+ toolName,
194
+ args: RJSON.stringify(args)
195
+ };
196
+ })
197
+ };
198
+ },
199
+ transformParams: async ({ params }) => {
200
+ const processedPrompt = params.prompt.map((message) => {
201
+ if (message.role === "assistant") {
202
+ return {
203
+ role: "assistant",
204
+ content: message.content.map((content) => {
205
+ if (content.type === "tool-call") {
206
+ return {
207
+ type: "text",
208
+ text: `${toolCallTag}${JSON.stringify({
209
+ arguments: content.args,
210
+ name: content.toolName
211
+ })}${toolCallEndTag}`
212
+ };
213
+ }
214
+ return content;
215
+ })
216
+ };
217
+ } else if (message.role === "tool") {
218
+ return {
219
+ role: "user",
220
+ content: [
221
+ {
222
+ type: "text",
223
+ text: message.content.map(
224
+ (content) => `${toolResponseTag}${JSON.stringify({
225
+ toolName: content.toolName,
226
+ result: content.result
227
+ })}${toolResponseEndTag}`
228
+ ).join("\n")
229
+ }
230
+ ]
231
+ };
232
+ }
233
+ return message;
234
+ });
235
+ const originalToolDefinitions = params.mode.type === "regular" && params.mode.tools ? params.mode.tools : {};
236
+ const HermesPrompt = toolSystemPromptTemplate(
237
+ JSON.stringify(Object.entries(originalToolDefinitions))
238
+ );
239
+ const toolSystemPrompt = processedPrompt[0].role === "system" ? [
240
+ {
241
+ role: "system",
242
+ content: HermesPrompt + "\n\n" + processedPrompt[0].content
243
+ },
244
+ ...processedPrompt.slice(1)
245
+ ] : [
246
+ {
247
+ role: "system",
248
+ content: HermesPrompt
249
+ },
250
+ ...processedPrompt
251
+ ];
252
+ return {
253
+ ...params,
254
+ mode: {
255
+ // set the mode back to regular and remove the default tools.
256
+ type: "regular"
257
+ },
258
+ prompt: toolSystemPrompt
259
+ };
260
+ }
261
+ };
262
+ }
263
+
264
+ // src/index.ts
265
+ var gemmaToolMiddleware = createToolMiddleware({
266
+ toolSystemPromptTemplate(tools) {
267
+ return `You have access to functions. If you decide to invoke any of the function(s),
268
+ you MUST put it in the format of
269
+ \`\`\`tool_call
270
+ {'name': <function-name>, 'arguments': <args-dict>}
271
+ \`\`\`
272
+ You SHOULD NOT include any other text in the response if you call a function
273
+ ${tools}`;
274
+ },
275
+ toolCallTag: "```tool_call\n",
276
+ toolCallEndTag: "```",
277
+ toolResponseTag: "```tool_response\n",
278
+ toolResponseEndTag: "\n```"
279
+ });
280
+ var hermesToolMiddleware = createToolMiddleware({});
281
+ // Annotate the CommonJS export names for ESM import in node:
282
+ 0 && (module.exports = {
283
+ createToolMiddleware,
284
+ defaultTemplate,
285
+ gemmaToolMiddleware,
286
+ hermesToolMiddleware
287
+ });
288
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/tool-call-middleware.ts","../src/utils.ts"],"sourcesContent":["import { defaultTemplate, createToolMiddleware } from \"./tool-call-middleware\";\n\nconst gemmaToolMiddleware = createToolMiddleware({\n toolSystemPromptTemplate(tools) {\n return `You have access to functions. If you decide to invoke any of the function(s),\n you MUST put it in the format of\n \\`\\`\\`tool_call\n {'name': <function-name>, 'arguments': <args-dict>}\n \\`\\`\\`\n You SHOULD NOT include any other text in the response if you call a function\n ${tools}`;\n },\n toolCallTag: \"```tool_call\\n\",\n toolCallEndTag: \"```\",\n toolResponseTag: \"```tool_response\\n\",\n toolResponseEndTag: \"\\n```\",\n});\n\nconst hermesToolMiddleware = createToolMiddleware({});\n\nexport {\n defaultTemplate,\n gemmaToolMiddleware,\n hermesToolMiddleware,\n createToolMiddleware,\n};\n","import {\n generateId,\n LanguageModelV1Middleware,\n LanguageModelV1Prompt,\n LanguageModelV1StreamPart,\n} from \"ai\";\nimport * as RJSON from \"relaxed-json\";\nimport { getPotentialStartIndex } from \"./utils\";\n\nexport const defaultTemplate = (tools: string) =>\n `You are a function calling AI model.\nYou are provided with function signatures within <tools></tools> XML tags.\nYou may call one or more functions to assist with the user query.\nDon't make assumptions about what values to plug into functions.\nHere are the available tools: <tools>${tools}</tools>\nUse the following pydantic model json schema for each tool call you will make: {'title': 'FunctionCall', 'type': 'object', 'properties': {'arguments': {'title': 'Arguments', 'type': 'object'}, 'name': {'title': 'Name', 'type': 'string'}}, 'required': ['arguments', 'name']}\nFor each function call return a json object with function name and arguments within <tool_call></tool_call> XML tags as follows:\n<tool_call>\n{'arguments': <args-dict>, 'name': <function-name>}\n</tool_call>`;\n\nexport function createToolMiddleware({\n toolCallTag = \"<tool_call>\",\n toolCallEndTag = \"</tool_call>\",\n toolResponseTag = \"<tool_response>\",\n toolResponseEndTag = \"</tool_response>\",\n toolSystemPromptTemplate = defaultTemplate,\n}: {\n toolCallTag?: string;\n toolCallEndTag?: string;\n toolResponseTag?: string;\n toolResponseEndTag?: string;\n toolSystemPromptTemplate?: (tools: string) => string;\n}): LanguageModelV1Middleware {\n return {\n middlewareVersion: \"v1\",\n wrapStream: async ({ doStream }) => {\n const { stream, ...rest } = await doStream();\n\n let isFirstToolCall = true;\n let isFirstText = true;\n let afterSwitch = false;\n let isToolCall = false;\n let buffer = \"\";\n\n let toolCallIndex = -1;\n let toolCallBuffer: string[] = [];\n\n const transformStream = new TransformStream<\n LanguageModelV1StreamPart,\n LanguageModelV1StreamPart\n >({\n transform(chunk, controller) {\n if (chunk.type === \"finish\") {\n if (toolCallBuffer.length > 0) {\n toolCallBuffer.forEach((toolCall) => {\n try {\n const parsedToolCall = RJSON.parse(toolCall) as {\n name: string;\n arguments: string;\n };\n\n controller.enqueue({\n type: \"tool-call\",\n toolCallType: \"function\",\n toolCallId: generateId(),\n toolName: parsedToolCall.name,\n args: JSON.stringify(parsedToolCall.arguments),\n });\n } catch (e) {\n console.error(`Error parsing tool call: ${toolCall}`, e);\n\n controller.enqueue({\n type: \"text-delta\",\n textDelta: `Failed to parse tool call: ${e}`,\n });\n }\n });\n }\n\n // stop token\n controller.enqueue(chunk);\n\n return;\n } else if (chunk.type !== \"text-delta\") {\n controller.enqueue(chunk);\n return;\n }\n\n buffer += chunk.textDelta;\n\n function publish(text: string) {\n if (text.length > 0) {\n const prefix =\n afterSwitch && (isToolCall ? !isFirstToolCall : !isFirstText)\n ? \"\\n\" // separator\n : \"\";\n\n if (isToolCall) {\n if (!toolCallBuffer[toolCallIndex]) {\n toolCallBuffer[toolCallIndex] = \"\";\n }\n\n toolCallBuffer[toolCallIndex] += text;\n } else {\n controller.enqueue({\n type: \"text-delta\",\n textDelta: prefix + text,\n });\n }\n\n afterSwitch = false;\n\n if (isToolCall) {\n isFirstToolCall = false;\n } else {\n isFirstText = false;\n }\n }\n }\n\n do {\n const nextTag = isToolCall ? toolCallEndTag : toolCallTag;\n const startIndex = getPotentialStartIndex(buffer, nextTag);\n\n // no opening or closing tag found, publish the buffer\n if (startIndex == null) {\n publish(buffer);\n buffer = \"\";\n break;\n }\n\n // publish text before the tag\n publish(buffer.slice(0, startIndex));\n\n const foundFullMatch = startIndex + nextTag.length <= buffer.length;\n\n if (foundFullMatch) {\n buffer = buffer.slice(startIndex + nextTag.length);\n toolCallIndex++;\n isToolCall = !isToolCall;\n afterSwitch = true;\n } else {\n buffer = buffer.slice(startIndex);\n break;\n }\n } while (true);\n },\n });\n\n return {\n stream: stream.pipeThrough(transformStream),\n ...rest,\n };\n },\n wrapGenerate: async ({ doGenerate }) => {\n const result = await doGenerate();\n\n if (!result.text?.includes(toolCallTag)) {\n return result;\n }\n\n const toolCallRegex = new RegExp(\n `${toolCallTag}(.*?)(?:${toolCallEndTag}|$)`,\n \"gs\"\n );\n const matches = [...result.text.matchAll(toolCallRegex)];\n const function_call_tuples = matches.map((match) => match[1] || match[2]);\n\n return {\n ...result,\n // TODO: Return the remaining value after extracting the tool call tag.\n text: \"\",\n toolCalls: function_call_tuples.map((toolCall) => {\n const parsedToolCall = RJSON.parse(toolCall) as {\n name: string;\n arguments: string;\n };\n\n const toolName = parsedToolCall.name;\n const args = parsedToolCall.arguments;\n\n return {\n toolCallType: \"function\",\n toolCallId: generateId(),\n toolName: toolName,\n args: RJSON.stringify(args),\n };\n }),\n };\n },\n\n transformParams: async ({ params }) => {\n const processedPrompt = params.prompt.map((message) => {\n if (message.role === \"assistant\") {\n return {\n role: \"assistant\",\n content: message.content.map((content) => {\n if (content.type === \"tool-call\") {\n return {\n type: \"text\",\n text: `${toolCallTag}${JSON.stringify({\n arguments: content.args,\n name: content.toolName,\n })}${toolCallEndTag}`,\n };\n }\n\n return content;\n }),\n };\n } else if (message.role === \"tool\") {\n return {\n role: \"user\",\n content: [\n {\n type: \"text\",\n text: message.content\n .map(\n (content) =>\n `${toolResponseTag}${JSON.stringify({\n toolName: content.toolName,\n result: content.result,\n })}${toolResponseEndTag}`\n )\n .join(\"\\n\"),\n },\n ],\n };\n }\n\n return message;\n }) as LanguageModelV1Prompt;\n\n // Appropriate fixes are needed as they are disappearing in LanguageModelV2\n const originalToolDefinitions =\n params.mode.type === \"regular\" && params.mode.tools\n ? params.mode.tools\n : {};\n\n const HermesPrompt = toolSystemPromptTemplate(\n JSON.stringify(Object.entries(originalToolDefinitions))\n );\n\n const toolSystemPrompt: LanguageModelV1Prompt =\n processedPrompt[0].role === \"system\"\n ? [\n {\n role: \"system\",\n content: HermesPrompt + \"\\n\\n\" + processedPrompt[0].content,\n },\n ...processedPrompt.slice(1),\n ]\n : [\n {\n role: \"system\",\n content: HermesPrompt,\n },\n ...processedPrompt,\n ];\n\n return {\n ...params,\n mode: {\n // set the mode back to regular and remove the default tools.\n type: \"regular\",\n },\n prompt: toolSystemPrompt,\n };\n },\n };\n}\n","/**\n * Returns the index of the start of the searchedText in the text, or null if it\n * is not found.\n * ref: https://github.com/vercel/ai/blob/452bf12f0be9cb398d4af85a006bca13c8ce36d8/packages/ai/core/util/get-potential-start-index.ts\n */\nexport function getPotentialStartIndex(\n text: string,\n searchedText: string\n): number | null {\n // Return null immediately if searchedText is empty.\n if (searchedText.length === 0) {\n return null;\n }\n\n // Check if the searchedText exists as a direct substring of text.\n const directIndex = text.indexOf(searchedText);\n if (directIndex !== -1) {\n return directIndex;\n }\n\n // Otherwise, look for the largest suffix of \"text\" that matches\n // a prefix of \"searchedText\". We go from the end of text inward.\n for (let i = text.length - 1; i >= 0; i--) {\n const suffix = text.substring(i);\n if (searchedText.startsWith(suffix)) {\n return i;\n }\n }\n\n return null;\n}\n"],"mappings":"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","names":[]}
@@ -1,114 +1,101 @@
1
+ // src/tool-call-middleware.ts
1
2
  import {
2
- generateId,
3
- LanguageModelV1Middleware,
4
- LanguageModelV1Prompt,
5
- LanguageModelV1StreamPart,
3
+ generateId
6
4
  } from "ai";
7
5
  import * as RJSON from "relaxed-json";
8
- import { getPotentialStartIndex } from "./utils";
9
6
 
10
- const defaultTemplate = (tools: string) =>
11
- `You are a function calling AI model. You are provided with function signatures within <tools></tools> XML tags.
12
- You may call one or more functions to assist with the user query. Don't make assumptions about what values to plug into functions.
7
+ // src/utils.ts
8
+ function getPotentialStartIndex(text, searchedText) {
9
+ if (searchedText.length === 0) {
10
+ return null;
11
+ }
12
+ const directIndex = text.indexOf(searchedText);
13
+ if (directIndex !== -1) {
14
+ return directIndex;
15
+ }
16
+ for (let i = text.length - 1; i >= 0; i--) {
17
+ const suffix = text.substring(i);
18
+ if (searchedText.startsWith(suffix)) {
19
+ return i;
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+
25
+ // src/tool-call-middleware.ts
26
+ var defaultTemplate = (tools) => `You are a function calling AI model.
27
+ You are provided with function signatures within <tools></tools> XML tags.
28
+ You may call one or more functions to assist with the user query.
29
+ Don't make assumptions about what values to plug into functions.
13
30
  Here are the available tools: <tools>${tools}</tools>
14
31
  Use the following pydantic model json schema for each tool call you will make: {'title': 'FunctionCall', 'type': 'object', 'properties': {'arguments': {'title': 'Arguments', 'type': 'object'}, 'name': {'title': 'Name', 'type': 'string'}}, 'required': ['arguments', 'name']}
15
32
  For each function call return a json object with function name and arguments within <tool_call></tool_call> XML tags as follows:
16
33
  <tool_call>
17
34
  {'arguments': <args-dict>, 'name': <function-name>}
18
35
  </tool_call>`;
19
-
20
- export function hermesToolMiddleware({
36
+ function createToolMiddleware({
21
37
  toolCallTag = "<tool_call>",
22
38
  toolCallEndTag = "</tool_call>",
23
39
  toolResponseTag = "<tool_response>",
24
40
  toolResponseEndTag = "</tool_response>",
25
- toolSystemPromptTemplate = defaultTemplate,
26
- }: {
27
- toolCallTag?: string;
28
- toolCallEndTag?: string;
29
- toolResponseTag?: string;
30
- toolResponseEndTag?: string;
31
- toolSystemPromptTemplate?: (tools: string) => string;
32
- }): LanguageModelV1Middleware {
41
+ toolSystemPromptTemplate = defaultTemplate
42
+ }) {
33
43
  return {
34
44
  middlewareVersion: "v1",
35
45
  wrapStream: async ({ doStream }) => {
36
46
  const { stream, ...rest } = await doStream();
37
-
38
47
  let isFirstToolCall = true;
39
48
  let isFirstText = true;
40
49
  let afterSwitch = false;
41
50
  let isToolCall = false;
42
51
  let buffer = "";
43
-
44
52
  let toolCallIndex = -1;
45
- let toolCallBuffer: string[] = [];
46
-
47
- const transformStream = new TransformStream<
48
- LanguageModelV1StreamPart,
49
- LanguageModelV1StreamPart
50
- >({
53
+ let toolCallBuffer = [];
54
+ const transformStream = new TransformStream({
51
55
  transform(chunk, controller) {
52
56
  if (chunk.type === "finish") {
53
57
  if (toolCallBuffer.length > 0) {
54
58
  toolCallBuffer.forEach((toolCall) => {
55
59
  try {
56
- const parsedToolCall = RJSON.parse(toolCall) as {
57
- name: string;
58
- arguments: string;
59
- };
60
-
60
+ const parsedToolCall = RJSON.parse(toolCall);
61
61
  controller.enqueue({
62
62
  type: "tool-call",
63
63
  toolCallType: "function",
64
64
  toolCallId: generateId(),
65
65
  toolName: parsedToolCall.name,
66
- args: JSON.stringify(parsedToolCall.arguments),
66
+ args: JSON.stringify(parsedToolCall.arguments)
67
67
  });
68
68
  } catch (e) {
69
69
  console.error(`Error parsing tool call: ${toolCall}`, e);
70
-
71
70
  controller.enqueue({
72
71
  type: "text-delta",
73
- textDelta: `Failed to parse tool call: ${e.message}`,
72
+ textDelta: `Failed to parse tool call: ${e}`
74
73
  });
75
74
  }
76
75
  });
77
76
  }
78
-
79
- // stop token
80
77
  controller.enqueue(chunk);
81
-
82
78
  return;
83
79
  } else if (chunk.type !== "text-delta") {
84
80
  controller.enqueue(chunk);
85
81
  return;
86
82
  }
87
-
88
83
  buffer += chunk.textDelta;
89
-
90
- function publish(text: string) {
84
+ function publish(text) {
91
85
  if (text.length > 0) {
92
- const prefix =
93
- afterSwitch && (isToolCall ? !isFirstToolCall : !isFirstText)
94
- ? "\n" // separator
95
- : "";
96
-
86
+ const prefix = afterSwitch && (isToolCall ? !isFirstToolCall : !isFirstText) ? "\n" : "";
97
87
  if (isToolCall) {
98
88
  if (!toolCallBuffer[toolCallIndex]) {
99
89
  toolCallBuffer[toolCallIndex] = "";
100
90
  }
101
-
102
91
  toolCallBuffer[toolCallIndex] += text;
103
92
  } else {
104
93
  controller.enqueue({
105
94
  type: "text-delta",
106
- textDelta: prefix + text,
95
+ textDelta: prefix + text
107
96
  });
108
97
  }
109
-
110
98
  afterSwitch = false;
111
-
112
99
  if (isToolCall) {
113
100
  isFirstToolCall = false;
114
101
  } else {
@@ -116,23 +103,16 @@ export function hermesToolMiddleware({
116
103
  }
117
104
  }
118
105
  }
119
-
120
106
  do {
121
107
  const nextTag = isToolCall ? toolCallEndTag : toolCallTag;
122
108
  const startIndex = getPotentialStartIndex(buffer, nextTag);
123
-
124
- // no opening or closing tag found, publish the buffer
125
109
  if (startIndex == null) {
126
110
  publish(buffer);
127
111
  buffer = "";
128
112
  break;
129
113
  }
130
-
131
- // publish text before the tag
132
114
  publish(buffer.slice(0, startIndex));
133
-
134
115
  const foundFullMatch = startIndex + nextTag.length <= buffer.length;
135
-
136
116
  if (foundFullMatch) {
137
117
  buffer = buffer.slice(startIndex + nextTag.length);
138
118
  toolCallIndex++;
@@ -143,51 +123,42 @@ export function hermesToolMiddleware({
143
123
  break;
144
124
  }
145
125
  } while (true);
146
- },
126
+ }
147
127
  });
148
-
149
128
  return {
150
129
  stream: stream.pipeThrough(transformStream),
151
- ...rest,
130
+ ...rest
152
131
  };
153
132
  },
154
133
  wrapGenerate: async ({ doGenerate }) => {
134
+ var _a;
155
135
  const result = await doGenerate();
156
-
157
- if (!result.text?.includes(toolCallTag)) {
136
+ if (!((_a = result.text) == null ? void 0 : _a.includes(toolCallTag))) {
158
137
  return result;
159
138
  }
160
-
161
139
  const toolCallRegex = new RegExp(
162
140
  `${toolCallTag}(.*?)(?:${toolCallEndTag}|$)`,
163
141
  "gs"
164
142
  );
165
143
  const matches = [...result.text.matchAll(toolCallRegex)];
166
144
  const function_call_tuples = matches.map((match) => match[1] || match[2]);
167
-
168
145
  return {
169
146
  ...result,
170
147
  // TODO: Return the remaining value after extracting the tool call tag.
171
148
  text: "",
172
149
  toolCalls: function_call_tuples.map((toolCall) => {
173
- const parsedToolCall = RJSON.parse(toolCall) as {
174
- name: string;
175
- arguments: string;
176
- };
177
-
150
+ const parsedToolCall = RJSON.parse(toolCall);
178
151
  const toolName = parsedToolCall.name;
179
152
  const args = parsedToolCall.arguments;
180
-
181
153
  return {
182
154
  toolCallType: "function",
183
155
  toolCallId: generateId(),
184
- toolName: toolName,
185
- args: RJSON.stringify(args),
156
+ toolName,
157
+ args: RJSON.stringify(args)
186
158
  };
187
- }),
159
+ })
188
160
  };
189
161
  },
190
-
191
162
  transformParams: async ({ params }) => {
192
163
  const processedPrompt = params.prompt.map((message) => {
193
164
  if (message.role === "assistant") {
@@ -199,13 +170,12 @@ export function hermesToolMiddleware({
199
170
  type: "text",
200
171
  text: `${toolCallTag}${JSON.stringify({
201
172
  arguments: content.args,
202
- name: content.toolName,
203
- })}${toolCallEndTag}`,
173
+ name: content.toolName
174
+ })}${toolCallEndTag}`
204
175
  };
205
176
  }
206
-
207
177
  return content;
208
- }),
178
+ })
209
179
  };
210
180
  } else if (message.role === "tool") {
211
181
  return {
@@ -213,58 +183,68 @@ export function hermesToolMiddleware({
213
183
  content: [
214
184
  {
215
185
  type: "text",
216
- text: message.content
217
- .map(
218
- (content) =>
219
- `${toolResponseTag}${JSON.stringify({
220
- toolName: content.toolName,
221
- result: content.result,
222
- })}${toolResponseEndTag}`
223
- )
224
- .join("\n"),
225
- },
226
- ],
186
+ text: message.content.map(
187
+ (content) => `${toolResponseTag}${JSON.stringify({
188
+ toolName: content.toolName,
189
+ result: content.result
190
+ })}${toolResponseEndTag}`
191
+ ).join("\n")
192
+ }
193
+ ]
227
194
  };
228
195
  }
229
-
230
196
  return message;
231
- }) as LanguageModelV1Prompt;
232
-
233
- // Appropriate fixes are needed as they are disappearing in LanguageModelV2
234
- const originalToolDefinitions =
235
- params.mode.type === "regular" && params.mode.tools
236
- ? params.mode.tools
237
- : {};
238
-
197
+ });
198
+ const originalToolDefinitions = params.mode.type === "regular" && params.mode.tools ? params.mode.tools : {};
239
199
  const HermesPrompt = toolSystemPromptTemplate(
240
200
  JSON.stringify(Object.entries(originalToolDefinitions))
241
201
  );
242
-
243
- const toolSystemPrompt: LanguageModelV1Prompt =
244
- processedPrompt[0].role === "system"
245
- ? [
246
- {
247
- role: "system",
248
- content: HermesPrompt + "\n\n" + processedPrompt[0].content,
249
- },
250
- ...processedPrompt.slice(1),
251
- ]
252
- : [
253
- {
254
- role: "system",
255
- content: HermesPrompt,
256
- },
257
- ...processedPrompt,
258
- ];
259
-
202
+ const toolSystemPrompt = processedPrompt[0].role === "system" ? [
203
+ {
204
+ role: "system",
205
+ content: HermesPrompt + "\n\n" + processedPrompt[0].content
206
+ },
207
+ ...processedPrompt.slice(1)
208
+ ] : [
209
+ {
210
+ role: "system",
211
+ content: HermesPrompt
212
+ },
213
+ ...processedPrompt
214
+ ];
260
215
  return {
261
216
  ...params,
262
217
  mode: {
263
218
  // set the mode back to regular and remove the default tools.
264
- type: "regular",
219
+ type: "regular"
265
220
  },
266
- prompt: toolSystemPrompt,
221
+ prompt: toolSystemPrompt
267
222
  };
268
- },
223
+ }
269
224
  };
270
225
  }
226
+
227
+ // src/index.ts
228
+ var gemmaToolMiddleware = createToolMiddleware({
229
+ toolSystemPromptTemplate(tools) {
230
+ return `You have access to functions. If you decide to invoke any of the function(s),
231
+ you MUST put it in the format of
232
+ \`\`\`tool_call
233
+ {'name': <function-name>, 'arguments': <args-dict>}
234
+ \`\`\`
235
+ You SHOULD NOT include any other text in the response if you call a function
236
+ ${tools}`;
237
+ },
238
+ toolCallTag: "```tool_call\n",
239
+ toolCallEndTag: "```",
240
+ toolResponseTag: "```tool_response\n",
241
+ toolResponseEndTag: "\n```"
242
+ });
243
+ var hermesToolMiddleware = createToolMiddleware({});
244
+ export {
245
+ createToolMiddleware,
246
+ defaultTemplate,
247
+ gemmaToolMiddleware,
248
+ hermesToolMiddleware
249
+ };
250
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tool-call-middleware.ts","../src/utils.ts","../src/index.ts"],"sourcesContent":["import {\n generateId,\n LanguageModelV1Middleware,\n LanguageModelV1Prompt,\n LanguageModelV1StreamPart,\n} from \"ai\";\nimport * as RJSON from \"relaxed-json\";\nimport { getPotentialStartIndex } from \"./utils\";\n\nexport const defaultTemplate = (tools: string) =>\n `You are a function calling AI model.\nYou are provided with function signatures within <tools></tools> XML tags.\nYou may call one or more functions to assist with the user query.\nDon't make assumptions about what values to plug into functions.\nHere are the available tools: <tools>${tools}</tools>\nUse the following pydantic model json schema for each tool call you will make: {'title': 'FunctionCall', 'type': 'object', 'properties': {'arguments': {'title': 'Arguments', 'type': 'object'}, 'name': {'title': 'Name', 'type': 'string'}}, 'required': ['arguments', 'name']}\nFor each function call return a json object with function name and arguments within <tool_call></tool_call> XML tags as follows:\n<tool_call>\n{'arguments': <args-dict>, 'name': <function-name>}\n</tool_call>`;\n\nexport function createToolMiddleware({\n toolCallTag = \"<tool_call>\",\n toolCallEndTag = \"</tool_call>\",\n toolResponseTag = \"<tool_response>\",\n toolResponseEndTag = \"</tool_response>\",\n toolSystemPromptTemplate = defaultTemplate,\n}: {\n toolCallTag?: string;\n toolCallEndTag?: string;\n toolResponseTag?: string;\n toolResponseEndTag?: string;\n toolSystemPromptTemplate?: (tools: string) => string;\n}): LanguageModelV1Middleware {\n return {\n middlewareVersion: \"v1\",\n wrapStream: async ({ doStream }) => {\n const { stream, ...rest } = await doStream();\n\n let isFirstToolCall = true;\n let isFirstText = true;\n let afterSwitch = false;\n let isToolCall = false;\n let buffer = \"\";\n\n let toolCallIndex = -1;\n let toolCallBuffer: string[] = [];\n\n const transformStream = new TransformStream<\n LanguageModelV1StreamPart,\n LanguageModelV1StreamPart\n >({\n transform(chunk, controller) {\n if (chunk.type === \"finish\") {\n if (toolCallBuffer.length > 0) {\n toolCallBuffer.forEach((toolCall) => {\n try {\n const parsedToolCall = RJSON.parse(toolCall) as {\n name: string;\n arguments: string;\n };\n\n controller.enqueue({\n type: \"tool-call\",\n toolCallType: \"function\",\n toolCallId: generateId(),\n toolName: parsedToolCall.name,\n args: JSON.stringify(parsedToolCall.arguments),\n });\n } catch (e) {\n console.error(`Error parsing tool call: ${toolCall}`, e);\n\n controller.enqueue({\n type: \"text-delta\",\n textDelta: `Failed to parse tool call: ${e}`,\n });\n }\n });\n }\n\n // stop token\n controller.enqueue(chunk);\n\n return;\n } else if (chunk.type !== \"text-delta\") {\n controller.enqueue(chunk);\n return;\n }\n\n buffer += chunk.textDelta;\n\n function publish(text: string) {\n if (text.length > 0) {\n const prefix =\n afterSwitch && (isToolCall ? !isFirstToolCall : !isFirstText)\n ? \"\\n\" // separator\n : \"\";\n\n if (isToolCall) {\n if (!toolCallBuffer[toolCallIndex]) {\n toolCallBuffer[toolCallIndex] = \"\";\n }\n\n toolCallBuffer[toolCallIndex] += text;\n } else {\n controller.enqueue({\n type: \"text-delta\",\n textDelta: prefix + text,\n });\n }\n\n afterSwitch = false;\n\n if (isToolCall) {\n isFirstToolCall = false;\n } else {\n isFirstText = false;\n }\n }\n }\n\n do {\n const nextTag = isToolCall ? toolCallEndTag : toolCallTag;\n const startIndex = getPotentialStartIndex(buffer, nextTag);\n\n // no opening or closing tag found, publish the buffer\n if (startIndex == null) {\n publish(buffer);\n buffer = \"\";\n break;\n }\n\n // publish text before the tag\n publish(buffer.slice(0, startIndex));\n\n const foundFullMatch = startIndex + nextTag.length <= buffer.length;\n\n if (foundFullMatch) {\n buffer = buffer.slice(startIndex + nextTag.length);\n toolCallIndex++;\n isToolCall = !isToolCall;\n afterSwitch = true;\n } else {\n buffer = buffer.slice(startIndex);\n break;\n }\n } while (true);\n },\n });\n\n return {\n stream: stream.pipeThrough(transformStream),\n ...rest,\n };\n },\n wrapGenerate: async ({ doGenerate }) => {\n const result = await doGenerate();\n\n if (!result.text?.includes(toolCallTag)) {\n return result;\n }\n\n const toolCallRegex = new RegExp(\n `${toolCallTag}(.*?)(?:${toolCallEndTag}|$)`,\n \"gs\"\n );\n const matches = [...result.text.matchAll(toolCallRegex)];\n const function_call_tuples = matches.map((match) => match[1] || match[2]);\n\n return {\n ...result,\n // TODO: Return the remaining value after extracting the tool call tag.\n text: \"\",\n toolCalls: function_call_tuples.map((toolCall) => {\n const parsedToolCall = RJSON.parse(toolCall) as {\n name: string;\n arguments: string;\n };\n\n const toolName = parsedToolCall.name;\n const args = parsedToolCall.arguments;\n\n return {\n toolCallType: \"function\",\n toolCallId: generateId(),\n toolName: toolName,\n args: RJSON.stringify(args),\n };\n }),\n };\n },\n\n transformParams: async ({ params }) => {\n const processedPrompt = params.prompt.map((message) => {\n if (message.role === \"assistant\") {\n return {\n role: \"assistant\",\n content: message.content.map((content) => {\n if (content.type === \"tool-call\") {\n return {\n type: \"text\",\n text: `${toolCallTag}${JSON.stringify({\n arguments: content.args,\n name: content.toolName,\n })}${toolCallEndTag}`,\n };\n }\n\n return content;\n }),\n };\n } else if (message.role === \"tool\") {\n return {\n role: \"user\",\n content: [\n {\n type: \"text\",\n text: message.content\n .map(\n (content) =>\n `${toolResponseTag}${JSON.stringify({\n toolName: content.toolName,\n result: content.result,\n })}${toolResponseEndTag}`\n )\n .join(\"\\n\"),\n },\n ],\n };\n }\n\n return message;\n }) as LanguageModelV1Prompt;\n\n // Appropriate fixes are needed as they are disappearing in LanguageModelV2\n const originalToolDefinitions =\n params.mode.type === \"regular\" && params.mode.tools\n ? params.mode.tools\n : {};\n\n const HermesPrompt = toolSystemPromptTemplate(\n JSON.stringify(Object.entries(originalToolDefinitions))\n );\n\n const toolSystemPrompt: LanguageModelV1Prompt =\n processedPrompt[0].role === \"system\"\n ? [\n {\n role: \"system\",\n content: HermesPrompt + \"\\n\\n\" + processedPrompt[0].content,\n },\n ...processedPrompt.slice(1),\n ]\n : [\n {\n role: \"system\",\n content: HermesPrompt,\n },\n ...processedPrompt,\n ];\n\n return {\n ...params,\n mode: {\n // set the mode back to regular and remove the default tools.\n type: \"regular\",\n },\n prompt: toolSystemPrompt,\n };\n },\n };\n}\n","/**\n * Returns the index of the start of the searchedText in the text, or null if it\n * is not found.\n * ref: https://github.com/vercel/ai/blob/452bf12f0be9cb398d4af85a006bca13c8ce36d8/packages/ai/core/util/get-potential-start-index.ts\n */\nexport function getPotentialStartIndex(\n text: string,\n searchedText: string\n): number | null {\n // Return null immediately if searchedText is empty.\n if (searchedText.length === 0) {\n return null;\n }\n\n // Check if the searchedText exists as a direct substring of text.\n const directIndex = text.indexOf(searchedText);\n if (directIndex !== -1) {\n return directIndex;\n }\n\n // Otherwise, look for the largest suffix of \"text\" that matches\n // a prefix of \"searchedText\". We go from the end of text inward.\n for (let i = text.length - 1; i >= 0; i--) {\n const suffix = text.substring(i);\n if (searchedText.startsWith(suffix)) {\n return i;\n }\n }\n\n return null;\n}\n","import { defaultTemplate, createToolMiddleware } from \"./tool-call-middleware\";\n\nconst gemmaToolMiddleware = createToolMiddleware({\n toolSystemPromptTemplate(tools) {\n return `You have access to functions. If you decide to invoke any of the function(s),\n you MUST put it in the format of\n \\`\\`\\`tool_call\n {'name': <function-name>, 'arguments': <args-dict>}\n \\`\\`\\`\n You SHOULD NOT include any other text in the response if you call a function\n ${tools}`;\n },\n toolCallTag: \"```tool_call\\n\",\n toolCallEndTag: \"```\",\n toolResponseTag: \"```tool_response\\n\",\n toolResponseEndTag: \"\\n```\",\n});\n\nconst hermesToolMiddleware = createToolMiddleware({});\n\nexport {\n defaultTemplate,\n gemmaToolMiddleware,\n hermesToolMiddleware,\n createToolMiddleware,\n};\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,23 +1,30 @@
1
1
  {
2
2
  "name": "@ai-sdk-tool/parser",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist/**/*"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
8
13
  },
9
14
  "keywords": [],
10
15
  "author": "",
11
16
  "license": "ISC",
12
- "packageManager": "pnpm@9.14.4+sha1.64b6e81e79630419b675c555ef3b65607cfd6315",
13
17
  "dependencies": {
14
- "@ai-sdk/openai-compatible": "^0.2.9",
15
- "ai": "^4.3.4",
16
- "relaxed-json": "^1.0.3",
17
- "tsx": "^4.19.3",
18
- "zod": "^3.24.2"
18
+ "ai": "^4.3.9",
19
+ "relaxed-json": "^1.0.3"
19
20
  },
20
21
  "devDependencies": {
21
- "@types/node": "^22.14.0"
22
+ "@types/node": "^22.14.1",
23
+ "@types/relaxed-json": "^1.0.4",
24
+ "tsup": "^8.4.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsup --watch"
22
29
  }
23
30
  }
package/src/index.ts DELETED
@@ -1,83 +0,0 @@
1
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
2
- import { generateText, streamText, wrapLanguageModel } from "ai";
3
- import { z } from "zod";
4
- import { hermesToolMiddleware } from "./hermes-middleware";
5
-
6
- const openrouter = createOpenAICompatible({
7
- name: "openrouter",
8
- apiKey: process.env.OPENROUTER_API_KEY,
9
- baseURL: "https://openrouter.ai/api/v1",
10
- });
11
-
12
- async function main() {
13
- const result = streamText({
14
- // model: openrouter("openai/gpt-4o"),
15
- model: wrapLanguageModel({
16
- model: openrouter("google/gemma-3-27b-it"),
17
- // model: openrouter("nousresearch/hermes-3-llama-3.1-70b"),
18
- middleware: hermesToolMiddleware({
19
- toolSystemPromptTemplate(tools) {
20
- return `You have access to functions. If you decide to invoke any of the function(s),
21
- you MUST put it in the format of
22
- \`\`\`tool_call
23
- {'name': <function-name>, 'arguments': <args-dict>}
24
- \`\`\`
25
- You SHOULD NOT include any other text in the response if you call a function
26
- ${tools}`;
27
- },
28
- toolCallTag: "```tool_call\n",
29
- toolCallEndTag: "```",
30
- toolResponseTag: "```tool_response\n",
31
- toolResponseEndTag: "\n```",
32
- }),
33
- }),
34
- system: "You are a helpful assistant.",
35
- // prompt: "What is the weather in New York and Los Angeles?",
36
- prompt: "What is the weather in my city?",
37
- maxSteps: 4,
38
- tools: {
39
- get_location: {
40
- description: "Get the User's location.",
41
- parameters: z.object({}),
42
- execute: async () => {
43
- // Simulate a location API call
44
- return {
45
- city: "New York",
46
- country: "USA",
47
- };
48
- },
49
- },
50
- get_weather: {
51
- description:
52
- "Get the weather for a given city. " +
53
- "Example cities: 'New York', 'Los Angeles', 'Paris'.",
54
- parameters: z.object({ city: z.string() }),
55
- execute: async ({ city }) => {
56
- // Simulate a weather API call
57
- const temperature = Math.floor(Math.random() * 100);
58
- return {
59
- city,
60
- temperature,
61
- condition: "sunny",
62
- };
63
- },
64
- },
65
- },
66
- });
67
-
68
- for await (const part of result.fullStream) {
69
- if (part.type === "text-delta") {
70
- process.stdout.write(part.textDelta);
71
- } else if (part.type === "tool-result") {
72
- console.log({
73
- name: part.toolName,
74
- args: part.args,
75
- result: part.result,
76
- });
77
- }
78
- }
79
-
80
- console.log("\n\n[done]");
81
- }
82
-
83
- main().catch(console.error);
package/src/utils.ts DELETED
@@ -1,31 +0,0 @@
1
- /**
2
- * Returns the index of the start of the searchedText in the text, or null if it
3
- * is not found.
4
- * ref: https://github.com/vercel/ai/blob/452bf12f0be9cb398d4af85a006bca13c8ce36d8/packages/ai/core/util/get-potential-start-index.ts
5
- */
6
- export function getPotentialStartIndex(
7
- text: string,
8
- searchedText: string
9
- ): number | null {
10
- // Return null immediately if searchedText is empty.
11
- if (searchedText.length === 0) {
12
- return null;
13
- }
14
-
15
- // Check if the searchedText exists as a direct substring of text.
16
- const directIndex = text.indexOf(searchedText);
17
- if (directIndex !== -1) {
18
- return directIndex;
19
- }
20
-
21
- // Otherwise, look for the largest suffix of "text" that matches
22
- // a prefix of "searchedText". We go from the end of text inward.
23
- for (let i = text.length - 1; i >= 0; i--) {
24
- const suffix = text.substring(i);
25
- if (searchedText.startsWith(suffix)) {
26
- return i;
27
- }
28
- }
29
-
30
- return null;
31
- }