@ai-sdk-tool/parser 1.0.2 → 2.0.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/dist/index.mjs CHANGED
@@ -1,10 +1,7 @@
1
1
  // src/tool-call-middleware.ts
2
- import {
3
- generateId
4
- } from "ai";
5
- import * as RJSON from "relaxed-json";
2
+ import { generateId } from "@ai-sdk/provider-utils";
6
3
 
7
- // src/utils.ts
4
+ // src/utils/get-potential-start-index.ts
8
5
  function getPotentialStartIndex(text, searchedText) {
9
6
  if (searchedText.length === 0) {
10
7
  return null;
@@ -22,26 +19,606 @@ function getPotentialStartIndex(text, searchedText) {
22
19
  return null;
23
20
  }
24
21
 
22
+ // src/utils/relaxed-json.ts
23
+ function some(array, f) {
24
+ let acc = false;
25
+ for (let i = 0; i < array.length; i++) {
26
+ const result = f(array[i], i, array);
27
+ acc = result === void 0 ? false : result;
28
+ if (acc) {
29
+ return acc;
30
+ }
31
+ }
32
+ return acc;
33
+ }
34
+ function makeLexer(tokenSpecs) {
35
+ return function(contents) {
36
+ const tokens = [];
37
+ let line = 1;
38
+ function findToken() {
39
+ const result = some(tokenSpecs, (tokenSpec) => {
40
+ const m = tokenSpec.re.exec(contents);
41
+ if (m) {
42
+ const raw = m[0];
43
+ contents = contents.slice(raw.length);
44
+ return {
45
+ raw,
46
+ matched: tokenSpec.f(m)
47
+ // Process the match using the spec's function
48
+ };
49
+ } else {
50
+ return void 0;
51
+ }
52
+ });
53
+ return result === false ? void 0 : result;
54
+ }
55
+ while (contents !== "") {
56
+ const matched = findToken();
57
+ if (!matched) {
58
+ const err = new SyntaxError(
59
+ `Unexpected character: ${contents[0]}; input: ${contents.substr(
60
+ 0,
61
+ 100
62
+ )}`
63
+ );
64
+ err.line = line;
65
+ throw err;
66
+ }
67
+ const tokenWithLine = matched.matched;
68
+ tokenWithLine.line = line;
69
+ line += matched.raw.replace(/[^\n]/g, "").length;
70
+ tokens.push(tokenWithLine);
71
+ }
72
+ return tokens;
73
+ };
74
+ }
75
+ function fStringSingle(m) {
76
+ const content = m[1].replace(
77
+ /([^'\\]|\\['bnrtf\\]|\\u[0-9a-fA-F]{4})/g,
78
+ (mm) => {
79
+ if (mm === '"') {
80
+ return '\\"';
81
+ } else if (mm === "\\'") {
82
+ return "'";
83
+ } else {
84
+ return mm;
85
+ }
86
+ }
87
+ );
88
+ const match = `"${content}"`;
89
+ return {
90
+ type: "string",
91
+ match,
92
+ // The transformed, double-quoted string representation
93
+ // Use JSON.parse on the transformed string to handle escape sequences correctly
94
+ value: JSON.parse(match)
95
+ };
96
+ }
97
+ function fStringDouble(m) {
98
+ return {
99
+ type: "string",
100
+ match: m[0],
101
+ // The raw matched string (including quotes)
102
+ value: JSON.parse(m[0])
103
+ // Use JSON.parse to handle escapes and get the value
104
+ };
105
+ }
106
+ function fIdentifier(m) {
107
+ const value = m[0];
108
+ const match = '"' + value.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + // Escape backslashes and quotes
109
+ '"';
110
+ return {
111
+ type: "string",
112
+ // Treat identifiers as strings
113
+ value,
114
+ // The original identifier name
115
+ match
116
+ // The double-quoted string representation
117
+ };
118
+ }
119
+ function fComment(m) {
120
+ const match = m[0].replace(/./g, (c) => /\s/.test(c) ? c : " ");
121
+ return {
122
+ type: " ",
123
+ // Represent comments as whitespace tokens
124
+ match,
125
+ // String containing original newlines and spaces for other chars
126
+ value: void 0
127
+ // Comments don't have a semantic value
128
+ };
129
+ }
130
+ function fNumber(m) {
131
+ return {
132
+ type: "number",
133
+ match: m[0],
134
+ // The raw matched number string
135
+ value: parseFloat(m[0])
136
+ // Convert string to number
137
+ };
138
+ }
139
+ function fKeyword(m) {
140
+ let value;
141
+ switch (m[0]) {
142
+ case "null":
143
+ value = null;
144
+ break;
145
+ case "true":
146
+ value = true;
147
+ break;
148
+ case "false":
149
+ value = false;
150
+ break;
151
+ default:
152
+ throw new Error(`Unexpected keyword: ${m[0]}`);
153
+ }
154
+ return {
155
+ type: "atom",
156
+ // Use 'atom' type for these literals
157
+ match: m[0],
158
+ // The raw matched keyword
159
+ value
160
+ // The corresponding JavaScript value
161
+ };
162
+ }
163
+ function makeTokenSpecs(relaxed) {
164
+ function f(type) {
165
+ return function(m) {
166
+ return { type, match: m[0], value: void 0 };
167
+ };
168
+ }
169
+ let tokenSpecs = [
170
+ { re: /^\s+/, f: f(" ") },
171
+ // Whitespace
172
+ { re: /^\{/, f: f("{") },
173
+ // Object start
174
+ { re: /^\}/, f: f("}") },
175
+ // Object end
176
+ { re: /^\[/, f: f("[") },
177
+ // Array start
178
+ { re: /^\]/, f: f("]") },
179
+ // Array end
180
+ { re: /^,/, f: f(",") },
181
+ // Comma separator
182
+ { re: /^:/, f: f(":") },
183
+ // Key-value separator
184
+ { re: /^(?:true|false|null)/, f: fKeyword },
185
+ // Keywords
186
+ // Number: optional sign, digits, optional decimal part, optional exponent
187
+ { re: /^\-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/, f: fNumber },
188
+ // String: double-quoted, handles escapes
189
+ { re: /^"(?:[^"\\]|\\["bnrtf\\\/]|\\u[0-9a-fA-F]{4})*"/, f: fStringDouble }
190
+ ];
191
+ if (relaxed) {
192
+ tokenSpecs = tokenSpecs.concat([
193
+ // Single-quoted strings
194
+ {
195
+ re: /^'((?:[^'\\]|\\['bnrtf\\\/]|\\u[0-9a-fA-F]{4})*)'/,
196
+ f: fStringSingle
197
+ },
198
+ // Single-line comments (// ...)
199
+ { re: /^\/\/.*?(?:\r\n|\r|\n)/, f: fComment },
200
+ // Multi-line comments (/* ... */)
201
+ { re: /^\/\*[\s\S]*?\*\//, f: fComment },
202
+ // Unquoted identifiers (treated as strings)
203
+ // Allows letters, numbers, _, -, +, ., *, ?, !, |, &, %, ^, /, #, \
204
+ { re: /^[$a-zA-Z0-9_\-+\.\*\?!\|&%\^\/#\\]+/, f: fIdentifier }
205
+ // Note: The order matters here. Identifiers are checked after keywords/numbers.
206
+ ]);
207
+ }
208
+ return tokenSpecs;
209
+ }
210
+ var lexer = makeLexer(makeTokenSpecs(true));
211
+ var strictLexer = makeLexer(makeTokenSpecs(false));
212
+ function previousNWSToken(tokens, index) {
213
+ for (; index >= 0; index--) {
214
+ if (tokens[index].type !== " ") {
215
+ return index;
216
+ }
217
+ }
218
+ return void 0;
219
+ }
220
+ function stripTrailingComma(tokens) {
221
+ const res = [];
222
+ tokens.forEach((token, index) => {
223
+ if (index > 0 && (token.type === "]" || token.type === "}")) {
224
+ const prevNWSTokenIndex = previousNWSToken(res, res.length - 1);
225
+ if (prevNWSTokenIndex !== void 0 && res[prevNWSTokenIndex].type === ",") {
226
+ const preCommaIndex = previousNWSToken(res, prevNWSTokenIndex - 1);
227
+ if (preCommaIndex !== void 0 && res[preCommaIndex].type !== "[" && res[preCommaIndex].type !== "{") {
228
+ res[prevNWSTokenIndex] = {
229
+ type: " ",
230
+ match: " ",
231
+ // Represent as a single space
232
+ value: void 0,
233
+ // Whitespace has no value
234
+ line: res[prevNWSTokenIndex].line
235
+ // Preserve original line number
236
+ };
237
+ }
238
+ }
239
+ }
240
+ res.push(token);
241
+ });
242
+ return res;
243
+ }
244
+ function popToken(tokens, state) {
245
+ const token = tokens[state.pos];
246
+ state.pos += 1;
247
+ if (!token) {
248
+ const lastLine = tokens.length !== 0 ? tokens[tokens.length - 1].line : 1;
249
+ return { type: "eof", match: "", value: void 0, line: lastLine };
250
+ }
251
+ return token;
252
+ }
253
+ function strToken(token) {
254
+ switch (token.type) {
255
+ case "atom":
256
+ case "string":
257
+ case "number":
258
+ return `${token.type} ${token.match}`;
259
+ case "eof":
260
+ return "end-of-file";
261
+ default:
262
+ return `'${token.type}'`;
263
+ }
264
+ }
265
+ function skipColon(tokens, state) {
266
+ const colon = popToken(tokens, state);
267
+ if (colon.type !== ":") {
268
+ const message = `Unexpected token: ${strToken(colon)}, expected ':'`;
269
+ if (state.tolerant) {
270
+ state.warnings.push({
271
+ message,
272
+ line: colon.line
273
+ });
274
+ state.pos -= 1;
275
+ } else {
276
+ const err = new SyntaxError(message);
277
+ err.line = colon.line;
278
+ throw err;
279
+ }
280
+ }
281
+ }
282
+ function skipPunctuation(tokens, state, valid) {
283
+ const punctuation = [",", ":", "]", "}"];
284
+ let token = popToken(tokens, state);
285
+ while (true) {
286
+ if (valid && valid.includes(token.type)) {
287
+ return token;
288
+ } else if (token.type === "eof") {
289
+ return token;
290
+ } else if (punctuation.includes(token.type)) {
291
+ const message = `Unexpected token: ${strToken(
292
+ token
293
+ )}, expected '[', '{', number, string or atom`;
294
+ if (state.tolerant) {
295
+ state.warnings.push({
296
+ message,
297
+ line: token.line
298
+ });
299
+ token = popToken(tokens, state);
300
+ } else {
301
+ const err = new SyntaxError(message);
302
+ err.line = token.line;
303
+ throw err;
304
+ }
305
+ } else {
306
+ return token;
307
+ }
308
+ }
309
+ }
310
+ function raiseError(state, token, message) {
311
+ if (state.tolerant) {
312
+ state.warnings.push({
313
+ message,
314
+ line: token.line
315
+ });
316
+ } else {
317
+ const err = new SyntaxError(message);
318
+ err.line = token.line;
319
+ throw err;
320
+ }
321
+ }
322
+ function raiseUnexpected(state, token, expected) {
323
+ raiseError(
324
+ state,
325
+ token,
326
+ `Unexpected token: ${strToken(token)}, expected ${expected}`
327
+ );
328
+ }
329
+ function checkDuplicates(state, obj, token) {
330
+ const key = String(token.value);
331
+ if (!state.duplicate && Object.prototype.hasOwnProperty.call(obj, key)) {
332
+ raiseError(state, token, `Duplicate key: ${key}`);
333
+ }
334
+ }
335
+ function appendPair(state, obj, key, value) {
336
+ const finalValue = state.reviver ? state.reviver(key, value) : value;
337
+ if (finalValue !== void 0) {
338
+ obj[key] = finalValue;
339
+ }
340
+ }
341
+ function parsePair(tokens, state, obj) {
342
+ let token = skipPunctuation(tokens, state, [":", "string", "number", "atom"]);
343
+ let key;
344
+ let value;
345
+ if (token.type !== "string") {
346
+ raiseUnexpected(state, token, "string key");
347
+ if (state.tolerant) {
348
+ switch (token.type) {
349
+ case ":":
350
+ token = {
351
+ type: "string",
352
+ value: "null",
353
+ match: '"null"',
354
+ line: token.line
355
+ };
356
+ state.pos -= 1;
357
+ break;
358
+ case "number":
359
+ // Use number as string key
360
+ case "atom":
361
+ token = {
362
+ type: "string",
363
+ value: String(token.value),
364
+ match: `"${token.value}"`,
365
+ line: token.line
366
+ };
367
+ break;
368
+ case "[":
369
+ // Assume missing key before an array
370
+ case "{":
371
+ state.pos -= 1;
372
+ value = parseAny(tokens, state);
373
+ checkDuplicates(state, obj, {
374
+ type: "string",
375
+ value: "null",
376
+ match: '"null"',
377
+ line: token.line
378
+ });
379
+ appendPair(state, obj, "null", value);
380
+ return;
381
+ // Finished parsing this "pair"
382
+ case "eof":
383
+ return;
384
+ // Cannot recover
385
+ default:
386
+ return;
387
+ }
388
+ } else {
389
+ return;
390
+ }
391
+ }
392
+ checkDuplicates(state, obj, token);
393
+ key = String(token.value);
394
+ skipColon(tokens, state);
395
+ value = parseAny(tokens, state);
396
+ appendPair(state, obj, key, value);
397
+ }
398
+ function parseElement(tokens, state, arr) {
399
+ const key = arr.length;
400
+ const value = parseAny(tokens, state);
401
+ arr[key] = state.reviver ? state.reviver(String(key), value) : value;
402
+ }
403
+ function parseObject(tokens, state) {
404
+ const obj = {};
405
+ return parseMany(tokens, state, obj, {
406
+ skip: [":", "}"],
407
+ // Initially skip over colon or closing brace (for empty/tolerant cases)
408
+ elementParser: parsePair,
409
+ // Use parsePair to parse each key-value element
410
+ elementName: "string key",
411
+ // Expected element type for errors
412
+ endSymbol: "}"
413
+ // The closing token for an object
414
+ });
415
+ }
416
+ function parseArray(tokens, state) {
417
+ const arr = [];
418
+ return parseMany(tokens, state, arr, {
419
+ skip: ["]"],
420
+ // Initially skip over closing bracket (for empty/tolerant cases)
421
+ elementParser: parseElement,
422
+ // Use parseElement to parse each array item
423
+ elementName: "json value",
424
+ // Expected element type for errors
425
+ endSymbol: "]"
426
+ // The closing token for an array
427
+ });
428
+ }
429
+ function parseMany(tokens, state, result, opts) {
430
+ let token = skipPunctuation(tokens, state, opts.skip);
431
+ if (token.type === "eof") {
432
+ raiseUnexpected(state, token, `'${opts.endSymbol}' or ${opts.elementName}`);
433
+ if (state.tolerant) {
434
+ return result;
435
+ } else {
436
+ return result;
437
+ }
438
+ }
439
+ if (token.type === opts.endSymbol) {
440
+ return result;
441
+ }
442
+ state.pos -= 1;
443
+ opts.elementParser(tokens, state, result);
444
+ while (true) {
445
+ token = popToken(tokens, state);
446
+ if (token.type !== opts.endSymbol && token.type !== ",") {
447
+ raiseUnexpected(state, token, `',' or '${opts.endSymbol}'`);
448
+ if (state.tolerant) {
449
+ if (token.type === "eof") {
450
+ return result;
451
+ }
452
+ state.pos -= 1;
453
+ } else {
454
+ return result;
455
+ }
456
+ }
457
+ switch (token.type) {
458
+ case opts.endSymbol:
459
+ return result;
460
+ case ",":
461
+ const nextToken = tokens[state.pos];
462
+ if (state.tolerant && nextToken && nextToken.type === opts.endSymbol) {
463
+ raiseError(state, token, `Trailing comma before '${opts.endSymbol}'`);
464
+ popToken(tokens, state);
465
+ return result;
466
+ }
467
+ opts.elementParser(tokens, state, result);
468
+ break;
469
+ // Default case is only reachable in tolerant mode recovery above
470
+ default:
471
+ opts.elementParser(tokens, state, result);
472
+ break;
473
+ }
474
+ }
475
+ }
476
+ function endChecks(tokens, state, ret) {
477
+ if (state.pos < tokens.length) {
478
+ if (state.tolerant) {
479
+ skipPunctuation(tokens, state);
480
+ }
481
+ if (state.pos < tokens.length) {
482
+ raiseError(
483
+ state,
484
+ tokens[state.pos],
485
+ `Unexpected token: ${strToken(tokens[state.pos])}, expected end-of-input`
486
+ );
487
+ }
488
+ }
489
+ if (state.tolerant && state.warnings.length > 0) {
490
+ const message = state.warnings.length === 1 ? state.warnings[0].message : `${state.warnings.length} parse warnings`;
491
+ const err = new SyntaxError(message);
492
+ err.line = state.warnings[0].line;
493
+ err.warnings = state.warnings;
494
+ err.obj = ret;
495
+ throw err;
496
+ }
497
+ }
498
+ function parseAny(tokens, state, end = false) {
499
+ const token = skipPunctuation(tokens, state);
500
+ let ret;
501
+ if (token.type === "eof") {
502
+ if (end) {
503
+ raiseUnexpected(state, token, "json value");
504
+ }
505
+ raiseUnexpected(state, token, "json value");
506
+ return void 0;
507
+ }
508
+ switch (token.type) {
509
+ case "{":
510
+ ret = parseObject(tokens, state);
511
+ break;
512
+ case "[":
513
+ ret = parseArray(tokens, state);
514
+ break;
515
+ case "string":
516
+ // String literal
517
+ case "number":
518
+ // Number literal
519
+ case "atom":
520
+ ret = token.value;
521
+ break;
522
+ default:
523
+ raiseUnexpected(state, token, "json value");
524
+ if (state.tolerant) {
525
+ ret = null;
526
+ } else {
527
+ return void 0;
528
+ }
529
+ }
530
+ if (end) {
531
+ ret = state.reviver ? state.reviver("", ret) : ret;
532
+ endChecks(tokens, state, ret);
533
+ }
534
+ return ret;
535
+ }
536
+ function parse(text, optsOrReviver) {
537
+ let options = {};
538
+ if (typeof optsOrReviver === "function") {
539
+ options.reviver = optsOrReviver;
540
+ } else if (optsOrReviver !== null && typeof optsOrReviver === "object") {
541
+ options = { ...optsOrReviver };
542
+ } else if (optsOrReviver !== void 0) {
543
+ throw new TypeError(
544
+ "Second argument must be a reviver function or an options object."
545
+ );
546
+ }
547
+ if (options.relaxed === void 0) {
548
+ if (options.warnings === true || options.tolerant === true) {
549
+ options.relaxed = true;
550
+ } else if (options.warnings === false && options.tolerant === false) {
551
+ options.relaxed = false;
552
+ } else {
553
+ options.relaxed = true;
554
+ }
555
+ }
556
+ options.tolerant = options.tolerant || options.warnings || false;
557
+ options.warnings = options.warnings || false;
558
+ options.duplicate = options.duplicate || false;
559
+ if (!options.relaxed && !options.warnings && !options.tolerant) {
560
+ if (!options.duplicate) {
561
+ } else {
562
+ return JSON.parse(text, options.reviver);
563
+ }
564
+ }
565
+ const lexerToUse = options.relaxed ? lexer : strictLexer;
566
+ let tokens = lexerToUse(text);
567
+ if (options.relaxed) {
568
+ tokens = stripTrailingComma(tokens);
569
+ }
570
+ if (options.warnings || options.tolerant) {
571
+ tokens = tokens.filter((token) => token.type !== " ");
572
+ const state = {
573
+ pos: 0,
574
+ reviver: options.reviver,
575
+ tolerant: options.tolerant,
576
+ duplicate: !options.duplicate,
577
+ // Internal state: true means *check* for duplicates
578
+ warnings: []
579
+ };
580
+ return parseAny(tokens, state, true);
581
+ } else {
582
+ const newtext = tokens.reduce((str, token) => {
583
+ return str + token.match;
584
+ }, "");
585
+ if (!options.relaxed && !options.warnings && !options.tolerant && options.duplicate) {
586
+ return JSON.parse(text, options.reviver);
587
+ } else if (options.warnings || options.tolerant || !options.duplicate) {
588
+ tokens = lexerToUse(text);
589
+ if (options.relaxed) {
590
+ tokens = stripTrailingComma(tokens);
591
+ }
592
+ tokens = tokens.filter((token) => token.type !== " ");
593
+ const state = {
594
+ pos: 0,
595
+ reviver: options.reviver,
596
+ tolerant: options.tolerant || false,
597
+ // Ensure boolean
598
+ duplicate: !options.duplicate,
599
+ // true = check duplicates
600
+ warnings: []
601
+ };
602
+ return parseAny(tokens, state, true);
603
+ } else {
604
+ tokens = lexer(text);
605
+ tokens = stripTrailingComma(tokens);
606
+ const newtext2 = tokens.reduce((str, token) => str + token.match, "");
607
+ return JSON.parse(newtext2, options.reviver);
608
+ }
609
+ }
610
+ }
611
+
25
612
  // 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.
30
- Here are the available tools: <tools>${tools}</tools>
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']}
32
- For each function call return a json object with function name and arguments within <tool_call></tool_call> XML tags as follows:
33
- <tool_call>
34
- {'arguments': <args-dict>, 'name': <function-name>}
35
- </tool_call>`;
36
613
  function createToolMiddleware({
37
- toolCallTag = "<tool_call>",
38
- toolCallEndTag = "</tool_call>",
39
- toolResponseTag = "<tool_response>",
40
- toolResponseEndTag = "</tool_response>",
41
- toolSystemPromptTemplate = defaultTemplate
614
+ toolCallTag,
615
+ toolCallEndTag,
616
+ toolResponseTag,
617
+ toolResponseEndTag,
618
+ toolSystemPromptTemplate
42
619
  }) {
43
620
  return {
44
- middlewareVersion: "v1",
621
+ middlewareVersion: "v2",
45
622
  wrapStream: async ({ doStream }) => {
46
623
  const { stream, ...rest } = await doStream();
47
624
  let isFirstToolCall = true;
@@ -57,7 +634,7 @@ function createToolMiddleware({
57
634
  if (toolCallBuffer.length > 0) {
58
635
  toolCallBuffer.forEach((toolCall) => {
59
636
  try {
60
- const parsedToolCall = RJSON.parse(toolCall);
637
+ const parsedToolCall = parse(toolCall);
61
638
  controller.enqueue({
62
639
  type: "tool-call",
63
640
  toolCallType: "function",
@@ -68,19 +645,19 @@ function createToolMiddleware({
68
645
  } catch (e) {
69
646
  console.error(`Error parsing tool call: ${toolCall}`, e);
70
647
  controller.enqueue({
71
- type: "text-delta",
72
- textDelta: `Failed to parse tool call: ${e}`
648
+ type: "text",
649
+ text: `Failed to parse tool call: ${e}`
73
650
  });
74
651
  }
75
652
  });
76
653
  }
77
654
  controller.enqueue(chunk);
78
655
  return;
79
- } else if (chunk.type !== "text-delta") {
656
+ } else if (chunk.type !== "text") {
80
657
  controller.enqueue(chunk);
81
658
  return;
82
659
  }
83
- buffer += chunk.textDelta;
660
+ buffer += chunk.text;
84
661
  function publish(text) {
85
662
  if (text.length > 0) {
86
663
  const prefix = afterSwitch && (isToolCall ? !isFirstToolCall : !isFirstText) ? "\n" : "";
@@ -91,8 +668,8 @@ function createToolMiddleware({
91
668
  toolCallBuffer[toolCallIndex] += text;
92
669
  } else {
93
670
  controller.enqueue({
94
- type: "text-delta",
95
- textDelta: prefix + text
671
+ type: "text",
672
+ text: prefix + text
96
673
  });
97
674
  }
98
675
  afterSwitch = false;
@@ -131,32 +708,86 @@ function createToolMiddleware({
131
708
  };
132
709
  },
133
710
  wrapGenerate: async ({ doGenerate }) => {
134
- var _a;
135
711
  const result = await doGenerate();
136
- if (!((_a = result.text) == null ? void 0 : _a.includes(toolCallTag))) {
712
+ if (result.content.length === 0) {
137
713
  return result;
138
714
  }
139
715
  const toolCallRegex = new RegExp(
140
716
  `${toolCallTag}(.*?)(?:${toolCallEndTag}|$)`,
141
717
  "gs"
142
718
  );
143
- const matches = [...result.text.matchAll(toolCallRegex)];
144
- const function_call_tuples = matches.map((match) => match[1] || match[2]);
719
+ const newContent = result.content.flatMap(
720
+ (contentItem) => {
721
+ if (contentItem.type !== "text" || !contentItem.text.includes(toolCallTag)) {
722
+ return [contentItem];
723
+ }
724
+ const text = contentItem.text;
725
+ const processedElements = [];
726
+ let currentIndex = 0;
727
+ let match;
728
+ const parseAndCreateToolCall = (toolCallJson) => {
729
+ try {
730
+ const parsedToolCall = parse(toolCallJson);
731
+ if (!parsedToolCall || typeof parsedToolCall.name !== "string" || typeof parsedToolCall.arguments === "undefined") {
732
+ console.error(
733
+ "Failed to parse tool call: Invalid structure",
734
+ toolCallJson
735
+ );
736
+ return null;
737
+ }
738
+ return {
739
+ type: "tool-call",
740
+ toolCallType: "function",
741
+ toolCallId: generateId(),
742
+ toolName: parsedToolCall.name,
743
+ // Ensure args is always a JSON string
744
+ args: typeof parsedToolCall.arguments === "string" ? parsedToolCall.arguments : JSON.stringify(parsedToolCall.arguments)
745
+ };
746
+ } catch (error) {
747
+ console.error(
748
+ "Failed to parse tool call JSON:",
749
+ error,
750
+ "JSON:",
751
+ toolCallJson
752
+ );
753
+ return null;
754
+ }
755
+ };
756
+ while ((match = toolCallRegex.exec(text)) !== null) {
757
+ const startIndex = match.index;
758
+ const endIndex = startIndex + match[0].length;
759
+ const toolCallJson = match[1];
760
+ if (startIndex > currentIndex) {
761
+ const textSegment = text.substring(currentIndex, startIndex);
762
+ if (textSegment.trim()) {
763
+ processedElements.push({ type: "text", text: textSegment });
764
+ }
765
+ }
766
+ if (toolCallJson) {
767
+ const toolCallObject = parseAndCreateToolCall(toolCallJson);
768
+ if (toolCallObject) {
769
+ processedElements.push(toolCallObject);
770
+ } else {
771
+ console.warn(
772
+ `Could not process tool call, keeping original text: ${match[0]}`
773
+ );
774
+ processedElements.push({ type: "text", text: match[0] });
775
+ }
776
+ }
777
+ currentIndex = endIndex;
778
+ }
779
+ if (currentIndex < text.length) {
780
+ const remainingText = text.substring(currentIndex);
781
+ if (remainingText.trim()) {
782
+ processedElements.push({ type: "text", text: remainingText });
783
+ }
784
+ }
785
+ return processedElements;
786
+ }
787
+ );
145
788
  return {
146
789
  ...result,
147
- // TODO: Return the remaining value after extracting the tool call tag.
148
- text: "",
149
- toolCalls: function_call_tuples.map((toolCall) => {
150
- const parsedToolCall = RJSON.parse(toolCall);
151
- const toolName = parsedToolCall.name;
152
- const args = parsedToolCall.arguments;
153
- return {
154
- toolCallType: "function",
155
- toolCallId: generateId(),
156
- toolName,
157
- args: RJSON.stringify(args)
158
- };
159
- })
790
+ content: newContent
160
791
  };
161
792
  },
162
793
  transformParams: async ({ params }) => {
@@ -195,9 +826,8 @@ function createToolMiddleware({
195
826
  }
196
827
  return message;
197
828
  });
198
- const originalToolDefinitions = params.mode.type === "regular" && params.mode.tools ? params.mode.tools : {};
199
829
  const HermesPrompt = toolSystemPromptTemplate(
200
- JSON.stringify(Object.entries(originalToolDefinitions))
830
+ JSON.stringify(Object.entries(params.tools || {}))
201
831
  );
202
832
  const toolSystemPrompt = processedPrompt[0].role === "system" ? [
203
833
  {
@@ -214,11 +844,10 @@ function createToolMiddleware({
214
844
  ];
215
845
  return {
216
846
  ...params,
217
- mode: {
218
- // set the mode back to regular and remove the default tools.
219
- type: "regular"
220
- },
221
- prompt: toolSystemPrompt
847
+ prompt: toolSystemPrompt,
848
+ // set the mode back to regular and remove the default tools.
849
+ tools: [],
850
+ toolChoice: void 0
222
851
  };
223
852
  }
224
853
  };
@@ -228,22 +857,38 @@ function createToolMiddleware({
228
857
  var gemmaToolMiddleware = createToolMiddleware({
229
858
  toolSystemPromptTemplate(tools) {
230
859
  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}`;
860
+ you MUST put it in the format of
861
+ \`\`\`tool_call
862
+ {'name': <function-name>, 'arguments': <args-dict>}
863
+ \`\`\`
864
+ You SHOULD NOT include any other text in the response if you call a function
865
+ ${tools}`;
237
866
  },
238
867
  toolCallTag: "```tool_call\n",
239
868
  toolCallEndTag: "```",
240
869
  toolResponseTag: "```tool_response\n",
241
870
  toolResponseEndTag: "\n```"
242
871
  });
243
- var hermesToolMiddleware = createToolMiddleware({});
872
+ var hermesToolMiddleware = createToolMiddleware({
873
+ toolSystemPromptTemplate(tools) {
874
+ return `You are a function calling AI model.
875
+ You are provided with function signatures within <tools></tools> XML tags.
876
+ You may call one or more functions to assist with the user query.
877
+ Don't make assumptions about what values to plug into functions.
878
+ Here are the available tools: <tools>${tools}</tools>
879
+ 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']}
880
+ For each function call return a json object with function name and arguments within <tool_call></tool_call> XML tags as follows:
881
+ <tool_call>
882
+ {'arguments': <args-dict>, 'name': <function-name>}
883
+ </tool_call>`;
884
+ },
885
+ toolCallTag: "<tool_call>",
886
+ toolCallEndTag: "</tool_call>",
887
+ toolResponseTag: "<tool_response>",
888
+ toolResponseEndTag: "</tool_response>"
889
+ });
244
890
  export {
245
891
  createToolMiddleware,
246
- defaultTemplate,
247
892
  gemmaToolMiddleware,
248
893
  hermesToolMiddleware
249
894
  };