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