@browser-ai/web-llm 2.1.4 → 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.mjs CHANGED
@@ -858,7 +858,182 @@ function extractArgumentsDelta(content, state) {
858
858
  return delta;
859
859
  }
860
860
 
861
- // src/convert-to-webllm-messages.tsx
861
+ // ../shared/src/streaming/stream-processor.ts
862
+ function generateToolCallId2() {
863
+ return `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
864
+ }
865
+ async function processToolCallStream(chunks, emitTextDelta, controller, options) {
866
+ const fenceDetector = new ToolCallFenceDetector();
867
+ let currentToolCallId = null;
868
+ let toolInputStartEmitted = false;
869
+ let accumulatedFenceContent = "";
870
+ let argumentsStreamState = createArgumentsStreamState();
871
+ let insideFence = false;
872
+ let toolCallDetected = false;
873
+ let toolCalls = [];
874
+ let trailingText = "";
875
+ const resetFenceState = () => {
876
+ currentToolCallId = null;
877
+ toolInputStartEmitted = false;
878
+ accumulatedFenceContent = "";
879
+ argumentsStreamState = createArgumentsStreamState();
880
+ insideFence = false;
881
+ };
882
+ for await (const chunk of chunks) {
883
+ if (toolCallDetected) {
884
+ continue;
885
+ }
886
+ fenceDetector.addChunk(chunk);
887
+ while (fenceDetector.hasContent()) {
888
+ const wasInsideFence = insideFence;
889
+ const result = fenceDetector.detectStreamingFence();
890
+ insideFence = result.inFence;
891
+ let madeProgress = false;
892
+ if (!wasInsideFence && result.inFence) {
893
+ if (result.safeContent) {
894
+ emitTextDelta(result.safeContent);
895
+ madeProgress = true;
896
+ }
897
+ currentToolCallId = generateToolCallId2();
898
+ toolInputStartEmitted = false;
899
+ accumulatedFenceContent = "";
900
+ argumentsStreamState = createArgumentsStreamState();
901
+ insideFence = true;
902
+ continue;
903
+ }
904
+ if (result.completeFence) {
905
+ madeProgress = true;
906
+ if (result.safeContent) {
907
+ accumulatedFenceContent += result.safeContent;
908
+ }
909
+ if (toolInputStartEmitted && currentToolCallId) {
910
+ const delta = extractArgumentsDelta(
911
+ accumulatedFenceContent,
912
+ argumentsStreamState
913
+ );
914
+ if (delta.length > 0) {
915
+ controller.enqueue({
916
+ type: "tool-input-delta",
917
+ id: currentToolCallId,
918
+ delta
919
+ });
920
+ }
921
+ }
922
+ const parsed = parseJsonFunctionCalls(result.completeFence);
923
+ const selectedToolCalls = parsed.toolCalls.slice(0, 1);
924
+ if (selectedToolCalls.length === 0) {
925
+ emitTextDelta(result.completeFence);
926
+ if (result.textAfterFence) {
927
+ emitTextDelta(result.textAfterFence);
928
+ }
929
+ resetFenceState();
930
+ continue;
931
+ }
932
+ if (currentToolCallId) {
933
+ selectedToolCalls[0].toolCallId = currentToolCallId;
934
+ }
935
+ for (const [index, call] of selectedToolCalls.entries()) {
936
+ const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
937
+ const toolName = call.toolName;
938
+ const argsJson = JSON.stringify(call.args ?? {});
939
+ if (toolCallId === currentToolCallId) {
940
+ if (!toolInputStartEmitted) {
941
+ controller.enqueue({
942
+ type: "tool-input-start",
943
+ id: toolCallId,
944
+ toolName
945
+ });
946
+ toolInputStartEmitted = true;
947
+ }
948
+ const delta = extractArgumentsDelta(
949
+ accumulatedFenceContent,
950
+ argumentsStreamState
951
+ );
952
+ if (delta.length > 0) {
953
+ controller.enqueue({
954
+ type: "tool-input-delta",
955
+ id: toolCallId,
956
+ delta
957
+ });
958
+ }
959
+ } else {
960
+ controller.enqueue({
961
+ type: "tool-input-start",
962
+ id: toolCallId,
963
+ toolName
964
+ });
965
+ if (argsJson.length > 0) {
966
+ controller.enqueue({
967
+ type: "tool-input-delta",
968
+ id: toolCallId,
969
+ delta: argsJson
970
+ });
971
+ }
972
+ }
973
+ controller.enqueue({ type: "tool-input-end", id: toolCallId });
974
+ controller.enqueue({
975
+ type: "tool-call",
976
+ toolCallId,
977
+ toolName,
978
+ input: argsJson,
979
+ providerExecuted: false
980
+ });
981
+ }
982
+ trailingText = result.textAfterFence ?? "";
983
+ toolCalls = selectedToolCalls;
984
+ toolCallDetected = true;
985
+ resetFenceState();
986
+ break;
987
+ }
988
+ if (insideFence) {
989
+ if (result.safeContent) {
990
+ accumulatedFenceContent += result.safeContent;
991
+ madeProgress = true;
992
+ const toolName = extractToolName(accumulatedFenceContent);
993
+ if (toolName && !toolInputStartEmitted && currentToolCallId) {
994
+ controller.enqueue({
995
+ type: "tool-input-start",
996
+ id: currentToolCallId,
997
+ toolName
998
+ });
999
+ toolInputStartEmitted = true;
1000
+ }
1001
+ if (toolInputStartEmitted && currentToolCallId) {
1002
+ const delta = extractArgumentsDelta(
1003
+ accumulatedFenceContent,
1004
+ argumentsStreamState
1005
+ );
1006
+ if (delta.length > 0) {
1007
+ controller.enqueue({
1008
+ type: "tool-input-delta",
1009
+ id: currentToolCallId,
1010
+ delta
1011
+ });
1012
+ }
1013
+ }
1014
+ }
1015
+ continue;
1016
+ }
1017
+ if (!insideFence && result.safeContent) {
1018
+ emitTextDelta(result.safeContent);
1019
+ madeProgress = true;
1020
+ }
1021
+ if (!madeProgress) {
1022
+ break;
1023
+ }
1024
+ }
1025
+ if (toolCallDetected && options?.stopEarlyOnToolCall) {
1026
+ break;
1027
+ }
1028
+ }
1029
+ if (!toolCallDetected && fenceDetector.hasContent()) {
1030
+ emitTextDelta(fenceDetector.getBuffer());
1031
+ fenceDetector.clearBuffer();
1032
+ }
1033
+ return { toolCallDetected, toolCalls, trailingText };
1034
+ }
1035
+
1036
+ // src/utils/convert-to-webllm-messages.tsx
862
1037
  function convertToolResultOutput(output) {
863
1038
  switch (output.type) {
864
1039
  case "text":
@@ -997,7 +1172,7 @@ function convertToWebLLMMessages(prompt) {
997
1172
  return messages;
998
1173
  }
999
1174
 
1000
- // src/web-llm-language-model.ts
1175
+ // src/chat/web-llm-language-model.ts
1001
1176
  import {
1002
1177
  CreateWebWorkerMLCEngine,
1003
1178
  MLCEngine
@@ -1060,7 +1235,7 @@ function doesBrowserSupportWebLLM() {
1060
1235
  return checkWebGPU();
1061
1236
  }
1062
1237
 
1063
- // src/web-llm-language-model.ts
1238
+ // src/chat/web-llm-language-model.ts
1064
1239
  var WebLLMLanguageModel = class {
1065
1240
  constructor(modelId, options = {}) {
1066
1241
  this.specificationVersion = "v3";
@@ -1504,195 +1679,27 @@ var WebLLMLanguageModel = class {
1504
1679
  ...options.abortSignal && !useWorker && { signal: options.abortSignal }
1505
1680
  };
1506
1681
  const response = await engine.chat.completions.create(streamingRequest);
1507
- const fenceDetector = new ToolCallFenceDetector();
1508
- let accumulatedText = "";
1509
- let currentToolCallId = null;
1510
- let toolInputStartEmitted = false;
1511
- let accumulatedFenceContent = "";
1512
- let argumentsStreamState = createArgumentsStreamState();
1513
- let insideFence = false;
1514
- for await (const chunk of response) {
1515
- const choice = chunk.choices[0];
1516
- if (!choice) continue;
1517
- if (choice.delta.content) {
1518
- const delta = choice.delta.content;
1519
- accumulatedText += delta;
1520
- fenceDetector.addChunk(delta);
1521
- while (fenceDetector.hasContent()) {
1522
- const wasInsideFence = insideFence;
1523
- const result = fenceDetector.detectStreamingFence();
1524
- insideFence = result.inFence;
1525
- let madeProgress = false;
1526
- if (!wasInsideFence && result.inFence) {
1527
- if (result.safeContent) {
1528
- emitTextDelta(result.safeContent);
1529
- madeProgress = true;
1530
- }
1531
- currentToolCallId = `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
1532
- toolInputStartEmitted = false;
1533
- accumulatedFenceContent = "";
1534
- argumentsStreamState = createArgumentsStreamState();
1535
- insideFence = true;
1536
- continue;
1537
- }
1538
- if (result.completeFence) {
1539
- madeProgress = true;
1540
- if (result.safeContent) {
1541
- accumulatedFenceContent += result.safeContent;
1542
- }
1543
- if (toolInputStartEmitted && currentToolCallId) {
1544
- const delta2 = extractArgumentsDelta(
1545
- accumulatedFenceContent,
1546
- argumentsStreamState
1547
- );
1548
- if (delta2.length > 0) {
1549
- controller.enqueue({
1550
- type: "tool-input-delta",
1551
- id: currentToolCallId,
1552
- delta: delta2
1553
- });
1554
- }
1555
- }
1556
- const parsed = parseJsonFunctionCalls(result.completeFence);
1557
- const parsedToolCalls = parsed.toolCalls;
1558
- const selectedToolCalls = parsedToolCalls.slice(0, 1);
1559
- if (selectedToolCalls.length === 0) {
1560
- emitTextDelta(result.completeFence);
1561
- if (result.textAfterFence) {
1562
- emitTextDelta(result.textAfterFence);
1563
- }
1564
- currentToolCallId = null;
1565
- toolInputStartEmitted = false;
1566
- accumulatedFenceContent = "";
1567
- argumentsStreamState = createArgumentsStreamState();
1568
- insideFence = false;
1569
- continue;
1570
- }
1571
- if (selectedToolCalls.length > 0 && currentToolCallId) {
1572
- selectedToolCalls[0].toolCallId = currentToolCallId;
1573
- }
1574
- for (const [index, call] of selectedToolCalls.entries()) {
1575
- const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
1576
- const toolName = call.toolName;
1577
- const argsJson = JSON.stringify(call.args ?? {});
1578
- if (toolCallId === currentToolCallId) {
1579
- if (!toolInputStartEmitted) {
1580
- controller.enqueue({
1581
- type: "tool-input-start",
1582
- id: toolCallId,
1583
- toolName
1584
- });
1585
- toolInputStartEmitted = true;
1586
- }
1587
- const delta2 = extractArgumentsDelta(
1588
- accumulatedFenceContent,
1589
- argumentsStreamState
1590
- );
1591
- if (delta2.length > 0) {
1592
- controller.enqueue({
1593
- type: "tool-input-delta",
1594
- id: toolCallId,
1595
- delta: delta2
1596
- });
1597
- }
1598
- } else {
1599
- controller.enqueue({
1600
- type: "tool-input-start",
1601
- id: toolCallId,
1602
- toolName
1603
- });
1604
- if (argsJson.length > 0) {
1605
- controller.enqueue({
1606
- type: "tool-input-delta",
1607
- id: toolCallId,
1608
- delta: argsJson
1609
- });
1610
- }
1611
- }
1612
- controller.enqueue({
1613
- type: "tool-input-end",
1614
- id: toolCallId
1615
- });
1616
- controller.enqueue({
1617
- type: "tool-call",
1618
- toolCallId,
1619
- toolName,
1620
- input: argsJson,
1621
- providerExecuted: false
1622
- });
1623
- }
1624
- if (result.textAfterFence) {
1625
- emitTextDelta(result.textAfterFence);
1626
- }
1627
- madeProgress = true;
1628
- currentToolCallId = null;
1629
- toolInputStartEmitted = false;
1630
- accumulatedFenceContent = "";
1631
- argumentsStreamState = createArgumentsStreamState();
1632
- insideFence = false;
1633
- continue;
1634
- }
1635
- if (insideFence) {
1636
- if (result.safeContent) {
1637
- accumulatedFenceContent += result.safeContent;
1638
- madeProgress = true;
1639
- const toolName = extractToolName(accumulatedFenceContent);
1640
- if (toolName && !toolInputStartEmitted && currentToolCallId) {
1641
- controller.enqueue({
1642
- type: "tool-input-start",
1643
- id: currentToolCallId,
1644
- toolName
1645
- });
1646
- toolInputStartEmitted = true;
1647
- }
1648
- if (toolInputStartEmitted && currentToolCallId) {
1649
- const delta2 = extractArgumentsDelta(
1650
- accumulatedFenceContent,
1651
- argumentsStreamState
1652
- );
1653
- if (delta2.length > 0) {
1654
- controller.enqueue({
1655
- type: "tool-input-delta",
1656
- id: currentToolCallId,
1657
- delta: delta2
1658
- });
1659
- }
1660
- }
1661
- }
1662
- continue;
1663
- }
1664
- if (!insideFence && result.safeContent) {
1665
- emitTextDelta(result.safeContent);
1666
- madeProgress = true;
1667
- }
1668
- if (!madeProgress) {
1669
- break;
1670
- }
1671
- }
1682
+ let lastUsage;
1683
+ let isAbort = false;
1684
+ const chunks = (async function* () {
1685
+ for await (const chunk of response) {
1686
+ const choice = chunk.choices[0];
1687
+ if (!choice) continue;
1688
+ if (choice.delta.content) yield choice.delta.content;
1689
+ if (chunk.usage) lastUsage = chunk.usage;
1690
+ if (choice.finish_reason === "abort") isAbort = true;
1672
1691
  }
1673
- if (choice.finish_reason) {
1674
- if (fenceDetector.hasContent()) {
1675
- emitTextDelta(fenceDetector.getBuffer());
1676
- fenceDetector.clearBuffer();
1677
- }
1678
- let finishReason = {
1679
- unified: "stop",
1680
- raw: "stop"
1681
- };
1682
- if (choice.finish_reason === "abort") {
1683
- finishReason = { unified: "other", raw: "abort" };
1684
- } else {
1685
- const { toolCalls } = parseJsonFunctionCalls(accumulatedText);
1686
- if (toolCalls.length > 0) {
1687
- finishReason = { unified: "tool-calls", raw: "tool-calls" };
1688
- }
1689
- }
1690
- finishStream(finishReason, chunk.usage);
1691
- }
1692
- }
1693
- if (!finished) {
1694
- finishStream({ unified: "stop", raw: "stop" });
1692
+ })();
1693
+ const result = await processToolCallStream(
1694
+ chunks,
1695
+ emitTextDelta,
1696
+ controller
1697
+ );
1698
+ if (result.trailingText) {
1699
+ emitTextDelta(result.trailingText);
1695
1700
  }
1701
+ const finishReason = isAbort ? { unified: "other", raw: "abort" } : result.toolCallDetected ? { unified: "tool-calls", raw: "tool-calls" } : { unified: "stop", raw: "stop" };
1702
+ finishStream(finishReason, lastUsage);
1696
1703
  } catch (error) {
1697
1704
  controller.error(error);
1698
1705
  } finally {
@@ -1712,7 +1719,7 @@ var WebLLMLanguageModel = class {
1712
1719
  }
1713
1720
  };
1714
1721
 
1715
- // src/web-llm-embedding-model.ts
1722
+ // src/embedding/web-llm-embedding-model.ts
1716
1723
  import {
1717
1724
  CreateWebWorkerMLCEngine as CreateWebWorkerMLCEngine2,
1718
1725
  MLCEngine as MLCEngine2