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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d3172a4471dd7326d3455cbb4d39ed5f19092df52e81828b98d71a0c541da57
4
- data.tar.gz: 1ec235057d668445bcb40cdb6b584cc73c1c579ee54c2cf36b7bcef699efb5b9
3
+ metadata.gz: 98514af704b8e59db0091c55a0d42f60cc3d9da77ed8debbf8aa2b94e514576d
4
+ data.tar.gz: e0b25d087956a31ad5bc21ce232b25abe1aa0ca78a917abdaf6f0e97c7ba7237
5
5
  SHA512:
6
- metadata.gz: 1dcda012841f3a4c1f253c837d1149292f0c19cdc6a5396cb7ab7064bfb0ac0dbae5414bc0e33529fb925a4d5b15de28a78cda13bba26bf4856fca84f13a94e2
7
- data.tar.gz: 338955619e9f6712e6889bbe62a295de75b7cf9cb6571d7cd9aab0ac227b52caf5d6c891b14ef136bcc115e6da0f721f441b4a9a7bd3be96248d55cde441870e
6
+ metadata.gz: 15101bd9cbac468ccc0c2f5e7e5206e153a459335954779f80694e7431713aa5835849b4b9278511df408bd237adc37cfc16ef658b7a85b279b23d6871eb46d8
7
+ data.tar.gz: e81e6404165550d0521eb1fba9aa18d4e10ebc0cac400f50e6ff9453084f84e082baba1f7b386155d0b0d567c2a0e51e6de93d16a6a80fba0260f949ad1460d7
data/CHANGELOG.md CHANGED
@@ -3,6 +3,56 @@
3
3
  This file starts at 0.8.0, the first release with a breaking change worth
4
4
  calling out. Earlier releases are in the git history (`git log --oneline v0.7.6`).
5
5
 
6
+ ## 0.13.0
7
+
8
+ ### Changed: a page card declares its transport in `transports`, not `remotes`
9
+
10
+ The declaration that says how to reach a page's handlers has moved, following
11
+ SEP revision d44bd25a. It is now an array inside the extension data:
12
+
13
+ ```json
14
+ {
15
+ "_meta": {
16
+ "io.modelcontextprotocol/static-primitives": {
17
+ "transports": [ { "type": "page-global", "global": "mcp" } ],
18
+ "tools": [], "prompts": [], "resources": []
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ The reason is that the Server Card's `Remote.type` can only be
25
+ `streamable-http` or `sse`, so `page-global` was never a valid value at the top
26
+ level. It is an array because a page may come to offer more than one page-local
27
+ transport, and the order is the author's preference.
28
+
29
+ The widget reads the array in order and uses the first type it supports. It
30
+ will not skip a supported entry for a later one. Only `page-global` is
31
+ supported today; a card offering only `page-postmessage`, `page-jsonrpc` or
32
+ `page-worker` gets no handlers rather than the wrong ones, and says so.
33
+
34
+ **Top-level `remotes` still works in this release**, with a warning, so a page
35
+ and a widget can be updated in either order without the page losing its tools
36
+ in between. Move to `transports`; a later release will drop the fallback. If a
37
+ page declares both, `transports` decides and nothing is reported.
38
+
39
+
40
+ ### Added: the panel says when reference data did not fit
41
+
42
+ A resource refused by the budget used to disappear with nothing but a line in
43
+ the browser console: the model answered from less than the page offered, and
44
+ nobody was told. The panel now reports it, naming the resource, its size and
45
+ what the model allows — once per budget, on the first turn it affects, and
46
+ again if a different model changes the figures.
47
+
48
+ The model is deliberately NOT told. It cannot fetch a resource — only the
49
+ client reads those — so telling it would spend context on something it can do
50
+ nothing about. The reader can act: choose a model with a larger window, or ask
51
+ the page author for a smaller reference.
52
+
53
+ A resource the page withholds on purpose (`autoAttach: false`) is not reported,
54
+ since that is the author's choice rather than an omission.
55
+
6
56
  ## 0.12.2
7
57
 
8
58
  ### Changed: one oversized tool result is shortened, not paid for by the others
data/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
@@ -171,7 +171,35 @@ function boot(cfg) {
171
171
 
172
172
  // Rounded to the nearest thousand and grouped, so the numbers read as a
173
173
  // scale rather than as a precise count the estimate cannot support.
174
- function thousands(n) {
174
+ // Reference data the budget refused, so the panel can say so on the next
175
+ // turn. NOT announced at boot: appendTurn persists, so a boot-time notice
176
+ // would be saved into the transcript and repeat on every reload.
177
+ //
178
+ // The model is not told, deliberately. It cannot fetch a resource — only the
179
+ // client reads those — so telling it would spend tokens on something it can
180
+ // do nothing about. The person CAN act: pick a model with more room, or ask
181
+ // the page author for a smaller reference.
182
+ var skippedResources = [];
183
+ var announcedSkips = false;
184
+
185
+ function kb(bytes) { return Math.round((bytes || 0) / 1024) + " KB"; }
186
+
187
+ // `origin` is "page" for a page-card resource and "host" for one from a
188
+ // host-wide server. It matters when the budget changes: page resources are
189
+ // planned again, so their refusals are re-recorded, while a host resource is
190
+ // listed once at boot and never re-planned. Clearing both on a re-plan
191
+ // erased the host refusal before it could be reported — which silenced the
192
+ // case that matters most, since PubDictionaries' catalog arrives that way.
193
+ function noteSkippedResource(plan, budget, origin) {
194
+ // A resource the page marked autoAttach:false is a deliberate choice, not
195
+ // an omission — only a budget refusal is worth reporting.
196
+ if (plan.reason !== "over-budget") return;
197
+ var name = plan.name || plan.uri;
198
+ skippedResources = skippedResources.filter(function(s) { return s.name !== name; });
199
+ skippedResources.push({ name: name, size: plan.size, budget: budget, origin: origin });
200
+ }
201
+
202
+ function thousands(n) {
175
203
  return (Math.round(n / 1000) * 1000).toLocaleString("en-US");
176
204
  }
177
205
 
@@ -701,12 +729,19 @@ function boot(cfg) {
701
729
  var budget = resourceBudget();
702
730
  if (pageResourcePlans === null || budget !== pageResourcePlansBudget) {
703
731
  pageResourcePlansBudget = budget;
732
+ // A different budget is a different decision: say it again. Only the
733
+ // page's own refusals are dropped — those are about to be re-recorded
734
+ // by the planning below. A host resource is listed once at boot, so
735
+ // discarding its refusal here would lose it for good.
736
+ skippedResources = skippedResources.filter(function(s) { return s.origin !== "page"; });
737
+ announcedSkips = false;
704
738
  pageResourcePlans = planPageResources({
705
739
  entries: pageCard.resources,
706
740
  budgetBytes: budget,
707
741
  onSkip: function(plan) {
708
742
  console.info("[llm_meta_widget] not attaching " + plan.uri +
709
743
  " (" + plan.reason + ", size=" + plan.size + ")");
744
+ noteSkippedResource(plan, resourceBudget(), "page");
710
745
  }
711
746
  });
712
747
  }
@@ -759,6 +794,7 @@ function boot(cfg) {
759
794
  onSkip: function(plan) {
760
795
  console.info("[llm_meta_widget] not attaching " + plan.uri +
761
796
  " (" + plan.reason + ", size=" + plan.size + ")");
797
+ noteSkippedResource(plan, resourceBudget(), "host");
762
798
  }
763
799
  });
764
800
  // A volatile resource comes back with no context — it is read
@@ -1143,7 +1179,10 @@ function boot(cfg) {
1143
1179
  // pixels — while scrollTop and scrollHeight stay correct. So the reader can
1144
1180
  // scroll a few pixels and no further, by any input method, while
1145
1181
  // `scrollTop = 600` set from script still works. Chrome 154 reproduces it
1146
- // with no widget code at all; Firefox 158 never does (reported 2026-10-17).
1182
+ // with no widget code at all; Firefox 158 never does.
1183
+ // https://issues.chromium.org/issues/574960594 — docs/chromium-scroll-bug.md has the
1184
+ // measurements and a self-checking test case. Remove this once the fix
1185
+ // has shipped widely enough.
1147
1186
  //
1148
1187
  // Taking the scroller off the inner box BEFORE the close avoids the state.
1149
1188
  // Two frames, because one is not enough for Chrome to pick up the new
@@ -1307,6 +1346,23 @@ function boot(cfg) {
1307
1346
  if (!userText) return;
1308
1347
  inputEl.value = "";
1309
1348
  appendTurn("user", userText, pendingTurnLabel);
1349
+
1350
+ // Reference data that did not fit, reported once per budget, at the moment
1351
+ // it affects an answer rather than at boot.
1352
+ // Plan the page's resources first: the refusals are recorded while planning,
1353
+ // which otherwise happens later in the turn — after this check had already
1354
+ // looked at an empty list and said nothing.
1355
+ pageResourcePlansNow();
1356
+ if (skippedResources.length && !announcedSkips) {
1357
+ announcedSkips = true;
1358
+ var skipped = skippedResources[0];
1359
+ var others = skippedResources.length - 1;
1360
+ var alsoText = others > 0 ? " and " + others + " other reference" + (others > 1 ? "s" : "") : "";
1361
+ appendTurn("system", "Did not attach the " + skipped.name + " reference" + alsoText +
1362
+ " (" + kb(skipped.size) + ", and a " + MODEL + " turn allows " + kb(skipped.budget) +
1363
+ " for reference data), so the answer may be less precise." +
1364
+ " A model with a larger context window would make room.");
1365
+ }
1310
1366
  pendingTurnLabel = null;
1311
1367
 
1312
1368
  if (currentAbort) { try { currentAbort.abort(); } catch (e) { /* noop */ } }
@@ -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"),
@@ -1,3 +1,3 @@
1
1
  module LlmMetaWidget
2
- VERSION = "0.12.2"
2
+ VERSION = "0.13.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm_meta_widget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.2
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - jdkim