@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.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";
@@ -1214,10 +1389,12 @@ var WebLLMLanguageModel = class {
1214
1389
  top_p: topP,
1215
1390
  seed
1216
1391
  };
1217
- if (providerOptions?.extra_body) {
1392
+ const webLLMOptions = providerOptions?.[this.provider];
1393
+ const extraBody = webLLMOptions?.extra_body;
1394
+ if (extraBody) {
1218
1395
  requestOptions.extra_body = {
1219
- enable_thinking: providerOptions.extra_body.enable_thinking,
1220
- enable_latency_breakdown: providerOptions.extra_body.enable_latency_breakdown
1396
+ enable_thinking: extraBody.enable_thinking,
1397
+ enable_latency_breakdown: extraBody.enable_latency_breakdown
1221
1398
  };
1222
1399
  }
1223
1400
  if (responseFormat?.type === "json") {
@@ -1313,7 +1490,7 @@ var WebLLMLanguageModel = class {
1313
1490
  reasoning: void 0
1314
1491
  }
1315
1492
  },
1316
- request: { body: { messages: promptMessages, ...requestOptions } },
1493
+ request: { body: { ...requestOptions, messages: promptMessages } },
1317
1494
  warnings
1318
1495
  };
1319
1496
  }
@@ -1349,7 +1526,7 @@ var WebLLMLanguageModel = class {
1349
1526
  total: response.usage?.total_tokens
1350
1527
  }
1351
1528
  },
1352
- request: { body: { messages: promptMessages, ...requestOptions } },
1529
+ request: { body: { ...requestOptions, messages: promptMessages } },
1353
1530
  warnings
1354
1531
  };
1355
1532
  } catch (error) {
@@ -1504,195 +1681,27 @@ var WebLLMLanguageModel = class {
1504
1681
  ...options.abortSignal && !useWorker && { signal: options.abortSignal }
1505
1682
  };
1506
1683
  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
- }
1684
+ let lastUsage;
1685
+ let isAbort = false;
1686
+ const chunks = (async function* () {
1687
+ for await (const chunk of response) {
1688
+ const choice = chunk.choices[0];
1689
+ if (!choice) continue;
1690
+ if (choice.delta.content) yield choice.delta.content;
1691
+ if (chunk.usage) lastUsage = chunk.usage;
1692
+ if (choice.finish_reason === "abort") isAbort = true;
1672
1693
  }
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" });
1694
+ })();
1695
+ const result = await processToolCallStream(
1696
+ chunks,
1697
+ emitTextDelta,
1698
+ controller
1699
+ );
1700
+ if (result.trailingText) {
1701
+ emitTextDelta(result.trailingText);
1695
1702
  }
1703
+ const finishReason = isAbort ? { unified: "other", raw: "abort" } : result.toolCallDetected ? { unified: "tool-calls", raw: "tool-calls" } : { unified: "stop", raw: "stop" };
1704
+ finishStream(finishReason, lastUsage);
1696
1705
  } catch (error) {
1697
1706
  controller.error(error);
1698
1707
  } finally {
@@ -1707,12 +1716,12 @@ var WebLLMLanguageModel = class {
1707
1716
  });
1708
1717
  return {
1709
1718
  stream,
1710
- request: { body: { messages: promptMessages, ...requestOptions } }
1719
+ request: { body: { ...requestOptions, messages: promptMessages } }
1711
1720
  };
1712
1721
  }
1713
1722
  };
1714
1723
 
1715
- // src/web-llm-embedding-model.ts
1724
+ // src/embedding/web-llm-embedding-model.ts
1716
1725
  import {
1717
1726
  CreateWebWorkerMLCEngine as CreateWebWorkerMLCEngine2,
1718
1727
  MLCEngine as MLCEngine2