@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.mjs CHANGED
@@ -539,7 +539,182 @@ 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";
@@ -846,7 +1021,7 @@ function getExpectedInputs(prompt) {
846
1021
  return Array.from(inputs).map((type) => ({ type }));
847
1022
  }
848
1023
 
849
- // src/models/session-manager.ts
1024
+ // src/chat/session-manager.ts
850
1025
  import { LoadSettingError } from "@ai-sdk/provider";
851
1026
  var SessionManager = class {
852
1027
  /**
@@ -1030,7 +1205,7 @@ var SessionManager = class {
1030
1205
  }
1031
1206
  };
1032
1207
 
1033
- // src/browser-ai-language-model.ts
1208
+ // src/chat/browser-ai-language-model.ts
1034
1209
  function doesBrowserSupportBrowserAI() {
1035
1210
  return typeof LanguageModel !== "undefined";
1036
1211
  }
@@ -1367,213 +1542,40 @@ var BrowserAIChatLanguageModel = class {
1367
1542
  if (options.abortSignal) {
1368
1543
  options.abortSignal.addEventListener("abort", abortHandler);
1369
1544
  }
1370
- const maxIterations = 10;
1371
- let iteration = 0;
1372
1545
  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;
1546
+ const promptStream = session.promptStreaming(
1547
+ conversationHistory,
1548
+ streamOptions
1549
+ );
1550
+ currentReader = promptStream.getReader();
1551
+ const chunks = (async function* () {
1389
1552
  while (!aborted) {
1390
1553
  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
- }
1554
+ if (done) break;
1555
+ yield value;
1555
1556
  }
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;
1567
- }
1568
- if (trailingTextAfterBlock) {
1569
- emitTextDelta(trailingTextAfterBlock);
1570
- }
1571
- 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" });
1572
1573
  return;
1573
1574
  }
1574
- if (!finished && !aborted) {
1575
- finishStream({ unified: "other", raw: "other" });
1575
+ if (result.trailingText) {
1576
+ emitTextDelta(result.trailingText);
1576
1577
  }
1578
+ finishStream({ unified: "tool-calls", raw: "tool-calls" });
1577
1579
  } catch (error) {
1578
1580
  controller.enqueue({ type: "error", error });
1579
1581
  controller.close();
@@ -1591,7 +1593,7 @@ var BrowserAIChatLanguageModel = class {
1591
1593
  }
1592
1594
  };
1593
1595
 
1594
- // src/browser-ai-embedding-model.ts
1596
+ // src/embedding/browser-ai-embedding-model.ts
1595
1597
  import { TextEmbedder } from "@mediapipe/tasks-text";
1596
1598
  var BrowserAIEmbeddingModel = class {
1597
1599
  constructor(settings = {}) {