llm_meta_widget 0.14.0 → 0.15.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: e67bd92aa30299ffd0f7d6e3a9291dc5c398800cf9276b04768101077e212880
4
- data.tar.gz: 487c187c3d20631b38f72cc4b6b9241db285ae5a7e2e5e5ef50f814bcb8cee42
3
+ metadata.gz: 5b7d8d8ce878a102002639b418702b741535fb67a23002ed74a6bb8382b4791f
4
+ data.tar.gz: 9f2482cef0da260962f8fa2ff5d78952d6bdf6d6ae9bdbab2b3abe9348d8b2b9
5
5
  SHA512:
6
- metadata.gz: 39e08820f2551e2eccd15b71b7bba68e2342911cb7ec57de0b1f9ee8d26a720b8e36283f2c033a019fe206de3dc1d10b4337c5fedaee94a39c72ba9482c36bcc
7
- data.tar.gz: e373ab9a30c2274817d7fae2da1bdea107d024c92f6204af3ae404debcbc22011b6de131bfaf6d25205d420f8f1b98ce8e9328220dcff81ae8142822dbec2a6e
6
+ metadata.gz: dc3ad442ac618dd9f8fa74af526a2d6e5a4d042a318af9492da7a5482dcb92f7fdcc1b22e004cdb5c6ce8d1b226ae81d9d425535d714ccac188fed1e6a6522e5
7
+ data.tar.gz: aa0875cbba4e0af9d92588db27718bf846143b019510418209c5146972a245223e6f92e7e5927cdd9f8a0de2a884b14f76db8fb885b276c4873b1c01b35f5956
data/CHANGELOG.md CHANGED
@@ -3,6 +3,70 @@
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.15.1
7
+
8
+ ### A repeat visit no longer loses the model's context window or the hub tools
9
+
10
+ The hub answers `/api/llms` and `/api/mcp_servers` with an ETag and
11
+ `must-revalidate`, so a browser revalidated them on the second page load and
12
+ got `304 Not Modified`. `Response.ok` is false for a 304, and both handlers
13
+ turned that into an empty payload.
14
+
15
+ Nothing announced it. Without the model's context window the resource budget
16
+ fell to its 8 KB floor, so a page's reference data was reported as skipped even
17
+ when it would have fitted; and tool-result truncation and oldest-result
18
+ eviction both stood down, because each returns its input unchanged when it has
19
+ no window to spend. An empty hub list meanwhile showed as "No hub-registered
20
+ tools available". It all worked on a first load and stopped on every one after,
21
+ which is why a hard reload always looked fine.
22
+
23
+ Both requests now set `cache: "no-store"`, so no conditional request is sent
24
+ and the answer always carries a body. A response that is not ok is reported in
25
+ the browser console, naming what is lost, rather than being treated as an empty
26
+ result.
27
+
28
+ The hub stopped sending those ETags as well, so this release is not required to
29
+ fix a page served by an up-to-date hub. It matters for any other server that
30
+ does send them, and for making the next failure audible.
31
+
32
+ ## 0.15.0
33
+
34
+ ### The declared global is resolved when a handler is needed, not at startup
35
+
36
+ A page card is parsed from the DOM before the page's own scripts have
37
+ necessarily run, and a consumer may be injected before them. Until now the
38
+ widget looked up the global named by the card's `page-global` transport once, at
39
+ startup, and kept the result: a page whose handler script ran later — with
40
+ `defer`, as an ES module, or from a `<script>` near the end of the document —
41
+ got no handlers at all for the rest of its life, with only a console warning to
42
+ explain it.
43
+
44
+ The lookup now happens each time a handler is wanted, so script order no longer
45
+ matters. A global defined after the card was read is found; a global that
46
+ disappears falls back to the older globals again instead of serving a stale map.
47
+
48
+ The "declares a global the page never defines" message moved with it. At startup
49
+ it was a false alarm, because the defining script may simply not have run yet;
50
+ it is now said when a handler is actually wanted, and said once rather than on
51
+ every lookup.
52
+
53
+ This is what SEP-0000 asks of a consumer: handler availability is a runtime
54
+ binding, and the burden sits on the consumer, which is the side that can know
55
+ its own injection timing.
56
+
57
+ ### Two smaller fixes from the same conformance audit
58
+
59
+ - A transport type declared twice was reported as "two blocks declare different
60
+ globals" even when a single block declared it twice. The message now covers
61
+ both cases.
62
+ - A card addressed by `page_card_id:` — the escape hatch for a host that cannot
63
+ set the media type — was read without the merge step, which is where a
64
+ duplicated transport is caught. It was therefore the one path where that
65
+ authoring error went unreported. It now goes through the same check.
66
+
67
+ Nothing a conformant page declares behaves differently, and no card needs
68
+ changing.
69
+
6
70
  ## 0.14.0
7
71
 
8
72
  ### Breaking: a transport declared in top-level `remotes` is no longer read
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.14.0"
64
+ gem "llm_meta_widget", "~> 0.15.1"
65
65
  ```
66
66
 
67
67
  ```bash
@@ -152,17 +152,17 @@ 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.14"></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.14` is a version range, and it stops at 0.14.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.15 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
@@ -602,6 +602,18 @@ so no other consumer can rely on them. Each kind falls back on its own, so you
602
602
  can move tools into the declared global and leave prompts and resources where
603
603
  they are.
604
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
+
605
617
  **The card is found by its media type.** A client matches
606
618
  `script[type="application/mcp-static-primitives+json"]`, so the element needs no
607
619
  `id`. This is how a client that was never told about your page can still read
@@ -387,7 +387,11 @@ function boot(cfg) {
387
387
  }));
388
388
  } else {
389
389
  var pageCardEl = document.getElementById(PAGE_CARD_ID);
390
- pageCard = readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null);
390
+ // Through the merge as well, with one card: that is where a transport
391
+ // declared twice is caught, and the escape hatch should not be the one
392
+ // path where an authoring error goes unreported.
393
+ pageCard = mergeStaticPrimitivesCards(
394
+ [ readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null) ]);
391
395
  }
392
396
  // Where the page's handlers live. The card may declare a global, which is
393
397
  // the only form an outside consumer can discover; the three legacy globals
@@ -499,8 +503,21 @@ function boot(cfg) {
499
503
  if (flat.length > 1) modelPicker.style.display = "";
500
504
  }));
501
505
  } else if (ENABLE_MODEL_PICKER && modelPicker) {
502
- tasks.push(fetch(LLM_BASE + "/api/llms", { headers: { "Accept": "application/json" } })
503
- .then(function(r) { return r.ok ? r.json() : { llms: [] }; })
506
+ // cache: "no-store" so the browser never sends a conditional request. The
507
+ // hub answers /api/llms with an ETag and must-revalidate, so a REPEAT visit
508
+ // got 304 Not Modified — and Response.ok is false for a 304, which became
509
+ // an empty payload here. The model's context window was then unknown, so
510
+ // the resource budget fell to its 8 KB floor and BOTH tool-result
511
+ // truncation and eviction stood down, each returning unchanged without a
512
+ // window. It worked on a first load and quietly stopped on every one after.
513
+ tasks.push(fetch(LLM_BASE + "/api/llms", { headers: { "Accept": "application/json" }, cache: "no-store" })
514
+ .then(function(r) {
515
+ if (r.ok) return r.json();
516
+ console.warn("[llm-meta-widget] /api/llms answered " + r.status +
517
+ "; the model context window is unknown, so reference data is" +
518
+ " budgeted at the floor and oversized tool results are not trimmed");
519
+ return { llms: [] };
520
+ })
504
521
  .then(function(payload) {
505
522
  // /api/llms is heterogeneous per family:
506
523
  // - OpenAI/Anthropic/Google: `models: [{ name, display_name, ... }]`
@@ -547,8 +564,16 @@ function boot(cfg) {
547
564
  }
548
565
 
549
566
  if (ENABLE_TOOL_PICKER && toolsListEl) {
550
- tasks.push(fetch(TOOL_HUB_BASE + "/api/mcp_servers", { headers: { "Accept": "application/json" } })
551
- .then(function(r) { return r.ok ? r.json() : { mcp_servers: [] }; })
567
+ // Same reason as /api/llms above: a 304 on a repeat visit emptied this, and
568
+ // the panel then said "No hub-registered tools available" although the hub
569
+ // had them.
570
+ tasks.push(fetch(TOOL_HUB_BASE + "/api/mcp_servers", { headers: { "Accept": "application/json" }, cache: "no-store" })
571
+ .then(function(r) {
572
+ if (r.ok) return r.json();
573
+ console.warn("[llm-meta-widget] /api/mcp_servers answered " + r.status +
574
+ "; hub-registered tools will be listed as unavailable");
575
+ return { mcp_servers: [] };
576
+ })
552
577
  .then(function(payload) {
553
578
  hubMcpServers = (payload.mcp_servers || []).filter(function(s) {
554
579
  return anyAllowedByAllowlist(s.name, HUB_TOOLS_ALLOWLIST);
@@ -708,24 +708,42 @@ function declaredPageGlobal(card, warn = console.warn) {
708
708
  }
709
709
  function pageHandlers({ card, scope = {}, legacy = {}, warn = console.warn } = {}) {
710
710
  const name = declaredPageGlobal(card, warn);
711
- const declared = name ? scope[name] : null;
712
- if (name && !declared) {
713
- 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?`);
714
- }
715
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
+ };
716
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
+ }
717
726
  const fromGlobal = declared ? declared[kind] : void 0;
718
727
  if (isMap(fromGlobal)) return fromGlobal;
719
728
  if (declared && fromGlobal !== void 0 && !isMap(fromGlobal)) {
720
- 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
+ );
721
733
  }
722
734
  return legacyName && scope[legacyName] || {};
723
735
  };
724
736
  return {
725
737
  global: name,
726
- tools: pick("tools", legacy.tools),
727
- prompts: pick("prompts", legacy.prompts),
728
- 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
+ }
729
747
  };
730
748
  }
731
749
  function mergeStaticPrimitivesCards(cards, warn = console.warn) {
@@ -736,7 +754,7 @@ function mergeStaticPrimitivesCards(cards, warn = console.warn) {
736
754
  if (!entry || typeof entry.type !== "string") continue;
737
755
  const clash = merged[kind].find((r) => r.type === entry.type && r.global !== entry.global);
738
756
  if (clash) {
739
- 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`);
740
758
  continue;
741
759
  }
742
760
  if (!merged[kind].some((r) => r.type === entry.type && r.global === entry.global)) {
@@ -4163,7 +4181,9 @@ function boot(cfg) {
4163
4181
  );
4164
4182
  } else {
4165
4183
  var pageCardEl = document.getElementById(PAGE_CARD_ID);
4166
- pageCard = readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null);
4184
+ pageCard = mergeStaticPrimitivesCards(
4185
+ [readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null)]
4186
+ );
4167
4187
  }
4168
4188
  var handlers = pageHandlers({
4169
4189
  card: pageCard,
@@ -4250,8 +4270,10 @@ function boot(cfg) {
4250
4270
  if (flat.length > 1) modelPicker.style.display = "";
4251
4271
  }));
4252
4272
  } else if (ENABLE_MODEL_PICKER && modelPicker) {
4253
- tasks.push(fetch(LLM_BASE + "/api/llms", { headers: { "Accept": "application/json" } }).then(function(r) {
4254
- return r.ok ? r.json() : { llms: [] };
4273
+ tasks.push(fetch(LLM_BASE + "/api/llms", { headers: { "Accept": "application/json" }, cache: "no-store" }).then(function(r) {
4274
+ if (r.ok) return r.json();
4275
+ console.warn("[llm-meta-widget] /api/llms answered " + r.status + "; the model context window is unknown, so reference data is budgeted at the floor and oversized tool results are not trimmed");
4276
+ return { llms: [] };
4255
4277
  }).then(function(payload) {
4256
4278
  llmsPayload = payload;
4257
4279
  modelWindow = modelWindowFrom(payload, MODEL);
@@ -4284,8 +4306,10 @@ function boot(cfg) {
4284
4306
  }));
4285
4307
  }
4286
4308
  if (ENABLE_TOOL_PICKER && toolsListEl) {
4287
- tasks.push(fetch(TOOL_HUB_BASE + "/api/mcp_servers", { headers: { "Accept": "application/json" } }).then(function(r) {
4288
- return r.ok ? r.json() : { mcp_servers: [] };
4309
+ tasks.push(fetch(TOOL_HUB_BASE + "/api/mcp_servers", { headers: { "Accept": "application/json" }, cache: "no-store" }).then(function(r) {
4310
+ if (r.ok) return r.json();
4311
+ console.warn("[llm-meta-widget] /api/mcp_servers answered " + r.status + "; hub-registered tools will be listed as unavailable");
4312
+ return { mcp_servers: [] };
4289
4313
  }).then(function(payload) {
4290
4314
  hubMcpServers = (payload.mcp_servers || []).filter(function(s) {
4291
4315
  return anyAllowedByAllowlist(s.name, HUB_TOOLS_ALLOWLIST);
@@ -1165,19 +1165,36 @@ export function declaredPageGlobal(card, warn = console.warn) {
1165
1165
  // keeps working instead of losing the primitives it has not moved yet.
1166
1166
  export function pageHandlers({ card, scope = {}, legacy = {}, warn = console.warn } = {}) {
1167
1167
  const name = declaredPageGlobal(card, warn)
1168
- const declared = name ? scope[name] : null
1169
-
1170
- // A card that names a global the page never defines is almost always a load
1171
- // order problem, or a script that threw before it got there. Falling back in
1172
- // silence hides that: the page then behaves as if it had declared nothing,
1173
- // and the first symptom is a prompt or tool that "has no handler".
1174
- if (name && !declared) {
1175
- warn(`[llm-meta-widget] the page card declares global "${name}", but window.${name} ` +
1176
- "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)
1177
1177
  }
1178
1178
 
1179
- const isMap = (v) => !!v && typeof v === "object" && !Array.isArray(v)
1180
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
+
1181
1198
  // `declared ? … : undefined`, not `declared && …`: the second yields null
1182
1199
  // when nothing is declared, and `typeof null === "object"`, so the check
1183
1200
  // below then warned about "window.null.tools" on every page with no card.
@@ -1186,16 +1203,20 @@ export function pageHandlers({ card, scope = {}, legacy = {}, warn = console.war
1186
1203
  // An array is the mistake worth naming: it passes a typeof check, so it
1187
1204
  // would be used as a map and answer every lookup with undefined.
1188
1205
  if (declared && fromGlobal !== undefined && !isMap(fromGlobal)) {
1189
- warn(`[llm-meta-widget] window.${name}.${kind} is ${Array.isArray(fromGlobal) ? "an array" : typeof fromGlobal}; ` +
1190
- `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"}`)
1191
1209
  }
1192
1210
  return (legacyName && scope[legacyName]) || {}
1193
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.
1194
1215
  return {
1195
1216
  global: name,
1196
- tools: pick("tools", legacy.tools),
1197
- prompts: pick("prompts", legacy.prompts),
1198
- 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) }
1199
1220
  }
1200
1221
  }
1201
1222
 
@@ -1224,8 +1245,9 @@ export function mergeStaticPrimitivesCards(cards, warn = console.warn) {
1224
1245
  if (!entry || typeof entry.type !== "string") continue
1225
1246
  const clash = merged[kind].find((r) => r.type === entry.type && r.global !== entry.global)
1226
1247
  if (clash) {
1227
- warn(`[llm-meta-widget] two blocks declare different ${entry.type} globals ` +
1228
- `("${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`)
1229
1251
  continue
1230
1252
  }
1231
1253
  if (!merged[kind].some((r) => r.type === entry.type && r.global === entry.global)) {
@@ -1,3 +1,3 @@
1
1
  module LlmMetaWidget
2
- VERSION = "0.14.0"
2
+ VERSION = "0.15.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.14.0
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - jdkim