@browser-ai/web-llm 2.1.4 → 2.1.6

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";
@@ -1242,10 +1417,12 @@ var WebLLMLanguageModel = class {
1242
1417
  top_p: topP,
1243
1418
  seed
1244
1419
  };
1245
- if (providerOptions?.extra_body) {
1420
+ const webLLMOptions = providerOptions?.[this.provider];
1421
+ const extraBody = webLLMOptions?.extra_body;
1422
+ if (extraBody) {
1246
1423
  requestOptions.extra_body = {
1247
- enable_thinking: providerOptions.extra_body.enable_thinking,
1248
- enable_latency_breakdown: providerOptions.extra_body.enable_latency_breakdown
1424
+ enable_thinking: extraBody.enable_thinking,
1425
+ enable_latency_breakdown: extraBody.enable_latency_breakdown
1249
1426
  };
1250
1427
  }
1251
1428
  if (responseFormat?.type === "json") {
@@ -1341,7 +1518,7 @@ var WebLLMLanguageModel = class {
1341
1518
  reasoning: void 0
1342
1519
  }
1343
1520
  },
1344
- request: { body: { messages: promptMessages, ...requestOptions } },
1521
+ request: { body: { ...requestOptions, messages: promptMessages } },
1345
1522
  warnings
1346
1523
  };
1347
1524
  }
@@ -1377,7 +1554,7 @@ var WebLLMLanguageModel = class {
1377
1554
  total: response.usage?.total_tokens
1378
1555
  }
1379
1556
  },
1380
- request: { body: { messages: promptMessages, ...requestOptions } },
1557
+ request: { body: { ...requestOptions, messages: promptMessages } },
1381
1558
  warnings
1382
1559
  };
1383
1560
  } catch (error) {
@@ -1532,195 +1709,27 @@ var WebLLMLanguageModel = class {
1532
1709
  ...options.abortSignal && !useWorker && { signal: options.abortSignal }
1533
1710
  };
1534
1711
  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
- }
1712
+ let lastUsage;
1713
+ let isAbort = false;
1714
+ const chunks = (async function* () {
1715
+ for await (const chunk of response) {
1716
+ const choice = chunk.choices[0];
1717
+ if (!choice) continue;
1718
+ if (choice.delta.content) yield choice.delta.content;
1719
+ if (chunk.usage) lastUsage = chunk.usage;
1720
+ if (choice.finish_reason === "abort") isAbort = true;
1700
1721
  }
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" });
1722
+ })();
1723
+ const result = await processToolCallStream(
1724
+ chunks,
1725
+ emitTextDelta,
1726
+ controller
1727
+ );
1728
+ if (result.trailingText) {
1729
+ emitTextDelta(result.trailingText);
1723
1730
  }
1731
+ const finishReason = isAbort ? { unified: "other", raw: "abort" } : result.toolCallDetected ? { unified: "tool-calls", raw: "tool-calls" } : { unified: "stop", raw: "stop" };
1732
+ finishStream(finishReason, lastUsage);
1724
1733
  } catch (error) {
1725
1734
  controller.error(error);
1726
1735
  } finally {
@@ -1735,12 +1744,12 @@ var WebLLMLanguageModel = class {
1735
1744
  });
1736
1745
  return {
1737
1746
  stream,
1738
- request: { body: { messages: promptMessages, ...requestOptions } }
1747
+ request: { body: { ...requestOptions, messages: promptMessages } }
1739
1748
  };
1740
1749
  }
1741
1750
  };
1742
1751
 
1743
- // src/web-llm-embedding-model.ts
1752
+ // src/embedding/web-llm-embedding-model.ts
1744
1753
  var import_web_llm2 = require("@mlc-ai/web-llm");
1745
1754
  var WebLLMEmbeddingModel = class {
1746
1755
  constructor(modelId, options = {}) {