@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.mjs CHANGED
@@ -454,7 +454,267 @@ var ToolCallFenceDetector = class {
454
454
  }
455
455
  };
456
456
 
457
- // src/convert-to-browser-ai-messages.ts
457
+ // ../shared/src/streaming/tool-call-stream-utils.ts
458
+ function extractToolName(content) {
459
+ const jsonMatch = content.match(/\{\s*"name"\s*:\s*"([^"]+)"/);
460
+ if (jsonMatch) {
461
+ return jsonMatch[1];
462
+ }
463
+ return null;
464
+ }
465
+ var ARGUMENTS_FIELD_REGEX = /"arguments"\s*:\s*/g;
466
+ var ARGUMENTS_SEARCH_OVERLAP = 32;
467
+ function createArgumentsStreamState() {
468
+ return {
469
+ searchFrom: 0,
470
+ valueStartIndex: null,
471
+ parseIndex: 0,
472
+ started: false,
473
+ depth: 0,
474
+ inString: false,
475
+ escaped: false,
476
+ complete: false
477
+ };
478
+ }
479
+ function extractArgumentsDelta(content, state) {
480
+ if (state.complete) {
481
+ return "";
482
+ }
483
+ if (state.valueStartIndex === null) {
484
+ ARGUMENTS_FIELD_REGEX.lastIndex = state.searchFrom;
485
+ const match = ARGUMENTS_FIELD_REGEX.exec(content);
486
+ ARGUMENTS_FIELD_REGEX.lastIndex = 0;
487
+ if (!match || match.index === void 0) {
488
+ state.searchFrom = Math.max(0, content.length - ARGUMENTS_SEARCH_OVERLAP);
489
+ return "";
490
+ }
491
+ state.valueStartIndex = match.index + match[0].length;
492
+ state.parseIndex = state.valueStartIndex;
493
+ state.searchFrom = state.valueStartIndex;
494
+ }
495
+ if (state.parseIndex >= content.length) {
496
+ return "";
497
+ }
498
+ let delta = "";
499
+ for (let i = state.parseIndex; i < content.length; i++) {
500
+ const char = content[i];
501
+ delta += char;
502
+ if (!state.started) {
503
+ if (!/\s/.test(char)) {
504
+ state.started = true;
505
+ if (char === "{" || char === "[") {
506
+ state.depth = 1;
507
+ }
508
+ }
509
+ continue;
510
+ }
511
+ if (state.escaped) {
512
+ state.escaped = false;
513
+ continue;
514
+ }
515
+ if (char === "\\") {
516
+ state.escaped = true;
517
+ continue;
518
+ }
519
+ if (char === '"') {
520
+ state.inString = !state.inString;
521
+ continue;
522
+ }
523
+ if (!state.inString) {
524
+ if (char === "{" || char === "[") {
525
+ state.depth += 1;
526
+ } else if (char === "}" || char === "]") {
527
+ if (state.depth > 0) {
528
+ state.depth -= 1;
529
+ if (state.depth === 0) {
530
+ state.parseIndex = i + 1;
531
+ state.complete = true;
532
+ return delta;
533
+ }
534
+ }
535
+ }
536
+ }
537
+ }
538
+ state.parseIndex = content.length;
539
+ return delta;
540
+ }
541
+
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
458
718
  import {
459
719
  UnsupportedFunctionalityError
460
720
  } from "@ai-sdk/provider";
@@ -761,7 +1021,7 @@ function getExpectedInputs(prompt) {
761
1021
  return Array.from(inputs).map((type) => ({ type }));
762
1022
  }
763
1023
 
764
- // src/models/session-manager.ts
1024
+ // src/chat/session-manager.ts
765
1025
  import { LoadSettingError } from "@ai-sdk/provider";
766
1026
  var SessionManager = class {
767
1027
  /**
@@ -945,67 +1205,10 @@ var SessionManager = class {
945
1205
  }
946
1206
  };
947
1207
 
948
- // src/browser-ai-language-model.ts
1208
+ // src/chat/browser-ai-language-model.ts
949
1209
  function doesBrowserSupportBrowserAI() {
950
1210
  return typeof LanguageModel !== "undefined";
951
1211
  }
952
- function extractToolName(content) {
953
- const jsonMatch = content.match(/\{\s*"name"\s*:\s*"([^"]+)"/);
954
- if (jsonMatch) {
955
- return jsonMatch[1];
956
- }
957
- return null;
958
- }
959
- function extractArgumentsContent(content) {
960
- const match = content.match(/"arguments"\s*:\s*/);
961
- if (!match || match.index === void 0) {
962
- return "";
963
- }
964
- const startIndex = match.index + match[0].length;
965
- let result = "";
966
- let depth = 0;
967
- let inString = false;
968
- let escaped = false;
969
- let started = false;
970
- for (let i = startIndex; i < content.length; i++) {
971
- const char = content[i];
972
- result += char;
973
- if (!started) {
974
- if (!/\s/.test(char)) {
975
- started = true;
976
- if (char === "{" || char === "[") {
977
- depth = 1;
978
- }
979
- }
980
- continue;
981
- }
982
- if (escaped) {
983
- escaped = false;
984
- continue;
985
- }
986
- if (char === "\\") {
987
- escaped = true;
988
- continue;
989
- }
990
- if (char === '"') {
991
- inString = !inString;
992
- continue;
993
- }
994
- if (!inString) {
995
- if (char === "{" || char === "[") {
996
- depth += 1;
997
- } else if (char === "}" || char === "]") {
998
- if (depth > 0) {
999
- depth -= 1;
1000
- if (depth === 0) {
1001
- break;
1002
- }
1003
- }
1004
- }
1005
- }
1006
- }
1007
- return result;
1008
- }
1009
1212
  var BrowserAIChatLanguageModel = class {
1010
1213
  constructor(modelId, options = {}) {
1011
1214
  this.specificationVersion = "v3";
@@ -1339,226 +1542,40 @@ var BrowserAIChatLanguageModel = class {
1339
1542
  if (options.abortSignal) {
1340
1543
  options.abortSignal.addEventListener("abort", abortHandler);
1341
1544
  }
1342
- const maxIterations = 10;
1343
- let iteration = 0;
1344
1545
  try {
1345
- const fenceDetector = new ToolCallFenceDetector();
1346
- while (iteration < maxIterations && !aborted && !finished) {
1347
- iteration += 1;
1348
- const promptStream = session.promptStreaming(
1349
- conversationHistory,
1350
- streamOptions
1351
- );
1352
- currentReader = promptStream.getReader();
1353
- let toolCalls = [];
1354
- let toolBlockDetected = false;
1355
- let trailingTextAfterBlock = "";
1356
- let currentToolCallId = null;
1357
- let toolInputStartEmitted = false;
1358
- let accumulatedFenceContent = "";
1359
- let streamedArgumentsLength = 0;
1360
- let insideFence = false;
1546
+ const promptStream = session.promptStreaming(
1547
+ conversationHistory,
1548
+ streamOptions
1549
+ );
1550
+ currentReader = promptStream.getReader();
1551
+ const chunks = (async function* () {
1361
1552
  while (!aborted) {
1362
1553
  const { done, value } = await currentReader.read();
1363
- if (done) {
1364
- break;
1365
- }
1366
- fenceDetector.addChunk(value);
1367
- while (fenceDetector.hasContent()) {
1368
- const wasInsideFence = insideFence;
1369
- const result = fenceDetector.detectStreamingFence();
1370
- insideFence = result.inFence;
1371
- let madeProgress = false;
1372
- if (!wasInsideFence && result.inFence) {
1373
- if (result.safeContent) {
1374
- emitTextDelta(result.safeContent);
1375
- madeProgress = true;
1376
- }
1377
- currentToolCallId = `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
1378
- toolInputStartEmitted = false;
1379
- accumulatedFenceContent = "";
1380
- streamedArgumentsLength = 0;
1381
- insideFence = true;
1382
- continue;
1383
- }
1384
- if (result.completeFence) {
1385
- madeProgress = true;
1386
- if (result.safeContent) {
1387
- accumulatedFenceContent += result.safeContent;
1388
- }
1389
- if (toolInputStartEmitted && currentToolCallId) {
1390
- const argsContent = extractArgumentsContent(
1391
- accumulatedFenceContent
1392
- );
1393
- if (argsContent.length > streamedArgumentsLength) {
1394
- const delta = argsContent.slice(streamedArgumentsLength);
1395
- streamedArgumentsLength = argsContent.length;
1396
- if (delta.length > 0) {
1397
- controller.enqueue({
1398
- type: "tool-input-delta",
1399
- id: currentToolCallId,
1400
- delta
1401
- });
1402
- }
1403
- }
1404
- }
1405
- const parsed = parseJsonFunctionCalls(result.completeFence);
1406
- const parsedToolCalls = parsed.toolCalls;
1407
- const selectedToolCalls = parsedToolCalls.slice(0, 1);
1408
- if (selectedToolCalls.length === 0) {
1409
- toolCalls = [];
1410
- toolBlockDetected = false;
1411
- emitTextDelta(result.completeFence);
1412
- if (result.textAfterFence) {
1413
- emitTextDelta(result.textAfterFence);
1414
- }
1415
- currentToolCallId = null;
1416
- toolInputStartEmitted = false;
1417
- accumulatedFenceContent = "";
1418
- streamedArgumentsLength = 0;
1419
- insideFence = false;
1420
- continue;
1421
- }
1422
- if (selectedToolCalls.length > 0 && currentToolCallId) {
1423
- selectedToolCalls[0].toolCallId = currentToolCallId;
1424
- }
1425
- toolCalls = selectedToolCalls;
1426
- toolBlockDetected = toolCalls.length > 0;
1427
- for (const [index, call] of toolCalls.entries()) {
1428
- const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
1429
- const toolName = call.toolName;
1430
- const argsJson = JSON.stringify(call.args ?? {});
1431
- if (toolCallId === currentToolCallId) {
1432
- if (!toolInputStartEmitted) {
1433
- controller.enqueue({
1434
- type: "tool-input-start",
1435
- id: toolCallId,
1436
- toolName
1437
- });
1438
- toolInputStartEmitted = true;
1439
- }
1440
- const argsContent = extractArgumentsContent(
1441
- accumulatedFenceContent
1442
- );
1443
- if (argsContent.length > streamedArgumentsLength) {
1444
- const delta = argsContent.slice(
1445
- streamedArgumentsLength
1446
- );
1447
- streamedArgumentsLength = argsContent.length;
1448
- if (delta.length > 0) {
1449
- controller.enqueue({
1450
- type: "tool-input-delta",
1451
- id: toolCallId,
1452
- delta
1453
- });
1454
- }
1455
- }
1456
- } else {
1457
- controller.enqueue({
1458
- type: "tool-input-start",
1459
- id: toolCallId,
1460
- toolName
1461
- });
1462
- if (argsJson.length > 0) {
1463
- controller.enqueue({
1464
- type: "tool-input-delta",
1465
- id: toolCallId,
1466
- delta: argsJson
1467
- });
1468
- }
1469
- }
1470
- controller.enqueue({
1471
- type: "tool-input-end",
1472
- id: toolCallId
1473
- });
1474
- controller.enqueue({
1475
- type: "tool-call",
1476
- toolCallId,
1477
- toolName,
1478
- input: argsJson,
1479
- providerExecuted: false
1480
- });
1481
- }
1482
- trailingTextAfterBlock += result.textAfterFence;
1483
- madeProgress = true;
1484
- if (toolBlockDetected && currentReader) {
1485
- await currentReader.cancel().catch(() => void 0);
1486
- break;
1487
- }
1488
- currentToolCallId = null;
1489
- toolInputStartEmitted = false;
1490
- accumulatedFenceContent = "";
1491
- streamedArgumentsLength = 0;
1492
- insideFence = false;
1493
- continue;
1494
- }
1495
- if (insideFence) {
1496
- if (result.safeContent) {
1497
- accumulatedFenceContent += result.safeContent;
1498
- madeProgress = true;
1499
- const toolName = extractToolName(accumulatedFenceContent);
1500
- if (toolName && !toolInputStartEmitted && currentToolCallId) {
1501
- controller.enqueue({
1502
- type: "tool-input-start",
1503
- id: currentToolCallId,
1504
- toolName
1505
- });
1506
- toolInputStartEmitted = true;
1507
- }
1508
- if (toolInputStartEmitted && currentToolCallId) {
1509
- const argsContent = extractArgumentsContent(
1510
- accumulatedFenceContent
1511
- );
1512
- if (argsContent.length > streamedArgumentsLength) {
1513
- const delta = argsContent.slice(
1514
- streamedArgumentsLength
1515
- );
1516
- streamedArgumentsLength = argsContent.length;
1517
- if (delta.length > 0) {
1518
- controller.enqueue({
1519
- type: "tool-input-delta",
1520
- id: currentToolCallId,
1521
- delta
1522
- });
1523
- }
1524
- }
1525
- }
1526
- }
1527
- continue;
1528
- }
1529
- if (!insideFence && result.safeContent) {
1530
- emitTextDelta(result.safeContent);
1531
- madeProgress = true;
1532
- }
1533
- if (!madeProgress) {
1534
- break;
1535
- }
1536
- }
1537
- if (toolBlockDetected) {
1538
- break;
1539
- }
1554
+ if (done) break;
1555
+ yield value;
1540
1556
  }
1541
- currentReader = null;
1542
- if (aborted) {
1543
- return;
1544
- }
1545
- if (!toolBlockDetected && fenceDetector.hasContent()) {
1546
- emitTextDelta(fenceDetector.getBuffer());
1547
- fenceDetector.clearBuffer();
1548
- }
1549
- if (!toolBlockDetected || toolCalls.length === 0) {
1550
- finishStream({ unified: "stop", raw: "stop" });
1551
- return;
1552
- }
1553
- if (trailingTextAfterBlock) {
1554
- emitTextDelta(trailingTextAfterBlock);
1555
- }
1556
- finishStream({ unified: "tool-calls", raw: "tool-calls" });
1557
+ })();
1558
+ const result = await processToolCallStream(
1559
+ chunks,
1560
+ emitTextDelta,
1561
+ controller,
1562
+ { stopEarlyOnToolCall: true }
1563
+ );
1564
+ if (result.toolCallDetected && currentReader) {
1565
+ await currentReader.cancel().catch(() => void 0);
1566
+ }
1567
+ currentReader = null;
1568
+ if (aborted) {
1569
+ return;
1570
+ }
1571
+ if (!result.toolCallDetected || result.toolCalls.length === 0) {
1572
+ finishStream({ unified: "stop", raw: "stop" });
1557
1573
  return;
1558
1574
  }
1559
- if (!finished && !aborted) {
1560
- finishStream({ unified: "other", raw: "other" });
1575
+ if (result.trailingText) {
1576
+ emitTextDelta(result.trailingText);
1561
1577
  }
1578
+ finishStream({ unified: "tool-calls", raw: "tool-calls" });
1562
1579
  } catch (error) {
1563
1580
  controller.enqueue({ type: "error", error });
1564
1581
  controller.close();
@@ -1576,7 +1593,7 @@ var BrowserAIChatLanguageModel = class {
1576
1593
  }
1577
1594
  };
1578
1595
 
1579
- // src/browser-ai-embedding-model.ts
1596
+ // src/embedding/browser-ai-embedding-model.ts
1580
1597
  import { TextEmbedder } from "@mediapipe/tasks-text";
1581
1598
  var BrowserAIEmbeddingModel = class {
1582
1599
  constructor(settings = {}) {