@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.js CHANGED
@@ -569,16 +569,187 @@ function extractArgumentsDelta(content, state) {
569
569
  return delta;
570
570
  }
571
571
 
572
- // src/convert-to-browser-ai-messages.ts
572
+ // ../shared/src/streaming/stream-processor.ts
573
+ function generateToolCallId2() {
574
+ return `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
575
+ }
576
+ async function processToolCallStream(chunks, emitTextDelta, controller, options) {
577
+ const fenceDetector = new ToolCallFenceDetector();
578
+ let currentToolCallId = null;
579
+ let toolInputStartEmitted = false;
580
+ let accumulatedFenceContent = "";
581
+ let argumentsStreamState = createArgumentsStreamState();
582
+ let insideFence = false;
583
+ let toolCallDetected = false;
584
+ let toolCalls = [];
585
+ let trailingText = "";
586
+ const resetFenceState = () => {
587
+ currentToolCallId = null;
588
+ toolInputStartEmitted = false;
589
+ accumulatedFenceContent = "";
590
+ argumentsStreamState = createArgumentsStreamState();
591
+ insideFence = false;
592
+ };
593
+ for await (const chunk of chunks) {
594
+ if (toolCallDetected) {
595
+ continue;
596
+ }
597
+ fenceDetector.addChunk(chunk);
598
+ while (fenceDetector.hasContent()) {
599
+ const wasInsideFence = insideFence;
600
+ const result = fenceDetector.detectStreamingFence();
601
+ insideFence = result.inFence;
602
+ let madeProgress = false;
603
+ if (!wasInsideFence && result.inFence) {
604
+ if (result.safeContent) {
605
+ emitTextDelta(result.safeContent);
606
+ madeProgress = true;
607
+ }
608
+ currentToolCallId = generateToolCallId2();
609
+ toolInputStartEmitted = false;
610
+ accumulatedFenceContent = "";
611
+ argumentsStreamState = createArgumentsStreamState();
612
+ insideFence = true;
613
+ continue;
614
+ }
615
+ if (result.completeFence) {
616
+ madeProgress = true;
617
+ if (result.safeContent) {
618
+ accumulatedFenceContent += result.safeContent;
619
+ }
620
+ if (toolInputStartEmitted && currentToolCallId) {
621
+ const delta = extractArgumentsDelta(
622
+ accumulatedFenceContent,
623
+ argumentsStreamState
624
+ );
625
+ if (delta.length > 0) {
626
+ controller.enqueue({
627
+ type: "tool-input-delta",
628
+ id: currentToolCallId,
629
+ delta
630
+ });
631
+ }
632
+ }
633
+ const parsed = parseJsonFunctionCalls(result.completeFence);
634
+ const selectedToolCalls = parsed.toolCalls.slice(0, 1);
635
+ if (selectedToolCalls.length === 0) {
636
+ emitTextDelta(result.completeFence);
637
+ if (result.textAfterFence) {
638
+ emitTextDelta(result.textAfterFence);
639
+ }
640
+ resetFenceState();
641
+ continue;
642
+ }
643
+ if (currentToolCallId) {
644
+ selectedToolCalls[0].toolCallId = currentToolCallId;
645
+ }
646
+ for (const [index, call] of selectedToolCalls.entries()) {
647
+ const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
648
+ const toolName = call.toolName;
649
+ const argsJson = JSON.stringify(call.args ?? {});
650
+ if (toolCallId === currentToolCallId) {
651
+ if (!toolInputStartEmitted) {
652
+ controller.enqueue({
653
+ type: "tool-input-start",
654
+ id: toolCallId,
655
+ toolName
656
+ });
657
+ toolInputStartEmitted = true;
658
+ }
659
+ const delta = extractArgumentsDelta(
660
+ accumulatedFenceContent,
661
+ argumentsStreamState
662
+ );
663
+ if (delta.length > 0) {
664
+ controller.enqueue({
665
+ type: "tool-input-delta",
666
+ id: toolCallId,
667
+ delta
668
+ });
669
+ }
670
+ } else {
671
+ controller.enqueue({
672
+ type: "tool-input-start",
673
+ id: toolCallId,
674
+ toolName
675
+ });
676
+ if (argsJson.length > 0) {
677
+ controller.enqueue({
678
+ type: "tool-input-delta",
679
+ id: toolCallId,
680
+ delta: argsJson
681
+ });
682
+ }
683
+ }
684
+ controller.enqueue({ type: "tool-input-end", id: toolCallId });
685
+ controller.enqueue({
686
+ type: "tool-call",
687
+ toolCallId,
688
+ toolName,
689
+ input: argsJson,
690
+ providerExecuted: false
691
+ });
692
+ }
693
+ trailingText = result.textAfterFence ?? "";
694
+ toolCalls = selectedToolCalls;
695
+ toolCallDetected = true;
696
+ resetFenceState();
697
+ break;
698
+ }
699
+ if (insideFence) {
700
+ if (result.safeContent) {
701
+ accumulatedFenceContent += result.safeContent;
702
+ madeProgress = true;
703
+ const toolName = extractToolName(accumulatedFenceContent);
704
+ if (toolName && !toolInputStartEmitted && currentToolCallId) {
705
+ controller.enqueue({
706
+ type: "tool-input-start",
707
+ id: currentToolCallId,
708
+ toolName
709
+ });
710
+ toolInputStartEmitted = true;
711
+ }
712
+ if (toolInputStartEmitted && currentToolCallId) {
713
+ const delta = extractArgumentsDelta(
714
+ accumulatedFenceContent,
715
+ argumentsStreamState
716
+ );
717
+ if (delta.length > 0) {
718
+ controller.enqueue({
719
+ type: "tool-input-delta",
720
+ id: currentToolCallId,
721
+ delta
722
+ });
723
+ }
724
+ }
725
+ }
726
+ continue;
727
+ }
728
+ if (!insideFence && result.safeContent) {
729
+ emitTextDelta(result.safeContent);
730
+ madeProgress = true;
731
+ }
732
+ if (!madeProgress) {
733
+ break;
734
+ }
735
+ }
736
+ if (toolCallDetected && options?.stopEarlyOnToolCall) {
737
+ break;
738
+ }
739
+ }
740
+ if (!toolCallDetected && fenceDetector.hasContent()) {
741
+ emitTextDelta(fenceDetector.getBuffer());
742
+ fenceDetector.clearBuffer();
743
+ }
744
+ return { toolCallDetected, toolCalls, trailingText };
745
+ }
746
+
747
+ // src/utils/convert-to-browser-ai-messages.ts
573
748
  var import_provider = require("@ai-sdk/provider");
574
749
  function convertBase64ToUint8Array(base64) {
575
750
  try {
576
751
  const binaryString = atob(base64);
577
- const bytes = new Uint8Array(binaryString.length);
578
- for (let i = 0; i < binaryString.length; i++) {
579
- bytes[i] = binaryString.charCodeAt(i);
580
- }
581
- return bytes;
752
+ return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
582
753
  } catch (error) {
583
754
  throw new Error(`Failed to convert base64 to Uint8Array: ${error}`);
584
755
  }
@@ -844,19 +1015,7 @@ function gatherUnsupportedSettingWarnings(options) {
844
1015
  }
845
1016
 
846
1017
  // src/utils/prompt-utils.ts
847
- function hasMultimodalContent(prompt) {
848
- for (const message of prompt) {
849
- if (message.role === "user") {
850
- for (const part of message.content) {
851
- if (part.type === "file") {
852
- return true;
853
- }
854
- }
855
- }
856
- }
857
- return false;
858
- }
859
- function getExpectedInputs(prompt) {
1018
+ function getMultimodalInfo(prompt) {
860
1019
  const inputs = /* @__PURE__ */ new Set();
861
1020
  for (const message of prompt) {
862
1021
  if (message.role === "user") {
@@ -871,10 +1030,14 @@ function getExpectedInputs(prompt) {
871
1030
  }
872
1031
  }
873
1032
  }
874
- return Array.from(inputs).map((type) => ({ type }));
1033
+ const hasMultiModalInput = inputs.size > 0;
1034
+ return {
1035
+ hasMultiModalInput,
1036
+ expectedInputs: hasMultiModalInput ? Array.from(inputs, (type) => ({ type })) : void 0
1037
+ };
875
1038
  }
876
1039
 
877
- // src/models/session-manager.ts
1040
+ // src/chat/session-manager.ts
878
1041
  var import_provider2 = require("@ai-sdk/provider");
879
1042
  var SessionManager = class {
880
1043
  /**
@@ -1045,20 +1208,11 @@ var SessionManager = class {
1045
1208
  };
1046
1209
  }
1047
1210
  }
1048
- this.sanitizeOptions(mergedOptions);
1049
1211
  return mergedOptions;
1050
1212
  }
1051
- /**
1052
- * Removes custom options that aren't part of LanguageModel.create API
1053
- *
1054
- * @param options - Options object to sanitize in-place
1055
- * @private
1056
- */
1057
- sanitizeOptions(options) {
1058
- }
1059
1213
  };
1060
1214
 
1061
- // src/browser-ai-language-model.ts
1215
+ // src/chat/browser-ai-language-model.ts
1062
1216
  function doesBrowserSupportBrowserAI() {
1063
1217
  return typeof LanguageModel !== "undefined";
1064
1218
  }
@@ -1131,7 +1285,7 @@ var BrowserAIChatLanguageModel = class {
1131
1285
  )
1132
1286
  );
1133
1287
  }
1134
- const hasMultiModalInput = hasMultimodalContent(prompt);
1288
+ const { hasMultiModalInput, expectedInputs } = getMultimodalInfo(prompt);
1135
1289
  const { systemMessage, messages } = convertToBrowserAIMessages(prompt);
1136
1290
  const promptOptions = {};
1137
1291
  if (responseFormat?.type === "json") {
@@ -1149,7 +1303,7 @@ var BrowserAIChatLanguageModel = class {
1149
1303
  warnings,
1150
1304
  promptOptions,
1151
1305
  hasMultiModalInput,
1152
- expectedInputs: hasMultiModalInput ? getExpectedInputs(prompt) : void 0,
1306
+ expectedInputs,
1153
1307
  functionTools
1154
1308
  };
1155
1309
  }
@@ -1395,213 +1549,40 @@ var BrowserAIChatLanguageModel = class {
1395
1549
  if (options.abortSignal) {
1396
1550
  options.abortSignal.addEventListener("abort", abortHandler);
1397
1551
  }
1398
- const maxIterations = 10;
1399
- let iteration = 0;
1400
1552
  try {
1401
- const fenceDetector = new ToolCallFenceDetector();
1402
- while (iteration < maxIterations && !aborted && !finished) {
1403
- iteration += 1;
1404
- const promptStream = session.promptStreaming(
1405
- conversationHistory,
1406
- streamOptions
1407
- );
1408
- currentReader = promptStream.getReader();
1409
- let toolCalls = [];
1410
- let toolBlockDetected = false;
1411
- let trailingTextAfterBlock = "";
1412
- let currentToolCallId = null;
1413
- let toolInputStartEmitted = false;
1414
- let accumulatedFenceContent = "";
1415
- let argumentsStreamState = createArgumentsStreamState();
1416
- let insideFence = false;
1553
+ const promptStream = session.promptStreaming(
1554
+ conversationHistory,
1555
+ streamOptions
1556
+ );
1557
+ currentReader = promptStream.getReader();
1558
+ const chunks = (async function* () {
1417
1559
  while (!aborted) {
1418
1560
  const { done, value } = await currentReader.read();
1419
- if (done) {
1420
- break;
1421
- }
1422
- fenceDetector.addChunk(value);
1423
- while (fenceDetector.hasContent()) {
1424
- const wasInsideFence = insideFence;
1425
- const result = fenceDetector.detectStreamingFence();
1426
- insideFence = result.inFence;
1427
- let madeProgress = false;
1428
- if (!wasInsideFence && result.inFence) {
1429
- if (result.safeContent) {
1430
- emitTextDelta(result.safeContent);
1431
- madeProgress = true;
1432
- }
1433
- currentToolCallId = `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
1434
- toolInputStartEmitted = false;
1435
- accumulatedFenceContent = "";
1436
- argumentsStreamState = createArgumentsStreamState();
1437
- insideFence = true;
1438
- continue;
1439
- }
1440
- if (result.completeFence) {
1441
- madeProgress = true;
1442
- if (result.safeContent) {
1443
- accumulatedFenceContent += result.safeContent;
1444
- }
1445
- if (toolInputStartEmitted && currentToolCallId) {
1446
- const delta = extractArgumentsDelta(
1447
- accumulatedFenceContent,
1448
- argumentsStreamState
1449
- );
1450
- if (delta.length > 0) {
1451
- controller.enqueue({
1452
- type: "tool-input-delta",
1453
- id: currentToolCallId,
1454
- delta
1455
- });
1456
- }
1457
- }
1458
- const parsed = parseJsonFunctionCalls(result.completeFence);
1459
- const parsedToolCalls = parsed.toolCalls;
1460
- const selectedToolCalls = parsedToolCalls.slice(0, 1);
1461
- if (selectedToolCalls.length === 0) {
1462
- toolCalls = [];
1463
- toolBlockDetected = false;
1464
- emitTextDelta(result.completeFence);
1465
- if (result.textAfterFence) {
1466
- emitTextDelta(result.textAfterFence);
1467
- }
1468
- currentToolCallId = null;
1469
- toolInputStartEmitted = false;
1470
- accumulatedFenceContent = "";
1471
- argumentsStreamState = createArgumentsStreamState();
1472
- insideFence = false;
1473
- continue;
1474
- }
1475
- if (selectedToolCalls.length > 0 && currentToolCallId) {
1476
- selectedToolCalls[0].toolCallId = currentToolCallId;
1477
- }
1478
- toolCalls = selectedToolCalls;
1479
- toolBlockDetected = toolCalls.length > 0;
1480
- for (const [index, call] of toolCalls.entries()) {
1481
- const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
1482
- const toolName = call.toolName;
1483
- const argsJson = JSON.stringify(call.args ?? {});
1484
- if (toolCallId === currentToolCallId) {
1485
- if (!toolInputStartEmitted) {
1486
- controller.enqueue({
1487
- type: "tool-input-start",
1488
- id: toolCallId,
1489
- toolName
1490
- });
1491
- toolInputStartEmitted = true;
1492
- }
1493
- const delta = extractArgumentsDelta(
1494
- accumulatedFenceContent,
1495
- argumentsStreamState
1496
- );
1497
- if (delta.length > 0) {
1498
- controller.enqueue({
1499
- type: "tool-input-delta",
1500
- id: toolCallId,
1501
- delta
1502
- });
1503
- }
1504
- } else {
1505
- controller.enqueue({
1506
- type: "tool-input-start",
1507
- id: toolCallId,
1508
- toolName
1509
- });
1510
- if (argsJson.length > 0) {
1511
- controller.enqueue({
1512
- type: "tool-input-delta",
1513
- id: toolCallId,
1514
- delta: argsJson
1515
- });
1516
- }
1517
- }
1518
- controller.enqueue({
1519
- type: "tool-input-end",
1520
- id: toolCallId
1521
- });
1522
- controller.enqueue({
1523
- type: "tool-call",
1524
- toolCallId,
1525
- toolName,
1526
- input: argsJson,
1527
- providerExecuted: false
1528
- });
1529
- }
1530
- trailingTextAfterBlock += result.textAfterFence;
1531
- madeProgress = true;
1532
- if (toolBlockDetected && currentReader) {
1533
- await currentReader.cancel().catch(() => void 0);
1534
- break;
1535
- }
1536
- currentToolCallId = null;
1537
- toolInputStartEmitted = false;
1538
- accumulatedFenceContent = "";
1539
- argumentsStreamState = createArgumentsStreamState();
1540
- insideFence = false;
1541
- continue;
1542
- }
1543
- if (insideFence) {
1544
- if (result.safeContent) {
1545
- accumulatedFenceContent += result.safeContent;
1546
- madeProgress = true;
1547
- const toolName = extractToolName(accumulatedFenceContent);
1548
- if (toolName && !toolInputStartEmitted && currentToolCallId) {
1549
- controller.enqueue({
1550
- type: "tool-input-start",
1551
- id: currentToolCallId,
1552
- toolName
1553
- });
1554
- toolInputStartEmitted = true;
1555
- }
1556
- if (toolInputStartEmitted && currentToolCallId) {
1557
- const delta = extractArgumentsDelta(
1558
- accumulatedFenceContent,
1559
- argumentsStreamState
1560
- );
1561
- if (delta.length > 0) {
1562
- controller.enqueue({
1563
- type: "tool-input-delta",
1564
- id: currentToolCallId,
1565
- delta
1566
- });
1567
- }
1568
- }
1569
- }
1570
- continue;
1571
- }
1572
- if (!insideFence && result.safeContent) {
1573
- emitTextDelta(result.safeContent);
1574
- madeProgress = true;
1575
- }
1576
- if (!madeProgress) {
1577
- break;
1578
- }
1579
- }
1580
- if (toolBlockDetected) {
1581
- break;
1582
- }
1583
- }
1584
- currentReader = null;
1585
- if (aborted) {
1586
- return;
1587
- }
1588
- if (!toolBlockDetected && fenceDetector.hasContent()) {
1589
- emitTextDelta(fenceDetector.getBuffer());
1590
- fenceDetector.clearBuffer();
1591
- }
1592
- if (!toolBlockDetected || toolCalls.length === 0) {
1593
- finishStream({ unified: "stop", raw: "stop" });
1594
- return;
1561
+ if (done) break;
1562
+ yield value;
1595
1563
  }
1596
- if (trailingTextAfterBlock) {
1597
- emitTextDelta(trailingTextAfterBlock);
1598
- }
1599
- finishStream({ unified: "tool-calls", raw: "tool-calls" });
1564
+ })();
1565
+ const result = await processToolCallStream(
1566
+ chunks,
1567
+ emitTextDelta,
1568
+ controller,
1569
+ { stopEarlyOnToolCall: true }
1570
+ );
1571
+ if (result.toolCallDetected && currentReader) {
1572
+ await currentReader.cancel().catch(() => void 0);
1573
+ }
1574
+ currentReader = null;
1575
+ if (aborted) {
1576
+ return;
1577
+ }
1578
+ if (!result.toolCallDetected || result.toolCalls.length === 0) {
1579
+ finishStream({ unified: "stop", raw: "stop" });
1600
1580
  return;
1601
1581
  }
1602
- if (!finished && !aborted) {
1603
- finishStream({ unified: "other", raw: "other" });
1582
+ if (result.trailingText) {
1583
+ emitTextDelta(result.trailingText);
1604
1584
  }
1585
+ finishStream({ unified: "tool-calls", raw: "tool-calls" });
1605
1586
  } catch (error) {
1606
1587
  controller.enqueue({ type: "error", error });
1607
1588
  controller.close();
@@ -1619,7 +1600,7 @@ var BrowserAIChatLanguageModel = class {
1619
1600
  }
1620
1601
  };
1621
1602
 
1622
- // src/browser-ai-embedding-model.ts
1603
+ // src/embedding/browser-ai-embedding-model.ts
1623
1604
  var import_tasks_text = require("@mediapipe/tasks-text");
1624
1605
  var BrowserAIEmbeddingModel = class {
1625
1606
  constructor(settings = {}) {