@browser-ai/web-llm 2.0.3 → 2.0.4

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
@@ -346,7 +346,28 @@ var UnsupportedFunctionalityError = class extends (_b14 = AISDKError, _a14 = sym
346
346
  }
347
347
  };
348
348
 
349
- // src/tool-calling/build-json-system-prompt.ts
349
+ // ../shared/src/utils/tool-utils.ts
350
+ function isFunctionTool(tool) {
351
+ return tool.type === "function";
352
+ }
353
+
354
+ // ../shared/src/utils/warnings.ts
355
+ function createUnsupportedSettingWarning(feature, details) {
356
+ return {
357
+ type: "unsupported",
358
+ feature,
359
+ details
360
+ };
361
+ }
362
+ function createUnsupportedToolWarning(tool, details) {
363
+ return {
364
+ type: "unsupported",
365
+ feature: `tool:${tool.name}`,
366
+ details
367
+ };
368
+ }
369
+
370
+ // ../shared/src/tool-calling/build-json-system-prompt.ts
350
371
  function buildJsonToolSystemPrompt(originalSystemPrompt, tools, options) {
351
372
  if (!tools || tools.length === 0) {
352
373
  return originalSystemPrompt || "";
@@ -400,34 +421,112 @@ function getParameters(tool) {
400
421
  return tool.inputSchema;
401
422
  }
402
423
 
403
- // src/tool-calling/parse-json-function-calls.ts
404
- var JSON_TOOL_CALL_FENCE_REGEX = /```tool[_-]?call\s*([\s\S]*?)```/gi;
424
+ // ../shared/src/tool-calling/format-tool-results.ts
425
+ function buildResultPayload(result) {
426
+ const payload = {
427
+ name: result.toolName,
428
+ result: result.result ?? null,
429
+ error: Boolean(result.isError)
430
+ };
431
+ if (result.toolCallId) {
432
+ payload.id = result.toolCallId;
433
+ }
434
+ return payload;
435
+ }
436
+ function formatToolResults(results) {
437
+ if (!results || results.length === 0) {
438
+ return "";
439
+ }
440
+ const payloads = results.map(
441
+ (result) => JSON.stringify(buildResultPayload(result))
442
+ );
443
+ return `\`\`\`tool_result
444
+ ${payloads.join("\n")}
445
+ \`\`\``;
446
+ }
447
+
448
+ // ../shared/src/tool-calling/parse-json-function-calls.ts
449
+ var DEFAULT_OPTIONS = {
450
+ supportXmlTags: true,
451
+ supportPythonStyle: true,
452
+ supportParametersField: true
453
+ };
405
454
  function generateToolCallId() {
406
455
  return `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
407
456
  }
408
- function parseJsonFunctionCalls(response) {
409
- const matches = Array.from(response.matchAll(JSON_TOOL_CALL_FENCE_REGEX));
410
- JSON_TOOL_CALL_FENCE_REGEX.lastIndex = 0;
457
+ function buildRegex(options) {
458
+ const patterns = [];
459
+ patterns.push("```tool[_-]?call\\s*([\\s\\S]*?)```");
460
+ if (options.supportXmlTags) {
461
+ patterns.push("<tool_call>\\s*([\\s\\S]*?)\\s*</tool_call>");
462
+ }
463
+ if (options.supportPythonStyle) {
464
+ patterns.push("\\[(\\w+)\\(([^)]*)\\)\\]");
465
+ }
466
+ return new RegExp(patterns.join("|"), "gi");
467
+ }
468
+ function parseJsonFunctionCalls(response, options = DEFAULT_OPTIONS) {
469
+ const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
470
+ const regex = buildRegex(mergedOptions);
471
+ const matches = Array.from(response.matchAll(regex));
472
+ regex.lastIndex = 0;
411
473
  if (matches.length === 0) {
412
474
  return { toolCalls: [], textContent: response };
413
475
  }
414
476
  const toolCalls = [];
415
477
  let textContent = response;
416
478
  for (const match of matches) {
417
- const [fullFence, innerContent] = match;
418
- textContent = textContent.replace(fullFence, "");
479
+ const fullMatch = match[0];
480
+ textContent = textContent.replace(fullMatch, "");
419
481
  try {
482
+ if (mergedOptions.supportPythonStyle && match[0].startsWith("[")) {
483
+ const pythonMatch = /\[(\w+)\(([^)]*)\)\]/.exec(match[0]);
484
+ if (pythonMatch) {
485
+ const [, funcName, pythonArgs] = pythonMatch;
486
+ const args = {};
487
+ if (pythonArgs && pythonArgs.trim()) {
488
+ const argPairs = pythonArgs.split(",").map((s) => s.trim());
489
+ for (const pair of argPairs) {
490
+ const equalIndex = pair.indexOf("=");
491
+ if (equalIndex > 0) {
492
+ const key = pair.substring(0, equalIndex).trim();
493
+ let value = pair.substring(equalIndex + 1).trim();
494
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
495
+ value = value.substring(1, value.length - 1);
496
+ }
497
+ args[key] = value;
498
+ }
499
+ }
500
+ }
501
+ toolCalls.push({
502
+ type: "tool-call",
503
+ toolCallId: generateToolCallId(),
504
+ toolName: funcName,
505
+ args
506
+ });
507
+ continue;
508
+ }
509
+ }
510
+ const innerContent = match[1] || match[2] || "";
420
511
  const trimmed = innerContent.trim();
512
+ if (!trimmed) continue;
421
513
  try {
422
514
  const parsed = JSON.parse(trimmed);
423
515
  const callsArray = Array.isArray(parsed) ? parsed : [parsed];
424
516
  for (const call of callsArray) {
425
517
  if (!call.name) continue;
518
+ let args = call.arguments || (mergedOptions.supportParametersField ? call.parameters : null) || {};
519
+ if (typeof args === "string") {
520
+ try {
521
+ args = JSON.parse(args);
522
+ } catch {
523
+ }
524
+ }
426
525
  toolCalls.push({
427
526
  type: "tool-call",
428
527
  toolCallId: call.id || generateToolCallId(),
429
528
  toolName: call.name,
430
- args: call.arguments || {}
529
+ args
431
530
  });
432
531
  }
433
532
  } catch {
@@ -436,11 +535,18 @@ function parseJsonFunctionCalls(response) {
436
535
  try {
437
536
  const call = JSON.parse(line.trim());
438
537
  if (!call.name) continue;
538
+ let args = call.arguments || (mergedOptions.supportParametersField ? call.parameters : null) || {};
539
+ if (typeof args === "string") {
540
+ try {
541
+ args = JSON.parse(args);
542
+ } catch {
543
+ }
544
+ }
439
545
  toolCalls.push({
440
546
  type: "tool-call",
441
547
  toolCallId: call.id || generateToolCallId(),
442
548
  toolName: call.name,
443
- args: call.arguments || {}
549
+ args
444
550
  });
445
551
  } catch {
446
552
  continue;
@@ -456,24 +562,245 @@ function parseJsonFunctionCalls(response) {
456
562
  return { toolCalls, textContent: textContent.trim() };
457
563
  }
458
564
 
459
- // src/tool-calling/format-tool-results.ts
460
- function formatToolResults(results) {
461
- if (results.length === 0) {
462
- return "";
565
+ // ../shared/src/streaming/tool-call-detector.ts
566
+ var DEFAULT_FENCE_PATTERNS = [
567
+ { start: "```tool_call", end: "```", reconstructStart: "```tool_call\n" },
568
+ { start: "```tool-call", end: "```", reconstructStart: "```tool-call\n" }
569
+ ];
570
+ var EXTENDED_FENCE_PATTERNS = [
571
+ ...DEFAULT_FENCE_PATTERNS,
572
+ {
573
+ start: "<tool_call>",
574
+ end: "</tool_call>",
575
+ reconstructStart: "<tool_call>"
576
+ }
577
+ ];
578
+ var ToolCallFenceDetector = class {
579
+ constructor(options = {}) {
580
+ this.pythonStyleRegex = /\[(\w+)\(/g;
581
+ this.buffer = "";
582
+ this.inFence = false;
583
+ this.fenceStartBuffer = "";
584
+ // Accumulated fence content
585
+ this.currentFencePattern = null;
586
+ this.fencePatterns = options.patterns ?? EXTENDED_FENCE_PATTERNS;
587
+ this.enablePythonStyle = options.enablePythonStyle ?? true;
588
+ this.fenceStarts = this.fencePatterns.map((p) => p.start);
463
589
  }
464
- const lines = results.map((result) => formatSingleToolResult(result)).join("\n");
465
- return `\`\`\`tool_result
466
- ${lines}
467
- \`\`\``;
468
- }
469
- function formatSingleToolResult(result) {
470
- return JSON.stringify({
471
- id: result.toolCallId,
472
- name: result.toolName,
473
- result: result.result,
474
- error: result.isError ?? false
475
- });
476
- }
590
+ addChunk(chunk) {
591
+ this.buffer += chunk;
592
+ }
593
+ getBuffer() {
594
+ return this.buffer;
595
+ }
596
+ clearBuffer() {
597
+ this.buffer = "";
598
+ }
599
+ /**
600
+ * Detects if there's a complete fence in the buffer
601
+ * @returns Detection result with fence info and safe text
602
+ */
603
+ detectFence() {
604
+ const {
605
+ index: startIdx,
606
+ prefix: matchedPrefix,
607
+ pattern
608
+ } = this.findFenceStart(this.buffer);
609
+ if (startIdx === -1) {
610
+ const overlap = this.computeOverlapLength(this.buffer, this.fenceStarts);
611
+ const safeTextLength = this.buffer.length - overlap;
612
+ const prefixText2 = safeTextLength > 0 ? this.buffer.slice(0, safeTextLength) : "";
613
+ const remaining = overlap > 0 ? this.buffer.slice(-overlap) : "";
614
+ this.buffer = remaining;
615
+ return {
616
+ fence: null,
617
+ prefixText: prefixText2,
618
+ remainingText: "",
619
+ overlapLength: overlap
620
+ };
621
+ }
622
+ const prefixText = this.buffer.slice(0, startIdx);
623
+ this.buffer = this.buffer.slice(startIdx);
624
+ const prefixLength = matchedPrefix?.length ?? 0;
625
+ const fenceEnd = pattern?.end ?? "```";
626
+ const closingIdx = this.buffer.indexOf(fenceEnd, prefixLength);
627
+ if (closingIdx === -1) {
628
+ return {
629
+ fence: null,
630
+ prefixText,
631
+ remainingText: "",
632
+ overlapLength: 0
633
+ };
634
+ }
635
+ const endPos = closingIdx + fenceEnd.length;
636
+ const fence = this.buffer.slice(0, endPos);
637
+ const remainingText = this.buffer.slice(endPos);
638
+ this.buffer = "";
639
+ return {
640
+ fence,
641
+ prefixText,
642
+ remainingText,
643
+ overlapLength: 0
644
+ };
645
+ }
646
+ /**
647
+ * Finds the first occurrence of any fence start marker
648
+ *
649
+ * @param text - Text to search in
650
+ * @returns Index of first fence start and which pattern matched
651
+ * @private
652
+ */
653
+ findFenceStart(text) {
654
+ let bestIndex = -1;
655
+ let matchedPrefix = null;
656
+ let matchedPattern = null;
657
+ for (const pattern of this.fencePatterns) {
658
+ const idx = text.indexOf(pattern.start);
659
+ if (idx !== -1 && (bestIndex === -1 || idx < bestIndex)) {
660
+ bestIndex = idx;
661
+ matchedPrefix = pattern.start;
662
+ matchedPattern = pattern;
663
+ }
664
+ }
665
+ if (this.enablePythonStyle) {
666
+ this.pythonStyleRegex.lastIndex = 0;
667
+ const pythonMatch = this.pythonStyleRegex.exec(text);
668
+ if (pythonMatch && (bestIndex === -1 || pythonMatch.index < bestIndex)) {
669
+ bestIndex = pythonMatch.index;
670
+ matchedPrefix = pythonMatch[0];
671
+ matchedPattern = {
672
+ start: pythonMatch[0],
673
+ end: ")]",
674
+ reconstructStart: pythonMatch[0],
675
+ isRegex: true
676
+ };
677
+ }
678
+ }
679
+ return { index: bestIndex, prefix: matchedPrefix, pattern: matchedPattern };
680
+ }
681
+ /**
682
+ * Computes the maximum overlap between the end of text and the start of any prefix
683
+ * @param text - Text to check for overlap
684
+ * @param prefixes - List of prefixes to check against
685
+ * @returns Length of the maximum overlap found
686
+ */
687
+ computeOverlapLength(text, prefixes) {
688
+ let overlap = 0;
689
+ for (const prefix of prefixes) {
690
+ const maxLength = Math.min(text.length, prefix.length - 1);
691
+ for (let size = maxLength; size > 0; size -= 1) {
692
+ if (prefix.startsWith(text.slice(-size))) {
693
+ overlap = Math.max(overlap, size);
694
+ break;
695
+ }
696
+ }
697
+ }
698
+ return overlap;
699
+ }
700
+ /**
701
+ * Checks if the buffer currently contains any text
702
+ */
703
+ hasContent() {
704
+ return this.buffer.length > 0;
705
+ }
706
+ /**
707
+ * Gets the buffer size
708
+ */
709
+ getBufferSize() {
710
+ return this.buffer.length;
711
+ }
712
+ /**
713
+ * Detect and stream fence content in real-time for true incremental streaming
714
+ * @returns Streaming result with current state and safe content to emit
715
+ */
716
+ detectStreamingFence() {
717
+ if (!this.inFence) {
718
+ const {
719
+ index: startIdx,
720
+ prefix: matchedPrefix,
721
+ pattern
722
+ } = this.findFenceStart(this.buffer);
723
+ if (startIdx === -1) {
724
+ const overlap = this.computeOverlapLength(
725
+ this.buffer,
726
+ this.fenceStarts
727
+ );
728
+ const safeTextLength = this.buffer.length - overlap;
729
+ const safeContent = safeTextLength > 0 ? this.buffer.slice(0, safeTextLength) : "";
730
+ this.buffer = this.buffer.slice(safeTextLength);
731
+ return {
732
+ inFence: false,
733
+ safeContent,
734
+ completeFence: null,
735
+ textAfterFence: ""
736
+ };
737
+ }
738
+ const prefixText = this.buffer.slice(0, startIdx);
739
+ const fenceStartLength = matchedPrefix?.length ?? 0;
740
+ this.buffer = this.buffer.slice(startIdx + fenceStartLength);
741
+ if (pattern && pattern.start.startsWith("```") && this.buffer.startsWith("\n")) {
742
+ this.buffer = this.buffer.slice(1);
743
+ }
744
+ this.inFence = true;
745
+ this.fenceStartBuffer = "";
746
+ this.currentFencePattern = pattern;
747
+ return {
748
+ inFence: true,
749
+ safeContent: prefixText,
750
+ // Emit any text before the fence
751
+ completeFence: null,
752
+ textAfterFence: ""
753
+ };
754
+ }
755
+ const fenceEnd = this.currentFencePattern?.end ?? "```";
756
+ const closingIdx = this.buffer.indexOf(fenceEnd);
757
+ if (closingIdx === -1) {
758
+ const overlap = this.computeOverlapLength(this.buffer, [fenceEnd]);
759
+ const safeContentLength = this.buffer.length - overlap;
760
+ if (safeContentLength > 0) {
761
+ const safeContent = this.buffer.slice(0, safeContentLength);
762
+ this.fenceStartBuffer += safeContent;
763
+ this.buffer = this.buffer.slice(safeContentLength);
764
+ return {
765
+ inFence: true,
766
+ safeContent,
767
+ completeFence: null,
768
+ textAfterFence: ""
769
+ };
770
+ }
771
+ return {
772
+ inFence: true,
773
+ safeContent: "",
774
+ completeFence: null,
775
+ textAfterFence: ""
776
+ };
777
+ }
778
+ const fenceContent = this.buffer.slice(0, closingIdx);
779
+ this.fenceStartBuffer += fenceContent;
780
+ const reconstructStart = this.currentFencePattern?.reconstructStart ?? "```tool_call\n";
781
+ const completeFence = `${reconstructStart}${this.fenceStartBuffer}${fenceEnd}`;
782
+ const textAfterFence = this.buffer.slice(closingIdx + fenceEnd.length);
783
+ this.inFence = false;
784
+ this.fenceStartBuffer = "";
785
+ this.currentFencePattern = null;
786
+ this.buffer = textAfterFence;
787
+ return {
788
+ inFence: false,
789
+ safeContent: fenceContent,
790
+ // Emit the last bit of fence content
791
+ completeFence,
792
+ textAfterFence
793
+ };
794
+ }
795
+ isInFence() {
796
+ return this.inFence;
797
+ }
798
+ resetStreamingState() {
799
+ this.inFence = false;
800
+ this.fenceStartBuffer = "";
801
+ this.currentFencePattern = null;
802
+ }
803
+ };
477
804
 
478
805
  // src/convert-to-webllm-messages.tsx
479
806
  function convertToolResultOutput(output) {
@@ -617,27 +944,6 @@ function convertToWebLLMMessages(prompt) {
617
944
  // src/web-llm-language-model.ts
618
945
  var import_web_llm = require("@mlc-ai/web-llm");
619
946
 
620
- // src/utils/warnings.ts
621
- function createUnsupportedSettingWarning(feature, details) {
622
- return {
623
- type: "unsupported",
624
- feature,
625
- details
626
- };
627
- }
628
- function createUnsupportedToolWarning(tool, details) {
629
- return {
630
- type: "unsupported",
631
- feature: `tool:${tool.name}`,
632
- details
633
- };
634
- }
635
-
636
- // src/utils/tool-utils.ts
637
- function isFunctionTool(tool) {
638
- return tool.type === "function";
639
- }
640
-
641
947
  // src/utils/prompt-utils.ts
642
948
  function extractSystemPrompt(messages) {
643
949
  const systemMessages = messages.filter((msg) => msg.role === "system");
@@ -677,206 +983,6 @@ ${existingContent}` : "")
677
983
  ];
678
984
  }
679
985
 
680
- // src/streaming/tool-call-detector.ts
681
- var ToolCallFenceDetector = class {
682
- constructor() {
683
- this.FENCE_STARTS = ["```tool_call"];
684
- this.FENCE_END = "```";
685
- this.buffer = "";
686
- this.inFence = false;
687
- this.fenceStartBuffer = "";
688
- }
689
- addChunk(chunk) {
690
- this.buffer += chunk;
691
- }
692
- getBuffer() {
693
- return this.buffer;
694
- }
695
- clearBuffer() {
696
- this.buffer = "";
697
- }
698
- /**
699
- * Detects if there's a complete fence in the buffer
700
- *
701
- * 1. Searches for fence start markers
702
- * 2. If found, looks for closing fence
703
- * 3. Computes overlap for partial fences
704
- * 4. Returns safe text that can be emitted
705
- *
706
- * @returns Detection result with fence info and safe text
707
- */
708
- detectFence() {
709
- const { index: startIdx, prefix: matchedPrefix } = this.findFenceStart(
710
- this.buffer
711
- );
712
- if (startIdx === -1) {
713
- const overlap = this.computeOverlapLength(this.buffer, this.FENCE_STARTS);
714
- const safeTextLength = this.buffer.length - overlap;
715
- const prefixText2 = safeTextLength > 0 ? this.buffer.slice(0, safeTextLength) : "";
716
- const remaining = overlap > 0 ? this.buffer.slice(-overlap) : "";
717
- this.buffer = remaining;
718
- return {
719
- fence: null,
720
- prefixText: prefixText2,
721
- remainingText: "",
722
- overlapLength: overlap
723
- };
724
- }
725
- const prefixText = this.buffer.slice(0, startIdx);
726
- this.buffer = this.buffer.slice(startIdx);
727
- const prefixLength = matchedPrefix?.length ?? 0;
728
- const closingIdx = this.buffer.indexOf(this.FENCE_END, prefixLength);
729
- if (closingIdx === -1) {
730
- return {
731
- fence: null,
732
- prefixText,
733
- remainingText: "",
734
- overlapLength: 0
735
- };
736
- }
737
- const endPos = closingIdx + this.FENCE_END.length;
738
- const fence = this.buffer.slice(0, endPos);
739
- const remainingText = this.buffer.slice(endPos);
740
- this.buffer = "";
741
- return {
742
- fence,
743
- prefixText,
744
- remainingText,
745
- overlapLength: 0
746
- };
747
- }
748
- /**
749
- * Finds the first occurrence of any fence start marker
750
- *
751
- * @param text - Text to search in
752
- * @returns Index of first fence start and which prefix matched
753
- * @private
754
- */
755
- findFenceStart(text) {
756
- let bestIndex = -1;
757
- let matchedPrefix = null;
758
- for (const prefix of this.FENCE_STARTS) {
759
- const idx = text.indexOf(prefix);
760
- if (idx !== -1 && (bestIndex === -1 || idx < bestIndex)) {
761
- bestIndex = idx;
762
- matchedPrefix = prefix;
763
- }
764
- }
765
- return { index: bestIndex, prefix: matchedPrefix };
766
- }
767
- computeOverlapLength(text, prefixes) {
768
- let overlap = 0;
769
- for (const prefix of prefixes) {
770
- const maxLength = Math.min(text.length, prefix.length - 1);
771
- for (let size = maxLength; size > 0; size -= 1) {
772
- if (prefix.startsWith(text.slice(-size))) {
773
- overlap = Math.max(overlap, size);
774
- break;
775
- }
776
- }
777
- }
778
- return overlap;
779
- }
780
- hasContent() {
781
- return this.buffer.length > 0;
782
- }
783
- getBufferSize() {
784
- return this.buffer.length;
785
- }
786
- /**
787
- * Detect and stream fence content in real-time for true incremental streaming
788
- *
789
- * This method is designed for streaming tool calls as they arrive:
790
- * 1. Detects when a fence starts and transitions to "inFence" state
791
- * 2. While inFence, emits safe content that won't conflict with fence end marker
792
- * 3. When fence ends, returns the complete fence for parsing
793
- *
794
- * @returns Streaming result with current state and safe content to emit
795
- */
796
- detectStreamingFence() {
797
- if (!this.inFence) {
798
- const { index: startIdx, prefix: matchedPrefix } = this.findFenceStart(
799
- this.buffer
800
- );
801
- if (startIdx === -1) {
802
- const overlap = this.computeOverlapLength(
803
- this.buffer,
804
- this.FENCE_STARTS
805
- );
806
- const safeTextLength = this.buffer.length - overlap;
807
- const safeContent = safeTextLength > 0 ? this.buffer.slice(0, safeTextLength) : "";
808
- this.buffer = this.buffer.slice(safeTextLength);
809
- return {
810
- inFence: false,
811
- safeContent,
812
- completeFence: null,
813
- textAfterFence: ""
814
- };
815
- }
816
- const prefixText = this.buffer.slice(0, startIdx);
817
- const fenceStartLength = matchedPrefix?.length ?? 0;
818
- this.buffer = this.buffer.slice(startIdx + fenceStartLength);
819
- if (this.buffer.startsWith("\n")) {
820
- this.buffer = this.buffer.slice(1);
821
- }
822
- this.inFence = true;
823
- this.fenceStartBuffer = "";
824
- return {
825
- inFence: true,
826
- safeContent: prefixText,
827
- completeFence: null,
828
- textAfterFence: ""
829
- };
830
- }
831
- const closingIdx = this.buffer.indexOf(this.FENCE_END);
832
- if (closingIdx === -1) {
833
- const overlap = this.computeOverlapLength(this.buffer, [this.FENCE_END]);
834
- const safeContentLength = this.buffer.length - overlap;
835
- if (safeContentLength > 0) {
836
- const safeContent = this.buffer.slice(0, safeContentLength);
837
- this.fenceStartBuffer += safeContent;
838
- this.buffer = this.buffer.slice(safeContentLength);
839
- return {
840
- inFence: true,
841
- safeContent,
842
- completeFence: null,
843
- textAfterFence: ""
844
- };
845
- }
846
- return {
847
- inFence: true,
848
- safeContent: "",
849
- completeFence: null,
850
- textAfterFence: ""
851
- };
852
- }
853
- const fenceContent = this.buffer.slice(0, closingIdx);
854
- this.fenceStartBuffer += fenceContent;
855
- const completeFence = `${this.FENCE_STARTS[0]}
856
- ${this.fenceStartBuffer}
857
- ${this.FENCE_END}`;
858
- const textAfterFence = this.buffer.slice(
859
- closingIdx + this.FENCE_END.length
860
- );
861
- this.inFence = false;
862
- this.fenceStartBuffer = "";
863
- this.buffer = textAfterFence;
864
- return {
865
- inFence: false,
866
- safeContent: fenceContent,
867
- completeFence,
868
- textAfterFence
869
- };
870
- }
871
- isInFence() {
872
- return this.inFence;
873
- }
874
- resetStreamingState() {
875
- this.inFence = false;
876
- this.fenceStartBuffer = "";
877
- }
878
- };
879
-
880
986
  // src/web-llm-language-model.ts
881
987
  function isMobile() {
882
988
  if (typeof navigator === "undefined") return false;