llm_meta_widget 0.12.2 → 0.13.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d3172a4471dd7326d3455cbb4d39ed5f19092df52e81828b98d71a0c541da57
4
- data.tar.gz: 1ec235057d668445bcb40cdb6b584cc73c1c579ee54c2cf36b7bcef699efb5b9
3
+ metadata.gz: b72c0af2bf75bb5a7d1b83b0048aac15dc0485d86448ca4ea99403737c7a95ec
4
+ data.tar.gz: 1c5dfb8782859c09727fde1f996a39055c0cbe8e75b6116258b9d547a4a7b7a4
5
5
  SHA512:
6
- metadata.gz: 1dcda012841f3a4c1f253c837d1149292f0c19cdc6a5396cb7ab7064bfb0ac0dbae5414bc0e33529fb925a4d5b15de28a78cda13bba26bf4856fca84f13a94e2
7
- data.tar.gz: 338955619e9f6712e6889bbe62a295de75b7cf9cb6571d7cd9aab0ac227b52caf5d6c891b14ef136bcc115e6da0f721f441b4a9a7bd3be96248d55cde441870e
6
+ metadata.gz: cf20127dce92b482a6c6a88a42e3a31fc2b80e127274c9cbae5e7d6f75d3043c9334e38459a7feddf90f0c985984f1a3cdced6550091953952ab44a9282fcf25
7
+ data.tar.gz: 5554707eda6744fdf8188c1c7d81765c27c068cb078a672d894825cb70f8f5c2c2e6ed4f3593f5a5432b7255bd7d0f01718c14804b1dcba9af661c0df7667a3c
data/CHANGELOG.md CHANGED
@@ -3,6 +3,75 @@
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.1
7
+
8
+ ### Documentation: the README caught up with 0.12.x and 0.13.0
9
+
10
+ Three examples and two explanations still told a page author to declare the
11
+ transport in a top-level `remotes` list. Following the README would have
12
+ produced a card this version warns about and a later version will stop reading.
13
+ They now use `transports` inside the extension data, and say that the list is
14
+ read in order and that the first supported type wins.
15
+
16
+ The README also said nothing about what the widget does when a turn does not fit
17
+ the model's context, although every part of that behaviour shipped in 0.12.x and
18
+ puts messages in the panel that a page's visitors will read. There is now a
19
+ section describing the three cases — one oversized tool result shortened, older
20
+ results given up, a reference resource refused for its size — with the wording
21
+ the reader actually sees.
22
+
23
+ No code changed in this release.
24
+
25
+ ## 0.13.0
26
+
27
+ ### Changed: a page card declares its transport in `transports`, not `remotes`
28
+
29
+ The declaration that says how to reach a page's handlers has moved, following
30
+ SEP revision d44bd25a. It is now an array inside the extension data:
31
+
32
+ ```json
33
+ {
34
+ "_meta": {
35
+ "io.modelcontextprotocol/static-primitives": {
36
+ "transports": [ { "type": "page-global", "global": "mcp" } ],
37
+ "tools": [], "prompts": [], "resources": []
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ The reason is that the Server Card's `Remote.type` can only be
44
+ `streamable-http` or `sse`, so `page-global` was never a valid value at the top
45
+ level. It is an array because a page may come to offer more than one page-local
46
+ transport, and the order is the author's preference.
47
+
48
+ The widget reads the array in order and uses the first type it supports. It
49
+ will not skip a supported entry for a later one. Only `page-global` is
50
+ supported today; a card offering only `page-postmessage`, `page-jsonrpc` or
51
+ `page-worker` gets no handlers rather than the wrong ones, and says so.
52
+
53
+ **Top-level `remotes` still works in this release**, with a warning, so a page
54
+ and a widget can be updated in either order without the page losing its tools
55
+ in between. Move to `transports`; a later release will drop the fallback. If a
56
+ page declares both, `transports` decides and nothing is reported.
57
+
58
+
59
+ ### Added: the panel says when reference data did not fit
60
+
61
+ A resource refused by the budget used to disappear with nothing but a line in
62
+ the browser console: the model answered from less than the page offered, and
63
+ nobody was told. The panel now reports it, naming the resource, its size and
64
+ what the model allows — once per budget, on the first turn it affects, and
65
+ again if a different model changes the figures.
66
+
67
+ The model is deliberately NOT told. It cannot fetch a resource — only the
68
+ client reads those — so telling it would spend context on something it can do
69
+ nothing about. The reader can act: choose a model with a larger window, or ask
70
+ the page author for a smaller reference.
71
+
72
+ A resource the page withholds on purpose (`autoAttach: false`) is not reported,
73
+ since that is the author's choice rather than an omission.
74
+
6
75
  ## 0.12.2
7
76
 
8
77
  ### 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.1"
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
@@ -268,8 +272,8 @@ primitive type:
268
272
  <script type="application/mcp-static-primitives+json">
269
273
  {
270
274
  "name": "org.example/your-page",
271
- "remotes": [ { "type": "page-global", "global": "mcp" } ],
272
275
  "_meta": { "io.modelcontextprotocol/static-primitives": {
276
+ "transports": [ { "type": "page-global", "global": "mcp" } ],
273
277
  "tools": [
274
278
  { "name": "add_dictionaries",
275
279
  "description": "Add the named dictionaries to the current selection.",
@@ -301,11 +305,18 @@ primitive type:
301
305
  A tool's schema may be spelled `inputSchema`, as MCP spells it, or
302
306
  `input_schema`. Both are accepted.
303
307
 
304
- `remotes` is how a consumer other than this widget finds your handlers, so the
305
- global is declared rather than assumed. Leave it out and the widget reads
308
+ `transports` is how a consumer other than this widget finds your handlers, so
309
+ the global is declared rather than assumed. It is a list, and the consumer uses
310
+ the first entry whose `type` it supports; `page-global` is the only type this
311
+ widget supports today. Leave `transports` out and the widget reads
306
312
  `window.aiActions`, `window.aiPrompts` and `window.aiResources` instead — see
307
313
  "Say where your handlers live".
308
314
 
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.
319
+
309
320
  A page may declare `prompts` in the same block. See [The page card](#the-page-card)
310
321
  for prompts, for the size and volatility hints on a resource, and for what
311
322
  happens when a document holds several blocks.
@@ -511,8 +522,8 @@ name the global that holds its handlers. Both are below.
511
522
  <script type="application/mcp-static-primitives+json">
512
523
  {
513
524
  "name": "org.example/your-page",
514
- "remotes": [ { "type": "page-global", "global": "mcp" } ],
515
525
  "_meta": { "io.modelcontextprotocol/static-primitives": {
526
+ "transports": [ { "type": "page-global", "global": "mcp" } ],
516
527
  "tools": [ { "name": "set_text", "description": "…", "inputSchema": { } } ],
517
528
  "prompts": [ { "name": "annotate", "description": "Annotate this page" } ],
518
529
  "resources": [ { "uri": "page:text", "name": "Text", "description": "the text to annotate",
@@ -522,8 +533,8 @@ name the global that holds its handlers. Both are below.
522
533
  </script>
523
534
  ```
524
535
 
525
- `name` identifies the card, as it does for a server. `remotes` says where the
526
- handlers live — see "Say where your handlers live" below. Only the `_meta`
536
+ `name` identifies the card, as it does for a server. `transports` says where
537
+ the handlers live — see "Say where your handlers live" below. Only the `_meta`
527
538
  block is required by this widget; the rest is what makes the card readable by
528
539
  anything else.
529
540
 
@@ -569,8 +580,8 @@ them:
569
580
  ```html
570
581
  <script type="application/mcp-static-primitives+json">
571
582
  { "name": "org.example/your-page",
572
- "remotes": [ { "type": "page-global", "global": "mcp" } ],
573
- "_meta": { "io.modelcontextprotocol/static-primitives": { "tools": [ ... ] } } }
583
+ "_meta": { "io.modelcontextprotocol/static-primitives": {
584
+ "transports": [ { "type": "page-global", "global": "mcp" } ], "tools": [ ... ] } } }
574
585
  </script>
575
586
  <script>
576
587
  window.mcp = {
@@ -718,6 +729,47 @@ as **optional**, and fill them from page state where you can. An argument the
718
729
  visitor must type in makes the button useless to the visitor who needed it most:
719
730
  to press it, they would already have to know your form.
720
731
 
732
+ ## When a turn does not fit the model's context
733
+
734
+ A model holds a fixed number of tokens. A long conversation, a large reference
735
+ resource and a few big tool results can exceed it, and the provider then refuses
736
+ the whole turn. The widget tries to keep a turn inside the limit instead, and
737
+ tells the reader whenever it has to change something. You do not configure any
738
+ of this, but your page's visitors will see the messages, so it is worth knowing
739
+ what they mean.
740
+
741
+ The widget learns each model's context window from the server's model list. If
742
+ the server does not report one, none of the behaviour below applies and the turn
743
+ is sent as it is.
744
+
745
+ **One oversized tool result is shortened.** A single result may use at most 40%
746
+ of the window. A larger one is cut to that size, and the model is told in the
747
+ result itself how much was shown and that it can call the tool again with a
748
+ narrower query. The panel says, for example:
749
+
750
+ > Shortened an oversized togovar_search_variant result to fit the context:
751
+ > 121,000 tokens, where one result may use at most 51,000. Ask again with a
752
+ > narrower question for the complete result.
753
+
754
+ **Older tool results are given up when the whole turn is too long.** This is for
755
+ a conversation that has grown over many turns, not for one large result. The
756
+ oldest results go first, one at a time, until the turn fits. The questions, the
757
+ answers and the model's own summaries of what it read are never touched, and a
758
+ dropped result leaves a note in its place so the model knows to ask again.
759
+
760
+ **A reference resource that does not fit is not attached.** Each turn may carry
761
+ about 6% of the window as reference data — roughly 8 KB for a 32,000-token model
762
+ and 32 KB for a 128,000-token one. A resource larger than that is not fetched at
763
+ all, and the panel says so, naming the resource and both figures. The model is
764
+ not told, because it cannot fetch a resource itself; the reader can act, by
765
+ choosing a model with a larger window or asking you for a smaller resource.
766
+
767
+ If you publish a large document through MCP, consider offering it in sections
768
+ behind one tool that takes a section name, rather than as a single tool result.
769
+ The list of section names in the tool's own schema costs a few hundred tokens
770
+ and is visible to every client, while a document of tens of kilobytes crowds out
771
+ the conversation it is supposed to support.
772
+
721
773
  ## Further reading
722
774
 
723
775
  - **The static-primitives extension** — `io.modelcontextprotocol/static-primitives`,
@@ -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.1"
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - jdkim