llm_meta_widget 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bbae9759cecc8cdd66372f7ea040db98c55f3a19877aed2e44f8cdd21baa5815
|
|
4
|
+
data.tar.gz: 55d7003f06b621d67a58d2a6fbd0a072371c8e288a4bf1c33de16dd2cc2bd326
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5240c9d3f3033c983f92d82ea3240b24049935cf7c26179f68f057540ada604292908aff6ca3c33ca4a443b42bfd19c181dadd674ec8655f6fe82646e09c0cc
|
|
7
|
+
data.tar.gz: d1d1750ce3d6d13a43be940afca2bb41c8978bc34b371b1c686e60b17c012d47c389462348ddf8c1b721e530ead404a7e66dbcffc9c50db781a6b64b48c48385
|
data/README.md
CHANGED
|
@@ -216,6 +216,7 @@ easy to get wrong:
|
|
|
216
216
|
| `llm-provider` | `llm_meta_server` (default) or `ollama` |
|
|
217
217
|
| `api-key-uuid`, `greeting`, `max-rounds` | as the helper options |
|
|
218
218
|
| `actions-schema-id`, `state-global`, `actions-global`, `remote-tools-schema-id` | as the helper options |
|
|
219
|
+
| `page-card-id`, `prompts-global`, `resources-global` | as the helper options |
|
|
219
220
|
| `models`, `hub-tools` | comma-separated. Omitted **or empty** means no allowlist — an allowlist permitting nothing is never what anyone meant |
|
|
220
221
|
| `enable-model-picker`, `enable-tool-picker` | **value attributes, not boolean attributes.** They default to true, so presence cannot mean true. Disable with `enable-tool-picker="false"`; any other value is true |
|
|
221
222
|
| `well-known-urls` | **Three different states**, because an attribute cannot tell "not set" from "set to empty": omitted = auto-discover same-origin `/.well-known/mcp.json`; `""` = discovery off; `"a,b"` = fetch those |
|
|
@@ -401,6 +402,9 @@ translate directly.
|
|
|
401
402
|
| `api_key_uuid:` | `"ollama-local"` | Hub API-key uuid to invoke |
|
|
402
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 |
|
|
403
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 |
|
|
406
|
+
| `prompts_global:` | `"aiPrompts"` | Global object holding page prompt handlers, keyed by prompt name |
|
|
407
|
+
| `resources_global:` | `"aiResources"` | Global object holding page resource readers, keyed by resource URI |
|
|
404
408
|
| `state_global:` | `"aiState"` | Global window object holding Class-3 state readers, each `{ description, read }` (see the breaking change in 0.8.0) |
|
|
405
409
|
| `actions_global:` | `"aiActions"` | Global window object holding Class-3 implementations |
|
|
406
410
|
| `remote_tools_schema_id:` | `"remote-mcp-tools"` | DOM id of a JSON block listing Class-1 tools (`{id, name, description, input_schema}`) to start with. They seed the picker rather than bypassing it — shown ticked, and the visitor may untick them. Read once, at boot |
|
|
@@ -412,6 +416,74 @@ translate directly.
|
|
|
412
416
|
| `models:` | `nil` | Model-name allowlist; `nil` = all anon-available |
|
|
413
417
|
| `hub_tools:` | `nil` | MCP-server-name allowlist; `nil` = all anon-public |
|
|
414
418
|
|
|
419
|
+
## The page card
|
|
420
|
+
|
|
421
|
+
Since 0.9.0 a page can declare what it offers in one block of JSON, instead of
|
|
422
|
+
using a separate channel for each kind of thing. The block uses the same envelope
|
|
423
|
+
an MCP server puts in its Server Card, so the page and the server describe
|
|
424
|
+
themselves the same way.
|
|
425
|
+
|
|
426
|
+
```html
|
|
427
|
+
<script type="application/mcp-static-primitives+json">
|
|
428
|
+
{ "_meta": { "io.modelcontextprotocol/static-primitives": {
|
|
429
|
+
"tools": [ { "name": "set_text", "description": "…", "inputSchema": { } } ],
|
|
430
|
+
"prompts": [ { "name": "annotate", "description": "Annotate this page" } ],
|
|
431
|
+
"resources": [ { "uri": "page:text", "name": "Text", "description": "the text to annotate",
|
|
432
|
+
"volatility": "volatile" } ]
|
|
433
|
+
} } }
|
|
434
|
+
</script>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
The card says what exists. Three global objects say what it does, and each one is
|
|
438
|
+
keyed by the identifier that the matching MCP operation takes:
|
|
439
|
+
|
|
440
|
+
| declared in the card | implemented in | keyed by |
|
|
441
|
+
|---|---|---|
|
|
442
|
+
| `tools` | `window.aiActions` | tool `name`, as `tools/call` takes |
|
|
443
|
+
| `prompts` | `window.aiPrompts` | prompt `name`, as `prompts/get` takes |
|
|
444
|
+
| `resources` | `window.aiResources` | resource `uri`, as `resources/read` takes |
|
|
445
|
+
|
|
446
|
+
```js
|
|
447
|
+
window.aiActions = { set_text: (args) => { /* change the page */ } }
|
|
448
|
+
window.aiPrompts = { annotate: (args) => "Annotate this: " + args.text }
|
|
449
|
+
window.aiResources = { "page:text": () => document.querySelector("#text").value }
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
A prompt handler may return a plain string or the full `messages` array that
|
|
453
|
+
`prompts/get` returns. A resource reader may return a string or any value that
|
|
454
|
+
can be turned into JSON.
|
|
455
|
+
|
|
456
|
+
**Resources carry two optional hints.** `volatility` is `"volatile"` when the
|
|
457
|
+
value changes between turns, so the widget reads it again every turn; the default
|
|
458
|
+
is `"stable"`, which is read once and reused. `autoAttach: false` keeps a resource
|
|
459
|
+
out of the turn unless something asks for it. A `size` in bytes lets the widget
|
|
460
|
+
decide whether the value fits its budget before reading it.
|
|
461
|
+
|
|
462
|
+
**The `page:` scheme is experimental.** It is not a registered URI scheme, and
|
|
463
|
+
nothing outside this widget gives it meaning. It is used because the protocol
|
|
464
|
+
asks for a URI, and because `https://` is reserved for resources a client can
|
|
465
|
+
fetch from the web by itself — which page resources are not. The form is
|
|
466
|
+
`page:<name>` with no path, because a page has only one namespace. Expect this to
|
|
467
|
+
change.
|
|
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
|
+
|
|
484
|
+
**The card is optional.** A page that declares nothing keeps working exactly as
|
|
485
|
+
before, and a page that declares no `tools` still uses its `ai-actions` block.
|
|
486
|
+
|
|
415
487
|
## Surviving navigation
|
|
416
488
|
|
|
417
489
|
A page action that navigates — submitting a form, following a link — no longer
|
|
@@ -24,7 +24,9 @@ import { runChatLoop, fetchMcpManifest, listMcpPrompts, getMcpPrompt,
|
|
|
24
24
|
promptButtonProps, promptArgumentSummary, fetchOllamaModels,
|
|
25
25
|
createConversationStore, conversationKeyFor,
|
|
26
26
|
readStaticPrimitivesCard, getPagePrompt,
|
|
27
|
-
planPageResources, pageResourceLinesForTurn
|
|
27
|
+
planPageResources, pageResourceLinesForTurn,
|
|
28
|
+
promptArgState, mergeStaticPrimitivesCards,
|
|
29
|
+
PAGE_CARD_MEDIA_TYPE } from "./orchestrator.js";
|
|
28
30
|
import { marked } from "./marked.esm.js";
|
|
29
31
|
import { readConfig } from "./config.js";
|
|
30
32
|
import conversationCss from "../../stylesheets/llm_meta_widget/conversation.css";
|
|
@@ -305,8 +307,24 @@ function boot(cfg) {
|
|
|
305
307
|
// older #ai-actions schema tag rather than merging with it: two sources
|
|
306
308
|
// for one list means a reader has to work out which the page meant, and a
|
|
307
309
|
// handler in window.aiActions answers to a name either way.
|
|
308
|
-
|
|
309
|
-
|
|
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
|
+
}
|
|
310
328
|
var actionsSchemaEl = document.getElementById(ACTIONS_SCHEMA_ID);
|
|
311
329
|
var localTools = pageCard.tools.length
|
|
312
330
|
? pageCard.tools
|
|
@@ -685,6 +703,15 @@ function boot(cfg) {
|
|
|
685
703
|
// submits — so it travels the exact path a typed message does.
|
|
686
704
|
|
|
687
705
|
|
|
706
|
+
// Where a prompt's declared arguments are filled from. A card-only page keeps
|
|
707
|
+
// its state in aiResources under `page:<name>` URIs, so the names the resolver
|
|
708
|
+
// looks up live there, not in aiState.
|
|
709
|
+
function argState() {
|
|
710
|
+
return promptArgState({ entries: pageCard.resources,
|
|
711
|
+
resources: window[RESOURCES_GLOBAL] || {},
|
|
712
|
+
state: window[STATE_GLOBAL] || {} });
|
|
713
|
+
}
|
|
714
|
+
|
|
688
715
|
function renderPromptButtons() {
|
|
689
716
|
if (!promptsEl) return;
|
|
690
717
|
promptsEl.textContent = "";
|
|
@@ -749,7 +776,7 @@ function boot(cfg) {
|
|
|
749
776
|
(hostWidePrompts.length ? ", or start with one of these:" : ".");
|
|
750
777
|
box.appendChild(hello);
|
|
751
778
|
|
|
752
|
-
var state =
|
|
779
|
+
var state = argState();
|
|
753
780
|
hostWidePrompts.forEach(function(prompt) {
|
|
754
781
|
var props = promptButtonProps(prompt);
|
|
755
782
|
var card = document.createElement("div");
|
|
@@ -792,7 +819,7 @@ function boot(cfg) {
|
|
|
792
819
|
}
|
|
793
820
|
|
|
794
821
|
async function runPromptTemplate(prompt, button) {
|
|
795
|
-
var resolved = resolvePromptArguments(prompt,
|
|
822
|
+
var resolved = resolvePromptArguments(prompt, argState());
|
|
796
823
|
var args = resolved.args;
|
|
797
824
|
var missing = resolved.missing;
|
|
798
825
|
|
|
@@ -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;
|
|
@@ -737,7 +756,20 @@ async function resourceLinesForTurn({ plan, cached, endpoint, read, budgetBytes
|
|
|
737
756
|
}
|
|
738
757
|
return { lines: resourceContextLines(cached), cached };
|
|
739
758
|
}
|
|
759
|
+
var PAGE_URI_PREFIX = "page:";
|
|
740
760
|
var PROMPT_ARG_ALIASES = { dictionaries: "selected_dictionaries" };
|
|
761
|
+
function promptArgState({ entries = [], resources = {}, state = {} } = {}) {
|
|
762
|
+
const merged = Object.assign({}, state);
|
|
763
|
+
for (const entry of entries) {
|
|
764
|
+
const uri = entry && entry.uri;
|
|
765
|
+
if (!uri || uri.indexOf(PAGE_URI_PREFIX) !== 0) continue;
|
|
766
|
+
const name = uri.slice(PAGE_URI_PREFIX.length);
|
|
767
|
+
const reader = resources[uri];
|
|
768
|
+
if (!name || merged[name] || typeof reader !== "function") continue;
|
|
769
|
+
merged[name] = { description: entry.description || "", read: reader };
|
|
770
|
+
}
|
|
771
|
+
return merged;
|
|
772
|
+
}
|
|
741
773
|
function promptArgFromState(argName, state) {
|
|
742
774
|
const entry = state && state[argName] || state && state[PROMPT_ARG_ALIASES[argName]];
|
|
743
775
|
const reader = entry && typeof entry.read === "function" ? entry.read : null;
|
|
@@ -3887,8 +3919,18 @@ function boot(cfg) {
|
|
|
3887
3919
|
model: MODEL
|
|
3888
3920
|
});
|
|
3889
3921
|
}
|
|
3890
|
-
var
|
|
3891
|
-
var pageCard
|
|
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
|
+
}
|
|
3892
3934
|
var actionsSchemaEl = document.getElementById(ACTIONS_SCHEMA_ID);
|
|
3893
3935
|
var localTools = pageCard.tools.length ? pageCard.tools : actionsSchemaEl ? JSON.parse(actionsSchemaEl.textContent) : [];
|
|
3894
3936
|
var remoteToolsEl = document.getElementById(REMOTE_TOOLS_SCHEMA_ID);
|
|
@@ -4162,6 +4204,13 @@ function boot(cfg) {
|
|
|
4162
4204
|
renderPromptButtons();
|
|
4163
4205
|
renderWelcome();
|
|
4164
4206
|
})();
|
|
4207
|
+
function argState() {
|
|
4208
|
+
return promptArgState({
|
|
4209
|
+
entries: pageCard.resources,
|
|
4210
|
+
resources: window[RESOURCES_GLOBAL] || {},
|
|
4211
|
+
state: window[STATE_GLOBAL] || {}
|
|
4212
|
+
});
|
|
4213
|
+
}
|
|
4165
4214
|
function renderPromptButtons() {
|
|
4166
4215
|
if (!promptsEl) return;
|
|
4167
4216
|
promptsEl.textContent = "";
|
|
@@ -4213,7 +4262,7 @@ function boot(cfg) {
|
|
|
4213
4262
|
hello.className = "lmw-welcome-hello";
|
|
4214
4263
|
hello.textContent = GREETING || "Hi \u2014 I can work this page for you. Tell me what you need in your own words" + (hostWidePrompts.length ? ", or start with one of these:" : ".");
|
|
4215
4264
|
box.appendChild(hello);
|
|
4216
|
-
var state =
|
|
4265
|
+
var state = argState();
|
|
4217
4266
|
hostWidePrompts.forEach(function(prompt) {
|
|
4218
4267
|
var props = promptButtonProps(prompt);
|
|
4219
4268
|
var card = document.createElement("div");
|
|
@@ -4249,7 +4298,7 @@ function boot(cfg) {
|
|
|
4249
4298
|
historyEl.appendChild(box);
|
|
4250
4299
|
}
|
|
4251
4300
|
async function runPromptTemplate(prompt, button) {
|
|
4252
|
-
var resolved = resolvePromptArguments(prompt,
|
|
4301
|
+
var resolved = resolvePromptArguments(prompt, argState());
|
|
4253
4302
|
var args = resolved.args;
|
|
4254
4303
|
var missing = resolved.missing;
|
|
4255
4304
|
if (missing.length) {
|
|
@@ -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
|
|
@@ -1129,8 +1166,35 @@ export async function resourceLinesForTurn({ plan, cached, endpoint, read, budge
|
|
|
1129
1166
|
// (window.aiState) by name. `dictionaries` is read from a
|
|
1130
1167
|
// `selected_dictionaries` reader: the page selects a LIST, and the prompt
|
|
1131
1168
|
// declares the CSV shape the text_annotation tool already takes.
|
|
1169
|
+
// Page resources are addressed `page:<name>`: a custom scheme because the
|
|
1170
|
+
// protocol asks for a URI and `https:` is reserved for what a client can fetch
|
|
1171
|
+
// itself, and with no path because a page has one namespace.
|
|
1172
|
+
const PAGE_URI_PREFIX = "page:"
|
|
1173
|
+
|
|
1132
1174
|
const PROMPT_ARG_ALIASES = { dictionaries: "selected_dictionaries" }
|
|
1133
1175
|
|
|
1176
|
+
// Prompt arguments are declared by plain name (`text`); page resources are
|
|
1177
|
+
// addressed by URI (`page:text`). So on a page that declares its state as
|
|
1178
|
+
// resources and keeps no `aiState`, the resolver below finds nothing under the
|
|
1179
|
+
// names it looks up and every argument arrives empty — the visitor is asked to
|
|
1180
|
+
// retype what is already on screen. This presents those resources in the shape
|
|
1181
|
+
// the resolver consumes, keyed by the name inside the URI.
|
|
1182
|
+
//
|
|
1183
|
+
// An explicit `aiState` entry wins: a page running both channels has said what
|
|
1184
|
+
// it wants under that name.
|
|
1185
|
+
export function promptArgState({ entries = [], resources = {}, state = {} } = {}) {
|
|
1186
|
+
const merged = Object.assign({}, state)
|
|
1187
|
+
for (const entry of entries) {
|
|
1188
|
+
const uri = entry && entry.uri
|
|
1189
|
+
if (!uri || uri.indexOf(PAGE_URI_PREFIX) !== 0) continue
|
|
1190
|
+
const name = uri.slice(PAGE_URI_PREFIX.length)
|
|
1191
|
+
const reader = resources[uri]
|
|
1192
|
+
if (!name || merged[name] || typeof reader !== "function") continue
|
|
1193
|
+
merged[name] = { description: entry.description || "", read: reader }
|
|
1194
|
+
}
|
|
1195
|
+
return merged
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1134
1198
|
export function promptArgFromState(argName, state) {
|
|
1135
1199
|
// Same object shape the system-prompt annex consumes: { description, read }.
|
|
1136
1200
|
// The bare-function form was removed in 0.8.0, so an entry without `read` is
|