llm_meta_widget 0.2.0 → 0.3.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: 6d7a58b9b553be09b66ca6e9aa01f8264a280bb725f80820ec3de506573701ae
|
|
4
|
+
data.tar.gz: 7b171f0c86c5f45a6250975ab02da586d620602d8fc1bea95261dd963d17e67a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e90d55856a37217816264730d1a65fd32af2ad4783ad0c1a09ed298fb5dfe35a618a1ca149bf77265aab346dba4437b960a98399e91b3b15695aa601ed9d2025
|
|
7
|
+
data.tar.gz: 042b6872987730e708758fee7c6043ac27b0a83b27935e33f0751a910307cd901e746f28a2557dc54dfc32f63d5a4039f43cefcbc1952d93613cc59568e4c44a
|
|
@@ -780,38 +780,50 @@ function parseSseFrame(raw) {
|
|
|
780
780
|
// ---- static-primitives extension ---------------------------------------
|
|
781
781
|
//
|
|
782
782
|
// Prototype of `io.modelcontextprotocol/static-primitives` (the SEP-2127
|
|
783
|
-
// follow-on)
|
|
784
|
-
// entry
|
|
785
|
-
// return, so a client can decide whether to attach it BEFORE fetching it.
|
|
786
|
-
// `attachmentHint` says how often attaching is worth it.
|
|
783
|
+
// follow-on). Three optional fields a server may declare on a resources/list
|
|
784
|
+
// entry:
|
|
787
785
|
//
|
|
788
|
-
//
|
|
789
|
-
//
|
|
786
|
+
// sizeBytes - byte length of the payload resources/read would return, so a
|
|
787
|
+
// client can decide whether to attach it BEFORE fetching it.
|
|
788
|
+
// volatility - "stable" (content fixed) or "volatile" (varies between
|
|
789
|
+
// turns, so a client re-reads it each turn).
|
|
790
|
+
// autoAttach - may a client attach this without the user asking for it?
|
|
791
|
+
//
|
|
792
|
+
// volatility and autoAttach are separate on purpose. Whether content changes
|
|
793
|
+
// says nothing about whether it may be attached unasked, and the client needs
|
|
794
|
+
// autoAttach as a boolean anyway, because its own byte budget can withhold a
|
|
795
|
+
// resource the server was happy to hand over.
|
|
796
|
+
//
|
|
797
|
+
// All three are optional, and a server declaring none must behave exactly as
|
|
798
|
+
// it did before the extension existed: fetch once, trim to budget, attach on
|
|
799
|
+
// the first turn only.
|
|
790
800
|
export const STATIC_PRIMITIVES_META = "io.modelcontextprotocol/static-primitives"
|
|
791
801
|
|
|
792
|
-
const
|
|
802
|
+
const VOLATILITIES = [ "stable", "volatile" ]
|
|
793
803
|
|
|
794
804
|
export function resourceHints(entry) {
|
|
795
805
|
const meta = (entry && entry._meta && entry._meta[STATIC_PRIMITIVES_META]) || {}
|
|
796
|
-
const hint = meta.attachmentHint
|
|
797
806
|
return {
|
|
798
807
|
// A non-numeric or absent size means "unknown", never 0 — 0 would read
|
|
799
808
|
// as a free resource and sail through every budget check.
|
|
800
809
|
sizeBytes: typeof meta.sizeBytes === "number" && isFinite(meta.sizeBytes) ? meta.sizeBytes : null,
|
|
801
|
-
|
|
810
|
+
volatility: VOLATILITIES.indexOf(meta.volatility) === -1 ? "stable" : meta.volatility,
|
|
811
|
+
autoAttach: typeof meta.autoAttach === "boolean" ? meta.autoAttach : true
|
|
802
812
|
}
|
|
803
813
|
}
|
|
804
814
|
|
|
805
815
|
// The pre-flight decision, made from the resources/list entry alone.
|
|
806
816
|
// `fetch: false` means the bytes never cross the wire at all.
|
|
807
817
|
export function planResourceAttachment(entry, budgetBytes) {
|
|
808
|
-
const { sizeBytes,
|
|
809
|
-
const base = { sizeBytes,
|
|
818
|
+
const { sizeBytes, volatility, autoAttach } = resourceHints(entry)
|
|
819
|
+
const base = { sizeBytes, volatility, uri: entry && entry.uri }
|
|
810
820
|
|
|
811
|
-
if (
|
|
812
|
-
return { ...base, fetch: false, autoAttach: false, reason: "
|
|
821
|
+
if (!autoAttach) {
|
|
822
|
+
return { ...base, fetch: false, autoAttach: false, reason: "not-auto-attach" }
|
|
813
823
|
}
|
|
814
824
|
if (sizeBytes !== null && sizeBytes > budgetBytes) {
|
|
825
|
+
// The client's budget overrides the server's willingness — which is why
|
|
826
|
+
// autoAttach has to be a boolean here rather than a restatement of a hint.
|
|
815
827
|
return { ...base, fetch: false, autoAttach: false, reason: "over-budget" }
|
|
816
828
|
}
|
|
817
829
|
return {
|
|
@@ -824,21 +836,19 @@ export function planResourceAttachment(entry, budgetBytes) {
|
|
|
824
836
|
}
|
|
825
837
|
}
|
|
826
838
|
|
|
827
|
-
// Per-turn gate.
|
|
828
|
-
//
|
|
829
|
-
// whose content actually varies between turns.
|
|
839
|
+
// Per-turn gate. A stable resource is attached once and re-used; a volatile
|
|
840
|
+
// one is owed a fresh copy every turn.
|
|
830
841
|
export function createResourceAttacher(plan) {
|
|
831
842
|
let attachedOnce = false
|
|
832
843
|
return {
|
|
833
|
-
// Returns whether to attach on THIS turn, and records the answer.
|
|
834
844
|
take() {
|
|
835
845
|
if (!plan || !plan.autoAttach) return false
|
|
836
|
-
if (plan.
|
|
846
|
+
if (plan.volatility === "volatile") return true
|
|
837
847
|
if (attachedOnce) return false
|
|
838
848
|
attachedOnce = true
|
|
839
849
|
return true
|
|
840
850
|
},
|
|
841
|
-
get
|
|
851
|
+
get volatility() { return plan ? plan.volatility : null }
|
|
842
852
|
}
|
|
843
853
|
}
|
|
844
854
|
|
|
@@ -858,38 +868,69 @@ export function trimResourceText(text, maxBytes) {
|
|
|
858
868
|
return text.slice(0, maxBytes) + "\n…truncated"
|
|
859
869
|
}
|
|
860
870
|
|
|
861
|
-
//
|
|
862
|
-
//
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
|
|
871
|
+
// Read one resource and shape it for the system prompt. The trim is applied
|
|
872
|
+
// on every read, not just the first: a declared size can go stale, and a
|
|
873
|
+
// volatile resource is a fresh gamble each turn.
|
|
874
|
+
async function readResourceContext({ endpoint, uri, name, read, budgetBytes }) {
|
|
875
|
+
try {
|
|
876
|
+
const result = await read({ endpoint, uri })
|
|
877
|
+
const entry = ((result && result.contents) || [])[0]
|
|
878
|
+
if (!entry || !entry.text) return null
|
|
879
|
+
return { uri, name: name || uri, text: trimResourceText(entry.text, budgetBytes) }
|
|
880
|
+
} catch (e) {
|
|
881
|
+
// Optional context — a failed read must never block the widget.
|
|
882
|
+
return null
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// The discovery sequence for one endpoint's reference resource, kept here
|
|
887
|
+
// rather than in the panel so the decision AND the call site are testable
|
|
888
|
+
// together: a gate nothing consults is exactly the bug this shape prevents.
|
|
889
|
+
//
|
|
890
|
+
// A volatile resource is NOT read here. Its content is only meaningful for
|
|
891
|
+
// the turn it is attached to, so reading it at boot would buy a copy that is
|
|
892
|
+
// already suspect by the time anyone sends a message.
|
|
866
893
|
export async function loadHostResource({ endpoint, budgetBytes, list, read, onSkip }) {
|
|
867
894
|
const resources = await list({ endpoint })
|
|
868
895
|
const candidate = (resources || []).filter((r) => (r.mimeType || "") === "application/json")[0]
|
|
869
896
|
if (!candidate) return null
|
|
870
897
|
|
|
871
898
|
const plan = planResourceAttachment(candidate, budgetBytes)
|
|
899
|
+
plan.name = candidate.name || candidate.uri
|
|
900
|
+
// Carried so a volatile re-read knows where to go: discovery happens once
|
|
901
|
+
// at boot, but the fetch it authorises happens on every later turn.
|
|
902
|
+
plan.endpoint = endpoint
|
|
872
903
|
if (!plan.fetch) {
|
|
873
904
|
if (onSkip) onSkip(plan)
|
|
874
905
|
return { plan, context: null }
|
|
875
906
|
}
|
|
907
|
+
if (plan.volatility === "volatile") return { plan, context: null }
|
|
876
908
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
909
|
+
const context = await readResourceContext({
|
|
910
|
+
endpoint, uri: candidate.uri, name: plan.name, read, budgetBytes
|
|
911
|
+
})
|
|
912
|
+
return { plan, context }
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export function resourceContextLines(context) {
|
|
916
|
+
if (!context) return []
|
|
917
|
+
return [ "", "Reference data — " + context.name + " (" + context.uri + "):", context.text ]
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// What to attach on THIS turn. Asking consumes the turn, so the decision and
|
|
921
|
+
// the fetch live together: a stable resource re-uses the copy read at boot,
|
|
922
|
+
// a volatile one is read again right now.
|
|
923
|
+
export async function resourceLinesForTurn({ plan, attacher, cached, endpoint, read, budgetBytes }) {
|
|
924
|
+
if (!attacher || !attacher.take()) return { lines: [], cached }
|
|
925
|
+
|
|
926
|
+
if (plan && plan.volatility === "volatile") {
|
|
927
|
+
const fresh = await readResourceContext({
|
|
928
|
+
endpoint, uri: plan.uri, name: plan.name, read, budgetBytes
|
|
929
|
+
})
|
|
930
|
+
return fresh ? { lines: resourceContextLines(fresh), cached: fresh } : { lines: [], cached }
|
|
892
931
|
}
|
|
932
|
+
|
|
933
|
+
return { lines: resourceContextLines(cached), cached }
|
|
893
934
|
}
|
|
894
935
|
|
|
895
936
|
// ---- prompt templates ---------------------------------------------------
|
|
@@ -937,9 +978,3 @@ export function promptButtonProps(prompt) {
|
|
|
937
978
|
}
|
|
938
979
|
}
|
|
939
980
|
|
|
940
|
-
// The per-turn attach decision AND its formatting, so the two cannot drift
|
|
941
|
-
// apart: asking whether to attach is what consumes the turn.
|
|
942
|
-
export function nextResourceContextLines(context, attacher) {
|
|
943
|
-
if (!context || !attacher || !attacher.take()) return []
|
|
944
|
-
return [ "", "Reference data — " + context.name + " (" + context.uri + "):", context.text ]
|
|
945
|
-
}
|
|
@@ -398,8 +398,8 @@
|
|
|
398
398
|
<script type="module">
|
|
399
399
|
import { runChatLoop, fetchMcpManifest, listMcpPrompts, getMcpPrompt,
|
|
400
400
|
listMcpResources, readMcpResource, promptMessagesToText,
|
|
401
|
-
loadHostResource, createResourceAttacher, resolvePromptArguments,
|
|
402
|
-
promptButtonProps
|
|
401
|
+
loadHostResource, createResourceAttacher, resourceLinesForTurn, resolvePromptArguments,
|
|
402
|
+
promptButtonProps } from "<%= orchestrator_path %>";
|
|
403
403
|
import { marked } from "/llm_meta_widget_assets/marked.esm.js";
|
|
404
404
|
|
|
405
405
|
// Standard prose settings — GFM (tables, autolinks, strikethrough),
|
|
@@ -789,7 +789,7 @@
|
|
|
789
789
|
var prompts = await listMcpPrompts({ endpoint: endpoint });
|
|
790
790
|
hostWidePrompts = hostWidePrompts.concat(prompts);
|
|
791
791
|
|
|
792
|
-
if (
|
|
792
|
+
if (resourcePlan === null) {
|
|
793
793
|
// Listing, size gate and read all live in the orchestrator, where
|
|
794
794
|
// they are tested together — a gate nothing consults is exactly
|
|
795
795
|
// the bug this shape prevents.
|
|
@@ -804,7 +804,10 @@
|
|
|
804
804
|
" (" + plan.reason + ", sizeBytes=" + plan.sizeBytes + ")");
|
|
805
805
|
}
|
|
806
806
|
});
|
|
807
|
-
|
|
807
|
+
// A volatile resource comes back with no context — it is read
|
|
808
|
+
// per turn instead — so the plan, not the payload, is what says
|
|
809
|
+
// whether this endpoint offered anything worth attaching.
|
|
810
|
+
if (loaded && loaded.plan && loaded.plan.fetch) {
|
|
808
811
|
resourceContext = loaded.context;
|
|
809
812
|
resourcePlan = loaded.plan;
|
|
810
813
|
resourceAttacher = createResourceAttacher(loaded.plan);
|
|
@@ -960,7 +963,7 @@
|
|
|
960
963
|
return out;
|
|
961
964
|
}
|
|
962
965
|
|
|
963
|
-
function currentSystemPrompt() {
|
|
966
|
+
function currentSystemPrompt(resourceLines) {
|
|
964
967
|
return [
|
|
965
968
|
"You are integrated into a web page as an AI assistant. You have tools available to change page state or fetch information.",
|
|
966
969
|
"",
|
|
@@ -972,16 +975,27 @@
|
|
|
972
975
|
"",
|
|
973
976
|
"Current page state:",
|
|
974
977
|
JSON.stringify(currentPageState(), null, 2)
|
|
975
|
-
].concat(
|
|
978
|
+
].concat(resourceLines || []).join("\n");
|
|
976
979
|
}
|
|
977
980
|
|
|
978
|
-
//
|
|
979
|
-
// extension's
|
|
980
|
-
//
|
|
981
|
-
//
|
|
982
|
-
//
|
|
983
|
-
|
|
984
|
-
|
|
981
|
+
// Whether to attach, and how often, is the SERVER's call — via the
|
|
982
|
+
// extension's autoAttach and volatility fields, which default to
|
|
983
|
+
// attach-once for a server that declares neither.
|
|
984
|
+
//
|
|
985
|
+
// Asking consumes the turn, and a volatile resource is re-read here rather
|
|
986
|
+
// than re-using the boot-time copy — so this runs once per send, before
|
|
987
|
+
// the system prompt is built.
|
|
988
|
+
async function resourceLinesForThisTurn() {
|
|
989
|
+
var turn = await resourceLinesForTurn({
|
|
990
|
+
plan: resourcePlan,
|
|
991
|
+
attacher: resourceAttacher,
|
|
992
|
+
cached: resourceContext,
|
|
993
|
+
endpoint: resourcePlan && resourcePlan.endpoint,
|
|
994
|
+
read: readMcpResource,
|
|
995
|
+
budgetBytes: RESOURCE_BUDGET_BYTES
|
|
996
|
+
});
|
|
997
|
+
resourceContext = turn.cached;
|
|
998
|
+
return turn.lines;
|
|
985
999
|
}
|
|
986
1000
|
|
|
987
1001
|
// Per-turn AbortController — lets the Clear button (or a new submit)
|
|
@@ -1028,7 +1042,8 @@
|
|
|
1028
1042
|
// it as sent.
|
|
1029
1043
|
await wellKnownReady;
|
|
1030
1044
|
|
|
1031
|
-
var
|
|
1045
|
+
var resourceLines = await resourceLinesForThisTurn();
|
|
1046
|
+
var messages = [{ role: "system", content: currentSystemPrompt(resourceLines) }]
|
|
1032
1047
|
.concat(conversation)
|
|
1033
1048
|
.concat([{ role: "user", content: userText }]);
|
|
1034
1049
|
|