@browser-ai/core 2.0.3 → 2.0.4

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.js CHANGED
@@ -28,10 +28,82 @@ __export(index_exports, {
28
28
  });
29
29
  module.exports = __toCommonJS(index_exports);
30
30
 
31
- // src/convert-to-browser-ai-messages.ts
32
- var import_provider = require("@ai-sdk/provider");
31
+ // ../shared/src/utils/tool-utils.ts
32
+ function isFunctionTool(tool) {
33
+ return tool.type === "function";
34
+ }
35
+
36
+ // ../shared/src/utils/warnings.ts
37
+ function createUnsupportedSettingWarning(feature, details) {
38
+ return {
39
+ type: "unsupported",
40
+ feature,
41
+ details
42
+ };
43
+ }
44
+ function createUnsupportedToolWarning(tool, details) {
45
+ return {
46
+ type: "unsupported",
47
+ feature: `tool:${tool.name}`,
48
+ details
49
+ };
50
+ }
51
+
52
+ // ../shared/src/tool-calling/build-json-system-prompt.ts
53
+ function buildJsonToolSystemPrompt(originalSystemPrompt, tools, options) {
54
+ if (!tools || tools.length === 0) {
55
+ return originalSystemPrompt || "";
56
+ }
57
+ const parallelInstruction = "Only request one tool call at a time. Wait for tool results before asking for another tool.";
58
+ const toolSchemas = tools.map((tool) => {
59
+ const schema = getParameters(tool);
60
+ return {
61
+ name: tool.name,
62
+ description: tool.description ?? "No description provided.",
63
+ parameters: schema || { type: "object", properties: {} }
64
+ };
65
+ });
66
+ const toolsJson = JSON.stringify(toolSchemas, null, 2);
67
+ const instructionBody = `You are a helpful AI assistant with access to tools.
68
+
69
+ # Available Tools
70
+ ${toolsJson}
71
+
72
+ # Tool Calling Instructions
73
+ ${parallelInstruction}
33
74
 
34
- // src/tool-calling/format-tool-results.ts
75
+ To call a tool, output JSON in this exact format inside a \`\`\`tool_call code fence:
76
+
77
+ \`\`\`tool_call
78
+ {"name": "tool_name", "arguments": {"param1": "value1", "param2": "value2"}}
79
+ \`\`\`
80
+
81
+ Tool responses will be provided in \`\`\`tool_result fences. Each line contains JSON like:
82
+ \`\`\`tool_result
83
+ {"id": "call_123", "name": "tool_name", "result": {...}, "error": false}
84
+ \`\`\`
85
+ Use the \`result\` payload (and treat \`error\` as a boolean flag) when continuing the conversation.
86
+
87
+ Important:
88
+ - Use exact tool and parameter names from the schema above
89
+ - Arguments must be a valid JSON object matching the tool's parameters
90
+ - You can include brief reasoning before or after the tool call
91
+ - If no tool is needed, respond directly without tool_call fences`;
92
+ if (originalSystemPrompt?.trim()) {
93
+ return `${originalSystemPrompt.trim()}
94
+
95
+ ${instructionBody}`;
96
+ }
97
+ return instructionBody;
98
+ }
99
+ function getParameters(tool) {
100
+ if ("parameters" in tool) {
101
+ return tool.parameters;
102
+ }
103
+ return tool.inputSchema;
104
+ }
105
+
106
+ // ../shared/src/tool-calling/format-tool-results.ts
35
107
  function buildResultPayload(result) {
36
108
  const payload = {
37
109
  name: result.toolName,
@@ -55,7 +127,365 @@ ${payloads.join("\n")}
55
127
  \`\`\``;
56
128
  }
57
129
 
130
+ // ../shared/src/tool-calling/parse-json-function-calls.ts
131
+ var DEFAULT_OPTIONS = {
132
+ supportXmlTags: true,
133
+ supportPythonStyle: true,
134
+ supportParametersField: true
135
+ };
136
+ function generateToolCallId() {
137
+ return `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
138
+ }
139
+ function buildRegex(options) {
140
+ const patterns = [];
141
+ patterns.push("```tool[_-]?call\\s*([\\s\\S]*?)```");
142
+ if (options.supportXmlTags) {
143
+ patterns.push("<tool_call>\\s*([\\s\\S]*?)\\s*</tool_call>");
144
+ }
145
+ if (options.supportPythonStyle) {
146
+ patterns.push("\\[(\\w+)\\(([^)]*)\\)\\]");
147
+ }
148
+ return new RegExp(patterns.join("|"), "gi");
149
+ }
150
+ function parseJsonFunctionCalls(response, options = DEFAULT_OPTIONS) {
151
+ const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
152
+ const regex = buildRegex(mergedOptions);
153
+ const matches = Array.from(response.matchAll(regex));
154
+ regex.lastIndex = 0;
155
+ if (matches.length === 0) {
156
+ return { toolCalls: [], textContent: response };
157
+ }
158
+ const toolCalls = [];
159
+ let textContent = response;
160
+ for (const match of matches) {
161
+ const fullMatch = match[0];
162
+ textContent = textContent.replace(fullMatch, "");
163
+ try {
164
+ if (mergedOptions.supportPythonStyle && match[0].startsWith("[")) {
165
+ const pythonMatch = /\[(\w+)\(([^)]*)\)\]/.exec(match[0]);
166
+ if (pythonMatch) {
167
+ const [, funcName, pythonArgs] = pythonMatch;
168
+ const args = {};
169
+ if (pythonArgs && pythonArgs.trim()) {
170
+ const argPairs = pythonArgs.split(",").map((s) => s.trim());
171
+ for (const pair of argPairs) {
172
+ const equalIndex = pair.indexOf("=");
173
+ if (equalIndex > 0) {
174
+ const key = pair.substring(0, equalIndex).trim();
175
+ let value = pair.substring(equalIndex + 1).trim();
176
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
177
+ value = value.substring(1, value.length - 1);
178
+ }
179
+ args[key] = value;
180
+ }
181
+ }
182
+ }
183
+ toolCalls.push({
184
+ type: "tool-call",
185
+ toolCallId: generateToolCallId(),
186
+ toolName: funcName,
187
+ args
188
+ });
189
+ continue;
190
+ }
191
+ }
192
+ const innerContent = match[1] || match[2] || "";
193
+ const trimmed = innerContent.trim();
194
+ if (!trimmed) continue;
195
+ try {
196
+ const parsed = JSON.parse(trimmed);
197
+ const callsArray = Array.isArray(parsed) ? parsed : [parsed];
198
+ for (const call of callsArray) {
199
+ if (!call.name) continue;
200
+ let args = call.arguments || (mergedOptions.supportParametersField ? call.parameters : null) || {};
201
+ if (typeof args === "string") {
202
+ try {
203
+ args = JSON.parse(args);
204
+ } catch {
205
+ }
206
+ }
207
+ toolCalls.push({
208
+ type: "tool-call",
209
+ toolCallId: call.id || generateToolCallId(),
210
+ toolName: call.name,
211
+ args
212
+ });
213
+ }
214
+ } catch {
215
+ const lines = trimmed.split("\n").filter((line) => line.trim());
216
+ for (const line of lines) {
217
+ try {
218
+ const call = JSON.parse(line.trim());
219
+ if (!call.name) continue;
220
+ let args = call.arguments || (mergedOptions.supportParametersField ? call.parameters : null) || {};
221
+ if (typeof args === "string") {
222
+ try {
223
+ args = JSON.parse(args);
224
+ } catch {
225
+ }
226
+ }
227
+ toolCalls.push({
228
+ type: "tool-call",
229
+ toolCallId: call.id || generateToolCallId(),
230
+ toolName: call.name,
231
+ args
232
+ });
233
+ } catch {
234
+ continue;
235
+ }
236
+ }
237
+ }
238
+ } catch (error) {
239
+ console.warn("Failed to parse JSON tool call:", error);
240
+ continue;
241
+ }
242
+ }
243
+ textContent = textContent.replace(/\n{2,}/g, "\n");
244
+ return { toolCalls, textContent: textContent.trim() };
245
+ }
246
+
247
+ // ../shared/src/streaming/tool-call-detector.ts
248
+ var DEFAULT_FENCE_PATTERNS = [
249
+ { start: "```tool_call", end: "```", reconstructStart: "```tool_call\n" },
250
+ { start: "```tool-call", end: "```", reconstructStart: "```tool-call\n" }
251
+ ];
252
+ var EXTENDED_FENCE_PATTERNS = [
253
+ ...DEFAULT_FENCE_PATTERNS,
254
+ {
255
+ start: "<tool_call>",
256
+ end: "</tool_call>",
257
+ reconstructStart: "<tool_call>"
258
+ }
259
+ ];
260
+ var ToolCallFenceDetector = class {
261
+ constructor(options = {}) {
262
+ this.pythonStyleRegex = /\[(\w+)\(/g;
263
+ this.buffer = "";
264
+ this.inFence = false;
265
+ this.fenceStartBuffer = "";
266
+ // Accumulated fence content
267
+ this.currentFencePattern = null;
268
+ this.fencePatterns = options.patterns ?? EXTENDED_FENCE_PATTERNS;
269
+ this.enablePythonStyle = options.enablePythonStyle ?? true;
270
+ this.fenceStarts = this.fencePatterns.map((p) => p.start);
271
+ }
272
+ addChunk(chunk) {
273
+ this.buffer += chunk;
274
+ }
275
+ getBuffer() {
276
+ return this.buffer;
277
+ }
278
+ clearBuffer() {
279
+ this.buffer = "";
280
+ }
281
+ /**
282
+ * Detects if there's a complete fence in the buffer
283
+ * @returns Detection result with fence info and safe text
284
+ */
285
+ detectFence() {
286
+ const {
287
+ index: startIdx,
288
+ prefix: matchedPrefix,
289
+ pattern
290
+ } = this.findFenceStart(this.buffer);
291
+ if (startIdx === -1) {
292
+ const overlap = this.computeOverlapLength(this.buffer, this.fenceStarts);
293
+ const safeTextLength = this.buffer.length - overlap;
294
+ const prefixText2 = safeTextLength > 0 ? this.buffer.slice(0, safeTextLength) : "";
295
+ const remaining = overlap > 0 ? this.buffer.slice(-overlap) : "";
296
+ this.buffer = remaining;
297
+ return {
298
+ fence: null,
299
+ prefixText: prefixText2,
300
+ remainingText: "",
301
+ overlapLength: overlap
302
+ };
303
+ }
304
+ const prefixText = this.buffer.slice(0, startIdx);
305
+ this.buffer = this.buffer.slice(startIdx);
306
+ const prefixLength = matchedPrefix?.length ?? 0;
307
+ const fenceEnd = pattern?.end ?? "```";
308
+ const closingIdx = this.buffer.indexOf(fenceEnd, prefixLength);
309
+ if (closingIdx === -1) {
310
+ return {
311
+ fence: null,
312
+ prefixText,
313
+ remainingText: "",
314
+ overlapLength: 0
315
+ };
316
+ }
317
+ const endPos = closingIdx + fenceEnd.length;
318
+ const fence = this.buffer.slice(0, endPos);
319
+ const remainingText = this.buffer.slice(endPos);
320
+ this.buffer = "";
321
+ return {
322
+ fence,
323
+ prefixText,
324
+ remainingText,
325
+ overlapLength: 0
326
+ };
327
+ }
328
+ /**
329
+ * Finds the first occurrence of any fence start marker
330
+ *
331
+ * @param text - Text to search in
332
+ * @returns Index of first fence start and which pattern matched
333
+ * @private
334
+ */
335
+ findFenceStart(text) {
336
+ let bestIndex = -1;
337
+ let matchedPrefix = null;
338
+ let matchedPattern = null;
339
+ for (const pattern of this.fencePatterns) {
340
+ const idx = text.indexOf(pattern.start);
341
+ if (idx !== -1 && (bestIndex === -1 || idx < bestIndex)) {
342
+ bestIndex = idx;
343
+ matchedPrefix = pattern.start;
344
+ matchedPattern = pattern;
345
+ }
346
+ }
347
+ if (this.enablePythonStyle) {
348
+ this.pythonStyleRegex.lastIndex = 0;
349
+ const pythonMatch = this.pythonStyleRegex.exec(text);
350
+ if (pythonMatch && (bestIndex === -1 || pythonMatch.index < bestIndex)) {
351
+ bestIndex = pythonMatch.index;
352
+ matchedPrefix = pythonMatch[0];
353
+ matchedPattern = {
354
+ start: pythonMatch[0],
355
+ end: ")]",
356
+ reconstructStart: pythonMatch[0],
357
+ isRegex: true
358
+ };
359
+ }
360
+ }
361
+ return { index: bestIndex, prefix: matchedPrefix, pattern: matchedPattern };
362
+ }
363
+ /**
364
+ * Computes the maximum overlap between the end of text and the start of any prefix
365
+ * @param text - Text to check for overlap
366
+ * @param prefixes - List of prefixes to check against
367
+ * @returns Length of the maximum overlap found
368
+ */
369
+ computeOverlapLength(text, prefixes) {
370
+ let overlap = 0;
371
+ for (const prefix of prefixes) {
372
+ const maxLength = Math.min(text.length, prefix.length - 1);
373
+ for (let size = maxLength; size > 0; size -= 1) {
374
+ if (prefix.startsWith(text.slice(-size))) {
375
+ overlap = Math.max(overlap, size);
376
+ break;
377
+ }
378
+ }
379
+ }
380
+ return overlap;
381
+ }
382
+ /**
383
+ * Checks if the buffer currently contains any text
384
+ */
385
+ hasContent() {
386
+ return this.buffer.length > 0;
387
+ }
388
+ /**
389
+ * Gets the buffer size
390
+ */
391
+ getBufferSize() {
392
+ return this.buffer.length;
393
+ }
394
+ /**
395
+ * Detect and stream fence content in real-time for true incremental streaming
396
+ * @returns Streaming result with current state and safe content to emit
397
+ */
398
+ detectStreamingFence() {
399
+ if (!this.inFence) {
400
+ const {
401
+ index: startIdx,
402
+ prefix: matchedPrefix,
403
+ pattern
404
+ } = this.findFenceStart(this.buffer);
405
+ if (startIdx === -1) {
406
+ const overlap = this.computeOverlapLength(
407
+ this.buffer,
408
+ this.fenceStarts
409
+ );
410
+ const safeTextLength = this.buffer.length - overlap;
411
+ const safeContent = safeTextLength > 0 ? this.buffer.slice(0, safeTextLength) : "";
412
+ this.buffer = this.buffer.slice(safeTextLength);
413
+ return {
414
+ inFence: false,
415
+ safeContent,
416
+ completeFence: null,
417
+ textAfterFence: ""
418
+ };
419
+ }
420
+ const prefixText = this.buffer.slice(0, startIdx);
421
+ const fenceStartLength = matchedPrefix?.length ?? 0;
422
+ this.buffer = this.buffer.slice(startIdx + fenceStartLength);
423
+ if (pattern && pattern.start.startsWith("```") && this.buffer.startsWith("\n")) {
424
+ this.buffer = this.buffer.slice(1);
425
+ }
426
+ this.inFence = true;
427
+ this.fenceStartBuffer = "";
428
+ this.currentFencePattern = pattern;
429
+ return {
430
+ inFence: true,
431
+ safeContent: prefixText,
432
+ // Emit any text before the fence
433
+ completeFence: null,
434
+ textAfterFence: ""
435
+ };
436
+ }
437
+ const fenceEnd = this.currentFencePattern?.end ?? "```";
438
+ const closingIdx = this.buffer.indexOf(fenceEnd);
439
+ if (closingIdx === -1) {
440
+ const overlap = this.computeOverlapLength(this.buffer, [fenceEnd]);
441
+ const safeContentLength = this.buffer.length - overlap;
442
+ if (safeContentLength > 0) {
443
+ const safeContent = this.buffer.slice(0, safeContentLength);
444
+ this.fenceStartBuffer += safeContent;
445
+ this.buffer = this.buffer.slice(safeContentLength);
446
+ return {
447
+ inFence: true,
448
+ safeContent,
449
+ completeFence: null,
450
+ textAfterFence: ""
451
+ };
452
+ }
453
+ return {
454
+ inFence: true,
455
+ safeContent: "",
456
+ completeFence: null,
457
+ textAfterFence: ""
458
+ };
459
+ }
460
+ const fenceContent = this.buffer.slice(0, closingIdx);
461
+ this.fenceStartBuffer += fenceContent;
462
+ const reconstructStart = this.currentFencePattern?.reconstructStart ?? "```tool_call\n";
463
+ const completeFence = `${reconstructStart}${this.fenceStartBuffer}${fenceEnd}`;
464
+ const textAfterFence = this.buffer.slice(closingIdx + fenceEnd.length);
465
+ this.inFence = false;
466
+ this.fenceStartBuffer = "";
467
+ this.currentFencePattern = null;
468
+ this.buffer = textAfterFence;
469
+ return {
470
+ inFence: false,
471
+ safeContent: fenceContent,
472
+ // Emit the last bit of fence content
473
+ completeFence,
474
+ textAfterFence
475
+ };
476
+ }
477
+ isInFence() {
478
+ return this.inFence;
479
+ }
480
+ resetStreamingState() {
481
+ this.inFence = false;
482
+ this.fenceStartBuffer = "";
483
+ this.currentFencePattern = null;
484
+ }
485
+ };
486
+
58
487
  // src/convert-to-browser-ai-messages.ts
488
+ var import_provider = require("@ai-sdk/provider");
59
489
  function convertBase64ToUint8Array(base64) {
60
490
  try {
61
491
  const binaryString = atob(base64);
@@ -229,168 +659,44 @@ function convertToBrowserAIMessages(prompt) {
229
659
  }
230
660
  }
231
661
  const toolCallJson = formatToolCallsJson(toolCallParts);
232
- const contentSegments = [];
233
- if (text.trim().length > 0) {
234
- contentSegments.push(text);
235
- } else if (text.length > 0) {
236
- contentSegments.push(text);
237
- }
238
- if (toolCallJson) {
239
- contentSegments.push(toolCallJson);
240
- }
241
- const content = contentSegments.length > 0 ? contentSegments.join("\n") : "";
242
- messages.push({
243
- role: "assistant",
244
- content
245
- });
246
- break;
247
- }
248
- case "tool": {
249
- const toolParts = message.content;
250
- const results = toolParts.map(toToolResult);
251
- const toolResultsJson = formatToolResults(results);
252
- messages.push({
253
- role: "user",
254
- content: toolResultsJson
255
- });
256
- break;
257
- }
258
- default: {
259
- const exhaustiveCheck = message;
260
- throw new Error(
261
- `Unsupported role: ${exhaustiveCheck.role ?? "unknown"}`
262
- );
263
- }
264
- }
265
- }
266
- return { systemMessage, messages };
267
- }
268
-
269
- // src/tool-calling/build-json-system-prompt.ts
270
- function buildJsonToolSystemPrompt(originalSystemPrompt, tools, options) {
271
- if (!tools || tools.length === 0) {
272
- return originalSystemPrompt || "";
273
- }
274
- const parallelInstruction = "Only request one tool call at a time. Wait for tool results before asking for another tool.";
275
- const toolSchemas = tools.map((tool) => {
276
- const schema = getParameters(tool);
277
- return {
278
- name: tool.name,
279
- description: tool.description ?? "No description provided.",
280
- parameters: schema || { type: "object", properties: {} }
281
- };
282
- });
283
- const toolsJson = JSON.stringify(toolSchemas, null, 2);
284
- const instructionBody = `You are a helpful AI assistant with access to tools.
285
-
286
- # Available Tools
287
- ${toolsJson}
288
-
289
- # Tool Calling Instructions
290
- ${parallelInstruction}
291
-
292
- To call a tool, output JSON in this exact format inside a \`\`\`tool_call code fence:
293
-
294
- \`\`\`tool_call
295
- {"name": "tool_name", "arguments": {"param1": "value1", "param2": "value2"}}
296
- \`\`\`
297
-
298
- Tool responses will be provided in \`\`\`tool_result fences. Each line contains JSON like:
299
- \`\`\`tool_result
300
- {"id": "call_123", "name": "tool_name", "result": {...}, "error": false}
301
- \`\`\`
302
- Use the \`result\` payload (and treat \`error\` as a boolean flag) when continuing the conversation.
303
-
304
- Important:
305
- - Use exact tool and parameter names from the schema above
306
- - Arguments must be a valid JSON object matching the tool's parameters
307
- - You can include brief reasoning before or after the tool call
308
- - If no tool is needed, respond directly without tool_call fences`;
309
- if (originalSystemPrompt?.trim()) {
310
- return `${originalSystemPrompt.trim()}
311
-
312
- ${instructionBody}`;
313
- }
314
- return instructionBody;
315
- }
316
- function getParameters(tool) {
317
- if ("parameters" in tool) {
318
- return tool.parameters;
319
- }
320
- return tool.inputSchema;
321
- }
322
-
323
- // src/tool-calling/parse-json-function-calls.ts
324
- var JSON_TOOL_CALL_FENCE_REGEX = /```tool[_-]?call\s*([\s\S]*?)```/gi;
325
- function generateToolCallId() {
326
- return `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
327
- }
328
- function parseJsonFunctionCalls(response) {
329
- const matches = Array.from(response.matchAll(JSON_TOOL_CALL_FENCE_REGEX));
330
- JSON_TOOL_CALL_FENCE_REGEX.lastIndex = 0;
331
- if (matches.length === 0) {
332
- return { toolCalls: [], textContent: response };
333
- }
334
- const toolCalls = [];
335
- let textContent = response;
336
- for (const match of matches) {
337
- const [fullFence, innerContent] = match;
338
- textContent = textContent.replace(fullFence, "");
339
- try {
340
- const trimmed = innerContent.trim();
341
- try {
342
- const parsed = JSON.parse(trimmed);
343
- const callsArray = Array.isArray(parsed) ? parsed : [parsed];
344
- for (const call of callsArray) {
345
- if (!call.name) continue;
346
- toolCalls.push({
347
- type: "tool-call",
348
- toolCallId: call.id || generateToolCallId(),
349
- toolName: call.name,
350
- args: call.arguments || {}
351
- });
352
- }
353
- } catch {
354
- const lines = trimmed.split("\n").filter((line) => line.trim());
355
- for (const line of lines) {
356
- try {
357
- const call = JSON.parse(line.trim());
358
- if (!call.name) continue;
359
- toolCalls.push({
360
- type: "tool-call",
361
- toolCallId: call.id || generateToolCallId(),
362
- toolName: call.name,
363
- args: call.arguments || {}
364
- });
365
- } catch {
366
- continue;
367
- }
662
+ const contentSegments = [];
663
+ if (text.trim().length > 0) {
664
+ contentSegments.push(text);
665
+ } else if (text.length > 0) {
666
+ contentSegments.push(text);
667
+ }
668
+ if (toolCallJson) {
669
+ contentSegments.push(toolCallJson);
368
670
  }
671
+ const content = contentSegments.length > 0 ? contentSegments.join("\n") : "";
672
+ messages.push({
673
+ role: "assistant",
674
+ content
675
+ });
676
+ break;
677
+ }
678
+ case "tool": {
679
+ const toolParts = message.content;
680
+ const results = toolParts.map(toToolResult);
681
+ const toolResultsJson = formatToolResults(results);
682
+ messages.push({
683
+ role: "user",
684
+ content: toolResultsJson
685
+ });
686
+ break;
687
+ }
688
+ default: {
689
+ const exhaustiveCheck = message;
690
+ throw new Error(
691
+ `Unsupported role: ${exhaustiveCheck.role ?? "unknown"}`
692
+ );
369
693
  }
370
- } catch (error) {
371
- console.warn("Failed to parse JSON tool call:", error);
372
- continue;
373
694
  }
374
695
  }
375
- textContent = textContent.replace(/\n{2,}/g, "\n");
376
- return { toolCalls, textContent: textContent.trim() };
696
+ return { systemMessage, messages };
377
697
  }
378
698
 
379
699
  // src/utils/warnings.ts
380
- function createUnsupportedSettingWarning(feature, details) {
381
- return {
382
- type: "unsupported",
383
- feature,
384
- details
385
- };
386
- }
387
- function createUnsupportedToolWarning(tool, details) {
388
- return {
389
- type: "unsupported",
390
- feature: `tool:${tool.name}`,
391
- details
392
- };
393
- }
394
700
  function gatherUnsupportedSettingWarnings(options) {
395
701
  const warnings = [];
396
702
  if (options.maxOutputTokens != null) {
@@ -483,11 +789,6 @@ function getExpectedInputs(prompt) {
483
789
  return Array.from(inputs).map((type) => ({ type }));
484
790
  }
485
791
 
486
- // src/utils/tool-utils.ts
487
- function isFunctionTool(tool) {
488
- return tool.type === "function";
489
- }
490
-
491
792
  // src/models/session-manager.ts
492
793
  var import_provider2 = require("@ai-sdk/provider");
493
794
  var SessionManager = class {
@@ -658,258 +959,6 @@ var SessionManager = class {
658
959
  }
659
960
  };
660
961
 
661
- // src/streaming/tool-call-detector.ts
662
- var ToolCallFenceDetector = class {
663
- constructor() {
664
- this.FENCE_STARTS = ["```tool_call"];
665
- this.FENCE_END = "```";
666
- this.buffer = "";
667
- // Streaming state
668
- this.inFence = false;
669
- this.fenceStartBuffer = "";
670
- }
671
- // Accumulated fence content
672
- /**
673
- * Adds a chunk of text to the internal buffer
674
- *
675
- * @param chunk - Text chunk from the stream
676
- */
677
- addChunk(chunk) {
678
- this.buffer += chunk;
679
- }
680
- /**
681
- * Gets the current buffer content
682
- */
683
- getBuffer() {
684
- return this.buffer;
685
- }
686
- /**
687
- * Clears the internal buffer
688
- */
689
- clearBuffer() {
690
- this.buffer = "";
691
- }
692
- /**
693
- * Detects if there's a complete fence in the buffer
694
- *
695
- * This method:
696
- * 1. Searches for fence start markers
697
- * 2. If found, looks for closing fence
698
- * 3. Computes overlap for partial fences
699
- * 4. Returns safe text that can be emitted
700
- *
701
- * @returns Detection result with fence info and safe text
702
- */
703
- detectFence() {
704
- const { index: startIdx, prefix: matchedPrefix } = this.findFenceStart(
705
- this.buffer
706
- );
707
- if (startIdx === -1) {
708
- const overlap = this.computeOverlapLength(this.buffer, this.FENCE_STARTS);
709
- const safeTextLength = this.buffer.length - overlap;
710
- const prefixText2 = safeTextLength > 0 ? this.buffer.slice(0, safeTextLength) : "";
711
- const remaining = overlap > 0 ? this.buffer.slice(-overlap) : "";
712
- this.buffer = remaining;
713
- return {
714
- fence: null,
715
- prefixText: prefixText2,
716
- remainingText: "",
717
- overlapLength: overlap
718
- };
719
- }
720
- const prefixText = this.buffer.slice(0, startIdx);
721
- this.buffer = this.buffer.slice(startIdx);
722
- const prefixLength = matchedPrefix?.length ?? 0;
723
- const closingIdx = this.buffer.indexOf(this.FENCE_END, prefixLength);
724
- if (closingIdx === -1) {
725
- return {
726
- fence: null,
727
- prefixText,
728
- remainingText: "",
729
- overlapLength: 0
730
- };
731
- }
732
- const endPos = closingIdx + this.FENCE_END.length;
733
- const fence = this.buffer.slice(0, endPos);
734
- const remainingText = this.buffer.slice(endPos);
735
- this.buffer = "";
736
- return {
737
- fence,
738
- prefixText,
739
- remainingText,
740
- overlapLength: 0
741
- };
742
- }
743
- /**
744
- * Finds the first occurrence of any fence start marker
745
- *
746
- * @param text - Text to search in
747
- * @returns Index of first fence start and which prefix matched
748
- * @private
749
- */
750
- findFenceStart(text) {
751
- let bestIndex = -1;
752
- let matchedPrefix = null;
753
- for (const prefix of this.FENCE_STARTS) {
754
- const idx = text.indexOf(prefix);
755
- if (idx !== -1 && (bestIndex === -1 || idx < bestIndex)) {
756
- bestIndex = idx;
757
- matchedPrefix = prefix;
758
- }
759
- }
760
- return { index: bestIndex, prefix: matchedPrefix };
761
- }
762
- /**
763
- * Computes the maximum overlap between the end of text and the start of any prefix
764
- *
765
- * This is crucial for streaming: if the buffer ends with "``", we can't emit it
766
- * because the next chunk might be "`tool_call", completing a fence marker.
767
- *
768
- * @param text - Text to check for overlap
769
- * @param prefixes - List of prefixes to check against
770
- * @returns Length of the maximum overlap found
771
- *
772
- * @example
773
- * ```typescript
774
- * computeOverlapLength("hello ``", ["```tool_call"])
775
- * // Returns: 2 (because "``" matches start of "```tool_call")
776
- *
777
- * computeOverlapLength("hello `", ["```tool_call"])
778
- * // Returns: 1
779
- *
780
- * computeOverlapLength("hello world", ["```tool_call"])
781
- * // Returns: 0 (no overlap)
782
- * ```
783
- *
784
- * @private
785
- */
786
- computeOverlapLength(text, prefixes) {
787
- let overlap = 0;
788
- for (const prefix of prefixes) {
789
- const maxLength = Math.min(text.length, prefix.length - 1);
790
- for (let size = maxLength; size > 0; size -= 1) {
791
- if (prefix.startsWith(text.slice(-size))) {
792
- overlap = Math.max(overlap, size);
793
- break;
794
- }
795
- }
796
- }
797
- return overlap;
798
- }
799
- /**
800
- * Checks if the buffer currently contains any text
801
- */
802
- hasContent() {
803
- return this.buffer.length > 0;
804
- }
805
- /**
806
- * Gets the buffer size
807
- */
808
- getBufferSize() {
809
- return this.buffer.length;
810
- }
811
- /**
812
- * Detect and stream fence content in real-time for true incremental streaming
813
- *
814
- * This method is designed for streaming tool calls as they arrive:
815
- * 1. Detects when a fence starts and transitions to "inFence" state
816
- * 2. While inFence, emits safe content that won't conflict with fence end marker
817
- * 3. When fence ends, returns the complete fence for parsing
818
- *
819
- * @returns Streaming result with current state and safe content to emit
820
- */
821
- detectStreamingFence() {
822
- if (!this.inFence) {
823
- const { index: startIdx, prefix: matchedPrefix } = this.findFenceStart(
824
- this.buffer
825
- );
826
- if (startIdx === -1) {
827
- const overlap = this.computeOverlapLength(
828
- this.buffer,
829
- this.FENCE_STARTS
830
- );
831
- const safeTextLength = this.buffer.length - overlap;
832
- const safeContent = safeTextLength > 0 ? this.buffer.slice(0, safeTextLength) : "";
833
- this.buffer = this.buffer.slice(safeTextLength);
834
- return {
835
- inFence: false,
836
- safeContent,
837
- completeFence: null,
838
- textAfterFence: ""
839
- };
840
- }
841
- const prefixText = this.buffer.slice(0, startIdx);
842
- const fenceStartLength = matchedPrefix?.length ?? 0;
843
- this.buffer = this.buffer.slice(startIdx + fenceStartLength);
844
- if (this.buffer.startsWith("\n")) {
845
- this.buffer = this.buffer.slice(1);
846
- }
847
- this.inFence = true;
848
- this.fenceStartBuffer = "";
849
- return {
850
- inFence: true,
851
- safeContent: prefixText,
852
- // Emit any text before the fence
853
- completeFence: null,
854
- textAfterFence: ""
855
- };
856
- }
857
- const closingIdx = this.buffer.indexOf(this.FENCE_END);
858
- if (closingIdx === -1) {
859
- const overlap = this.computeOverlapLength(this.buffer, [this.FENCE_END]);
860
- const safeContentLength = this.buffer.length - overlap;
861
- if (safeContentLength > 0) {
862
- const safeContent = this.buffer.slice(0, safeContentLength);
863
- this.fenceStartBuffer += safeContent;
864
- this.buffer = this.buffer.slice(safeContentLength);
865
- return {
866
- inFence: true,
867
- safeContent,
868
- completeFence: null,
869
- textAfterFence: ""
870
- };
871
- }
872
- return {
873
- inFence: true,
874
- safeContent: "",
875
- completeFence: null,
876
- textAfterFence: ""
877
- };
878
- }
879
- const fenceContent = this.buffer.slice(0, closingIdx);
880
- this.fenceStartBuffer += fenceContent;
881
- const completeFence = `${this.FENCE_STARTS[0]}
882
- ${this.fenceStartBuffer}
883
- ${this.FENCE_END}`;
884
- const textAfterFence = this.buffer.slice(
885
- closingIdx + this.FENCE_END.length
886
- );
887
- this.inFence = false;
888
- this.fenceStartBuffer = "";
889
- this.buffer = textAfterFence;
890
- return {
891
- inFence: false,
892
- safeContent: fenceContent,
893
- // Emit the last bit of fence content
894
- completeFence,
895
- textAfterFence
896
- };
897
- }
898
- /**
899
- * Check if currently inside a fence
900
- */
901
- isInFence() {
902
- return this.inFence;
903
- }
904
- /**
905
- * Reset streaming state
906
- */
907
- resetStreamingState() {
908
- this.inFence = false;
909
- this.fenceStartBuffer = "";
910
- }
911
- };
912
-
913
962
  // src/browser-ai-language-model.ts
914
963
  function doesBrowserSupportBrowserAI() {
915
964
  return typeof LanguageModel !== "undefined";