@browser-ai/core 2.1.3 → 2.1.5

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
@@ -539,18 +539,189 @@ function extractArgumentsDelta(content, state) {
539
539
  return delta;
540
540
  }
541
541
 
542
- // src/convert-to-browser-ai-messages.ts
542
+ // ../shared/src/streaming/stream-processor.ts
543
+ function generateToolCallId2() {
544
+ return `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
545
+ }
546
+ async function processToolCallStream(chunks, emitTextDelta, controller, options) {
547
+ const fenceDetector = new ToolCallFenceDetector();
548
+ let currentToolCallId = null;
549
+ let toolInputStartEmitted = false;
550
+ let accumulatedFenceContent = "";
551
+ let argumentsStreamState = createArgumentsStreamState();
552
+ let insideFence = false;
553
+ let toolCallDetected = false;
554
+ let toolCalls = [];
555
+ let trailingText = "";
556
+ const resetFenceState = () => {
557
+ currentToolCallId = null;
558
+ toolInputStartEmitted = false;
559
+ accumulatedFenceContent = "";
560
+ argumentsStreamState = createArgumentsStreamState();
561
+ insideFence = false;
562
+ };
563
+ for await (const chunk of chunks) {
564
+ if (toolCallDetected) {
565
+ continue;
566
+ }
567
+ fenceDetector.addChunk(chunk);
568
+ while (fenceDetector.hasContent()) {
569
+ const wasInsideFence = insideFence;
570
+ const result = fenceDetector.detectStreamingFence();
571
+ insideFence = result.inFence;
572
+ let madeProgress = false;
573
+ if (!wasInsideFence && result.inFence) {
574
+ if (result.safeContent) {
575
+ emitTextDelta(result.safeContent);
576
+ madeProgress = true;
577
+ }
578
+ currentToolCallId = generateToolCallId2();
579
+ toolInputStartEmitted = false;
580
+ accumulatedFenceContent = "";
581
+ argumentsStreamState = createArgumentsStreamState();
582
+ insideFence = true;
583
+ continue;
584
+ }
585
+ if (result.completeFence) {
586
+ madeProgress = true;
587
+ if (result.safeContent) {
588
+ accumulatedFenceContent += result.safeContent;
589
+ }
590
+ if (toolInputStartEmitted && currentToolCallId) {
591
+ const delta = extractArgumentsDelta(
592
+ accumulatedFenceContent,
593
+ argumentsStreamState
594
+ );
595
+ if (delta.length > 0) {
596
+ controller.enqueue({
597
+ type: "tool-input-delta",
598
+ id: currentToolCallId,
599
+ delta
600
+ });
601
+ }
602
+ }
603
+ const parsed = parseJsonFunctionCalls(result.completeFence);
604
+ const selectedToolCalls = parsed.toolCalls.slice(0, 1);
605
+ if (selectedToolCalls.length === 0) {
606
+ emitTextDelta(result.completeFence);
607
+ if (result.textAfterFence) {
608
+ emitTextDelta(result.textAfterFence);
609
+ }
610
+ resetFenceState();
611
+ continue;
612
+ }
613
+ if (currentToolCallId) {
614
+ selectedToolCalls[0].toolCallId = currentToolCallId;
615
+ }
616
+ for (const [index, call] of selectedToolCalls.entries()) {
617
+ const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
618
+ const toolName = call.toolName;
619
+ const argsJson = JSON.stringify(call.args ?? {});
620
+ if (toolCallId === currentToolCallId) {
621
+ if (!toolInputStartEmitted) {
622
+ controller.enqueue({
623
+ type: "tool-input-start",
624
+ id: toolCallId,
625
+ toolName
626
+ });
627
+ toolInputStartEmitted = true;
628
+ }
629
+ const delta = extractArgumentsDelta(
630
+ accumulatedFenceContent,
631
+ argumentsStreamState
632
+ );
633
+ if (delta.length > 0) {
634
+ controller.enqueue({
635
+ type: "tool-input-delta",
636
+ id: toolCallId,
637
+ delta
638
+ });
639
+ }
640
+ } else {
641
+ controller.enqueue({
642
+ type: "tool-input-start",
643
+ id: toolCallId,
644
+ toolName
645
+ });
646
+ if (argsJson.length > 0) {
647
+ controller.enqueue({
648
+ type: "tool-input-delta",
649
+ id: toolCallId,
650
+ delta: argsJson
651
+ });
652
+ }
653
+ }
654
+ controller.enqueue({ type: "tool-input-end", id: toolCallId });
655
+ controller.enqueue({
656
+ type: "tool-call",
657
+ toolCallId,
658
+ toolName,
659
+ input: argsJson,
660
+ providerExecuted: false
661
+ });
662
+ }
663
+ trailingText = result.textAfterFence ?? "";
664
+ toolCalls = selectedToolCalls;
665
+ toolCallDetected = true;
666
+ resetFenceState();
667
+ break;
668
+ }
669
+ if (insideFence) {
670
+ if (result.safeContent) {
671
+ accumulatedFenceContent += result.safeContent;
672
+ madeProgress = true;
673
+ const toolName = extractToolName(accumulatedFenceContent);
674
+ if (toolName && !toolInputStartEmitted && currentToolCallId) {
675
+ controller.enqueue({
676
+ type: "tool-input-start",
677
+ id: currentToolCallId,
678
+ toolName
679
+ });
680
+ toolInputStartEmitted = true;
681
+ }
682
+ if (toolInputStartEmitted && currentToolCallId) {
683
+ const delta = extractArgumentsDelta(
684
+ accumulatedFenceContent,
685
+ argumentsStreamState
686
+ );
687
+ if (delta.length > 0) {
688
+ controller.enqueue({
689
+ type: "tool-input-delta",
690
+ id: currentToolCallId,
691
+ delta
692
+ });
693
+ }
694
+ }
695
+ }
696
+ continue;
697
+ }
698
+ if (!insideFence && result.safeContent) {
699
+ emitTextDelta(result.safeContent);
700
+ madeProgress = true;
701
+ }
702
+ if (!madeProgress) {
703
+ break;
704
+ }
705
+ }
706
+ if (toolCallDetected && options?.stopEarlyOnToolCall) {
707
+ break;
708
+ }
709
+ }
710
+ if (!toolCallDetected && fenceDetector.hasContent()) {
711
+ emitTextDelta(fenceDetector.getBuffer());
712
+ fenceDetector.clearBuffer();
713
+ }
714
+ return { toolCallDetected, toolCalls, trailingText };
715
+ }
716
+
717
+ // src/utils/convert-to-browser-ai-messages.ts
543
718
  import {
544
719
  UnsupportedFunctionalityError
545
720
  } from "@ai-sdk/provider";
546
721
  function convertBase64ToUint8Array(base64) {
547
722
  try {
548
723
  const binaryString = atob(base64);
549
- const bytes = new Uint8Array(binaryString.length);
550
- for (let i = 0; i < binaryString.length; i++) {
551
- bytes[i] = binaryString.charCodeAt(i);
552
- }
553
- return bytes;
724
+ return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
554
725
  } catch (error) {
555
726
  throw new Error(`Failed to convert base64 to Uint8Array: ${error}`);
556
727
  }
@@ -816,19 +987,7 @@ function gatherUnsupportedSettingWarnings(options) {
816
987
  }
817
988
 
818
989
  // src/utils/prompt-utils.ts
819
- function hasMultimodalContent(prompt) {
820
- for (const message of prompt) {
821
- if (message.role === "user") {
822
- for (const part of message.content) {
823
- if (part.type === "file") {
824
- return true;
825
- }
826
- }
827
- }
828
- }
829
- return false;
830
- }
831
- function getExpectedInputs(prompt) {
990
+ function getMultimodalInfo(prompt) {
832
991
  const inputs = /* @__PURE__ */ new Set();
833
992
  for (const message of prompt) {
834
993
  if (message.role === "user") {
@@ -843,10 +1002,14 @@ function getExpectedInputs(prompt) {
843
1002
  }
844
1003
  }
845
1004
  }
846
- return Array.from(inputs).map((type) => ({ type }));
1005
+ const hasMultiModalInput = inputs.size > 0;
1006
+ return {
1007
+ hasMultiModalInput,
1008
+ expectedInputs: hasMultiModalInput ? Array.from(inputs, (type) => ({ type })) : void 0
1009
+ };
847
1010
  }
848
1011
 
849
- // src/models/session-manager.ts
1012
+ // src/chat/session-manager.ts
850
1013
  import { LoadSettingError } from "@ai-sdk/provider";
851
1014
  var SessionManager = class {
852
1015
  /**
@@ -1017,20 +1180,11 @@ var SessionManager = class {
1017
1180
  };
1018
1181
  }
1019
1182
  }
1020
- this.sanitizeOptions(mergedOptions);
1021
1183
  return mergedOptions;
1022
1184
  }
1023
- /**
1024
- * Removes custom options that aren't part of LanguageModel.create API
1025
- *
1026
- * @param options - Options object to sanitize in-place
1027
- * @private
1028
- */
1029
- sanitizeOptions(options) {
1030
- }
1031
1185
  };
1032
1186
 
1033
- // src/browser-ai-language-model.ts
1187
+ // src/chat/browser-ai-language-model.ts
1034
1188
  function doesBrowserSupportBrowserAI() {
1035
1189
  return typeof LanguageModel !== "undefined";
1036
1190
  }
@@ -1103,7 +1257,7 @@ var BrowserAIChatLanguageModel = class {
1103
1257
  )
1104
1258
  );
1105
1259
  }
1106
- const hasMultiModalInput = hasMultimodalContent(prompt);
1260
+ const { hasMultiModalInput, expectedInputs } = getMultimodalInfo(prompt);
1107
1261
  const { systemMessage, messages } = convertToBrowserAIMessages(prompt);
1108
1262
  const promptOptions = {};
1109
1263
  if (responseFormat?.type === "json") {
@@ -1121,7 +1275,7 @@ var BrowserAIChatLanguageModel = class {
1121
1275
  warnings,
1122
1276
  promptOptions,
1123
1277
  hasMultiModalInput,
1124
- expectedInputs: hasMultiModalInput ? getExpectedInputs(prompt) : void 0,
1278
+ expectedInputs,
1125
1279
  functionTools
1126
1280
  };
1127
1281
  }
@@ -1367,213 +1521,40 @@ var BrowserAIChatLanguageModel = class {
1367
1521
  if (options.abortSignal) {
1368
1522
  options.abortSignal.addEventListener("abort", abortHandler);
1369
1523
  }
1370
- const maxIterations = 10;
1371
- let iteration = 0;
1372
1524
  try {
1373
- const fenceDetector = new ToolCallFenceDetector();
1374
- while (iteration < maxIterations && !aborted && !finished) {
1375
- iteration += 1;
1376
- const promptStream = session.promptStreaming(
1377
- conversationHistory,
1378
- streamOptions
1379
- );
1380
- currentReader = promptStream.getReader();
1381
- let toolCalls = [];
1382
- let toolBlockDetected = false;
1383
- let trailingTextAfterBlock = "";
1384
- let currentToolCallId = null;
1385
- let toolInputStartEmitted = false;
1386
- let accumulatedFenceContent = "";
1387
- let argumentsStreamState = createArgumentsStreamState();
1388
- let insideFence = false;
1525
+ const promptStream = session.promptStreaming(
1526
+ conversationHistory,
1527
+ streamOptions
1528
+ );
1529
+ currentReader = promptStream.getReader();
1530
+ const chunks = (async function* () {
1389
1531
  while (!aborted) {
1390
1532
  const { done, value } = await currentReader.read();
1391
- if (done) {
1392
- break;
1393
- }
1394
- fenceDetector.addChunk(value);
1395
- while (fenceDetector.hasContent()) {
1396
- const wasInsideFence = insideFence;
1397
- const result = fenceDetector.detectStreamingFence();
1398
- insideFence = result.inFence;
1399
- let madeProgress = false;
1400
- if (!wasInsideFence && result.inFence) {
1401
- if (result.safeContent) {
1402
- emitTextDelta(result.safeContent);
1403
- madeProgress = true;
1404
- }
1405
- currentToolCallId = `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
1406
- toolInputStartEmitted = false;
1407
- accumulatedFenceContent = "";
1408
- argumentsStreamState = createArgumentsStreamState();
1409
- insideFence = true;
1410
- continue;
1411
- }
1412
- if (result.completeFence) {
1413
- madeProgress = true;
1414
- if (result.safeContent) {
1415
- accumulatedFenceContent += result.safeContent;
1416
- }
1417
- if (toolInputStartEmitted && currentToolCallId) {
1418
- const delta = extractArgumentsDelta(
1419
- accumulatedFenceContent,
1420
- argumentsStreamState
1421
- );
1422
- if (delta.length > 0) {
1423
- controller.enqueue({
1424
- type: "tool-input-delta",
1425
- id: currentToolCallId,
1426
- delta
1427
- });
1428
- }
1429
- }
1430
- const parsed = parseJsonFunctionCalls(result.completeFence);
1431
- const parsedToolCalls = parsed.toolCalls;
1432
- const selectedToolCalls = parsedToolCalls.slice(0, 1);
1433
- if (selectedToolCalls.length === 0) {
1434
- toolCalls = [];
1435
- toolBlockDetected = false;
1436
- emitTextDelta(result.completeFence);
1437
- if (result.textAfterFence) {
1438
- emitTextDelta(result.textAfterFence);
1439
- }
1440
- currentToolCallId = null;
1441
- toolInputStartEmitted = false;
1442
- accumulatedFenceContent = "";
1443
- argumentsStreamState = createArgumentsStreamState();
1444
- insideFence = false;
1445
- continue;
1446
- }
1447
- if (selectedToolCalls.length > 0 && currentToolCallId) {
1448
- selectedToolCalls[0].toolCallId = currentToolCallId;
1449
- }
1450
- toolCalls = selectedToolCalls;
1451
- toolBlockDetected = toolCalls.length > 0;
1452
- for (const [index, call] of toolCalls.entries()) {
1453
- const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
1454
- const toolName = call.toolName;
1455
- const argsJson = JSON.stringify(call.args ?? {});
1456
- if (toolCallId === currentToolCallId) {
1457
- if (!toolInputStartEmitted) {
1458
- controller.enqueue({
1459
- type: "tool-input-start",
1460
- id: toolCallId,
1461
- toolName
1462
- });
1463
- toolInputStartEmitted = true;
1464
- }
1465
- const delta = extractArgumentsDelta(
1466
- accumulatedFenceContent,
1467
- argumentsStreamState
1468
- );
1469
- if (delta.length > 0) {
1470
- controller.enqueue({
1471
- type: "tool-input-delta",
1472
- id: toolCallId,
1473
- delta
1474
- });
1475
- }
1476
- } else {
1477
- controller.enqueue({
1478
- type: "tool-input-start",
1479
- id: toolCallId,
1480
- toolName
1481
- });
1482
- if (argsJson.length > 0) {
1483
- controller.enqueue({
1484
- type: "tool-input-delta",
1485
- id: toolCallId,
1486
- delta: argsJson
1487
- });
1488
- }
1489
- }
1490
- controller.enqueue({
1491
- type: "tool-input-end",
1492
- id: toolCallId
1493
- });
1494
- controller.enqueue({
1495
- type: "tool-call",
1496
- toolCallId,
1497
- toolName,
1498
- input: argsJson,
1499
- providerExecuted: false
1500
- });
1501
- }
1502
- trailingTextAfterBlock += result.textAfterFence;
1503
- madeProgress = true;
1504
- if (toolBlockDetected && currentReader) {
1505
- await currentReader.cancel().catch(() => void 0);
1506
- break;
1507
- }
1508
- currentToolCallId = null;
1509
- toolInputStartEmitted = false;
1510
- accumulatedFenceContent = "";
1511
- argumentsStreamState = createArgumentsStreamState();
1512
- insideFence = false;
1513
- continue;
1514
- }
1515
- if (insideFence) {
1516
- if (result.safeContent) {
1517
- accumulatedFenceContent += result.safeContent;
1518
- madeProgress = true;
1519
- const toolName = extractToolName(accumulatedFenceContent);
1520
- if (toolName && !toolInputStartEmitted && currentToolCallId) {
1521
- controller.enqueue({
1522
- type: "tool-input-start",
1523
- id: currentToolCallId,
1524
- toolName
1525
- });
1526
- toolInputStartEmitted = true;
1527
- }
1528
- if (toolInputStartEmitted && currentToolCallId) {
1529
- const delta = extractArgumentsDelta(
1530
- accumulatedFenceContent,
1531
- argumentsStreamState
1532
- );
1533
- if (delta.length > 0) {
1534
- controller.enqueue({
1535
- type: "tool-input-delta",
1536
- id: currentToolCallId,
1537
- delta
1538
- });
1539
- }
1540
- }
1541
- }
1542
- continue;
1543
- }
1544
- if (!insideFence && result.safeContent) {
1545
- emitTextDelta(result.safeContent);
1546
- madeProgress = true;
1547
- }
1548
- if (!madeProgress) {
1549
- break;
1550
- }
1551
- }
1552
- if (toolBlockDetected) {
1553
- break;
1554
- }
1555
- }
1556
- currentReader = null;
1557
- if (aborted) {
1558
- return;
1559
- }
1560
- if (!toolBlockDetected && fenceDetector.hasContent()) {
1561
- emitTextDelta(fenceDetector.getBuffer());
1562
- fenceDetector.clearBuffer();
1563
- }
1564
- if (!toolBlockDetected || toolCalls.length === 0) {
1565
- finishStream({ unified: "stop", raw: "stop" });
1566
- return;
1533
+ if (done) break;
1534
+ yield value;
1567
1535
  }
1568
- if (trailingTextAfterBlock) {
1569
- emitTextDelta(trailingTextAfterBlock);
1570
- }
1571
- finishStream({ unified: "tool-calls", raw: "tool-calls" });
1536
+ })();
1537
+ const result = await processToolCallStream(
1538
+ chunks,
1539
+ emitTextDelta,
1540
+ controller,
1541
+ { stopEarlyOnToolCall: true }
1542
+ );
1543
+ if (result.toolCallDetected && currentReader) {
1544
+ await currentReader.cancel().catch(() => void 0);
1545
+ }
1546
+ currentReader = null;
1547
+ if (aborted) {
1548
+ return;
1549
+ }
1550
+ if (!result.toolCallDetected || result.toolCalls.length === 0) {
1551
+ finishStream({ unified: "stop", raw: "stop" });
1572
1552
  return;
1573
1553
  }
1574
- if (!finished && !aborted) {
1575
- finishStream({ unified: "other", raw: "other" });
1554
+ if (result.trailingText) {
1555
+ emitTextDelta(result.trailingText);
1576
1556
  }
1557
+ finishStream({ unified: "tool-calls", raw: "tool-calls" });
1577
1558
  } catch (error) {
1578
1559
  controller.enqueue({ type: "error", error });
1579
1560
  controller.close();
@@ -1591,7 +1572,7 @@ var BrowserAIChatLanguageModel = class {
1591
1572
  }
1592
1573
  };
1593
1574
 
1594
- // src/browser-ai-embedding-model.ts
1575
+ // src/embedding/browser-ai-embedding-model.ts
1595
1576
  import { TextEmbedder } from "@mediapipe/tasks-text";
1596
1577
  var BrowserAIEmbeddingModel = class {
1597
1578
  constructor(settings = {}) {