@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.mjs CHANGED
@@ -773,7 +773,267 @@ var ToolCallFenceDetector = class {
773
773
  }
774
774
  };
775
775
 
776
- // src/convert-to-webllm-messages.tsx
776
+ // ../shared/src/streaming/tool-call-stream-utils.ts
777
+ function extractToolName(content) {
778
+ const jsonMatch = content.match(/\{\s*"name"\s*:\s*"([^"]+)"/);
779
+ if (jsonMatch) {
780
+ return jsonMatch[1];
781
+ }
782
+ return null;
783
+ }
784
+ var ARGUMENTS_FIELD_REGEX = /"arguments"\s*:\s*/g;
785
+ var ARGUMENTS_SEARCH_OVERLAP = 32;
786
+ function createArgumentsStreamState() {
787
+ return {
788
+ searchFrom: 0,
789
+ valueStartIndex: null,
790
+ parseIndex: 0,
791
+ started: false,
792
+ depth: 0,
793
+ inString: false,
794
+ escaped: false,
795
+ complete: false
796
+ };
797
+ }
798
+ function extractArgumentsDelta(content, state) {
799
+ if (state.complete) {
800
+ return "";
801
+ }
802
+ if (state.valueStartIndex === null) {
803
+ ARGUMENTS_FIELD_REGEX.lastIndex = state.searchFrom;
804
+ const match = ARGUMENTS_FIELD_REGEX.exec(content);
805
+ ARGUMENTS_FIELD_REGEX.lastIndex = 0;
806
+ if (!match || match.index === void 0) {
807
+ state.searchFrom = Math.max(0, content.length - ARGUMENTS_SEARCH_OVERLAP);
808
+ return "";
809
+ }
810
+ state.valueStartIndex = match.index + match[0].length;
811
+ state.parseIndex = state.valueStartIndex;
812
+ state.searchFrom = state.valueStartIndex;
813
+ }
814
+ if (state.parseIndex >= content.length) {
815
+ return "";
816
+ }
817
+ let delta = "";
818
+ for (let i = state.parseIndex; i < content.length; i++) {
819
+ const char = content[i];
820
+ delta += char;
821
+ if (!state.started) {
822
+ if (!/\s/.test(char)) {
823
+ state.started = true;
824
+ if (char === "{" || char === "[") {
825
+ state.depth = 1;
826
+ }
827
+ }
828
+ continue;
829
+ }
830
+ if (state.escaped) {
831
+ state.escaped = false;
832
+ continue;
833
+ }
834
+ if (char === "\\") {
835
+ state.escaped = true;
836
+ continue;
837
+ }
838
+ if (char === '"') {
839
+ state.inString = !state.inString;
840
+ continue;
841
+ }
842
+ if (!state.inString) {
843
+ if (char === "{" || char === "[") {
844
+ state.depth += 1;
845
+ } else if (char === "}" || char === "]") {
846
+ if (state.depth > 0) {
847
+ state.depth -= 1;
848
+ if (state.depth === 0) {
849
+ state.parseIndex = i + 1;
850
+ state.complete = true;
851
+ return delta;
852
+ }
853
+ }
854
+ }
855
+ }
856
+ }
857
+ state.parseIndex = content.length;
858
+ return delta;
859
+ }
860
+
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
777
1037
  function convertToolResultOutput(output) {
778
1038
  switch (output.type) {
779
1039
  case "text":
@@ -912,7 +1172,7 @@ function convertToWebLLMMessages(prompt) {
912
1172
  return messages;
913
1173
  }
914
1174
 
915
- // src/web-llm-language-model.ts
1175
+ // src/chat/web-llm-language-model.ts
916
1176
  import {
917
1177
  CreateWebWorkerMLCEngine,
918
1178
  MLCEngine
@@ -975,64 +1235,7 @@ function doesBrowserSupportWebLLM() {
975
1235
  return checkWebGPU();
976
1236
  }
977
1237
 
978
- // src/web-llm-language-model.ts
979
- function extractToolName(content) {
980
- const jsonMatch = content.match(/\{\s*"name"\s*:\s*"([^"]+)"/);
981
- if (jsonMatch) {
982
- return jsonMatch[1];
983
- }
984
- return null;
985
- }
986
- function extractArgumentsContent(content) {
987
- const match = content.match(/"arguments"\s*:\s*/);
988
- if (!match || match.index === void 0) {
989
- return "";
990
- }
991
- const startIndex = match.index + match[0].length;
992
- let result = "";
993
- let depth = 0;
994
- let inString = false;
995
- let escaped = false;
996
- let started = false;
997
- for (let i = startIndex; i < content.length; i++) {
998
- const char = content[i];
999
- result += char;
1000
- if (!started) {
1001
- if (!/\s/.test(char)) {
1002
- started = true;
1003
- if (char === "{" || char === "[") {
1004
- depth = 1;
1005
- }
1006
- }
1007
- continue;
1008
- }
1009
- if (escaped) {
1010
- escaped = false;
1011
- continue;
1012
- }
1013
- if (char === "\\") {
1014
- escaped = true;
1015
- continue;
1016
- }
1017
- if (char === '"') {
1018
- inString = !inString;
1019
- continue;
1020
- }
1021
- if (!inString) {
1022
- if (char === "{" || char === "[") {
1023
- depth += 1;
1024
- } else if (char === "}" || char === "]") {
1025
- if (depth > 0) {
1026
- depth -= 1;
1027
- if (depth === 0) {
1028
- break;
1029
- }
1030
- }
1031
- }
1032
- }
1033
- }
1034
- return result;
1035
- }
1238
+ // src/chat/web-llm-language-model.ts
1036
1239
  var WebLLMLanguageModel = class {
1037
1240
  constructor(modelId, options = {}) {
1038
1241
  this.specificationVersion = "v3";
@@ -1476,208 +1679,27 @@ var WebLLMLanguageModel = class {
1476
1679
  ...options.abortSignal && !useWorker && { signal: options.abortSignal }
1477
1680
  };
1478
1681
  const response = await engine.chat.completions.create(streamingRequest);
1479
- const fenceDetector = new ToolCallFenceDetector();
1480
- let accumulatedText = "";
1481
- let currentToolCallId = null;
1482
- let toolInputStartEmitted = false;
1483
- let accumulatedFenceContent = "";
1484
- let streamedArgumentsLength = 0;
1485
- let insideFence = false;
1486
- for await (const chunk of response) {
1487
- const choice = chunk.choices[0];
1488
- if (!choice) continue;
1489
- if (choice.delta.content) {
1490
- const delta = choice.delta.content;
1491
- accumulatedText += delta;
1492
- fenceDetector.addChunk(delta);
1493
- while (fenceDetector.hasContent()) {
1494
- const wasInsideFence = insideFence;
1495
- const result = fenceDetector.detectStreamingFence();
1496
- insideFence = result.inFence;
1497
- let madeProgress = false;
1498
- if (!wasInsideFence && result.inFence) {
1499
- if (result.safeContent) {
1500
- emitTextDelta(result.safeContent);
1501
- madeProgress = true;
1502
- }
1503
- currentToolCallId = `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
1504
- toolInputStartEmitted = false;
1505
- accumulatedFenceContent = "";
1506
- streamedArgumentsLength = 0;
1507
- insideFence = true;
1508
- continue;
1509
- }
1510
- if (result.completeFence) {
1511
- madeProgress = true;
1512
- if (result.safeContent) {
1513
- accumulatedFenceContent += result.safeContent;
1514
- }
1515
- if (toolInputStartEmitted && currentToolCallId) {
1516
- const argsContent = extractArgumentsContent(
1517
- accumulatedFenceContent
1518
- );
1519
- if (argsContent.length > streamedArgumentsLength) {
1520
- const delta2 = argsContent.slice(streamedArgumentsLength);
1521
- streamedArgumentsLength = argsContent.length;
1522
- if (delta2.length > 0) {
1523
- controller.enqueue({
1524
- type: "tool-input-delta",
1525
- id: currentToolCallId,
1526
- delta: delta2
1527
- });
1528
- }
1529
- }
1530
- }
1531
- const parsed = parseJsonFunctionCalls(result.completeFence);
1532
- const parsedToolCalls = parsed.toolCalls;
1533
- const selectedToolCalls = parsedToolCalls.slice(0, 1);
1534
- if (selectedToolCalls.length === 0) {
1535
- emitTextDelta(result.completeFence);
1536
- if (result.textAfterFence) {
1537
- emitTextDelta(result.textAfterFence);
1538
- }
1539
- currentToolCallId = null;
1540
- toolInputStartEmitted = false;
1541
- accumulatedFenceContent = "";
1542
- streamedArgumentsLength = 0;
1543
- insideFence = false;
1544
- continue;
1545
- }
1546
- if (selectedToolCalls.length > 0 && currentToolCallId) {
1547
- selectedToolCalls[0].toolCallId = currentToolCallId;
1548
- }
1549
- for (const [index, call] of selectedToolCalls.entries()) {
1550
- const toolCallId = index === 0 && currentToolCallId ? currentToolCallId : call.toolCallId;
1551
- const toolName = call.toolName;
1552
- const argsJson = JSON.stringify(call.args ?? {});
1553
- if (toolCallId === currentToolCallId) {
1554
- if (!toolInputStartEmitted) {
1555
- controller.enqueue({
1556
- type: "tool-input-start",
1557
- id: toolCallId,
1558
- toolName
1559
- });
1560
- toolInputStartEmitted = true;
1561
- }
1562
- const argsContent = extractArgumentsContent(
1563
- accumulatedFenceContent
1564
- );
1565
- if (argsContent.length > streamedArgumentsLength) {
1566
- const delta2 = argsContent.slice(
1567
- streamedArgumentsLength
1568
- );
1569
- streamedArgumentsLength = argsContent.length;
1570
- if (delta2.length > 0) {
1571
- controller.enqueue({
1572
- type: "tool-input-delta",
1573
- id: toolCallId,
1574
- delta: delta2
1575
- });
1576
- }
1577
- }
1578
- } else {
1579
- controller.enqueue({
1580
- type: "tool-input-start",
1581
- id: toolCallId,
1582
- toolName
1583
- });
1584
- if (argsJson.length > 0) {
1585
- controller.enqueue({
1586
- type: "tool-input-delta",
1587
- id: toolCallId,
1588
- delta: argsJson
1589
- });
1590
- }
1591
- }
1592
- controller.enqueue({
1593
- type: "tool-input-end",
1594
- id: toolCallId
1595
- });
1596
- controller.enqueue({
1597
- type: "tool-call",
1598
- toolCallId,
1599
- toolName,
1600
- input: argsJson,
1601
- providerExecuted: false
1602
- });
1603
- }
1604
- if (result.textAfterFence) {
1605
- emitTextDelta(result.textAfterFence);
1606
- }
1607
- madeProgress = true;
1608
- currentToolCallId = null;
1609
- toolInputStartEmitted = false;
1610
- accumulatedFenceContent = "";
1611
- streamedArgumentsLength = 0;
1612
- insideFence = false;
1613
- continue;
1614
- }
1615
- if (insideFence) {
1616
- if (result.safeContent) {
1617
- accumulatedFenceContent += result.safeContent;
1618
- madeProgress = true;
1619
- const toolName = extractToolName(accumulatedFenceContent);
1620
- if (toolName && !toolInputStartEmitted && currentToolCallId) {
1621
- controller.enqueue({
1622
- type: "tool-input-start",
1623
- id: currentToolCallId,
1624
- toolName
1625
- });
1626
- toolInputStartEmitted = true;
1627
- }
1628
- if (toolInputStartEmitted && currentToolCallId) {
1629
- const argsContent = extractArgumentsContent(
1630
- accumulatedFenceContent
1631
- );
1632
- if (argsContent.length > streamedArgumentsLength) {
1633
- const delta2 = argsContent.slice(
1634
- streamedArgumentsLength
1635
- );
1636
- streamedArgumentsLength = argsContent.length;
1637
- if (delta2.length > 0) {
1638
- controller.enqueue({
1639
- type: "tool-input-delta",
1640
- id: currentToolCallId,
1641
- delta: delta2
1642
- });
1643
- }
1644
- }
1645
- }
1646
- }
1647
- continue;
1648
- }
1649
- if (!insideFence && result.safeContent) {
1650
- emitTextDelta(result.safeContent);
1651
- madeProgress = true;
1652
- }
1653
- if (!madeProgress) {
1654
- break;
1655
- }
1656
- }
1657
- }
1658
- if (choice.finish_reason) {
1659
- if (fenceDetector.hasContent()) {
1660
- emitTextDelta(fenceDetector.getBuffer());
1661
- fenceDetector.clearBuffer();
1662
- }
1663
- let finishReason = {
1664
- unified: "stop",
1665
- raw: "stop"
1666
- };
1667
- if (choice.finish_reason === "abort") {
1668
- finishReason = { unified: "other", raw: "abort" };
1669
- } else {
1670
- const { toolCalls } = parseJsonFunctionCalls(accumulatedText);
1671
- if (toolCalls.length > 0) {
1672
- finishReason = { unified: "tool-calls", raw: "tool-calls" };
1673
- }
1674
- }
1675
- finishStream(finishReason, chunk.usage);
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;
1676
1691
  }
1692
+ })();
1693
+ const result = await processToolCallStream(
1694
+ chunks,
1695
+ emitTextDelta,
1696
+ controller
1697
+ );
1698
+ if (result.trailingText) {
1699
+ emitTextDelta(result.trailingText);
1677
1700
  }
1678
- if (!finished) {
1679
- finishStream({ unified: "stop", raw: "stop" });
1680
- }
1701
+ const finishReason = isAbort ? { unified: "other", raw: "abort" } : result.toolCallDetected ? { unified: "tool-calls", raw: "tool-calls" } : { unified: "stop", raw: "stop" };
1702
+ finishStream(finishReason, lastUsage);
1681
1703
  } catch (error) {
1682
1704
  controller.error(error);
1683
1705
  } finally {
@@ -1697,7 +1719,7 @@ var WebLLMLanguageModel = class {
1697
1719
  }
1698
1720
  };
1699
1721
 
1700
- // src/web-llm-embedding-model.ts
1722
+ // src/embedding/web-llm-embedding-model.ts
1701
1723
  import {
1702
1724
  CreateWebWorkerMLCEngine as CreateWebWorkerMLCEngine2,
1703
1725
  MLCEngine as MLCEngine2