llm_meta_widget 0.9.0 → 0.9.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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6dba4c1cd88e26bde9f0179009ae2f47cdd07a30aae6035e619f0ca5b83d32e
|
|
4
|
+
data.tar.gz: 95cca5b804adab95a89dfbdf23b7624cb43304462d4cf6c72a605a7ebef170de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2c499f1d83a689e52fff3fabb0cc08f359c4d34571295085718f3fa5b8c13ccf565f6f47c9ae73b210f1bea350581e7e1c4ae47ac27a759116364518fce2bd2
|
|
7
|
+
data.tar.gz: bb45042dcae5849cf88750d8d6235416fda8e531f0852ed454405c59c4358ff3c31137b78349b6b87be8fc490c3235de62b5b9bebd4f0a61cea790c0cf0925ba
|
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,59 @@ 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/json" id="ai-page">
|
|
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 optional.** A page that declares nothing keeps working exactly as
|
|
470
|
+
before, and a page that declares no `tools` still uses its `ai-actions` block.
|
|
471
|
+
|
|
415
472
|
## Surviving navigation
|
|
416
473
|
|
|
417
474
|
A page action that navigates — submitting a form, following a link — no longer
|
|
@@ -24,7 +24,8 @@ 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 } from "./orchestrator.js";
|
|
28
29
|
import { marked } from "./marked.esm.js";
|
|
29
30
|
import { readConfig } from "./config.js";
|
|
30
31
|
import conversationCss from "../../stylesheets/llm_meta_widget/conversation.css";
|
|
@@ -685,6 +686,15 @@ function boot(cfg) {
|
|
|
685
686
|
// submits — so it travels the exact path a typed message does.
|
|
686
687
|
|
|
687
688
|
|
|
689
|
+
// Where a prompt's declared arguments are filled from. A card-only page keeps
|
|
690
|
+
// its state in aiResources under `page:<name>` URIs, so the names the resolver
|
|
691
|
+
// looks up live there, not in aiState.
|
|
692
|
+
function argState() {
|
|
693
|
+
return promptArgState({ entries: pageCard.resources,
|
|
694
|
+
resources: window[RESOURCES_GLOBAL] || {},
|
|
695
|
+
state: window[STATE_GLOBAL] || {} });
|
|
696
|
+
}
|
|
697
|
+
|
|
688
698
|
function renderPromptButtons() {
|
|
689
699
|
if (!promptsEl) return;
|
|
690
700
|
promptsEl.textContent = "";
|
|
@@ -749,7 +759,7 @@ function boot(cfg) {
|
|
|
749
759
|
(hostWidePrompts.length ? ", or start with one of these:" : ".");
|
|
750
760
|
box.appendChild(hello);
|
|
751
761
|
|
|
752
|
-
var state =
|
|
762
|
+
var state = argState();
|
|
753
763
|
hostWidePrompts.forEach(function(prompt) {
|
|
754
764
|
var props = promptButtonProps(prompt);
|
|
755
765
|
var card = document.createElement("div");
|
|
@@ -792,7 +802,7 @@ function boot(cfg) {
|
|
|
792
802
|
}
|
|
793
803
|
|
|
794
804
|
async function runPromptTemplate(prompt, button) {
|
|
795
|
-
var resolved = resolvePromptArguments(prompt,
|
|
805
|
+
var resolved = resolvePromptArguments(prompt, argState());
|
|
796
806
|
var args = resolved.args;
|
|
797
807
|
var missing = resolved.missing;
|
|
798
808
|
|
|
@@ -737,7 +737,20 @@ async function resourceLinesForTurn({ plan, cached, endpoint, read, budgetBytes
|
|
|
737
737
|
}
|
|
738
738
|
return { lines: resourceContextLines(cached), cached };
|
|
739
739
|
}
|
|
740
|
+
var PAGE_URI_PREFIX = "page:";
|
|
740
741
|
var PROMPT_ARG_ALIASES = { dictionaries: "selected_dictionaries" };
|
|
742
|
+
function promptArgState({ entries = [], resources = {}, state = {} } = {}) {
|
|
743
|
+
const merged = Object.assign({}, state);
|
|
744
|
+
for (const entry of entries) {
|
|
745
|
+
const uri = entry && entry.uri;
|
|
746
|
+
if (!uri || uri.indexOf(PAGE_URI_PREFIX) !== 0) continue;
|
|
747
|
+
const name = uri.slice(PAGE_URI_PREFIX.length);
|
|
748
|
+
const reader = resources[uri];
|
|
749
|
+
if (!name || merged[name] || typeof reader !== "function") continue;
|
|
750
|
+
merged[name] = { description: entry.description || "", read: reader };
|
|
751
|
+
}
|
|
752
|
+
return merged;
|
|
753
|
+
}
|
|
741
754
|
function promptArgFromState(argName, state) {
|
|
742
755
|
const entry = state && state[argName] || state && state[PROMPT_ARG_ALIASES[argName]];
|
|
743
756
|
const reader = entry && typeof entry.read === "function" ? entry.read : null;
|
|
@@ -4162,6 +4175,13 @@ function boot(cfg) {
|
|
|
4162
4175
|
renderPromptButtons();
|
|
4163
4176
|
renderWelcome();
|
|
4164
4177
|
})();
|
|
4178
|
+
function argState() {
|
|
4179
|
+
return promptArgState({
|
|
4180
|
+
entries: pageCard.resources,
|
|
4181
|
+
resources: window[RESOURCES_GLOBAL] || {},
|
|
4182
|
+
state: window[STATE_GLOBAL] || {}
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4165
4185
|
function renderPromptButtons() {
|
|
4166
4186
|
if (!promptsEl) return;
|
|
4167
4187
|
promptsEl.textContent = "";
|
|
@@ -4213,7 +4233,7 @@ function boot(cfg) {
|
|
|
4213
4233
|
hello.className = "lmw-welcome-hello";
|
|
4214
4234
|
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
4235
|
box.appendChild(hello);
|
|
4216
|
-
var state =
|
|
4236
|
+
var state = argState();
|
|
4217
4237
|
hostWidePrompts.forEach(function(prompt) {
|
|
4218
4238
|
var props = promptButtonProps(prompt);
|
|
4219
4239
|
var card = document.createElement("div");
|
|
@@ -4249,7 +4269,7 @@ function boot(cfg) {
|
|
|
4249
4269
|
historyEl.appendChild(box);
|
|
4250
4270
|
}
|
|
4251
4271
|
async function runPromptTemplate(prompt, button) {
|
|
4252
|
-
var resolved = resolvePromptArguments(prompt,
|
|
4272
|
+
var resolved = resolvePromptArguments(prompt, argState());
|
|
4253
4273
|
var args = resolved.args;
|
|
4254
4274
|
var missing = resolved.missing;
|
|
4255
4275
|
if (missing.length) {
|
|
@@ -1129,8 +1129,35 @@ export async function resourceLinesForTurn({ plan, cached, endpoint, read, budge
|
|
|
1129
1129
|
// (window.aiState) by name. `dictionaries` is read from a
|
|
1130
1130
|
// `selected_dictionaries` reader: the page selects a LIST, and the prompt
|
|
1131
1131
|
// declares the CSV shape the text_annotation tool already takes.
|
|
1132
|
+
// Page resources are addressed `page:<name>`: a custom scheme because the
|
|
1133
|
+
// protocol asks for a URI and `https:` is reserved for what a client can fetch
|
|
1134
|
+
// itself, and with no path because a page has one namespace.
|
|
1135
|
+
const PAGE_URI_PREFIX = "page:"
|
|
1136
|
+
|
|
1132
1137
|
const PROMPT_ARG_ALIASES = { dictionaries: "selected_dictionaries" }
|
|
1133
1138
|
|
|
1139
|
+
// Prompt arguments are declared by plain name (`text`); page resources are
|
|
1140
|
+
// addressed by URI (`page:text`). So on a page that declares its state as
|
|
1141
|
+
// resources and keeps no `aiState`, the resolver below finds nothing under the
|
|
1142
|
+
// names it looks up and every argument arrives empty — the visitor is asked to
|
|
1143
|
+
// retype what is already on screen. This presents those resources in the shape
|
|
1144
|
+
// the resolver consumes, keyed by the name inside the URI.
|
|
1145
|
+
//
|
|
1146
|
+
// An explicit `aiState` entry wins: a page running both channels has said what
|
|
1147
|
+
// it wants under that name.
|
|
1148
|
+
export function promptArgState({ entries = [], resources = {}, state = {} } = {}) {
|
|
1149
|
+
const merged = Object.assign({}, state)
|
|
1150
|
+
for (const entry of entries) {
|
|
1151
|
+
const uri = entry && entry.uri
|
|
1152
|
+
if (!uri || uri.indexOf(PAGE_URI_PREFIX) !== 0) continue
|
|
1153
|
+
const name = uri.slice(PAGE_URI_PREFIX.length)
|
|
1154
|
+
const reader = resources[uri]
|
|
1155
|
+
if (!name || merged[name] || typeof reader !== "function") continue
|
|
1156
|
+
merged[name] = { description: entry.description || "", read: reader }
|
|
1157
|
+
}
|
|
1158
|
+
return merged
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1134
1161
|
export function promptArgFromState(argName, state) {
|
|
1135
1162
|
// Same object shape the system-prompt annex consumes: { description, read }.
|
|
1136
1163
|
// The bare-function form was removed in 0.8.0, so an entry without `read` is
|