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 +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +8 -4
- data/app/assets/javascripts/llm_meta_widget/element.js +58 -2
- data/app/assets/javascripts/llm_meta_widget/llm-meta-widget.js +63 -14
- data/app/assets/javascripts/llm_meta_widget/orchestrator.js +62 -18
- data/lib/llm_meta_widget/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98514af704b8e59db0091c55a0d42f60cc3d9da77ed8debbf8aa2b94e514576d
|
|
4
|
+
data.tar.gz: e0b25d087956a31ad5bc21ce232b25abe1aa0ca78a917abdaf6f0e97c7ba7237
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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() !== "")
|
|
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
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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() !== "")
|
|
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
|
|
1184
|
-
//
|
|
1185
|
-
//
|
|
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
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
-
//
|
|
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"),
|