@browser-ai/core 2.1.2 → 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
@@ -484,7 +484,267 @@ var ToolCallFenceDetector = class {
484
484
  }
485
485
  };
486
486
 
487
- // src/convert-to-browser-ai-messages.ts
487
+ // ../shared/src/streaming/tool-call-stream-utils.ts
488
+ function extractToolName(content) {
489
+ const jsonMatch = content.match(/\{\s*"name"\s*:\s*"([^"]+)"/);
490
+ if (jsonMatch) {
491
+ return jsonMatch[1];
492
+ }
493
+ return null;
494
+ }
495
+ var ARGUMENTS_FIELD_REGEX = /"arguments"\s*:\s*/g;
496
+ var ARGUMENTS_SEARCH_OVERLAP = 32;
497
+ function createArgumentsStreamState() {
498
+ return {
499
+ searchFrom: 0,
500
+ valueStartIndex: null,
501
+ parseIndex: 0,
502
+ started: false,
503
+ depth: 0,
504
+ inString: false,
505
+ escaped: false,
506
+ complete: false
507
+ };
508
+ }
509
+ function extractArgumentsDelta(content, state) {
510
+ if (state.complete) {
511
+ return "";
512
+ }
513
+ if (state.valueStartIndex === null) {
514
+ ARGUMENTS_FIELD_REGEX.lastIndex = state.searchFrom;
515
+ const match = ARGUMENTS_FIELD_REGEX.exec(content);
516
+ ARGUMENTS_FIELD_REGEX.lastIndex = 0;
517
+ if (!match || match.index === void 0) {
518
+ state.searchFrom = Math.max(0, content.length - ARGUMENTS_SEARCH_OVERLAP);
519
+ return "";
520
+ }
521
+ state.valueStartIndex = match.index + match[0].length;
522
+ state.parseIndex = state.valueStartIndex;
523
+ state.searchFrom = state.valueStartIndex;
524
+ }
525
+ if (state.parseIndex >= content.length) {
526
+ return "";
527
+ }
528
+ let delta = "";
529
+ for (let i = state.parseIndex; i < content.length; i++) {
530
+ const char = content[i];
531
+ delta += char;
532
+ if (!state.started) {
533
+ if (!/\s/.test(char)) {
534
+ state.started = true;
535
+ if (char === "{" || char === "[") {
536
+ state.depth = 1;
537
+ }
538
+ }
539
+ continue;
540
+ }
541
+ if (state.escaped) {
542
+ state.escaped = false;
543
+ continue;
544
+ }
545
+ if (char === "\\") {
546
+ state.escaped = true;
547
+ continue;
548
+ }
549
+ if (char === '"') {
550
+ state.inString = !state.inString;
551
+ continue;
552
+ }
553
+ if (!state.inString) {
554
+ if (char === "{" || char === "[") {
555
+ state.depth += 1;
556
+ } else if (char === "}" || char === "]") {
557
+ if (state.depth > 0) {
558
+ state.depth -= 1;
559
+ if (state.depth === 0) {
560
+ state.parseIndex = i + 1;
561
+ state.complete = true;
562
+ return delta;
563
+ }
564
+ }
565
+ }
566
+ }
567
+ }
568
+ state.parseIndex = content.length;
569
+ return delta;
570
+ }
571
+
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
488
748
  var import_provider = require("@ai-sdk/provider");
489
749
  function convertBase64ToUint8Array(base64) {
490
750
  try {
@@ -789,7 +1049,7 @@ function getExpectedInputs(prompt) {
789
1049
  return Array.from(inputs).map((type) => ({ type }));
790
1050
  }
791
1051
 
792
- // src/models/session-manager.ts
1052
+ // src/chat/session-manager.ts
793
1053
  var import_provider2 = require("@ai-sdk/provider");
794
1054
  var SessionManager = class {
795
1055
  /**
@@ -973,67 +1233,10 @@ var SessionManager = class {
973
1233
  }
974
1234
  };
975
1235
 
976
- // src/browser-ai-language-model.ts
1236
+ // src/chat/browser-ai-language-model.ts
977
1237
  function doesBrowserSupportBrowserAI() {
978
1238
  return typeof LanguageModel !== "undefined";
979
1239
  }
980
- function extractToolName(content) {
981
- const jsonMatch = content.match(/\{\s*"name"\s*:\s*"([^"]+)"/);
982
- if (jsonMatch) {
983
- return jsonMatch[1];
984
- }
985
- return null;
986
- }
987
- function extractArgumentsContent(content) {
988
- const match = content.match(/"arguments"\s*:\s*/);
989
- if (!match || match.index === void 0) {
990
- return "";
991
- }
992
- const startIndex = match.index + match[0].length;
993
- let result = "";
994
- let depth = 0;
995
- let inString = false;
996
- let escaped = false;
997
- let started = false;
998
- for (let i = startIndex; i < content.length; i++) {
999
- const char = content[i];
1000
- result += char;
1001
- if (!started) {
1002
- if (!/\s/.test(char)) {
1003
- started = true;
1004
- if (char === "{" || char === "[") {
1005
- depth = 1;
1006
- }
1007
- }
1008
- continue;
1009
- }
1010
- if (escaped) {
1011
- escaped = false;
1012
- continue;
1013
- }
1014
- if (char === "\\") {
1015
- escaped = true;
1016
- continue;
1017
- }
1018
- if (char === '"') {
1019
- inString = !inString;
1020
- continue;
1021
- }
1022
- if (!inString) {
1023
- if (char === "{" || char === "[") {
1024
- depth += 1;
1025
- } else if (char === "}" || char === "]") {
1026
- if (depth > 0) {
1027
- depth -= 1;
1028
- if (depth === 0) {
1029
- break;
1030
- }
1031
- }
1032
- }
1033
- }
1034
- }
1035
- return result;
1036
- }
1037
1240
  var BrowserAIChatLanguageModel = class {
1038
1241
  constructor(modelId, options = {}) {
1039
1242
  this.specificationVersion = "v3";
@@ -1367,226 +1570,40 @@ var BrowserAIChatLanguageModel = class {
1367
1570
  if (options.abortSignal) {
1368
1571
  options.abortSignal.addEventListener("abort", abortHandler);
1369
1572
  }
1370
- const maxIterations = 10;
1371
- let iteration = 0;
1372
1573
  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 streamedArgumentsLength = 0;
1388
- let insideFence = false;
1574
+ const promptStream = session.promptStreaming(
1575
+ conversationHistory,
1576
+ streamOptions
1577
+ );
1578
+ currentReader = promptStream.getReader();
1579
+ const chunks = (async function* () {
1389
1580
  while (!aborted) {
1390
1581
  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
- streamedArgumentsLength = 0;
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 argsContent = extractArgumentsContent(
1419
- accumulatedFenceContent
1420
- );
1421
- if (argsContent.length > streamedArgumentsLength) {
1422
- const delta = argsContent.slice(streamedArgumentsLength);
1423
- streamedArgumentsLength = argsContent.length;
1424
- if (delta.length > 0) {
1425
- controller.enqueue({
1426
- type: "tool-input-delta",
1427
- id: currentToolCallId,
1428
- delta
1429
- });
1430
- }
1431
- }
1432
- }
1433
- const parsed = parseJsonFunctionCalls(result.completeFence);
1434
- const parsedToolCalls = parsed.toolCalls;
1435
- const selectedToolCalls = parsedToolCalls.slice(0, 1);
1436
- if (selectedToolCalls.length === 0) {
1437
- toolCalls = [];
1438
- toolBlockDetected = false;
1439
- emitTextDelta(result.completeFence);
1440
- if (result.textAfterFence) {
1441
- emitTextDelta(result.textAfterFence);
1442
- }
1443
- currentToolCallId = null;
1444
- toolInputStartEmitted = false;
1445
- accumulatedFenceContent = "";
1446
- streamedArgumentsLength = 0;
1447
- insideFence = false;
1448
- continue;
1449
- }
1450
- if (selectedToolCalls.length > 0 && currentToolCallId) {
1451
- selectedToolCalls[0].toolCallId = currentToolCallId;
1452
- }
1453
- toolCalls = selectedToolCalls;
1454
- toolBlockDetected = toolCalls.length > 0;
1455
- for (const [index, call] of toolCalls.entries()) {
1456
- const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
1457
- const toolName = call.toolName;
1458
- const argsJson = JSON.stringify(call.args ?? {});
1459
- if (toolCallId === currentToolCallId) {
1460
- if (!toolInputStartEmitted) {
1461
- controller.enqueue({
1462
- type: "tool-input-start",
1463
- id: toolCallId,
1464
- toolName
1465
- });
1466
- toolInputStartEmitted = true;
1467
- }
1468
- const argsContent = extractArgumentsContent(
1469
- accumulatedFenceContent
1470
- );
1471
- if (argsContent.length > streamedArgumentsLength) {
1472
- const delta = argsContent.slice(
1473
- streamedArgumentsLength
1474
- );
1475
- streamedArgumentsLength = argsContent.length;
1476
- if (delta.length > 0) {
1477
- controller.enqueue({
1478
- type: "tool-input-delta",
1479
- id: toolCallId,
1480
- delta
1481
- });
1482
- }
1483
- }
1484
- } else {
1485
- controller.enqueue({
1486
- type: "tool-input-start",
1487
- id: toolCallId,
1488
- toolName
1489
- });
1490
- if (argsJson.length > 0) {
1491
- controller.enqueue({
1492
- type: "tool-input-delta",
1493
- id: toolCallId,
1494
- delta: argsJson
1495
- });
1496
- }
1497
- }
1498
- controller.enqueue({
1499
- type: "tool-input-end",
1500
- id: toolCallId
1501
- });
1502
- controller.enqueue({
1503
- type: "tool-call",
1504
- toolCallId,
1505
- toolName,
1506
- input: argsJson,
1507
- providerExecuted: false
1508
- });
1509
- }
1510
- trailingTextAfterBlock += result.textAfterFence;
1511
- madeProgress = true;
1512
- if (toolBlockDetected && currentReader) {
1513
- await currentReader.cancel().catch(() => void 0);
1514
- break;
1515
- }
1516
- currentToolCallId = null;
1517
- toolInputStartEmitted = false;
1518
- accumulatedFenceContent = "";
1519
- streamedArgumentsLength = 0;
1520
- insideFence = false;
1521
- continue;
1522
- }
1523
- if (insideFence) {
1524
- if (result.safeContent) {
1525
- accumulatedFenceContent += result.safeContent;
1526
- madeProgress = true;
1527
- const toolName = extractToolName(accumulatedFenceContent);
1528
- if (toolName && !toolInputStartEmitted && currentToolCallId) {
1529
- controller.enqueue({
1530
- type: "tool-input-start",
1531
- id: currentToolCallId,
1532
- toolName
1533
- });
1534
- toolInputStartEmitted = true;
1535
- }
1536
- if (toolInputStartEmitted && currentToolCallId) {
1537
- const argsContent = extractArgumentsContent(
1538
- accumulatedFenceContent
1539
- );
1540
- if (argsContent.length > streamedArgumentsLength) {
1541
- const delta = argsContent.slice(
1542
- streamedArgumentsLength
1543
- );
1544
- streamedArgumentsLength = argsContent.length;
1545
- if (delta.length > 0) {
1546
- controller.enqueue({
1547
- type: "tool-input-delta",
1548
- id: currentToolCallId,
1549
- delta
1550
- });
1551
- }
1552
- }
1553
- }
1554
- }
1555
- continue;
1556
- }
1557
- if (!insideFence && result.safeContent) {
1558
- emitTextDelta(result.safeContent);
1559
- madeProgress = true;
1560
- }
1561
- if (!madeProgress) {
1562
- break;
1563
- }
1564
- }
1565
- if (toolBlockDetected) {
1566
- break;
1567
- }
1582
+ if (done) break;
1583
+ yield value;
1568
1584
  }
1569
- currentReader = null;
1570
- if (aborted) {
1571
- return;
1572
- }
1573
- if (!toolBlockDetected && fenceDetector.hasContent()) {
1574
- emitTextDelta(fenceDetector.getBuffer());
1575
- fenceDetector.clearBuffer();
1576
- }
1577
- if (!toolBlockDetected || toolCalls.length === 0) {
1578
- finishStream({ unified: "stop", raw: "stop" });
1579
- return;
1580
- }
1581
- if (trailingTextAfterBlock) {
1582
- emitTextDelta(trailingTextAfterBlock);
1583
- }
1584
- 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" });
1585
1601
  return;
1586
1602
  }
1587
- if (!finished && !aborted) {
1588
- finishStream({ unified: "other", raw: "other" });
1603
+ if (result.trailingText) {
1604
+ emitTextDelta(result.trailingText);
1589
1605
  }
1606
+ finishStream({ unified: "tool-calls", raw: "tool-calls" });
1590
1607
  } catch (error) {
1591
1608
  controller.enqueue({ type: "error", error });
1592
1609
  controller.close();
@@ -1604,7 +1621,7 @@ var BrowserAIChatLanguageModel = class {
1604
1621
  }
1605
1622
  };
1606
1623
 
1607
- // src/browser-ai-embedding-model.ts
1624
+ // src/embedding/browser-ai-embedding-model.ts
1608
1625
  var import_tasks_text = require("@mediapipe/tasks-text");
1609
1626
  var BrowserAIEmbeddingModel = class {
1610
1627
  constructor(settings = {}) {