@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.js CHANGED
@@ -889,7 +889,182 @@ function extractArgumentsDelta(content, state) {
889
889
  return delta;
890
890
  }
891
891
 
892
- // src/convert-to-webllm-messages.tsx
892
+ // ../shared/src/streaming/stream-processor.ts
893
+ function generateToolCallId2() {
894
+ return `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
895
+ }
896
+ async function processToolCallStream(chunks, emitTextDelta, controller, options) {
897
+ const fenceDetector = new ToolCallFenceDetector();
898
+ let currentToolCallId = null;
899
+ let toolInputStartEmitted = false;
900
+ let accumulatedFenceContent = "";
901
+ let argumentsStreamState = createArgumentsStreamState();
902
+ let insideFence = false;
903
+ let toolCallDetected = false;
904
+ let toolCalls = [];
905
+ let trailingText = "";
906
+ const resetFenceState = () => {
907
+ currentToolCallId = null;
908
+ toolInputStartEmitted = false;
909
+ accumulatedFenceContent = "";
910
+ argumentsStreamState = createArgumentsStreamState();
911
+ insideFence = false;
912
+ };
913
+ for await (const chunk of chunks) {
914
+ if (toolCallDetected) {
915
+ continue;
916
+ }
917
+ fenceDetector.addChunk(chunk);
918
+ while (fenceDetector.hasContent()) {
919
+ const wasInsideFence = insideFence;
920
+ const result = fenceDetector.detectStreamingFence();
921
+ insideFence = result.inFence;
922
+ let madeProgress = false;
923
+ if (!wasInsideFence && result.inFence) {
924
+ if (result.safeContent) {
925
+ emitTextDelta(result.safeContent);
926
+ madeProgress = true;
927
+ }
928
+ currentToolCallId = generateToolCallId2();
929
+ toolInputStartEmitted = false;
930
+ accumulatedFenceContent = "";
931
+ argumentsStreamState = createArgumentsStreamState();
932
+ insideFence = true;
933
+ continue;
934
+ }
935
+ if (result.completeFence) {
936
+ madeProgress = true;
937
+ if (result.safeContent) {
938
+ accumulatedFenceContent += result.safeContent;
939
+ }
940
+ if (toolInputStartEmitted && currentToolCallId) {
941
+ const delta = extractArgumentsDelta(
942
+ accumulatedFenceContent,
943
+ argumentsStreamState
944
+ );
945
+ if (delta.length > 0) {
946
+ controller.enqueue({
947
+ type: "tool-input-delta",
948
+ id: currentToolCallId,
949
+ delta
950
+ });
951
+ }
952
+ }
953
+ const parsed = parseJsonFunctionCalls(result.completeFence);
954
+ const selectedToolCalls = parsed.toolCalls.slice(0, 1);
955
+ if (selectedToolCalls.length === 0) {
956
+ emitTextDelta(result.completeFence);
957
+ if (result.textAfterFence) {
958
+ emitTextDelta(result.textAfterFence);
959
+ }
960
+ resetFenceState();
961
+ continue;
962
+ }
963
+ if (currentToolCallId) {
964
+ selectedToolCalls[0].toolCallId = currentToolCallId;
965
+ }
966
+ for (const [index, call] of selectedToolCalls.entries()) {
967
+ const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
968
+ const toolName = call.toolName;
969
+ const argsJson = JSON.stringify(call.args ?? {});
970
+ if (toolCallId === currentToolCallId) {
971
+ if (!toolInputStartEmitted) {
972
+ controller.enqueue({
973
+ type: "tool-input-start",
974
+ id: toolCallId,
975
+ toolName
976
+ });
977
+ toolInputStartEmitted = true;
978
+ }
979
+ const delta = extractArgumentsDelta(
980
+ accumulatedFenceContent,
981
+ argumentsStreamState
982
+ );
983
+ if (delta.length > 0) {
984
+ controller.enqueue({
985
+ type: "tool-input-delta",
986
+ id: toolCallId,
987
+ delta
988
+ });
989
+ }
990
+ } else {
991
+ controller.enqueue({
992
+ type: "tool-input-start",
993
+ id: toolCallId,
994
+ toolName
995
+ });
996
+ if (argsJson.length > 0) {
997
+ controller.enqueue({
998
+ type: "tool-input-delta",
999
+ id: toolCallId,
1000
+ delta: argsJson
1001
+ });
1002
+ }
1003
+ }
1004
+ controller.enqueue({ type: "tool-input-end", id: toolCallId });
1005
+ controller.enqueue({
1006
+ type: "tool-call",
1007
+ toolCallId,
1008
+ toolName,
1009
+ input: argsJson,
1010
+ providerExecuted: false
1011
+ });
1012
+ }
1013
+ trailingText = result.textAfterFence ?? "";
1014
+ toolCalls = selectedToolCalls;
1015
+ toolCallDetected = true;
1016
+ resetFenceState();
1017
+ break;
1018
+ }
1019
+ if (insideFence) {
1020
+ if (result.safeContent) {
1021
+ accumulatedFenceContent += result.safeContent;
1022
+ madeProgress = true;
1023
+ const toolName = extractToolName(accumulatedFenceContent);
1024
+ if (toolName && !toolInputStartEmitted && currentToolCallId) {
1025
+ controller.enqueue({
1026
+ type: "tool-input-start",
1027
+ id: currentToolCallId,
1028
+ toolName
1029
+ });
1030
+ toolInputStartEmitted = true;
1031
+ }
1032
+ if (toolInputStartEmitted && currentToolCallId) {
1033
+ const delta = extractArgumentsDelta(
1034
+ accumulatedFenceContent,
1035
+ argumentsStreamState
1036
+ );
1037
+ if (delta.length > 0) {
1038
+ controller.enqueue({
1039
+ type: "tool-input-delta",
1040
+ id: currentToolCallId,
1041
+ delta
1042
+ });
1043
+ }
1044
+ }
1045
+ }
1046
+ continue;
1047
+ }
1048
+ if (!insideFence && result.safeContent) {
1049
+ emitTextDelta(result.safeContent);
1050
+ madeProgress = true;
1051
+ }
1052
+ if (!madeProgress) {
1053
+ break;
1054
+ }
1055
+ }
1056
+ if (toolCallDetected && options?.stopEarlyOnToolCall) {
1057
+ break;
1058
+ }
1059
+ }
1060
+ if (!toolCallDetected && fenceDetector.hasContent()) {
1061
+ emitTextDelta(fenceDetector.getBuffer());
1062
+ fenceDetector.clearBuffer();
1063
+ }
1064
+ return { toolCallDetected, toolCalls, trailingText };
1065
+ }
1066
+
1067
+ // src/utils/convert-to-webllm-messages.tsx
893
1068
  function convertToolResultOutput(output) {
894
1069
  switch (output.type) {
895
1070
  case "text":
@@ -1028,7 +1203,7 @@ function convertToWebLLMMessages(prompt) {
1028
1203
  return messages;
1029
1204
  }
1030
1205
 
1031
- // src/web-llm-language-model.ts
1206
+ // src/chat/web-llm-language-model.ts
1032
1207
  var import_web_llm = require("@mlc-ai/web-llm");
1033
1208
 
1034
1209
  // src/utils/prompt-utils.ts
@@ -1088,7 +1263,7 @@ function doesBrowserSupportWebLLM() {
1088
1263
  return checkWebGPU();
1089
1264
  }
1090
1265
 
1091
- // src/web-llm-language-model.ts
1266
+ // src/chat/web-llm-language-model.ts
1092
1267
  var WebLLMLanguageModel = class {
1093
1268
  constructor(modelId, options = {}) {
1094
1269
  this.specificationVersion = "v3";
@@ -1532,195 +1707,27 @@ var WebLLMLanguageModel = class {
1532
1707
  ...options.abortSignal && !useWorker && { signal: options.abortSignal }
1533
1708
  };
1534
1709
  const response = await engine.chat.completions.create(streamingRequest);
1535
- const fenceDetector = new ToolCallFenceDetector();
1536
- let accumulatedText = "";
1537
- let currentToolCallId = null;
1538
- let toolInputStartEmitted = false;
1539
- let accumulatedFenceContent = "";
1540
- let argumentsStreamState = createArgumentsStreamState();
1541
- let insideFence = false;
1542
- for await (const chunk of response) {
1543
- const choice = chunk.choices[0];
1544
- if (!choice) continue;
1545
- if (choice.delta.content) {
1546
- const delta = choice.delta.content;
1547
- accumulatedText += delta;
1548
- fenceDetector.addChunk(delta);
1549
- while (fenceDetector.hasContent()) {
1550
- const wasInsideFence = insideFence;
1551
- const result = fenceDetector.detectStreamingFence();
1552
- insideFence = result.inFence;
1553
- let madeProgress = false;
1554
- if (!wasInsideFence && result.inFence) {
1555
- if (result.safeContent) {
1556
- emitTextDelta(result.safeContent);
1557
- madeProgress = true;
1558
- }
1559
- currentToolCallId = `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
1560
- toolInputStartEmitted = false;
1561
- accumulatedFenceContent = "";
1562
- argumentsStreamState = createArgumentsStreamState();
1563
- insideFence = true;
1564
- continue;
1565
- }
1566
- if (result.completeFence) {
1567
- madeProgress = true;
1568
- if (result.safeContent) {
1569
- accumulatedFenceContent += result.safeContent;
1570
- }
1571
- if (toolInputStartEmitted && currentToolCallId) {
1572
- const delta2 = extractArgumentsDelta(
1573
- accumulatedFenceContent,
1574
- argumentsStreamState
1575
- );
1576
- if (delta2.length > 0) {
1577
- controller.enqueue({
1578
- type: "tool-input-delta",
1579
- id: currentToolCallId,
1580
- delta: delta2
1581
- });
1582
- }
1583
- }
1584
- const parsed = parseJsonFunctionCalls(result.completeFence);
1585
- const parsedToolCalls = parsed.toolCalls;
1586
- const selectedToolCalls = parsedToolCalls.slice(0, 1);
1587
- if (selectedToolCalls.length === 0) {
1588
- emitTextDelta(result.completeFence);
1589
- if (result.textAfterFence) {
1590
- emitTextDelta(result.textAfterFence);
1591
- }
1592
- currentToolCallId = null;
1593
- toolInputStartEmitted = false;
1594
- accumulatedFenceContent = "";
1595
- argumentsStreamState = createArgumentsStreamState();
1596
- insideFence = false;
1597
- continue;
1598
- }
1599
- if (selectedToolCalls.length > 0 && currentToolCallId) {
1600
- selectedToolCalls[0].toolCallId = currentToolCallId;
1601
- }
1602
- for (const [index, call] of selectedToolCalls.entries()) {
1603
- const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
1604
- const toolName = call.toolName;
1605
- const argsJson = JSON.stringify(call.args ?? {});
1606
- if (toolCallId === currentToolCallId) {
1607
- if (!toolInputStartEmitted) {
1608
- controller.enqueue({
1609
- type: "tool-input-start",
1610
- id: toolCallId,
1611
- toolName
1612
- });
1613
- toolInputStartEmitted = true;
1614
- }
1615
- const delta2 = extractArgumentsDelta(
1616
- accumulatedFenceContent,
1617
- argumentsStreamState
1618
- );
1619
- if (delta2.length > 0) {
1620
- controller.enqueue({
1621
- type: "tool-input-delta",
1622
- id: toolCallId,
1623
- delta: delta2
1624
- });
1625
- }
1626
- } else {
1627
- controller.enqueue({
1628
- type: "tool-input-start",
1629
- id: toolCallId,
1630
- toolName
1631
- });
1632
- if (argsJson.length > 0) {
1633
- controller.enqueue({
1634
- type: "tool-input-delta",
1635
- id: toolCallId,
1636
- delta: argsJson
1637
- });
1638
- }
1639
- }
1640
- controller.enqueue({
1641
- type: "tool-input-end",
1642
- id: toolCallId
1643
- });
1644
- controller.enqueue({
1645
- type: "tool-call",
1646
- toolCallId,
1647
- toolName,
1648
- input: argsJson,
1649
- providerExecuted: false
1650
- });
1651
- }
1652
- if (result.textAfterFence) {
1653
- emitTextDelta(result.textAfterFence);
1654
- }
1655
- madeProgress = true;
1656
- currentToolCallId = null;
1657
- toolInputStartEmitted = false;
1658
- accumulatedFenceContent = "";
1659
- argumentsStreamState = createArgumentsStreamState();
1660
- insideFence = false;
1661
- continue;
1662
- }
1663
- if (insideFence) {
1664
- if (result.safeContent) {
1665
- accumulatedFenceContent += result.safeContent;
1666
- madeProgress = true;
1667
- const toolName = extractToolName(accumulatedFenceContent);
1668
- if (toolName && !toolInputStartEmitted && currentToolCallId) {
1669
- controller.enqueue({
1670
- type: "tool-input-start",
1671
- id: currentToolCallId,
1672
- toolName
1673
- });
1674
- toolInputStartEmitted = true;
1675
- }
1676
- if (toolInputStartEmitted && currentToolCallId) {
1677
- const delta2 = extractArgumentsDelta(
1678
- accumulatedFenceContent,
1679
- argumentsStreamState
1680
- );
1681
- if (delta2.length > 0) {
1682
- controller.enqueue({
1683
- type: "tool-input-delta",
1684
- id: currentToolCallId,
1685
- delta: delta2
1686
- });
1687
- }
1688
- }
1689
- }
1690
- continue;
1691
- }
1692
- if (!insideFence && result.safeContent) {
1693
- emitTextDelta(result.safeContent);
1694
- madeProgress = true;
1695
- }
1696
- if (!madeProgress) {
1697
- break;
1698
- }
1699
- }
1710
+ let lastUsage;
1711
+ let isAbort = false;
1712
+ const chunks = (async function* () {
1713
+ for await (const chunk of response) {
1714
+ const choice = chunk.choices[0];
1715
+ if (!choice) continue;
1716
+ if (choice.delta.content) yield choice.delta.content;
1717
+ if (chunk.usage) lastUsage = chunk.usage;
1718
+ if (choice.finish_reason === "abort") isAbort = true;
1700
1719
  }
1701
- if (choice.finish_reason) {
1702
- if (fenceDetector.hasContent()) {
1703
- emitTextDelta(fenceDetector.getBuffer());
1704
- fenceDetector.clearBuffer();
1705
- }
1706
- let finishReason = {
1707
- unified: "stop",
1708
- raw: "stop"
1709
- };
1710
- if (choice.finish_reason === "abort") {
1711
- finishReason = { unified: "other", raw: "abort" };
1712
- } else {
1713
- const { toolCalls } = parseJsonFunctionCalls(accumulatedText);
1714
- if (toolCalls.length > 0) {
1715
- finishReason = { unified: "tool-calls", raw: "tool-calls" };
1716
- }
1717
- }
1718
- finishStream(finishReason, chunk.usage);
1719
- }
1720
- }
1721
- if (!finished) {
1722
- finishStream({ unified: "stop", raw: "stop" });
1720
+ })();
1721
+ const result = await processToolCallStream(
1722
+ chunks,
1723
+ emitTextDelta,
1724
+ controller
1725
+ );
1726
+ if (result.trailingText) {
1727
+ emitTextDelta(result.trailingText);
1723
1728
  }
1729
+ const finishReason = isAbort ? { unified: "other", raw: "abort" } : result.toolCallDetected ? { unified: "tool-calls", raw: "tool-calls" } : { unified: "stop", raw: "stop" };
1730
+ finishStream(finishReason, lastUsage);
1724
1731
  } catch (error) {
1725
1732
  controller.error(error);
1726
1733
  } finally {
@@ -1740,7 +1747,7 @@ var WebLLMLanguageModel = class {
1740
1747
  }
1741
1748
  };
1742
1749
 
1743
- // src/web-llm-embedding-model.ts
1750
+ // src/embedding/web-llm-embedding-model.ts
1744
1751
  var import_web_llm2 = require("@mlc-ai/web-llm");
1745
1752
  var WebLLMEmbeddingModel = class {
1746
1753
  constructor(modelId, options = {}) {