@browser-ai/web-llm 2.1.3 → 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
@@ -804,7 +804,267 @@ var ToolCallFenceDetector = class {
804
804
  }
805
805
  };
806
806
 
807
- // src/convert-to-webllm-messages.tsx
807
+ // ../shared/src/streaming/tool-call-stream-utils.ts
808
+ function extractToolName(content) {
809
+ const jsonMatch = content.match(/\{\s*"name"\s*:\s*"([^"]+)"/);
810
+ if (jsonMatch) {
811
+ return jsonMatch[1];
812
+ }
813
+ return null;
814
+ }
815
+ var ARGUMENTS_FIELD_REGEX = /"arguments"\s*:\s*/g;
816
+ var ARGUMENTS_SEARCH_OVERLAP = 32;
817
+ function createArgumentsStreamState() {
818
+ return {
819
+ searchFrom: 0,
820
+ valueStartIndex: null,
821
+ parseIndex: 0,
822
+ started: false,
823
+ depth: 0,
824
+ inString: false,
825
+ escaped: false,
826
+ complete: false
827
+ };
828
+ }
829
+ function extractArgumentsDelta(content, state) {
830
+ if (state.complete) {
831
+ return "";
832
+ }
833
+ if (state.valueStartIndex === null) {
834
+ ARGUMENTS_FIELD_REGEX.lastIndex = state.searchFrom;
835
+ const match = ARGUMENTS_FIELD_REGEX.exec(content);
836
+ ARGUMENTS_FIELD_REGEX.lastIndex = 0;
837
+ if (!match || match.index === void 0) {
838
+ state.searchFrom = Math.max(0, content.length - ARGUMENTS_SEARCH_OVERLAP);
839
+ return "";
840
+ }
841
+ state.valueStartIndex = match.index + match[0].length;
842
+ state.parseIndex = state.valueStartIndex;
843
+ state.searchFrom = state.valueStartIndex;
844
+ }
845
+ if (state.parseIndex >= content.length) {
846
+ return "";
847
+ }
848
+ let delta = "";
849
+ for (let i = state.parseIndex; i < content.length; i++) {
850
+ const char = content[i];
851
+ delta += char;
852
+ if (!state.started) {
853
+ if (!/\s/.test(char)) {
854
+ state.started = true;
855
+ if (char === "{" || char === "[") {
856
+ state.depth = 1;
857
+ }
858
+ }
859
+ continue;
860
+ }
861
+ if (state.escaped) {
862
+ state.escaped = false;
863
+ continue;
864
+ }
865
+ if (char === "\\") {
866
+ state.escaped = true;
867
+ continue;
868
+ }
869
+ if (char === '"') {
870
+ state.inString = !state.inString;
871
+ continue;
872
+ }
873
+ if (!state.inString) {
874
+ if (char === "{" || char === "[") {
875
+ state.depth += 1;
876
+ } else if (char === "}" || char === "]") {
877
+ if (state.depth > 0) {
878
+ state.depth -= 1;
879
+ if (state.depth === 0) {
880
+ state.parseIndex = i + 1;
881
+ state.complete = true;
882
+ return delta;
883
+ }
884
+ }
885
+ }
886
+ }
887
+ }
888
+ state.parseIndex = content.length;
889
+ return delta;
890
+ }
891
+
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
808
1068
  function convertToolResultOutput(output) {
809
1069
  switch (output.type) {
810
1070
  case "text":
@@ -943,7 +1203,7 @@ function convertToWebLLMMessages(prompt) {
943
1203
  return messages;
944
1204
  }
945
1205
 
946
- // src/web-llm-language-model.ts
1206
+ // src/chat/web-llm-language-model.ts
947
1207
  var import_web_llm = require("@mlc-ai/web-llm");
948
1208
 
949
1209
  // src/utils/prompt-utils.ts
@@ -1003,64 +1263,7 @@ function doesBrowserSupportWebLLM() {
1003
1263
  return checkWebGPU();
1004
1264
  }
1005
1265
 
1006
- // src/web-llm-language-model.ts
1007
- function extractToolName(content) {
1008
- const jsonMatch = content.match(/\{\s*"name"\s*:\s*"([^"]+)"/);
1009
- if (jsonMatch) {
1010
- return jsonMatch[1];
1011
- }
1012
- return null;
1013
- }
1014
- function extractArgumentsContent(content) {
1015
- const match = content.match(/"arguments"\s*:\s*/);
1016
- if (!match || match.index === void 0) {
1017
- return "";
1018
- }
1019
- const startIndex = match.index + match[0].length;
1020
- let result = "";
1021
- let depth = 0;
1022
- let inString = false;
1023
- let escaped = false;
1024
- let started = false;
1025
- for (let i = startIndex; i < content.length; i++) {
1026
- const char = content[i];
1027
- result += char;
1028
- if (!started) {
1029
- if (!/\s/.test(char)) {
1030
- started = true;
1031
- if (char === "{" || char === "[") {
1032
- depth = 1;
1033
- }
1034
- }
1035
- continue;
1036
- }
1037
- if (escaped) {
1038
- escaped = false;
1039
- continue;
1040
- }
1041
- if (char === "\\") {
1042
- escaped = true;
1043
- continue;
1044
- }
1045
- if (char === '"') {
1046
- inString = !inString;
1047
- continue;
1048
- }
1049
- if (!inString) {
1050
- if (char === "{" || char === "[") {
1051
- depth += 1;
1052
- } else if (char === "}" || char === "]") {
1053
- if (depth > 0) {
1054
- depth -= 1;
1055
- if (depth === 0) {
1056
- break;
1057
- }
1058
- }
1059
- }
1060
- }
1061
- }
1062
- return result;
1063
- }
1266
+ // src/chat/web-llm-language-model.ts
1064
1267
  var WebLLMLanguageModel = class {
1065
1268
  constructor(modelId, options = {}) {
1066
1269
  this.specificationVersion = "v3";
@@ -1504,208 +1707,27 @@ var WebLLMLanguageModel = class {
1504
1707
  ...options.abortSignal && !useWorker && { signal: options.abortSignal }
1505
1708
  };
1506
1709
  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 streamedArgumentsLength = 0;
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
- streamedArgumentsLength = 0;
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 argsContent = extractArgumentsContent(
1545
- accumulatedFenceContent
1546
- );
1547
- if (argsContent.length > streamedArgumentsLength) {
1548
- const delta2 = argsContent.slice(streamedArgumentsLength);
1549
- streamedArgumentsLength = argsContent.length;
1550
- if (delta2.length > 0) {
1551
- controller.enqueue({
1552
- type: "tool-input-delta",
1553
- id: currentToolCallId,
1554
- delta: delta2
1555
- });
1556
- }
1557
- }
1558
- }
1559
- const parsed = parseJsonFunctionCalls(result.completeFence);
1560
- const parsedToolCalls = parsed.toolCalls;
1561
- const selectedToolCalls = parsedToolCalls.slice(0, 1);
1562
- if (selectedToolCalls.length === 0) {
1563
- emitTextDelta(result.completeFence);
1564
- if (result.textAfterFence) {
1565
- emitTextDelta(result.textAfterFence);
1566
- }
1567
- currentToolCallId = null;
1568
- toolInputStartEmitted = false;
1569
- accumulatedFenceContent = "";
1570
- streamedArgumentsLength = 0;
1571
- insideFence = false;
1572
- continue;
1573
- }
1574
- if (selectedToolCalls.length > 0 && currentToolCallId) {
1575
- selectedToolCalls[0].toolCallId = currentToolCallId;
1576
- }
1577
- for (const [index, call] of selectedToolCalls.entries()) {
1578
- const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
1579
- const toolName = call.toolName;
1580
- const argsJson = JSON.stringify(call.args ?? {});
1581
- if (toolCallId === currentToolCallId) {
1582
- if (!toolInputStartEmitted) {
1583
- controller.enqueue({
1584
- type: "tool-input-start",
1585
- id: toolCallId,
1586
- toolName
1587
- });
1588
- toolInputStartEmitted = true;
1589
- }
1590
- const argsContent = extractArgumentsContent(
1591
- accumulatedFenceContent
1592
- );
1593
- if (argsContent.length > streamedArgumentsLength) {
1594
- const delta2 = argsContent.slice(
1595
- streamedArgumentsLength
1596
- );
1597
- streamedArgumentsLength = argsContent.length;
1598
- if (delta2.length > 0) {
1599
- controller.enqueue({
1600
- type: "tool-input-delta",
1601
- id: toolCallId,
1602
- delta: delta2
1603
- });
1604
- }
1605
- }
1606
- } else {
1607
- controller.enqueue({
1608
- type: "tool-input-start",
1609
- id: toolCallId,
1610
- toolName
1611
- });
1612
- if (argsJson.length > 0) {
1613
- controller.enqueue({
1614
- type: "tool-input-delta",
1615
- id: toolCallId,
1616
- delta: argsJson
1617
- });
1618
- }
1619
- }
1620
- controller.enqueue({
1621
- type: "tool-input-end",
1622
- id: toolCallId
1623
- });
1624
- controller.enqueue({
1625
- type: "tool-call",
1626
- toolCallId,
1627
- toolName,
1628
- input: argsJson,
1629
- providerExecuted: false
1630
- });
1631
- }
1632
- if (result.textAfterFence) {
1633
- emitTextDelta(result.textAfterFence);
1634
- }
1635
- madeProgress = true;
1636
- currentToolCallId = null;
1637
- toolInputStartEmitted = false;
1638
- accumulatedFenceContent = "";
1639
- streamedArgumentsLength = 0;
1640
- insideFence = false;
1641
- continue;
1642
- }
1643
- if (insideFence) {
1644
- if (result.safeContent) {
1645
- accumulatedFenceContent += result.safeContent;
1646
- madeProgress = true;
1647
- const toolName = extractToolName(accumulatedFenceContent);
1648
- if (toolName && !toolInputStartEmitted && currentToolCallId) {
1649
- controller.enqueue({
1650
- type: "tool-input-start",
1651
- id: currentToolCallId,
1652
- toolName
1653
- });
1654
- toolInputStartEmitted = true;
1655
- }
1656
- if (toolInputStartEmitted && currentToolCallId) {
1657
- const argsContent = extractArgumentsContent(
1658
- accumulatedFenceContent
1659
- );
1660
- if (argsContent.length > streamedArgumentsLength) {
1661
- const delta2 = argsContent.slice(
1662
- streamedArgumentsLength
1663
- );
1664
- streamedArgumentsLength = argsContent.length;
1665
- if (delta2.length > 0) {
1666
- controller.enqueue({
1667
- type: "tool-input-delta",
1668
- id: currentToolCallId,
1669
- delta: delta2
1670
- });
1671
- }
1672
- }
1673
- }
1674
- }
1675
- continue;
1676
- }
1677
- if (!insideFence && result.safeContent) {
1678
- emitTextDelta(result.safeContent);
1679
- madeProgress = true;
1680
- }
1681
- if (!madeProgress) {
1682
- break;
1683
- }
1684
- }
1685
- }
1686
- if (choice.finish_reason) {
1687
- if (fenceDetector.hasContent()) {
1688
- emitTextDelta(fenceDetector.getBuffer());
1689
- fenceDetector.clearBuffer();
1690
- }
1691
- let finishReason = {
1692
- unified: "stop",
1693
- raw: "stop"
1694
- };
1695
- if (choice.finish_reason === "abort") {
1696
- finishReason = { unified: "other", raw: "abort" };
1697
- } else {
1698
- const { toolCalls } = parseJsonFunctionCalls(accumulatedText);
1699
- if (toolCalls.length > 0) {
1700
- finishReason = { unified: "tool-calls", raw: "tool-calls" };
1701
- }
1702
- }
1703
- finishStream(finishReason, chunk.usage);
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;
1704
1719
  }
1720
+ })();
1721
+ const result = await processToolCallStream(
1722
+ chunks,
1723
+ emitTextDelta,
1724
+ controller
1725
+ );
1726
+ if (result.trailingText) {
1727
+ emitTextDelta(result.trailingText);
1705
1728
  }
1706
- if (!finished) {
1707
- finishStream({ unified: "stop", raw: "stop" });
1708
- }
1729
+ const finishReason = isAbort ? { unified: "other", raw: "abort" } : result.toolCallDetected ? { unified: "tool-calls", raw: "tool-calls" } : { unified: "stop", raw: "stop" };
1730
+ finishStream(finishReason, lastUsage);
1709
1731
  } catch (error) {
1710
1732
  controller.error(error);
1711
1733
  } finally {
@@ -1725,7 +1747,7 @@ var WebLLMLanguageModel = class {
1725
1747
  }
1726
1748
  };
1727
1749
 
1728
- // src/web-llm-embedding-model.ts
1750
+ // src/embedding/web-llm-embedding-model.ts
1729
1751
  var import_web_llm2 = require("@mlc-ai/web-llm");
1730
1752
  var WebLLMEmbeddingModel = class {
1731
1753
  constructor(modelId, options = {}) {