@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.12.2"
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.12"></script>
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.12` is a version range, and it stops at 0.12.x on purpose.** You receive
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.13 is therefore a change you make yourself. Read
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() !== "") return 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 remote of card && card.remotes || []) {
725
- const clash = merged.remotes.find((r) => r.type === remote.type && r.global !== remote.global);
726
- if (clash) {
727
- warn(`[llm-meta-widget] two blocks declare different ${remote.type} globals ("${clash.global}" and "${remote.global}"); using "${clash.global}"`);
728
- continue;
729
- }
730
- if (!merged.remotes.some((r) => r.type === remote.type && r.global === remote.global)) {
731
- merged.remotes.push(remote);
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
- // The whole card's transport, so what it declares survives the parse.
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
- export function declaredPageGlobal(card) {
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() !== "") return 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 a transport. The first
1184
- // one wins; a second block naming a DIFFERENT global is an authoring error,
1185
- // because only one of them can be dispatched to.
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 remote of (card && card.remotes) || []) {
1188
- const clash = merged.remotes.find((r) => r.type === remote.type && r.global !== remote.global)
1189
- if (clash) {
1190
- warn(`[llm-meta-widget] two blocks declare different ${remote.type} globals ` +
1191
- `("${clash.global}" and "${remote.global}"); using "${clash.global}"`)
1192
- continue
1193
- }
1194
- if (!merged.remotes.some((r) => r.type === remote.type && r.global === remote.global)) {
1195
- merged.remotes.push(remote)
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
- // The whole card's transport, so what it declares survives the parse.
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.12.2",
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": [