@browser-ai/core 2.1.3 → 2.1.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
@@ -569,7 +569,182 @@ 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 {
@@ -874,7 +1049,7 @@ function getExpectedInputs(prompt) {
874
1049
  return Array.from(inputs).map((type) => ({ type }));
875
1050
  }
876
1051
 
877
- // src/models/session-manager.ts
1052
+ // src/chat/session-manager.ts
878
1053
  var import_provider2 = require("@ai-sdk/provider");
879
1054
  var SessionManager = class {
880
1055
  /**
@@ -1058,7 +1233,7 @@ var SessionManager = class {
1058
1233
  }
1059
1234
  };
1060
1235
 
1061
- // src/browser-ai-language-model.ts
1236
+ // src/chat/browser-ai-language-model.ts
1062
1237
  function doesBrowserSupportBrowserAI() {
1063
1238
  return typeof LanguageModel !== "undefined";
1064
1239
  }
@@ -1395,213 +1570,40 @@ var BrowserAIChatLanguageModel = class {
1395
1570
  if (options.abortSignal) {
1396
1571
  options.abortSignal.addEventListener("abort", abortHandler);
1397
1572
  }
1398
- const maxIterations = 10;
1399
- let iteration = 0;
1400
1573
  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;
1574
+ const promptStream = session.promptStreaming(
1575
+ conversationHistory,
1576
+ streamOptions
1577
+ );
1578
+ currentReader = promptStream.getReader();
1579
+ const chunks = (async function* () {
1417
1580
  while (!aborted) {
1418
1581
  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
- }
1582
+ if (done) break;
1583
+ yield value;
1583
1584
  }
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;
1595
- }
1596
- if (trailingTextAfterBlock) {
1597
- emitTextDelta(trailingTextAfterBlock);
1598
- }
1599
- finishStream({ unified: "tool-calls", raw: "tool-calls" });
1585
+ })();
1586
+ const result = await processToolCallStream(
1587
+ chunks,
1588
+ emitTextDelta,
1589
+ controller,
1590
+ { stopEarlyOnToolCall: true }
1591
+ );
1592
+ if (result.toolCallDetected && currentReader) {
1593
+ await currentReader.cancel().catch(() => void 0);
1594
+ }
1595
+ currentReader = null;
1596
+ if (aborted) {
1597
+ return;
1598
+ }
1599
+ if (!result.toolCallDetected || result.toolCalls.length === 0) {
1600
+ finishStream({ unified: "stop", raw: "stop" });
1600
1601
  return;
1601
1602
  }
1602
- if (!finished && !aborted) {
1603
- finishStream({ unified: "other", raw: "other" });
1603
+ if (result.trailingText) {
1604
+ emitTextDelta(result.trailingText);
1604
1605
  }
1606
+ finishStream({ unified: "tool-calls", raw: "tool-calls" });
1605
1607
  } catch (error) {
1606
1608
  controller.enqueue({ type: "error", error });
1607
1609
  controller.close();
@@ -1619,7 +1621,7 @@ var BrowserAIChatLanguageModel = class {
1619
1621
  }
1620
1622
  };
1621
1623
 
1622
- // src/browser-ai-embedding-model.ts
1624
+ // src/embedding/browser-ai-embedding-model.ts
1623
1625
  var import_tasks_text = require("@mediapipe/tasks-text");
1624
1626
  var BrowserAIEmbeddingModel = class {
1625
1627
  constructor(settings = {}) {