llm_meta_widget 0.9.1 → 0.10.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: f6dba4c1cd88e26bde9f0179009ae2f47cdd07a30aae6035e619f0ca5b83d32e
4
- data.tar.gz: 95cca5b804adab95a89dfbdf23b7624cb43304462d4cf6c72a605a7ebef170de
3
+ metadata.gz: 451052ef64f425f3944991ffb56c4c3990beb5703832d13dcda11eb27d91511e
4
+ data.tar.gz: 3d408043ea3d236ba7ac0c6a96d28a944d775866a6dc025940eb0bb51a87fafc
5
5
  SHA512:
6
- metadata.gz: d2c499f1d83a689e52fff3fabb0cc08f359c4d34571295085718f3fa5b8c13ccf565f6f47c9ae73b210f1bea350581e7e1c4ae47ac27a759116364518fce2bd2
7
- data.tar.gz: bb45042dcae5849cf88750d8d6235416fda8e531f0852ed454405c59c4358ff3c31137b78349b6b87be8fc490c3235de62b5b9bebd4f0a61cea790c0cf0925ba
6
+ metadata.gz: a0de8fbfee7c44435077225f675aebc77558a707494a763215e82185ad4b73ba1f8eff65df9f2f6d820f5f0c66b6ff9934bc72bf601285adcf2b1968081f10b2
7
+ data.tar.gz: b325fed836e89c214b497575e1eead985d0548299938ad32a5a00751b4fc1d3846162c0e0b798e8668beda7bb77945457d50a5fe10080940b177879c4c3134c6
data/CHANGELOG.md CHANGED
@@ -3,6 +3,92 @@
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.10.1
7
+
8
+ Documentation only. The 0.10.0 release notes and the README section on the page
9
+ card were written after that release was published, so the copies on RubyGems
10
+ and npm still described 0.9.1. There is no code change: a page running 0.10.0
11
+ behaves exactly as one running 0.10.1.
12
+
13
+ ## 0.10.0
14
+
15
+ ### Changed: a page card is found by its media type
16
+
17
+ A card inside a page is now located by the `type` attribute of its `<script>`
18
+ element, not by an `id`:
19
+
20
+ ```html
21
+ <script type="application/mcp-static-primitives+json"> … </script>
22
+ ```
23
+
24
+ A client matches `script[type="application/mcp-static-primitives+json"]`, so the
25
+ element needs no `id`. This follows SEP-0000's "HTML-delivery mode". The reason
26
+ for the change: an `id` that each page chooses cannot be found by a client that
27
+ was not told the id, so an `id` cannot serve as a locator.
28
+
29
+ **Existing pages keep working.** When no block carries the media type, the widget
30
+ reads `page_card_id:` (still `"ai-page"` by default) exactly as before. That
31
+ option is now only an escape hatch for a host that cannot set the type. Pages
32
+ using it should move to the media type, because the fallback may be removed in a
33
+ later release.
34
+
35
+ ### Added: a document may hold several cards
36
+
37
+ A page built from components can give each component its own card block. The
38
+ widget reads every block and joins the arrays by primitive type.
39
+
40
+ Declaring the same tool or prompt `name`, or the same resource `uri`, in two
41
+ blocks is an error: every copy is dropped and a message naming it is written to
42
+ the console. The first block does not win, because which block comes first
43
+ depends on the order the page's parts are rendered in, and the page author did
44
+ not choose that order.
45
+
46
+ A block whose JSON cannot be read costs only that block. The other blocks still
47
+ load.
48
+
49
+ ## 0.9.1
50
+
51
+ ### Fixed: prompt arguments were empty on a page that declares resources
52
+
53
+ A prompt's arguments are looked up by plain name (`text`), while page resources
54
+ are addressed by URI (`page:text`). A page that kept its state in resources and
55
+ had no `window.aiState` therefore had every prompt argument arrive empty, and
56
+ the person was asked to type in something the page could already see.
57
+
58
+ Page resources now fill prompt arguments of the matching name. An explicit
59
+ `window.aiState` entry still wins, so a page running both channels keeps the
60
+ behaviour it declared.
61
+
62
+ ## 0.9.0
63
+
64
+ ### Added: the page card
65
+
66
+ A page can declare its tools, prompts and resources in one block of JSON, using
67
+ the same envelope an MCP server puts in its Server Card. Three globals hold the
68
+ handlers, each keyed by the identifier its own MCP operation takes:
69
+
70
+ | declared in the card | implemented in | keyed by |
71
+ |---|---|---|
72
+ | `tools` | `window.aiActions` | tool `name` |
73
+ | `prompts` | `window.aiPrompts` | prompt `name` |
74
+ | `resources` | `window.aiResources` | resource `uri` |
75
+
76
+ See "The page card" in the README. Nothing is required: a page that declares no
77
+ card behaves as before, and a page that declares no `tools` still uses its
78
+ `ai-actions` block.
79
+
80
+ Page resources are addressed `page:<name>`. That scheme is experimental and
81
+ unregistered.
82
+
83
+ ## 0.8.6
84
+
85
+ ### Changed: a resource's size is declared in the base `size` field
86
+
87
+ `size` on the resource entry replaces `_meta…sizeBytes`, following the change to
88
+ the base MCP `Resource` interface. `sizeBytes` is still read if `size` is absent,
89
+ and is still reported for now; both will be removed in a later release. Servers
90
+ should emit `size`.
91
+
6
92
  ## 0.8.0
7
93
 
8
94
  ### Breaking: state readers must declare a description
data/README.md CHANGED
@@ -402,7 +402,7 @@ translate directly.
402
402
  | `api_key_uuid:` | `"ollama-local"` | Hub API-key uuid to invoke |
403
403
  | `element_path:` | `"/llm_meta_widget_assets/llm-meta-widget.js"` | The bundled element the page loads. Served by the gem's engine; override to load it from elsewhere |
404
404
  | `actions_schema_id:` | `"ai-actions"` | DOM id of the Class-3 schema block |
405
- | `page_card_id:` | `"ai-page"` | DOM id of the page card (see "The page card"). Its tools replace the `actions_schema_id` block |
405
+ | `page_card_id:` | `"ai-page"` | **Fallback only.** A card is normally found by its media type (see "The page card"); this id is read when the page has no block with that type. Its tools replace the `actions_schema_id` block |
406
406
  | `prompts_global:` | `"aiPrompts"` | Global object holding page prompt handlers, keyed by prompt name |
407
407
  | `resources_global:` | `"aiResources"` | Global object holding page resource readers, keyed by resource URI |
408
408
  | `state_global:` | `"aiState"` | Global window object holding Class-3 state readers, each `{ description, read }` (see the breaking change in 0.8.0) |
@@ -424,7 +424,7 @@ an MCP server puts in its Server Card, so the page and the server describe
424
424
  themselves the same way.
425
425
 
426
426
  ```html
427
- <script type="application/json" id="ai-page">
427
+ <script type="application/mcp-static-primitives+json">
428
428
  { "_meta": { "io.modelcontextprotocol/static-primitives": {
429
429
  "tools": [ { "name": "set_text", "description": "…", "inputSchema": { } } ],
430
430
  "prompts": [ { "name": "annotate", "description": "Annotate this page" } ],
@@ -466,6 +466,21 @@ fetch from the web by itself — which page resources are not. The form is
466
466
  `page:<name>` with no path, because a page has only one namespace. Expect this to
467
467
  change.
468
468
 
469
+ **The card is found by its media type.** A client matches
470
+ `script[type="application/mcp-static-primitives+json"]`, so the element needs no
471
+ `id`. This is how a client that was never told about your page can still read
472
+ it, and it follows SEP-0000's "HTML-delivery mode". The `page_card_id:` option
473
+ is kept only for a host that cannot set the type; it is read when no block with
474
+ the media type is present.
475
+
476
+ **A document may hold several blocks.** A page built from components can let
477
+ each component declare its own primitives, and the arrays join by primitive
478
+ type. Declaring the same tool or prompt `name`, or the same resource `uri`,
479
+ twice is an error: every copy is dropped and a message is written to the
480
+ console. The first one does not win, because which block comes first depends on
481
+ the order the page's parts are rendered in, and that is not something the author
482
+ chose.
483
+
469
484
  **The card is optional.** A page that declares nothing keeps working exactly as
470
485
  before, and a page that declares no `tools` still uses its `ai-actions` block.
471
486
 
@@ -25,7 +25,8 @@ import { runChatLoop, fetchMcpManifest, listMcpPrompts, getMcpPrompt,
25
25
  createConversationStore, conversationKeyFor,
26
26
  readStaticPrimitivesCard, getPagePrompt,
27
27
  planPageResources, pageResourceLinesForTurn,
28
- promptArgState } from "./orchestrator.js";
28
+ promptArgState, mergeStaticPrimitivesCards,
29
+ PAGE_CARD_MEDIA_TYPE } from "./orchestrator.js";
29
30
  import { marked } from "./marked.esm.js";
30
31
  import { readConfig } from "./config.js";
31
32
  import conversationCss from "../../stylesheets/llm_meta_widget/conversation.css";
@@ -306,8 +307,24 @@ function boot(cfg) {
306
307
  // older #ai-actions schema tag rather than merging with it: two sources
307
308
  // for one list means a reader has to work out which the page meant, and a
308
309
  // handler in window.aiActions answers to a name either way.
309
- var pageCardEl = document.getElementById(PAGE_CARD_ID);
310
- var pageCard = readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null);
310
+ //
311
+ // Discovery is by MEDIA TYPE, which is the locator the spec defines: an id
312
+ // the page chooses cannot be found by a client that was not told it.
313
+ // Several blocks may appear, so a page built from components can let each
314
+ // one declare its own primitives. `page-card-id` survives only as an escape
315
+ // hatch for a host that cannot set the type, and is read when no typed
316
+ // block is present.
317
+ var typedCards = document.querySelectorAll('script[type="' + PAGE_CARD_MEDIA_TYPE + '"]');
318
+ var pageCard;
319
+ if (typedCards.length) {
320
+ pageCard = mergeStaticPrimitivesCards(
321
+ Array.prototype.map.call(typedCards, function(el) {
322
+ return readStaticPrimitivesCard(el.textContent);
323
+ }));
324
+ } else {
325
+ var pageCardEl = document.getElementById(PAGE_CARD_ID);
326
+ pageCard = readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null);
327
+ }
311
328
  var actionsSchemaEl = document.getElementById(ACTIONS_SCHEMA_ID);
312
329
  var localTools = pageCard.tools.length
313
330
  ? pageCard.tools
@@ -560,6 +560,25 @@ function parseSseFrame(raw) {
560
560
  return { name, data };
561
561
  }
562
562
  var STATIC_PRIMITIVES_META = "io.modelcontextprotocol/static-primitives";
563
+ var PAGE_CARD_MEDIA_TYPE = "application/mcp-static-primitives+json";
564
+ function mergeStaticPrimitivesCards(cards, warn = console.warn) {
565
+ const merged = { tools: [], prompts: [], resources: [] };
566
+ const keyOf = { tools: "name", prompts: "name", resources: "uri" };
567
+ for (const kind of ["tools", "prompts", "resources"]) {
568
+ const key = keyOf[kind];
569
+ const all = [];
570
+ for (const card of cards || []) all.push(...card && card[kind] || []);
571
+ const counts = /* @__PURE__ */ new Map();
572
+ for (const entry of all) counts.set(entry[key], (counts.get(entry[key]) || 0) + 1);
573
+ for (const [id, n] of counts) {
574
+ if (n > 1) {
575
+ warn(`[llm-meta-widget] the page declares ${kind} "${id}" ${n} times; dropping every copy \u2014 remove the duplicate to say which one you mean`);
576
+ }
577
+ }
578
+ merged[kind] = all.filter((entry) => counts.get(entry[key]) === 1);
579
+ }
580
+ return merged;
581
+ }
563
582
  function readStaticPrimitivesCard(text, warn = console.warn) {
564
583
  const empty = { tools: [], prompts: [], resources: [] };
565
584
  if (typeof text !== "string" || text.trim() === "") return empty;
@@ -3900,8 +3919,18 @@ function boot(cfg) {
3900
3919
  model: MODEL
3901
3920
  });
3902
3921
  }
3903
- var pageCardEl = document.getElementById(PAGE_CARD_ID);
3904
- var pageCard = readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null);
3922
+ var typedCards = document.querySelectorAll('script[type="' + PAGE_CARD_MEDIA_TYPE + '"]');
3923
+ var pageCard;
3924
+ if (typedCards.length) {
3925
+ pageCard = mergeStaticPrimitivesCards(
3926
+ Array.prototype.map.call(typedCards, function(el) {
3927
+ return readStaticPrimitivesCard(el.textContent);
3928
+ })
3929
+ );
3930
+ } else {
3931
+ var pageCardEl = document.getElementById(PAGE_CARD_ID);
3932
+ pageCard = readStaticPrimitivesCard(pageCardEl ? pageCardEl.textContent : null);
3933
+ }
3905
3934
  var actionsSchemaEl = document.getElementById(ACTIONS_SCHEMA_ID);
3906
3935
  var localTools = pageCard.tools.length ? pageCard.tools : actionsSchemaEl ? JSON.parse(actionsSchemaEl.textContent) : [];
3907
3936
  var remoteToolsEl = document.getElementById(REMOTE_TOOLS_SCHEMA_ID);
@@ -879,6 +879,43 @@ export const STATIC_PRIMITIVES_META = "io.modelcontextprotocol/static-primitives
879
879
  // typo in it should cost that one entry, not the assistant. Everything
880
880
  // unparseable returns empty arrays and says so on the console, which is how
881
881
  // aiState already treats a reader missing its description.
882
+ // The media type that locates a card inside an HTML document. It is the
883
+ // locator, so it is NOT configurable: a client that has to be told where to
884
+ // look cannot discover anything. SEP-0000, "HTML-delivery mode".
885
+ export const PAGE_CARD_MEDIA_TYPE = "application/mcp-static-primitives+json"
886
+
887
+ // Several blocks may appear in one document, so that a page assembled from
888
+ // components can let each component declare its own primitives. The arrays
889
+ // concatenate by primitive type.
890
+ //
891
+ // A duplicate identifier across blocks is an ERROR, not a precedence puzzle:
892
+ // the spec says a client should report it and should not silently pick one. So
893
+ // every copy of a duplicated identifier is dropped and the author is told.
894
+ // Picking the first would make which component wins depend on DOM order, which
895
+ // is not something a page author controls when the components are partials.
896
+ export function mergeStaticPrimitivesCards(cards, warn = console.warn) {
897
+ const merged = { tools: [], prompts: [], resources: [] }
898
+ const keyOf = { tools: "name", prompts: "name", resources: "uri" }
899
+
900
+ for (const kind of [ "tools", "prompts", "resources" ]) {
901
+ const key = keyOf[kind]
902
+ const all = []
903
+ for (const card of cards || []) all.push(...((card && card[kind]) || []))
904
+
905
+ const counts = new Map()
906
+ for (const entry of all) counts.set(entry[key], (counts.get(entry[key]) || 0) + 1)
907
+
908
+ for (const [ id, n ] of counts) {
909
+ if (n > 1) {
910
+ warn(`[llm-meta-widget] the page declares ${kind} "${id}" ${n} times; ` +
911
+ "dropping every copy — remove the duplicate to say which one you mean")
912
+ }
913
+ }
914
+ merged[kind] = all.filter((entry) => counts.get(entry[key]) === 1)
915
+ }
916
+ return merged
917
+ }
918
+
882
919
  export function readStaticPrimitivesCard(text, warn = console.warn) {
883
920
  const empty = { tools: [], prompts: [], resources: [] }
884
921
  if (typeof text !== "string" || text.trim() === "") return empty
@@ -1,3 +1,3 @@
1
1
  module LlmMetaWidget
2
- VERSION = "0.9.1"
2
+ VERSION = "0.10.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.9.1
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - jdkim