@aibranch/llm-meta-widget 0.12.0 → 0.12.1
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/README.md
CHANGED
|
@@ -589,17 +589,29 @@ function parseSseFrame(raw) {
|
|
|
589
589
|
var STATIC_PRIMITIVES_META = "io.modelcontextprotocol/static-primitives";
|
|
590
590
|
var PAGE_CARD_MEDIA_TYPE = "application/mcp-static-primitives+json";
|
|
591
591
|
var RESOURCE_SHARE_OF_WINDOW = 0.0625;
|
|
592
|
+
var RESOURCE_BYTES_PER_TOKEN = 4;
|
|
592
593
|
function resourceBudgetBytes(window2, { floor = 8e3, cap = 64e3 } = {}) {
|
|
593
594
|
if (!window2 || !Number.isFinite(window2)) return floor;
|
|
594
|
-
const share = Math.round(window2 * RESOURCE_SHARE_OF_WINDOW *
|
|
595
|
+
const share = Math.round(window2 * RESOURCE_SHARE_OF_WINDOW * RESOURCE_BYTES_PER_TOKEN);
|
|
595
596
|
return Math.min(cap, Math.max(floor, share));
|
|
596
597
|
}
|
|
597
|
-
var
|
|
598
|
+
var PROSE_CHARS_PER_TOKEN = 3.5;
|
|
599
|
+
var DENSE_CHARS_PER_TOKEN = 1.6;
|
|
600
|
+
var CJK_CHARS_PER_TOKEN = 1;
|
|
598
601
|
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
602
|
+
var CJK_PATTERN = /[\u3000-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]/g;
|
|
603
|
+
var PROSE_PATTERN = /[A-Za-z \n\t]/g;
|
|
604
|
+
function estimateTextTokens(text) {
|
|
605
|
+
const str = typeof text === "string" ? text : String(text ?? "");
|
|
606
|
+
const cjk = (str.match(CJK_PATTERN) || []).length;
|
|
607
|
+
const prose = (str.match(PROSE_PATTERN) || []).length;
|
|
608
|
+
const dense = str.length - cjk - prose;
|
|
609
|
+
return Math.ceil(cjk / CJK_CHARS_PER_TOKEN + prose / PROSE_CHARS_PER_TOKEN + dense / DENSE_CHARS_PER_TOKEN);
|
|
610
|
+
}
|
|
599
611
|
function estimateTokens(messages) {
|
|
600
612
|
return (messages || []).reduce((sum, m) => {
|
|
601
613
|
const content = typeof m?.content === "string" ? m.content : JSON.stringify(m?.content ?? "");
|
|
602
|
-
return sum +
|
|
614
|
+
return sum + estimateTextTokens(content) + MESSAGE_OVERHEAD_TOKENS;
|
|
603
615
|
}, 0);
|
|
604
616
|
}
|
|
605
617
|
function estimateToolsTokens(tools) {
|
|
@@ -608,7 +620,7 @@ function estimateToolsTokens(tools) {
|
|
|
608
620
|
description: t?.description,
|
|
609
621
|
schema: t?.input_schema || t?.inputSchema
|
|
610
622
|
})).join("");
|
|
611
|
-
return
|
|
623
|
+
return estimateTextTokens(text);
|
|
612
624
|
}
|
|
613
625
|
function evictOldToolResults({ messages, window: window2, reserve = 0, dropped = [] }) {
|
|
614
626
|
if (!window2 || !Number.isFinite(window2)) return { messages, dropped };
|
|
@@ -3940,19 +3952,12 @@ function boot(cfg) {
|
|
|
3940
3952
|
var toggleBtn = document.getElementById("llm-meta-widget-toggle");
|
|
3941
3953
|
var historyEl = root.querySelector(".lmw-messages");
|
|
3942
3954
|
var SCROLL_PIN_SLACK = 40;
|
|
3943
|
-
var followingOutput = true;
|
|
3944
3955
|
function atBottom() {
|
|
3945
3956
|
return historyEl.scrollHeight - historyEl.scrollTop - historyEl.clientHeight <= SCROLL_PIN_SLACK;
|
|
3946
3957
|
}
|
|
3947
3958
|
function pinToBottom() {
|
|
3948
3959
|
historyEl.scrollTop = historyEl.scrollHeight;
|
|
3949
3960
|
}
|
|
3950
|
-
function pinIfFollowing() {
|
|
3951
|
-
if (followingOutput) pinToBottom();
|
|
3952
|
-
}
|
|
3953
|
-
historyEl.addEventListener("scroll", function() {
|
|
3954
|
-
followingOutput = atBottom();
|
|
3955
|
-
});
|
|
3956
3961
|
var formEl = root.querySelector(".lmw-form");
|
|
3957
3962
|
var inputEl = root.querySelector(".lmw-input");
|
|
3958
3963
|
var clearBtn = root.querySelector(".lmw-clear");
|
|
@@ -4505,6 +4510,7 @@ function boot(cfg) {
|
|
|
4505
4510
|
}
|
|
4506
4511
|
var pendingTurnLabel = null;
|
|
4507
4512
|
function appendTurn(role, text, turnLabel) {
|
|
4513
|
+
var following = atBottom();
|
|
4508
4514
|
var welcome = historyEl.querySelector(".lmw-welcome");
|
|
4509
4515
|
if (welcome) welcome.remove();
|
|
4510
4516
|
var div = document.createElement("div");
|
|
@@ -4531,7 +4537,7 @@ function boot(cfg) {
|
|
|
4531
4537
|
div.appendChild(document.createTextNode(" "));
|
|
4532
4538
|
div.appendChild(body);
|
|
4533
4539
|
historyEl.appendChild(div);
|
|
4534
|
-
|
|
4540
|
+
if (role === "user" || following) pinToBottom();
|
|
4535
4541
|
body.roleLabel = label;
|
|
4536
4542
|
return body;
|
|
4537
4543
|
}
|
|
@@ -4581,6 +4587,7 @@ function boot(cfg) {
|
|
|
4581
4587
|
return liveChips;
|
|
4582
4588
|
}
|
|
4583
4589
|
function announceToolCall(toolCall) {
|
|
4590
|
+
var following = atBottom();
|
|
4584
4591
|
var row = chipRow();
|
|
4585
4592
|
if (!row) return;
|
|
4586
4593
|
var chip = document.createElement("span");
|
|
@@ -4589,7 +4596,7 @@ function boot(cfg) {
|
|
|
4589
4596
|
chip.dataset.tool = toolCall.name;
|
|
4590
4597
|
row.appendChild(chip);
|
|
4591
4598
|
pendingChips.push(chip);
|
|
4592
|
-
|
|
4599
|
+
if (following) pinToBottom();
|
|
4593
4600
|
}
|
|
4594
4601
|
function resolveToolCall(outcome) {
|
|
4595
4602
|
var idx = pendingChips.findIndex(function(c) {
|
|
@@ -4605,6 +4612,7 @@ function boot(cfg) {
|
|
|
4605
4612
|
var currentThinkingBody = null;
|
|
4606
4613
|
var activeAssistantBody = null;
|
|
4607
4614
|
function ensureThinkingBlock() {
|
|
4615
|
+
var following = atBottom();
|
|
4608
4616
|
if (currentThinkingBody) return currentThinkingBody;
|
|
4609
4617
|
var details = document.createElement("details");
|
|
4610
4618
|
details.className = "message-thinking";
|
|
@@ -4629,7 +4637,7 @@ function boot(cfg) {
|
|
|
4629
4637
|
} else {
|
|
4630
4638
|
historyEl.appendChild(details);
|
|
4631
4639
|
}
|
|
4632
|
-
|
|
4640
|
+
if (following) pinToBottom();
|
|
4633
4641
|
currentThinkingBlock = details;
|
|
4634
4642
|
currentThinkingBody = body;
|
|
4635
4643
|
return body;
|
|
@@ -4746,7 +4754,6 @@ function boot(cfg) {
|
|
|
4746
4754
|
var userText = inputEl.value.trim();
|
|
4747
4755
|
if (!userText) return;
|
|
4748
4756
|
inputEl.value = "";
|
|
4749
|
-
followingOutput = true;
|
|
4750
4757
|
appendTurn("user", userText, pendingTurnLabel);
|
|
4751
4758
|
pendingTurnLabel = null;
|
|
4752
4759
|
if (currentAbort) {
|
|
@@ -4809,19 +4816,21 @@ function boot(cfg) {
|
|
|
4809
4816
|
collapseThinkingBlock();
|
|
4810
4817
|
},
|
|
4811
4818
|
onThinkingDelta: function(delta) {
|
|
4819
|
+
var following = atBottom();
|
|
4812
4820
|
var body = ensureThinkingBlock();
|
|
4813
4821
|
body.appendChild(document.createTextNode(delta));
|
|
4814
4822
|
body.scrollTop = body.scrollHeight;
|
|
4815
|
-
|
|
4823
|
+
if (following) pinToBottom();
|
|
4816
4824
|
},
|
|
4817
4825
|
onTextDelta: function(delta) {
|
|
4826
|
+
var following = atBottom();
|
|
4818
4827
|
markDone(assistantBody.roleLabel);
|
|
4819
4828
|
collapseThinkingBlock();
|
|
4820
4829
|
assistantMarkdown += delta;
|
|
4821
4830
|
renderMarkdownInto(assistantBody, assistantMarkdown);
|
|
4822
4831
|
liveTurn.content = assistantMarkdown;
|
|
4823
4832
|
persistSoon();
|
|
4824
|
-
|
|
4833
|
+
if (following) pinToBottom();
|
|
4825
4834
|
}
|
|
4826
4835
|
});
|
|
4827
4836
|
liveTurn.content = result.content;
|
|
@@ -937,23 +937,53 @@ export const PAGE_CARD_MEDIA_TYPE = "application/mcp-static-primitives+json"
|
|
|
937
937
|
// is already generous and a 1M-token model does not need 250 KB of it.
|
|
938
938
|
export const RESOURCE_SHARE_OF_WINDOW = 0.0625
|
|
939
939
|
|
|
940
|
+
// Bytes per token for SIZING that budget — not the estimator above, which
|
|
941
|
+
// measures real text. Kept at four so the budget stays exactly what it was
|
|
942
|
+
// (8 KB on a 32k model): how many tokens those bytes become depends on the
|
|
943
|
+
// resource, and re-sizing budgets is not this constant's job.
|
|
944
|
+
export const RESOURCE_BYTES_PER_TOKEN = 4
|
|
945
|
+
|
|
940
946
|
export function resourceBudgetBytes(window, { floor = 8000, cap = 64_000 } = {}) {
|
|
941
947
|
if (!window || !Number.isFinite(window)) return floor
|
|
942
|
-
const share = Math.round(window * RESOURCE_SHARE_OF_WINDOW *
|
|
948
|
+
const share = Math.round(window * RESOURCE_SHARE_OF_WINDOW * RESOURCE_BYTES_PER_TOKEN)
|
|
943
949
|
return Math.min(cap, Math.max(floor, share))
|
|
944
950
|
}
|
|
945
951
|
|
|
946
|
-
// A rough token count, the same
|
|
947
|
-
//
|
|
948
|
-
//
|
|
949
|
-
//
|
|
950
|
-
|
|
952
|
+
// A rough token count, the same three classes the hub's estimator uses.
|
|
953
|
+
//
|
|
954
|
+
// One ratio cannot describe both prose and a tool result. Letters and spaces
|
|
955
|
+
// tokenize at roughly 3.5 characters each; digits, punctuation and symbols go
|
|
956
|
+
// much denser, and a JSON tool result is mostly those.
|
|
957
|
+
//
|
|
958
|
+
// Measured against a real refusal (2026-10-17): a 289,101-character request
|
|
959
|
+
// the provider counted as 135,291 tokens was 33% letters, 16% digits and 45%
|
|
960
|
+
// punctuation. At a flat four characters per token that is 72,275 — 53% of the
|
|
961
|
+
// truth — so nothing was evicted and the provider refused the turn instead.
|
|
962
|
+
// These classes put it at 106%: high, which is the safe direction here, since
|
|
963
|
+
// underestimating means the turn dies while overestimating only gives up an
|
|
964
|
+
// old tool result slightly early.
|
|
965
|
+
export const PROSE_CHARS_PER_TOKEN = 3.5
|
|
966
|
+
export const DENSE_CHARS_PER_TOKEN = 1.6
|
|
967
|
+
export const CJK_CHARS_PER_TOKEN = 1
|
|
951
968
|
export const MESSAGE_OVERHEAD_TOKENS = 4
|
|
952
969
|
|
|
970
|
+
const CJK_PATTERN = /[\u3000-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]/g
|
|
971
|
+
const PROSE_PATTERN = /[A-Za-z \n\t]/g
|
|
972
|
+
|
|
973
|
+
export function estimateTextTokens(text) {
|
|
974
|
+
const str = typeof text === "string" ? text : String(text ?? "")
|
|
975
|
+
const cjk = (str.match(CJK_PATTERN) || []).length
|
|
976
|
+
const prose = (str.match(PROSE_PATTERN) || []).length
|
|
977
|
+
const dense = str.length - cjk - prose
|
|
978
|
+
return Math.ceil(cjk / CJK_CHARS_PER_TOKEN +
|
|
979
|
+
prose / PROSE_CHARS_PER_TOKEN +
|
|
980
|
+
dense / DENSE_CHARS_PER_TOKEN)
|
|
981
|
+
}
|
|
982
|
+
|
|
953
983
|
export function estimateTokens(messages) {
|
|
954
984
|
return (messages || []).reduce((sum, m) => {
|
|
955
985
|
const content = typeof m?.content === "string" ? m.content : JSON.stringify(m?.content ?? "")
|
|
956
|
-
return sum +
|
|
986
|
+
return sum + estimateTextTokens(content) + MESSAGE_OVERHEAD_TOKENS
|
|
957
987
|
}, 0)
|
|
958
988
|
}
|
|
959
989
|
|
|
@@ -969,7 +999,8 @@ export function estimateToolsTokens(tools) {
|
|
|
969
999
|
const text = (tools || []).map((t) => JSON.stringify({
|
|
970
1000
|
name: t?.name, description: t?.description, schema: t?.input_schema || t?.inputSchema
|
|
971
1001
|
})).join("")
|
|
972
|
-
|
|
1002
|
+
// Counted with the same classes: a schema is JSON, which is the dense case.
|
|
1003
|
+
return estimateTextTokens(text)
|
|
973
1004
|
}
|
|
974
1005
|
|
|
975
1006
|
// Makes room for the next round by dropping the OLDEST tool results, which is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aibranch/llm-meta-widget",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"//version": "Must stay in step with lib/llm_meta_widget/version.rb. Two independently versioned copies of the same client is the drift docs/custom-element-distribution.md warns about: bump both, release both.",
|
|
5
5
|
"description": "Embeddable browser chat widget: a custom element that discovers tools at page, origin and hub scope and dispatches each tool call to the right one. Two lines of HTML on any host, Rails or not.",
|
|
6
6
|
"keywords": [
|