@aibranch/llm-meta-widget 0.13.1 → 0.15.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.13.1"
64
+ gem "llm_meta_widget", "~> 0.15.0"
65
65
  ```
66
66
 
67
67
  ```bash
@@ -152,26 +152,25 @@ 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.13"></script>
155
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@aibranch/llm-meta-widget@0.15"></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.13` is a version range, and it stops at 0.13.x on purpose.** You receive
161
+ **`@0.15` is a version range, and it stops at 0.15.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.14 is therefore a change you make yourself. Read
165
+ Moving to 0.16 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.
172
+ 0.13.0 moved where a page declares its transport, and 0.14.0 stopped reading
173
+ the old spelling. Move your card to `transports` before taking 0.14.x.
175
174
 
176
175
  A Rails app is protected from that mismatch, because the `Gemfile` will not
177
176
  resolve a widget version the page cannot use. A page that loads the widget from
@@ -312,10 +311,10 @@ widget supports today. Leave `transports` out and the widget reads
312
311
  `window.aiActions`, `window.aiPrompts` and `window.aiResources` instead — see
313
312
  "Say where your handlers live".
314
313
 
315
- Widgets up to 0.12.2 read this declaration from a `remotes` list at the top
316
- level of the card instead. Version 0.13.0 still reads that, and warns, so a page
317
- and a widget can be updated in either order. Write `transports`; a page may
318
- declare both while a deployment is still catching up, and `transports` decides.
314
+ Widgets from 0.11.1 to 0.12.2 read this declaration from a `remotes` list at the
315
+ top level of the card, and 0.13.x read either spelling. From 0.14.0 the old one
316
+ is not read at all: a card that still carries it gets no handlers, and the
317
+ widget reports that in the browser console. Write `transports`.
319
318
 
320
319
  A page may declare `prompts` in the same block. See [The page card](#the-page-card)
321
320
  for prompts, for the size and volatility hints on a resource, and for what
@@ -603,6 +602,18 @@ so no other consumer can rely on them. Each kind falls back on its own, so you
603
602
  can move tools into the declared global and leave prompts and resources where
604
603
  they are.
605
604
 
605
+ **Script order does not matter.** The widget looks up your global at the moment
606
+ it needs a handler, not when it reads the card. The script that defines
607
+ `window.mcp` may therefore run before or after the widget loads: a `defer`
608
+ attribute, an ES module, or a `<script>` near the end of the page are all fine.
609
+ If a handler is wanted while the global is still undefined, the widget says so
610
+ in the browser console once, and falls back to the older globals for that call.
611
+
612
+ Before 0.15.0 the lookup happened once at startup, so a page whose handler
613
+ script ran later got no handlers at all for the rest of its life, with only a
614
+ console warning to say why. If you are pinned below 0.15.0, define your global
615
+ before the widget's own script tag.
616
+
606
617
  **The card is found by its media type.** A client matches
607
618
  `script[type="application/mcp-static-primitives+json"]`, so the element needs no
608
619
  `id`. This is how a client that was never told about your page can still read
@@ -701,35 +701,49 @@ function declaredPageGlobal(card, warn = console.warn) {
701
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
702
  return null;
703
703
  }
704
- for (const remote of card && card.remotes || []) {
705
- if (!remote || remote.type !== PAGE_GLOBAL_TRANSPORT) continue;
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
- }
704
+ if ((card && card.remotes || []).some((r) => r && r.type === PAGE_GLOBAL_TRANSPORT)) {
705
+ warn(`[llm-meta-widget] page card declares its transport in top-level "remotes", which is no longer read. Move it to _meta["${STATIC_PRIMITIVES_META}"].transports`);
710
706
  }
711
707
  return null;
712
708
  }
713
709
  function pageHandlers({ card, scope = {}, legacy = {}, warn = console.warn } = {}) {
714
710
  const name = declaredPageGlobal(card, warn);
715
- const declared = name ? scope[name] : null;
716
- if (name && !declared) {
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?`);
718
- }
719
711
  const isMap = (v) => !!v && typeof v === "object" && !Array.isArray(v);
712
+ const said = /* @__PURE__ */ new Set();
713
+ const sayOnce = (key, message) => {
714
+ if (said.has(key)) return;
715
+ said.add(key);
716
+ warn(message);
717
+ };
720
718
  const pick = (kind, legacyName) => {
719
+ const declared = name ? scope[name] : null;
720
+ if (name && !declared) {
721
+ sayOnce(
722
+ "missing",
723
+ `[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?`
724
+ );
725
+ }
721
726
  const fromGlobal = declared ? declared[kind] : void 0;
722
727
  if (isMap(fromGlobal)) return fromGlobal;
723
728
  if (declared && fromGlobal !== void 0 && !isMap(fromGlobal)) {
724
- warn(`[llm-meta-widget] window.${name}.${kind} is ${Array.isArray(fromGlobal) ? "an array" : typeof fromGlobal}; it must be an object keyed by ${kind === "resources" ? "uri" : "name"}`);
729
+ sayOnce(
730
+ `shape:${kind}`,
731
+ `[llm-meta-widget] window.${name}.${kind} is ${Array.isArray(fromGlobal) ? "an array" : typeof fromGlobal}; it must be an object keyed by ${kind === "resources" ? "uri" : "name"}`
732
+ );
725
733
  }
726
734
  return legacyName && scope[legacyName] || {};
727
735
  };
728
736
  return {
729
737
  global: name,
730
- tools: pick("tools", legacy.tools),
731
- prompts: pick("prompts", legacy.prompts),
732
- resources: pick("resources", legacy.resources)
738
+ get tools() {
739
+ return pick("tools", legacy.tools);
740
+ },
741
+ get prompts() {
742
+ return pick("prompts", legacy.prompts);
743
+ },
744
+ get resources() {
745
+ return pick("resources", legacy.resources);
746
+ }
733
747
  };
734
748
  }
735
749
  function mergeStaticPrimitivesCards(cards, warn = console.warn) {
@@ -740,7 +754,7 @@ function mergeStaticPrimitivesCards(cards, warn = console.warn) {
740
754
  if (!entry || typeof entry.type !== "string") continue;
741
755
  const clash = merged[kind].find((r) => r.type === entry.type && r.global !== entry.global);
742
756
  if (clash) {
743
- warn(`[llm-meta-widget] two blocks declare different ${entry.type} globals ("${clash.global}" and "${entry.global}"); using "${clash.global}"`);
757
+ warn(`[llm-meta-widget] the ${entry.type} transport is declared more than once, with different globals ("${clash.global}" and "${entry.global}"); using "${clash.global}". One block may declare it twice, or two blocks may disagree`);
744
758
  continue;
745
759
  }
746
760
  if (!merged[kind].some((r) => r.type === entry.type && r.global === entry.global)) {
@@ -4167,7 +4181,9 @@ function boot(cfg) {
4167
4181
  );
4168
4182
  } else {
4169
4183
  var pageCardEl = document.getElementById(PAGE_CARD_ID);
4170
- pageCard = readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null);
4184
+ pageCard = mergeStaticPrimitivesCards(
4185
+ [readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null)]
4186
+ );
4171
4187
  }
4172
4188
  var handlers = pageHandlers({
4173
4189
  card: pageCard,
@@ -1130,10 +1130,9 @@ export const PAGE_GLOBAL_TRANSPORT = "page-global"
1130
1130
  // `page-postmessage`, `page-jsonrpc` and `page-worker` are reserved, and a card
1131
1131
  // declaring only those gets no handlers rather than the wrong ones.
1132
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.
1133
+ // Top-level `remotes` is the pre-d44bd25a shape that 0.11.1 to 0.12.2 read and
1134
+ // 0.13.x accepted. It is no longer read, but a card still carrying it is
1135
+ // reported, because the alternative is a page that quietly stops working.
1137
1136
  export function declaredPageGlobal(card, warn = console.warn) {
1138
1137
  const declared = (card && card.transports) || []
1139
1138
  for (const transport of declared) {
@@ -1149,14 +1148,14 @@ export function declaredPageGlobal(card, warn = console.warn) {
1149
1148
  return null
1150
1149
  }
1151
1150
 
1152
- for (const remote of (card && card.remotes) || []) {
1153
- if (!remote || remote.type !== PAGE_GLOBAL_TRANSPORT) continue
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
- }
1151
+ // No longer read. Reporting it is still worth a few lines: without this a
1152
+ // card that declares its transport only in the old place gets no handlers,
1153
+ // falls through to the legacy globals, and a page that deliberately defines
1154
+ // none — as PubDictionaries does — simply stops working with nothing said.
1155
+ if (((card && card.remotes) || []).some((r) => r && r.type === PAGE_GLOBAL_TRANSPORT)) {
1156
+ warn('[llm-meta-widget] page card declares its transport in top-level ' +
1157
+ '"remotes", which is no longer read. Move it to ' +
1158
+ `_meta["${STATIC_PRIMITIVES_META}"].transports`)
1160
1159
  }
1161
1160
  return null
1162
1161
  }
@@ -1166,19 +1165,36 @@ export function declaredPageGlobal(card, warn = console.warn) {
1166
1165
  // keeps working instead of losing the primitives it has not moved yet.
1167
1166
  export function pageHandlers({ card, scope = {}, legacy = {}, warn = console.warn } = {}) {
1168
1167
  const name = declaredPageGlobal(card, warn)
1169
- const declared = name ? scope[name] : null
1170
-
1171
- // A card that names a global the page never defines is almost always a load
1172
- // order problem, or a script that threw before it got there. Falling back in
1173
- // silence hides that: the page then behaves as if it had declared nothing,
1174
- // and the first symptom is a prompt or tool that "has no handler".
1175
- if (name && !declared) {
1176
- warn(`[llm-meta-widget] the page card declares global "${name}", but window.${name} ` +
1177
- "is not defined — is the script that defines it loaded, and did it run?")
1168
+ const isMap = (v) => !!v && typeof v === "object" && !Array.isArray(v)
1169
+
1170
+ // Each said at most once. These now fire on dispatch rather than at mount, so
1171
+ // a page that never defines its global would otherwise repeat them every turn.
1172
+ const said = new Set()
1173
+ const sayOnce = (key, message) => {
1174
+ if (said.has(key)) return
1175
+ said.add(key)
1176
+ warn(message)
1178
1177
  }
1179
1178
 
1180
- const isMap = (v) => !!v && typeof v === "object" && !Array.isArray(v)
1181
1179
  const pick = (kind, legacyName) => {
1180
+ // Resolved HERE, when a handler is wanted, not when the card was read. The
1181
+ // card is parsed from the DOM before the page's own scripts have
1182
+ // necessarily run, and a consumer may be injected before them, so a global
1183
+ // that is missing at discovery may well be defined by the time a tool is
1184
+ // called. Caching that first lookup turned a load-order detail into a page
1185
+ // that silently had no handlers for the rest of its life.
1186
+ const declared = name ? scope[name] : null
1187
+
1188
+ // A card that names a global the page never defines is almost always a load
1189
+ // order problem, or a script that threw before it got there. Falling back in
1190
+ // silence hides that: the page then behaves as if it had declared nothing,
1191
+ // and the first symptom is a prompt or tool that "has no handler".
1192
+ if (name && !declared) {
1193
+ sayOnce("missing",
1194
+ `[llm-meta-widget] the page card declares global "${name}", but window.${name} ` +
1195
+ "is not defined — is the script that defines it loaded, and did it run?")
1196
+ }
1197
+
1182
1198
  // `declared ? … : undefined`, not `declared && …`: the second yields null
1183
1199
  // when nothing is declared, and `typeof null === "object"`, so the check
1184
1200
  // below then warned about "window.null.tools" on every page with no card.
@@ -1187,16 +1203,20 @@ export function pageHandlers({ card, scope = {}, legacy = {}, warn = console.war
1187
1203
  // An array is the mistake worth naming: it passes a typeof check, so it
1188
1204
  // would be used as a map and answer every lookup with undefined.
1189
1205
  if (declared && fromGlobal !== undefined && !isMap(fromGlobal)) {
1190
- warn(`[llm-meta-widget] window.${name}.${kind} is ${Array.isArray(fromGlobal) ? "an array" : typeof fromGlobal}; ` +
1191
- `it must be an object keyed by ${kind === "resources" ? "uri" : "name"}`)
1206
+ sayOnce(`shape:${kind}`,
1207
+ `[llm-meta-widget] window.${name}.${kind} is ${Array.isArray(fromGlobal) ? "an array" : typeof fromGlobal}; ` +
1208
+ `it must be an object keyed by ${kind === "resources" ? "uri" : "name"}`)
1192
1209
  }
1193
1210
  return (legacyName && scope[legacyName]) || {}
1194
1211
  }
1212
+
1213
+ // Getters, so each dispatch re-resolves. The call sites read
1214
+ // `handlers.tools` at the moment they need it, which is what makes this work.
1195
1215
  return {
1196
1216
  global: name,
1197
- tools: pick("tools", legacy.tools),
1198
- prompts: pick("prompts", legacy.prompts),
1199
- resources: pick("resources", legacy.resources)
1217
+ get tools() { return pick("tools", legacy.tools) },
1218
+ get prompts() { return pick("prompts", legacy.prompts) },
1219
+ get resources() { return pick("resources", legacy.resources) }
1200
1220
  }
1201
1221
  }
1202
1222
 
@@ -1225,8 +1245,9 @@ export function mergeStaticPrimitivesCards(cards, warn = console.warn) {
1225
1245
  if (!entry || typeof entry.type !== "string") continue
1226
1246
  const clash = merged[kind].find((r) => r.type === entry.type && r.global !== entry.global)
1227
1247
  if (clash) {
1228
- warn(`[llm-meta-widget] two blocks declare different ${entry.type} globals ` +
1229
- `("${clash.global}" and "${entry.global}"); using "${clash.global}"`)
1248
+ warn(`[llm-meta-widget] the ${entry.type} transport is declared more than once, ` +
1249
+ `with different globals ("${clash.global}" and "${entry.global}"); using ` +
1250
+ `"${clash.global}". One block may declare it twice, or two blocks may disagree`)
1230
1251
  continue
1231
1252
  }
1232
1253
  if (!merged[kind].some((r) => r.type === entry.type && r.global === entry.global)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aibranch/llm-meta-widget",
3
- "version": "0.13.1",
3
+ "version": "0.15.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": [