@aibranch/llm-meta-widget 0.12.2 → 0.13.0
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
|
@@ -61,7 +61,7 @@ Option 3 if it does not.
|
|
|
61
61
|
|
|
62
62
|
```ruby
|
|
63
63
|
# Gemfile
|
|
64
|
-
gem "llm_meta_widget", "~> 0.
|
|
64
|
+
gem "llm_meta_widget", "~> 0.13.0"
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
```bash
|
|
@@ -152,23 +152,27 @@ The widget is a custom element in a single self-contained ES module, so the
|
|
|
152
152
|
whole integration is two lines of HTML:
|
|
153
153
|
|
|
154
154
|
```html
|
|
155
|
-
<script type="module" src="https://cdn.jsdelivr.net/npm/@aibranch/llm-meta-widget@0.
|
|
155
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@aibranch/llm-meta-widget@0.13"></script>
|
|
156
156
|
<llm-meta-widget llm-url="https://your-meta-server.example"
|
|
157
157
|
model="gpt-oss-120b"
|
|
158
158
|
greeting="Hi — ask me anything about this page."></llm-meta-widget>
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
**`@0.
|
|
161
|
+
**`@0.13` is a version range, and it stops at 0.13.x on purpose.** You receive
|
|
162
162
|
bug fixes without editing your page, and a release that breaks your page cannot
|
|
163
163
|
arrive on its own.
|
|
164
164
|
|
|
165
|
-
Moving to 0.
|
|
165
|
+
Moving to 0.14 is therefore a change you make yourself. Read
|
|
166
166
|
[CHANGELOG.md](CHANGELOG.md) before you do, because this is where what your page
|
|
167
167
|
must provide can change. For example, 0.10.0 changed how the widget finds a page
|
|
168
168
|
card: by media type rather than by element id. A page that declared its card the
|
|
169
169
|
new way while still loading `@0.9` lost every tool, prompt and resource it had
|
|
170
170
|
declared. Nothing failed, anywhere. The CHANGELOG entry for 0.10.0 describes it.
|
|
171
171
|
|
|
172
|
+
0.13.0 moves where a page declares its transport. That one is safe to take
|
|
173
|
+
first, because 0.13.x still reads the old spelling and warns; the page can move
|
|
174
|
+
afterwards.
|
|
175
|
+
|
|
172
176
|
A Rails app is protected from that mismatch, because the `Gemfile` will not
|
|
173
177
|
resolve a widget version the page cannot use. A page that loads the widget from
|
|
174
178
|
the CDN has no such check, so the version you write in that URL is the only
|
|
@@ -689,15 +689,29 @@ function modelWindowFrom(payload, modelId) {
|
|
|
689
689
|
return null;
|
|
690
690
|
}
|
|
691
691
|
var PAGE_GLOBAL_TRANSPORT = "page-global";
|
|
692
|
-
function declaredPageGlobal(card) {
|
|
692
|
+
function declaredPageGlobal(card, warn = console.warn) {
|
|
693
|
+
const declared = card && card.transports || [];
|
|
694
|
+
for (const transport of declared) {
|
|
695
|
+
if (!transport || transport.type !== PAGE_GLOBAL_TRANSPORT) continue;
|
|
696
|
+
if (typeof transport.global === "string" && transport.global.trim() !== "") {
|
|
697
|
+
return transport.global.trim();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (declared.length) {
|
|
701
|
+
warn("[llm-meta-widget] page card declares transports " + JSON.stringify(declared.map((t) => t && t.type)) + ", none of which this widget supports; not dispatching to the page");
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
693
704
|
for (const remote of card && card.remotes || []) {
|
|
694
705
|
if (!remote || remote.type !== PAGE_GLOBAL_TRANSPORT) continue;
|
|
695
|
-
if (typeof remote.global === "string" && remote.global.trim() !== "")
|
|
706
|
+
if (typeof remote.global === "string" && remote.global.trim() !== "") {
|
|
707
|
+
warn(`[llm-meta-widget] page card declares its transport in top-level "remotes", which is deprecated; move it to _meta["${STATIC_PRIMITIVES_META}"].transports`);
|
|
708
|
+
return remote.global.trim();
|
|
709
|
+
}
|
|
696
710
|
}
|
|
697
711
|
return null;
|
|
698
712
|
}
|
|
699
713
|
function pageHandlers({ card, scope = {}, legacy = {}, warn = console.warn } = {}) {
|
|
700
|
-
const name = declaredPageGlobal(card);
|
|
714
|
+
const name = declaredPageGlobal(card, warn);
|
|
701
715
|
const declared = name ? scope[name] : null;
|
|
702
716
|
if (name && !declared) {
|
|
703
717
|
warn(`[llm-meta-widget] the page card declares global "${name}", but window.${name} is not defined \u2014 is the script that defines it loaded, and did it run?`);
|
|
@@ -719,16 +733,19 @@ function pageHandlers({ card, scope = {}, legacy = {}, warn = console.warn } = {
|
|
|
719
733
|
};
|
|
720
734
|
}
|
|
721
735
|
function mergeStaticPrimitivesCards(cards, warn = console.warn) {
|
|
722
|
-
const merged = { remotes: [], tools: [], prompts: [], resources: [] };
|
|
736
|
+
const merged = { transports: [], remotes: [], tools: [], prompts: [], resources: [] };
|
|
723
737
|
for (const card of cards || []) {
|
|
724
|
-
for (const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
738
|
+
for (const kind of ["transports", "remotes"]) {
|
|
739
|
+
for (const entry of card && card[kind] || []) {
|
|
740
|
+
if (!entry || typeof entry.type !== "string") continue;
|
|
741
|
+
const clash = merged[kind].find((r) => r.type === entry.type && r.global !== entry.global);
|
|
742
|
+
if (clash) {
|
|
743
|
+
warn(`[llm-meta-widget] two blocks declare different ${entry.type} globals ("${clash.global}" and "${entry.global}"); using "${clash.global}"`);
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
if (!merged[kind].some((r) => r.type === entry.type && r.global === entry.global)) {
|
|
747
|
+
merged[kind].push(entry);
|
|
748
|
+
}
|
|
732
749
|
}
|
|
733
750
|
}
|
|
734
751
|
}
|
|
@@ -749,7 +766,7 @@ function mergeStaticPrimitivesCards(cards, warn = console.warn) {
|
|
|
749
766
|
return merged;
|
|
750
767
|
}
|
|
751
768
|
function readStaticPrimitivesCard(text, warn = console.warn) {
|
|
752
|
-
const empty = { remotes: [], tools: [], prompts: [], resources: [] };
|
|
769
|
+
const empty = { transports: [], remotes: [], tools: [], prompts: [], resources: [] };
|
|
753
770
|
if (typeof text !== "string" || text.trim() === "") return empty;
|
|
754
771
|
let doc;
|
|
755
772
|
try {
|
|
@@ -769,7 +786,12 @@ function readStaticPrimitivesCard(text, warn = console.warn) {
|
|
|
769
786
|
return ok;
|
|
770
787
|
});
|
|
771
788
|
return {
|
|
772
|
-
//
|
|
789
|
+
// Where a page says how to reach its handlers. Since SEP d44bd25a this is
|
|
790
|
+
// `transports` inside the extension data, because the Server Card's
|
|
791
|
+
// `Remote.type` enum is streamable-http|sse and `page-global` was never a
|
|
792
|
+
// valid value there. Top-level `remotes` is what 0.11.1-0.12.2 read, and is
|
|
793
|
+
// still accepted for one release — see declaredPageGlobal.
|
|
794
|
+
transports: Array.isArray(ext.transports) ? ext.transports : [],
|
|
773
795
|
remotes: Array.isArray(doc.remotes) ? doc.remotes : [],
|
|
774
796
|
tools: named(ext.tools, "tool"),
|
|
775
797
|
prompts: named(ext.prompts, "prompt"),
|
|
@@ -3988,6 +4010,19 @@ function boot(cfg) {
|
|
|
3988
4010
|
var toggleBtn = document.getElementById("llm-meta-widget-toggle");
|
|
3989
4011
|
var historyEl = root.querySelector(".lmw-messages");
|
|
3990
4012
|
var SCROLL_PIN_SLACK = 40;
|
|
4013
|
+
var skippedResources = [];
|
|
4014
|
+
var announcedSkips = false;
|
|
4015
|
+
function kb(bytes) {
|
|
4016
|
+
return Math.round((bytes || 0) / 1024) + " KB";
|
|
4017
|
+
}
|
|
4018
|
+
function noteSkippedResource(plan, budget, origin) {
|
|
4019
|
+
if (plan.reason !== "over-budget") return;
|
|
4020
|
+
var name = plan.name || plan.uri;
|
|
4021
|
+
skippedResources = skippedResources.filter(function(s) {
|
|
4022
|
+
return s.name !== name;
|
|
4023
|
+
});
|
|
4024
|
+
skippedResources.push({ name, size: plan.size, budget, origin });
|
|
4025
|
+
}
|
|
3991
4026
|
function thousands(n) {
|
|
3992
4027
|
return (Math.round(n / 1e3) * 1e3).toLocaleString("en-US");
|
|
3993
4028
|
}
|
|
@@ -4382,11 +4417,16 @@ function boot(cfg) {
|
|
|
4382
4417
|
var budget = resourceBudget();
|
|
4383
4418
|
if (pageResourcePlans === null || budget !== pageResourcePlansBudget) {
|
|
4384
4419
|
pageResourcePlansBudget = budget;
|
|
4420
|
+
skippedResources = skippedResources.filter(function(s) {
|
|
4421
|
+
return s.origin !== "page";
|
|
4422
|
+
});
|
|
4423
|
+
announcedSkips = false;
|
|
4385
4424
|
pageResourcePlans = planPageResources({
|
|
4386
4425
|
entries: pageCard.resources,
|
|
4387
4426
|
budgetBytes: budget,
|
|
4388
4427
|
onSkip: function(plan) {
|
|
4389
4428
|
console.info("[llm_meta_widget] not attaching " + plan.uri + " (" + plan.reason + ", size=" + plan.size + ")");
|
|
4429
|
+
noteSkippedResource(plan, resourceBudget(), "page");
|
|
4390
4430
|
}
|
|
4391
4431
|
});
|
|
4392
4432
|
}
|
|
@@ -4417,6 +4457,7 @@ function boot(cfg) {
|
|
|
4417
4457
|
read: readMcpResource,
|
|
4418
4458
|
onSkip: function(plan) {
|
|
4419
4459
|
console.info("[llm_meta_widget] not attaching " + plan.uri + " (" + plan.reason + ", size=" + plan.size + ")");
|
|
4460
|
+
noteSkippedResource(plan, resourceBudget(), "host");
|
|
4420
4461
|
}
|
|
4421
4462
|
});
|
|
4422
4463
|
if (loaded && loaded.plan && loaded.plan.fetch) {
|
|
@@ -4807,6 +4848,14 @@ function boot(cfg) {
|
|
|
4807
4848
|
if (!userText) return;
|
|
4808
4849
|
inputEl.value = "";
|
|
4809
4850
|
appendTurn("user", userText, pendingTurnLabel);
|
|
4851
|
+
pageResourcePlansNow();
|
|
4852
|
+
if (skippedResources.length && !announcedSkips) {
|
|
4853
|
+
announcedSkips = true;
|
|
4854
|
+
var skipped = skippedResources[0];
|
|
4855
|
+
var others = skippedResources.length - 1;
|
|
4856
|
+
var alsoText = others > 0 ? " and " + others + " other reference" + (others > 1 ? "s" : "") : "";
|
|
4857
|
+
appendTurn("system", "Did not attach the " + skipped.name + " reference" + alsoText + " (" + kb(skipped.size) + ", and a " + MODEL + " turn allows " + kb(skipped.budget) + " for reference data), so the answer may be less precise. A model with a larger context window would make room.");
|
|
4858
|
+
}
|
|
4810
4859
|
pendingTurnLabel = null;
|
|
4811
4860
|
if (currentAbort) {
|
|
4812
4861
|
try {
|
|
@@ -1122,10 +1122,41 @@ export function modelWindowFrom(payload, modelId) {
|
|
|
1122
1122
|
|
|
1123
1123
|
export const PAGE_GLOBAL_TRANSPORT = "page-global"
|
|
1124
1124
|
|
|
1125
|
-
|
|
1125
|
+
// Which JS global holds the handlers, from the card's declared transports.
|
|
1126
|
+
//
|
|
1127
|
+
// The array is processed IN ORDER and the first entry whose type we support
|
|
1128
|
+
// wins: the page author's order is their preference, so a supported entry is
|
|
1129
|
+
// never skipped for a later one. Only `page-global` is supported today;
|
|
1130
|
+
// `page-postmessage`, `page-jsonrpc` and `page-worker` are reserved, and a card
|
|
1131
|
+
// declaring only those gets no handlers rather than the wrong ones.
|
|
1132
|
+
//
|
|
1133
|
+
// Top-level `remotes` is the pre-d44bd25a shape that 0.11.1 and 0.12.2 read. It
|
|
1134
|
+
// is accepted for one release so a page and a widget can be updated in either
|
|
1135
|
+
// order without the page going dark in between, and it warns so the author
|
|
1136
|
+
// knows to move.
|
|
1137
|
+
export function declaredPageGlobal(card, warn = console.warn) {
|
|
1138
|
+
const declared = (card && card.transports) || []
|
|
1139
|
+
for (const transport of declared) {
|
|
1140
|
+
if (!transport || transport.type !== PAGE_GLOBAL_TRANSPORT) continue
|
|
1141
|
+
if (typeof transport.global === "string" && transport.global.trim() !== "") {
|
|
1142
|
+
return transport.global.trim()
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (declared.length) {
|
|
1146
|
+
warn("[llm-meta-widget] page card declares transports " +
|
|
1147
|
+
JSON.stringify(declared.map((t) => t && t.type)) +
|
|
1148
|
+
", none of which this widget supports; not dispatching to the page")
|
|
1149
|
+
return null
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1126
1152
|
for (const remote of (card && card.remotes) || []) {
|
|
1127
1153
|
if (!remote || remote.type !== PAGE_GLOBAL_TRANSPORT) continue
|
|
1128
|
-
if (typeof remote.global === "string" && remote.global.trim() !== "")
|
|
1154
|
+
if (typeof remote.global === "string" && remote.global.trim() !== "") {
|
|
1155
|
+
warn('[llm-meta-widget] page card declares its transport in top-level ' +
|
|
1156
|
+
'"remotes", which is deprecated; move it to ' +
|
|
1157
|
+
`_meta["${STATIC_PRIMITIVES_META}"].transports`)
|
|
1158
|
+
return remote.global.trim()
|
|
1159
|
+
}
|
|
1129
1160
|
}
|
|
1130
1161
|
return null
|
|
1131
1162
|
}
|
|
@@ -1134,7 +1165,7 @@ export function declaredPageGlobal(card) {
|
|
|
1134
1165
|
// to its legacy global independently, so a page part-way through moving over
|
|
1135
1166
|
// keeps working instead of losing the primitives it has not moved yet.
|
|
1136
1167
|
export function pageHandlers({ card, scope = {}, legacy = {}, warn = console.warn } = {}) {
|
|
1137
|
-
const name = declaredPageGlobal(card)
|
|
1168
|
+
const name = declaredPageGlobal(card, warn)
|
|
1138
1169
|
const declared = name ? scope[name] : null
|
|
1139
1170
|
|
|
1140
1171
|
// A card that names a global the page never defines is almost always a load
|
|
@@ -1179,20 +1210,28 @@ export function pageHandlers({ card, scope = {}, legacy = {}, warn = console.war
|
|
|
1179
1210
|
// Picking the first would make which component wins depend on DOM order, which
|
|
1180
1211
|
// is not something a page author controls when the components are partials.
|
|
1181
1212
|
export function mergeStaticPrimitivesCards(cards, warn = console.warn) {
|
|
1182
|
-
const merged = { remotes: [], tools: [], prompts: [], resources: [] }
|
|
1183
|
-
// Each block is a full Server Card and may declare
|
|
1184
|
-
//
|
|
1185
|
-
//
|
|
1213
|
+
const merged = { transports: [], remotes: [], tools: [], prompts: [], resources: [] }
|
|
1214
|
+
// Each block is a full Server Card and may declare transports. They are
|
|
1215
|
+
// concatenated in document order, because the order is the author's
|
|
1216
|
+
// preference and selection honours it. A second block naming a DIFFERENT
|
|
1217
|
+
// global for the same type is an authoring error — only one of them can be
|
|
1218
|
+
// dispatched to — so the first is kept and the clash is reported.
|
|
1219
|
+
//
|
|
1220
|
+
// `remotes` is merged the same way: it is the deprecated spelling of the same
|
|
1221
|
+
// thing, and a page mid-migration may still carry it.
|
|
1186
1222
|
for (const card of cards || []) {
|
|
1187
|
-
for (const
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1223
|
+
for (const kind of [ "transports", "remotes" ]) {
|
|
1224
|
+
for (const entry of (card && card[kind]) || []) {
|
|
1225
|
+
if (!entry || typeof entry.type !== "string") continue
|
|
1226
|
+
const clash = merged[kind].find((r) => r.type === entry.type && r.global !== entry.global)
|
|
1227
|
+
if (clash) {
|
|
1228
|
+
warn(`[llm-meta-widget] two blocks declare different ${entry.type} globals ` +
|
|
1229
|
+
`("${clash.global}" and "${entry.global}"); using "${clash.global}"`)
|
|
1230
|
+
continue
|
|
1231
|
+
}
|
|
1232
|
+
if (!merged[kind].some((r) => r.type === entry.type && r.global === entry.global)) {
|
|
1233
|
+
merged[kind].push(entry)
|
|
1234
|
+
}
|
|
1196
1235
|
}
|
|
1197
1236
|
}
|
|
1198
1237
|
}
|
|
@@ -1218,7 +1257,7 @@ export function mergeStaticPrimitivesCards(cards, warn = console.warn) {
|
|
|
1218
1257
|
}
|
|
1219
1258
|
|
|
1220
1259
|
export function readStaticPrimitivesCard(text, warn = console.warn) {
|
|
1221
|
-
const empty = { remotes: [], tools: [], prompts: [], resources: [] }
|
|
1260
|
+
const empty = { transports: [], remotes: [], tools: [], prompts: [], resources: [] }
|
|
1222
1261
|
if (typeof text !== "string" || text.trim() === "") return empty
|
|
1223
1262
|
|
|
1224
1263
|
let doc
|
|
@@ -1242,7 +1281,12 @@ export function readStaticPrimitivesCard(text, warn = console.warn) {
|
|
|
1242
1281
|
})
|
|
1243
1282
|
|
|
1244
1283
|
return {
|
|
1245
|
-
//
|
|
1284
|
+
// Where a page says how to reach its handlers. Since SEP d44bd25a this is
|
|
1285
|
+
// `transports` inside the extension data, because the Server Card's
|
|
1286
|
+
// `Remote.type` enum is streamable-http|sse and `page-global` was never a
|
|
1287
|
+
// valid value there. Top-level `remotes` is what 0.11.1-0.12.2 read, and is
|
|
1288
|
+
// still accepted for one release — see declaredPageGlobal.
|
|
1289
|
+
transports: Array.isArray(ext.transports) ? ext.transports : [],
|
|
1246
1290
|
remotes: Array.isArray(doc.remotes) ? doc.remotes : [],
|
|
1247
1291
|
tools: named(ext.tools, "tool"),
|
|
1248
1292
|
prompts: named(ext.prompts, "prompt"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aibranch/llm-meta-widget",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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": [
|