@mcp-b/webmcp-polyfill 5.1.0 → 6.0.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.
- package/LICENSE.webmcp-polyfill +21 -0
- package/README.md +76 -140
- package/dist/index.d.ts +20 -13
- package/dist/index.iife.js +6 -1
- package/dist/index.js +1015 -561
- package/dist/index.js.map +1 -1
- package/package.json +10 -12
- package/dist/schema.d.ts +0 -31
- package/dist/schema.js +0 -274
- package/dist/schema.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,458 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/upstream/frames.ts
|
|
2
|
+
const annotationNames = [
|
|
3
|
+
"consequentialHint",
|
|
4
|
+
"debugging",
|
|
5
|
+
"readOnlyHint",
|
|
6
|
+
"untrustedContentHint"
|
|
7
|
+
];
|
|
8
|
+
const protocol = "webmcp-polyfill";
|
|
9
|
+
const deadline = 500;
|
|
10
|
+
const NativeDOMException$1 = globalThis.DOMException;
|
|
11
|
+
var FrameBridge = class {
|
|
12
|
+
#document;
|
|
13
|
+
#window;
|
|
14
|
+
#handlers;
|
|
15
|
+
#knownPeers = /* @__PURE__ */ new Set();
|
|
16
|
+
#unanswered = /* @__PURE__ */ new Set();
|
|
17
|
+
#started = Promise.withResolvers();
|
|
18
|
+
#handshakes = /* @__PURE__ */ new Map();
|
|
19
|
+
#sessions = /* @__PURE__ */ new Set();
|
|
20
|
+
#monitor;
|
|
21
|
+
#hidden = false;
|
|
22
|
+
constructor(view, handlers) {
|
|
23
|
+
this.#window = view;
|
|
24
|
+
this.#document = view.document;
|
|
25
|
+
this.#handlers = handlers;
|
|
26
|
+
this.#window.addEventListener("message", (event) => this.#receive(event), true);
|
|
27
|
+
this.#window.addEventListener("pagehide", () => {
|
|
28
|
+
this.#hidden = true;
|
|
29
|
+
for (const session of this.#sessions) session.end();
|
|
30
|
+
});
|
|
31
|
+
this.#window.addEventListener("pageshow", (event) => {
|
|
32
|
+
if (event.persisted) {
|
|
33
|
+
this.#hidden = false;
|
|
34
|
+
this.#announce();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
for (const frame of this.#announce()) this.#unanswered.add(frame);
|
|
38
|
+
if (this.#unanswered.size === 0) this.#started.resolve();
|
|
39
|
+
else setTimeout(() => {
|
|
40
|
+
this.#unanswered.clear();
|
|
41
|
+
this.#started.resolve();
|
|
42
|
+
}, deadline);
|
|
43
|
+
}
|
|
44
|
+
async allowed() {
|
|
45
|
+
if (this.#hidden) return false;
|
|
46
|
+
return this.#documentAllowed(this.#document);
|
|
47
|
+
}
|
|
48
|
+
async getTools(fromOrigins) {
|
|
49
|
+
await this.#started.promise;
|
|
50
|
+
const ownOrigin = this.#window.origin;
|
|
51
|
+
const origins = [ownOrigin, ...fromOrigins];
|
|
52
|
+
return (await Promise.all(this.#participants().map(async (peer) => {
|
|
53
|
+
if (peer === this.#window) return this.#handlers.getTools(ownOrigin).map((metadata) => toRegisteredTool(metadata, this.#window, ownOrigin));
|
|
54
|
+
try {
|
|
55
|
+
const reply = await this.#request(peer, origins, { kind: "getTools" });
|
|
56
|
+
if (!Array.isArray(reply.value)) throw new TypeError("Expected a tool list from the frame");
|
|
57
|
+
return reply.value.map((value) => toRegisteredTool(readToolMetadata(value), peer, reply.origin));
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (!(error instanceof NativeDOMException$1 && error.name === "UnknownError")) console.warn("WebMCP: could not read tools from a frame.", error);
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}))).flat();
|
|
63
|
+
}
|
|
64
|
+
async execute(target, expectedOrigin, name, serializedInput, signal) {
|
|
65
|
+
const reply = await this.#request(target, [expectedOrigin], {
|
|
66
|
+
kind: "execute",
|
|
67
|
+
name,
|
|
68
|
+
input: serializedInput
|
|
69
|
+
}, signal);
|
|
70
|
+
if (typeof reply.value !== "string") throw executionError();
|
|
71
|
+
return reply.value;
|
|
72
|
+
}
|
|
73
|
+
async notify(exposedTo) {
|
|
74
|
+
await this.#started.promise;
|
|
75
|
+
const origins = [this.#window.origin, ...exposedTo];
|
|
76
|
+
for (const peer of this.#participants()) {
|
|
77
|
+
if (peer === this.#window) {
|
|
78
|
+
await this.#handlers.changed();
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
await this.#request(peer, origins, { kind: "changed" });
|
|
83
|
+
} catch {}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
#request(target, origins, request, signal) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
signal?.throwIfAborted();
|
|
89
|
+
if (this.#hidden || !this.#related(target)) {
|
|
90
|
+
reject(executionError());
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const id = crypto.randomUUID();
|
|
94
|
+
const { port1, port2 } = new MessageChannel();
|
|
95
|
+
let peerOrigin;
|
|
96
|
+
const close = () => {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
stopWatching();
|
|
99
|
+
signal?.removeEventListener("abort", abort);
|
|
100
|
+
this.#handshakes.delete(id);
|
|
101
|
+
port1.close();
|
|
102
|
+
};
|
|
103
|
+
const fail = (error = executionError()) => {
|
|
104
|
+
port1.postMessage({ kind: "cancel" });
|
|
105
|
+
close();
|
|
106
|
+
reject(error);
|
|
107
|
+
};
|
|
108
|
+
const abort = () => fail(signal?.reason);
|
|
109
|
+
const expire = () => {
|
|
110
|
+
if (peerOrigin === void 0) this.#forget(target);
|
|
111
|
+
fail();
|
|
112
|
+
};
|
|
113
|
+
const connected = (event) => {
|
|
114
|
+
if (event.source !== target) return;
|
|
115
|
+
this.#handshakes.delete(id);
|
|
116
|
+
if (origins && !origins.includes(event.origin)) {
|
|
117
|
+
fail();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
peerOrigin = event.origin;
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
if (request.kind !== "execute" && request.kind !== "changed") timer = setTimeout(expire, deadline);
|
|
123
|
+
port1.postMessage(request);
|
|
124
|
+
};
|
|
125
|
+
let timer = setTimeout(expire, deadline);
|
|
126
|
+
const stopWatching = this.#watch(target, fail);
|
|
127
|
+
port1.onmessage = (event) => {
|
|
128
|
+
const reply = record(event.data);
|
|
129
|
+
if (reply?.kind === "error") {
|
|
130
|
+
fail();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (reply?.kind !== "result" || peerOrigin === void 0) return;
|
|
134
|
+
close();
|
|
135
|
+
resolve({
|
|
136
|
+
value: reply.value,
|
|
137
|
+
origin: peerOrigin
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
port1.onmessageerror = () => fail();
|
|
141
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
142
|
+
this.#handshakes.set(id, connected);
|
|
143
|
+
try {
|
|
144
|
+
target.postMessage(`${protocol}:connect:${id}`, "*", [port2]);
|
|
145
|
+
} catch {
|
|
146
|
+
port2.close();
|
|
147
|
+
fail();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
#receive(event) {
|
|
152
|
+
const message = readWindowMessage(event.data);
|
|
153
|
+
if (!message) return;
|
|
154
|
+
event.stopImmediatePropagation();
|
|
155
|
+
const source = event.source;
|
|
156
|
+
if (!event.isTrusted || !source || !isWindow(source) || !this.#related(source) || event.origin === "null" || this.#hidden) return;
|
|
157
|
+
if (message.kind === "announce") {
|
|
158
|
+
const isNew = !this.#knownPeers.has(source);
|
|
159
|
+
this.#learn(source);
|
|
160
|
+
source.postMessage(`${protocol}:present`, event.origin);
|
|
161
|
+
if (!isNew || this.#window !== this.#window.top) return;
|
|
162
|
+
for (const peer of this.#knownPeers) {
|
|
163
|
+
if (peer === source) continue;
|
|
164
|
+
peer.postMessage(`${protocol}:refresh`, "*");
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (message.kind === "present") {
|
|
169
|
+
this.#learn(source);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (message.kind === "refresh") {
|
|
173
|
+
if (source === this.#window.top) this.#announce();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (message.kind === "ready") {
|
|
177
|
+
this.#handshakes.get(message.id)?.(event);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const [port] = event.ports;
|
|
181
|
+
if (message.kind !== "connect" || !message.id || !port || event.ports.length !== 1) return;
|
|
182
|
+
this.#learn(source);
|
|
183
|
+
this.#accept(source, event.origin, message.id, port);
|
|
184
|
+
}
|
|
185
|
+
#accept(source, origin, id, port) {
|
|
186
|
+
const controller = new AbortController();
|
|
187
|
+
let requested = false;
|
|
188
|
+
const close = () => {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
stopWatching();
|
|
191
|
+
port.close();
|
|
192
|
+
};
|
|
193
|
+
const cancel = () => {
|
|
194
|
+
controller.abort();
|
|
195
|
+
close();
|
|
196
|
+
};
|
|
197
|
+
const fail = () => {
|
|
198
|
+
port.postMessage({ kind: "error" });
|
|
199
|
+
cancel();
|
|
200
|
+
};
|
|
201
|
+
const timer = setTimeout(fail, deadline);
|
|
202
|
+
const stopWatching = this.#watch(source, fail);
|
|
203
|
+
port.onmessageerror = fail;
|
|
204
|
+
port.onmessage = async (event) => {
|
|
205
|
+
const request = record(event.data);
|
|
206
|
+
if (request?.kind === "cancel") {
|
|
207
|
+
cancel();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (requested) return;
|
|
211
|
+
requested = true;
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
try {
|
|
214
|
+
const value = await this.#respond(source, origin, request, controller.signal);
|
|
215
|
+
port.postMessage({
|
|
216
|
+
kind: "result",
|
|
217
|
+
value
|
|
218
|
+
});
|
|
219
|
+
close();
|
|
220
|
+
} catch {
|
|
221
|
+
fail();
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
source.postMessage(`${protocol}:ready:${id}`, origin);
|
|
225
|
+
}
|
|
226
|
+
async #respond(source, origin, request, signal) {
|
|
227
|
+
if (!request || !this.#related(source)) throw executionError();
|
|
228
|
+
if (request.kind === "permission") {
|
|
229
|
+
const { childIndex, childOrigin } = request;
|
|
230
|
+
if (typeof childIndex !== "number" || !Number.isInteger(childIndex) || childIndex < 0 || typeof childOrigin !== "string") throw executionError();
|
|
231
|
+
return this.#childPermission(childIndex, childOrigin);
|
|
232
|
+
}
|
|
233
|
+
if (!await this.allowed() || !await this.#sourceAllowed(source, origin)) throw executionError();
|
|
234
|
+
if (!this.#related(source)) throw executionError();
|
|
235
|
+
signal.throwIfAborted();
|
|
236
|
+
if (request.kind === "getTools") return this.#handlers.getTools(origin);
|
|
237
|
+
if (request.kind === "execute" && typeof request.name === "string" && typeof request.input === "string") return this.#handlers.execute(origin, request.name, request.input, signal);
|
|
238
|
+
if (request.kind === "changed") {
|
|
239
|
+
await this.#handlers.changed();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
throw executionError();
|
|
243
|
+
}
|
|
244
|
+
async #childPermission(childIndex, childOrigin) {
|
|
245
|
+
const child = this.#window[childIndex];
|
|
246
|
+
return child !== void 0 && await this.allowed() && childAllowed(this.#document, child, childOrigin);
|
|
247
|
+
}
|
|
248
|
+
async #documentAllowed(owner) {
|
|
249
|
+
const view = activeWindow(owner);
|
|
250
|
+
if (!view) return false;
|
|
251
|
+
const policy = readToolsPolicy(owner);
|
|
252
|
+
if (policy !== void 0) return policy;
|
|
253
|
+
if (view === view.parent) return true;
|
|
254
|
+
const parentOrigin = view.location.ancestorOrigins?.[0];
|
|
255
|
+
const parentDocument = readDocument(view.parent, parentOrigin === void 0 || parentOrigin === view.origin);
|
|
256
|
+
if (!parentDocument) return this.#parentPermission(view, view.origin);
|
|
257
|
+
return childAllowed(parentDocument, view, view.origin) && await this.#documentAllowed(parentDocument);
|
|
258
|
+
}
|
|
259
|
+
async #sourceAllowed(source, origin) {
|
|
260
|
+
const sourceDocument = readDocument(source, origin === this.#window.origin);
|
|
261
|
+
return sourceDocument ? this.#documentAllowed(sourceDocument) : this.#parentPermission(source, origin);
|
|
262
|
+
}
|
|
263
|
+
async #parentPermission(child, origin) {
|
|
264
|
+
const parent = child.parent;
|
|
265
|
+
if (parent === child) return true;
|
|
266
|
+
let childIndex = -1;
|
|
267
|
+
for (let index = 0; index < parent.length; index++) if (parent[index] === child) {
|
|
268
|
+
childIndex = index;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
if (childIndex < 0) return false;
|
|
272
|
+
if (parent === this.#window) return await this.allowed() && childAllowed(this.#document, child, origin);
|
|
273
|
+
await this.#started.promise;
|
|
274
|
+
if (!this.#knownPeers.has(parent)) return false;
|
|
275
|
+
try {
|
|
276
|
+
const reply = await this.#request(parent, void 0, {
|
|
277
|
+
kind: "permission",
|
|
278
|
+
childIndex,
|
|
279
|
+
childOrigin: origin
|
|
280
|
+
});
|
|
281
|
+
return reply.value === true && await this.#sourceAllowed(parent, reply.origin);
|
|
282
|
+
} catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
#related(peer) {
|
|
287
|
+
try {
|
|
288
|
+
return !peer.closed && peer !== this.#window && peer.top === this.#window.top;
|
|
289
|
+
} catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
#tree() {
|
|
294
|
+
if (this.#window.closed) return [];
|
|
295
|
+
for (const peer of this.#knownPeers) if (!this.#related(peer)) this.#knownPeers.delete(peer);
|
|
296
|
+
const candidates = [this.#window, ...this.#knownPeers];
|
|
297
|
+
for (let ancestor = this.#window.parent; ancestor !== ancestor.parent; ancestor = ancestor.parent) candidates.push(ancestor);
|
|
298
|
+
const tree = /* @__PURE__ */ new Set();
|
|
299
|
+
const pending = [this.#window.top];
|
|
300
|
+
while (pending.length) {
|
|
301
|
+
const frame = pending.pop();
|
|
302
|
+
if (tree.has(frame)) continue;
|
|
303
|
+
tree.add(frame);
|
|
304
|
+
const children = [];
|
|
305
|
+
for (let index = 0; index < frame.length; index++) {
|
|
306
|
+
const child = frame[index];
|
|
307
|
+
if (child) children.push(child);
|
|
308
|
+
}
|
|
309
|
+
for (const candidate of candidates) if (candidate !== frame && candidate.parent === frame && !children.includes(candidate)) children.push(candidate);
|
|
310
|
+
pending.push(...children.reverse());
|
|
311
|
+
}
|
|
312
|
+
return Array.from(tree).filter((frame) => frame === this.#window || this.#related(frame));
|
|
313
|
+
}
|
|
314
|
+
#participants() {
|
|
315
|
+
return this.#tree().filter((frame) => frame === this.#window || this.#knownPeers.has(frame));
|
|
316
|
+
}
|
|
317
|
+
#announce() {
|
|
318
|
+
const others = this.#tree().filter((frame) => frame !== this.#window);
|
|
319
|
+
for (const frame of others) frame.postMessage(`${protocol}:announce`, "*");
|
|
320
|
+
return others;
|
|
321
|
+
}
|
|
322
|
+
#learn(peer) {
|
|
323
|
+
this.#knownPeers.add(peer);
|
|
324
|
+
this.#unanswered.delete(peer);
|
|
325
|
+
if (this.#unanswered.size === 0) this.#started.resolve();
|
|
326
|
+
}
|
|
327
|
+
#forget(peer) {
|
|
328
|
+
this.#knownPeers.delete(peer);
|
|
329
|
+
if (this.#related(peer)) peer.postMessage(`${protocol}:announce`, "*");
|
|
330
|
+
}
|
|
331
|
+
#watch(peer, end) {
|
|
332
|
+
const session = {
|
|
333
|
+
peer,
|
|
334
|
+
end
|
|
335
|
+
};
|
|
336
|
+
this.#sessions.add(session);
|
|
337
|
+
if (this.#monitor === void 0) this.#monitor = this.#window.setInterval(() => {
|
|
338
|
+
for (const open of this.#sessions) if (!this.#related(open.peer)) open.end();
|
|
339
|
+
}, 100);
|
|
340
|
+
return () => {
|
|
341
|
+
this.#sessions.delete(session);
|
|
342
|
+
if (this.#sessions.size === 0) {
|
|
343
|
+
this.#window.clearInterval(this.#monitor);
|
|
344
|
+
this.#monitor = void 0;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
function childAllowed(owner, child, origin) {
|
|
350
|
+
const ownerOrigin = owner.defaultView?.origin;
|
|
351
|
+
const frame = findFrame(owner, child, origin === ownerOrigin);
|
|
352
|
+
if (!frame) return false;
|
|
353
|
+
const directive = (frame.getAttribute("allow") ?? "").split(";").map((part) => part.trim().split(/\s+/u)).find(([name]) => name === "tools");
|
|
354
|
+
if (!directive) return origin === ownerOrigin;
|
|
355
|
+
const sources = directive.length === 1 ? ["'src'"] : directive.slice(1);
|
|
356
|
+
if (sources.includes("'none'")) return false;
|
|
357
|
+
const src = frame.hasAttribute("srcdoc") ? null : frame.getAttribute("src");
|
|
358
|
+
const srcURL = src ? URL.parse(src, owner.baseURI) : null;
|
|
359
|
+
const sourceOrigin = !src || srcURL?.protocol === "about:" ? ownerOrigin : srcURL?.origin;
|
|
360
|
+
return sources.some((source) => source === "*" || source === "'self'" && origin === ownerOrigin || source === "'src'" && origin === sourceOrigin || source === origin);
|
|
361
|
+
}
|
|
362
|
+
function readDocument(view, sameOrigin) {
|
|
363
|
+
if (!sameOrigin) return;
|
|
364
|
+
try {
|
|
365
|
+
return view.document;
|
|
366
|
+
} catch {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function findFrame(owner, child, sameOrigin) {
|
|
371
|
+
if (sameOrigin) try {
|
|
372
|
+
const frame = child.frameElement;
|
|
373
|
+
if (frame?.ownerDocument === owner) return frame;
|
|
374
|
+
} catch {}
|
|
375
|
+
return searchFrames(owner, child);
|
|
376
|
+
}
|
|
377
|
+
function searchFrames(root, child) {
|
|
378
|
+
const frames = root.querySelectorAll("iframe, frame");
|
|
379
|
+
for (const frame of frames) if (frame.contentWindow === child) return frame;
|
|
380
|
+
for (const element of root.querySelectorAll("*")) {
|
|
381
|
+
if (!element.shadowRoot) continue;
|
|
382
|
+
const frame = searchFrames(element.shadowRoot, child);
|
|
383
|
+
if (frame) return frame;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function activeWindow(owner) {
|
|
387
|
+
const view = owner.defaultView;
|
|
388
|
+
return view && !view.closed && view.document === owner ? view : void 0;
|
|
389
|
+
}
|
|
390
|
+
function readToolsPolicy(owner) {
|
|
391
|
+
const source = owner;
|
|
392
|
+
const policy = record(source.permissionsPolicy ?? source.featurePolicy);
|
|
393
|
+
if (typeof policy?.features !== "function" || typeof policy.allowsFeature !== "function") return;
|
|
394
|
+
const features = policy.features();
|
|
395
|
+
return Array.isArray(features) && features.includes("tools") ? Boolean(policy.allowsFeature("tools")) : void 0;
|
|
396
|
+
}
|
|
397
|
+
function toRegisteredTool(metadata, owner, origin) {
|
|
398
|
+
const inputSchema = metadata.serializedSchema === void 0 ? void 0 : JSON.parse(metadata.serializedSchema);
|
|
399
|
+
return {
|
|
400
|
+
...metadata.annotations !== void 0 && { annotations: { ...metadata.annotations } },
|
|
401
|
+
description: metadata.description,
|
|
402
|
+
...inputSchema !== void 0 && { inputSchema },
|
|
403
|
+
name: metadata.name,
|
|
404
|
+
origin,
|
|
405
|
+
title: metadata.title,
|
|
406
|
+
window: owner
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function readToolMetadata(value) {
|
|
410
|
+
const tool = record(value);
|
|
411
|
+
if (!tool || typeof tool.name !== "string" || typeof tool.title !== "string" || typeof tool.description !== "string" || tool.serializedSchema !== void 0 && typeof tool.serializedSchema !== "string") throw new TypeError("Invalid tool metadata from the frame");
|
|
412
|
+
const metadata = {
|
|
413
|
+
name: tool.name,
|
|
414
|
+
title: tool.title,
|
|
415
|
+
description: tool.description,
|
|
416
|
+
annotations: void 0,
|
|
417
|
+
serializedSchema: tool.serializedSchema
|
|
418
|
+
};
|
|
419
|
+
if (tool.annotations === void 0) return metadata;
|
|
420
|
+
const hints = record(tool.annotations);
|
|
421
|
+
if (!hints) throw new TypeError("Invalid tool annotations from the frame");
|
|
422
|
+
const annotations = {};
|
|
423
|
+
for (const name of annotationNames) {
|
|
424
|
+
const hint = hints[name];
|
|
425
|
+
if (hint === void 0) continue;
|
|
426
|
+
if (typeof hint !== "boolean") throw new TypeError(`Invalid ${name} annotation from the frame`);
|
|
427
|
+
annotations[name] = hint;
|
|
428
|
+
}
|
|
429
|
+
metadata.annotations = annotations;
|
|
430
|
+
return metadata;
|
|
431
|
+
}
|
|
432
|
+
function readWindowMessage(value) {
|
|
433
|
+
if (typeof value !== "string") return;
|
|
434
|
+
const [namespace, kind = "", id = "", extra] = value.split(":");
|
|
435
|
+
return namespace === protocol && extra === void 0 ? {
|
|
436
|
+
kind,
|
|
437
|
+
id
|
|
438
|
+
} : void 0;
|
|
439
|
+
}
|
|
440
|
+
function record(value) {
|
|
441
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
function isWindow(value) {
|
|
445
|
+
try {
|
|
446
|
+
return "postMessage" in value && "window" in value;
|
|
447
|
+
} catch {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function executionError() {
|
|
452
|
+
return new NativeDOMException$1("Tool execution failed", "UnknownError");
|
|
453
|
+
}
|
|
2
454
|
|
|
455
|
+
//#endregion
|
|
3
456
|
//#region src/declarative-forms.ts
|
|
4
457
|
const agentInvokedEvents = /* @__PURE__ */ new WeakSet();
|
|
5
458
|
const agentResponses = /* @__PURE__ */ new WeakMap();
|
|
@@ -37,25 +490,25 @@ function isControl(element) {
|
|
|
37
490
|
return element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement;
|
|
38
491
|
}
|
|
39
492
|
function getFormControls(form) {
|
|
40
|
-
return
|
|
493
|
+
return Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, "elements")?.get?.call(form);
|
|
41
494
|
}
|
|
42
495
|
function getFormAttribute(form, name) {
|
|
43
|
-
return
|
|
496
|
+
return Element.prototype.getAttribute.call(form, name);
|
|
44
497
|
}
|
|
45
498
|
function formHasAttribute(form, name) {
|
|
46
|
-
return
|
|
499
|
+
return Element.prototype.hasAttribute.call(form, name);
|
|
47
500
|
}
|
|
48
|
-
function
|
|
49
|
-
return
|
|
501
|
+
function isConnected(node) {
|
|
502
|
+
return Object.getOwnPropertyDescriptor(Node.prototype, "isConnected")?.get?.call(node);
|
|
50
503
|
}
|
|
51
504
|
function getOpenShadowRoot(element) {
|
|
52
|
-
return
|
|
505
|
+
return Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoot")?.get?.call(element);
|
|
53
506
|
}
|
|
54
507
|
function checkFormValidity(form) {
|
|
55
|
-
return
|
|
508
|
+
return HTMLFormElement.prototype.checkValidity.call(form);
|
|
56
509
|
}
|
|
57
510
|
function requestFormSubmit(form, submitter) {
|
|
58
|
-
|
|
511
|
+
HTMLFormElement.prototype.requestSubmit.call(form, submitter);
|
|
59
512
|
}
|
|
60
513
|
function getControls(form) {
|
|
61
514
|
return [...getFormControls(form)].filter((element) => isControl(element) && !element.matches(":disabled") && !("readOnly" in element && element.readOnly && (element instanceof HTMLTextAreaElement || READONLY_INPUT_TYPES.has(element.type))));
|
|
@@ -103,41 +556,42 @@ function validNumberAttribute(input, name) {
|
|
|
103
556
|
const value = Number(raw);
|
|
104
557
|
return Number.isFinite(value) ? value : void 0;
|
|
105
558
|
}
|
|
559
|
+
function validStep(input, fallback) {
|
|
560
|
+
const step = Number(input.getAttribute("step"));
|
|
561
|
+
return Number.isFinite(step) && step > 0 ? step : fallback;
|
|
562
|
+
}
|
|
106
563
|
function isStepBaseMultiple(stepBase, step) {
|
|
107
564
|
const quotient = stepBase / step;
|
|
108
565
|
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * 16;
|
|
109
566
|
}
|
|
110
|
-
function
|
|
567
|
+
function withPattern(schema, input) {
|
|
111
568
|
const pattern = input.getAttribute("pattern");
|
|
112
|
-
if (pattern === null) return
|
|
569
|
+
if (pattern === null) return schema;
|
|
113
570
|
try {
|
|
114
571
|
new RegExp(pattern, "v");
|
|
115
|
-
return pattern;
|
|
116
572
|
} catch {
|
|
117
|
-
return;
|
|
573
|
+
return schema;
|
|
118
574
|
}
|
|
575
|
+
return {
|
|
576
|
+
...schema,
|
|
577
|
+
pattern
|
|
578
|
+
};
|
|
119
579
|
}
|
|
120
|
-
function numberSchema(input
|
|
580
|
+
function numberSchema(input) {
|
|
121
581
|
const schema = { type: "number" };
|
|
122
582
|
const minimum = validNumberAttribute(input, "min");
|
|
123
583
|
const maximum = validNumberAttribute(input, "max");
|
|
124
584
|
if (minimum !== void 0) schema.minimum = minimum;
|
|
125
585
|
if (maximum !== void 0) schema.maximum = maximum;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const parsedStep = rawStep === null || rawStep === "" ? 1 : Number(rawStep);
|
|
129
|
-
const step = Number.isFinite(parsedStep) && parsedStep > 0 ? parsedStep : 1;
|
|
586
|
+
if (input.getAttribute("step") !== "any") {
|
|
587
|
+
const step = validStep(input, 1);
|
|
130
588
|
const rawValue = Number(input.getAttribute("value"));
|
|
131
589
|
if (isStepBaseMultiple(minimum ?? (Number.isFinite(rawValue) ? rawValue : 0), step)) schema.multipleOf = step;
|
|
132
590
|
}
|
|
133
|
-
const pattern = includePattern ? validPattern(input) : void 0;
|
|
134
|
-
if (pattern !== void 0) schema.pattern = pattern;
|
|
135
591
|
return schema;
|
|
136
592
|
}
|
|
137
593
|
function temporalFormat(input, datePrefix) {
|
|
138
|
-
const
|
|
139
|
-
const parsedStep = rawStep === null || rawStep === "" ? 60 : Number(rawStep);
|
|
140
|
-
const step = Number.isFinite(parsedStep) && parsedStep > 0 ? parsedStep : 60;
|
|
594
|
+
const step = validStep(input, 60);
|
|
141
595
|
if (step < 1) return `${datePrefix}(:[0-5][0-9](\\.[0-9]{1,3})?)?$`;
|
|
142
596
|
if (step < 60) return `${datePrefix}(:[0-5][0-9])?$`;
|
|
143
597
|
return `${datePrefix}$`;
|
|
@@ -156,38 +610,79 @@ function groupChoiceSchemas(controls) {
|
|
|
156
610
|
return {
|
|
157
611
|
anyOf: controls.map((control) => {
|
|
158
612
|
const title = labelText(control);
|
|
159
|
-
|
|
613
|
+
const schema = {
|
|
160
614
|
type: "string",
|
|
161
|
-
const: control.value
|
|
162
|
-
...title ? { title } : {}
|
|
615
|
+
const: control.value
|
|
163
616
|
};
|
|
617
|
+
if (title) schema.title = title;
|
|
618
|
+
return schema;
|
|
164
619
|
}),
|
|
165
620
|
enum: controls.map((control) => control.value)
|
|
166
621
|
};
|
|
167
622
|
}
|
|
623
|
+
function isInput(control, type) {
|
|
624
|
+
return control instanceof HTMLInputElement && control.type === type;
|
|
625
|
+
}
|
|
626
|
+
function inputSchema(input) {
|
|
627
|
+
if (TEXT_INPUT_TYPES.has(input.type)) return withPattern({ type: "string" }, input);
|
|
628
|
+
switch (input.type) {
|
|
629
|
+
case "hidden": return input.getAttribute("toolparamdescription") ? { type: "string" } : void 0;
|
|
630
|
+
case "number": return withPattern(numberSchema(input), input);
|
|
631
|
+
case "range": {
|
|
632
|
+
const schema = numberSchema(input);
|
|
633
|
+
schema.minimum ??= 0;
|
|
634
|
+
schema.maximum ??= 100;
|
|
635
|
+
return schema;
|
|
636
|
+
}
|
|
637
|
+
case "checkbox": return { type: "boolean" };
|
|
638
|
+
case "date": return {
|
|
639
|
+
type: "string",
|
|
640
|
+
format: "date"
|
|
641
|
+
};
|
|
642
|
+
case "month": return {
|
|
643
|
+
type: "string",
|
|
644
|
+
format: "^[0-9]{4}-(0[1-9]|1[0-2])$"
|
|
645
|
+
};
|
|
646
|
+
case "week": return {
|
|
647
|
+
type: "string",
|
|
648
|
+
format: "^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$"
|
|
649
|
+
};
|
|
650
|
+
case "time": return {
|
|
651
|
+
type: "string",
|
|
652
|
+
format: temporalFormat(input, "^([01][0-9]|2[0-3]):[0-5][0-9]")
|
|
653
|
+
};
|
|
654
|
+
case "datetime-local": return {
|
|
655
|
+
type: "string",
|
|
656
|
+
format: temporalFormat(input, "^[0-9]{4}-(0[1-9]|1[0-2])-[0-9]{2}T([01][0-9]|2[0-3]):[0-5][0-9]")
|
|
657
|
+
};
|
|
658
|
+
case "color": return {
|
|
659
|
+
type: "string",
|
|
660
|
+
format: "^#[0-9a-zA-Z]{6}$"
|
|
661
|
+
};
|
|
662
|
+
default: return;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
168
665
|
function parameterSchema(form, controls) {
|
|
169
|
-
const first = controls
|
|
666
|
+
const [first] = controls;
|
|
170
667
|
if (!first) return void 0;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
668
|
+
let schema;
|
|
669
|
+
if (controls.every((control) => isInput(control, "radio"))) schema = {
|
|
670
|
+
type: "string",
|
|
671
|
+
...groupChoiceSchemas(controls)
|
|
672
|
+
};
|
|
673
|
+
else if (controls.length > 1) {
|
|
674
|
+
if (controls.every((control) => isInput(control, "checkbox"))) schema = {
|
|
174
675
|
type: "array",
|
|
175
676
|
items: {
|
|
176
677
|
type: "string",
|
|
177
678
|
...groupChoiceSchemas(controls)
|
|
178
679
|
},
|
|
179
680
|
uniqueItems: true
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
...groupChoiceSchemas(controls)
|
|
184
|
-
}, form, controls);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (first instanceof HTMLTextAreaElement) return withDescription({ type: "string" }, form, controls);
|
|
188
|
-
if (first instanceof HTMLSelectElement) {
|
|
681
|
+
};
|
|
682
|
+
} else if (first instanceof HTMLTextAreaElement) schema = { type: "string" };
|
|
683
|
+
else if (first instanceof HTMLSelectElement) {
|
|
189
684
|
const choices = optionSchemas([...first.options]);
|
|
190
|
-
|
|
685
|
+
schema = first.multiple ? {
|
|
191
686
|
type: "array",
|
|
192
687
|
items: {
|
|
193
688
|
type: "string",
|
|
@@ -197,51 +692,10 @@ function parameterSchema(form, controls) {
|
|
|
197
692
|
} : {
|
|
198
693
|
type: "string",
|
|
199
694
|
...choices
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const pattern = validPattern(first);
|
|
205
|
-
if (pattern !== void 0) schema.pattern = pattern;
|
|
206
|
-
return withDescription(schema, form, controls);
|
|
207
|
-
}
|
|
208
|
-
if (first.type === "hidden") return first.getAttribute("toolparamdescription") ? withDescription({ type: "string" }, form, controls) : void 0;
|
|
209
|
-
if (first.type === "number") return withDescription(numberSchema(first), form, controls);
|
|
210
|
-
if (first.type === "range") {
|
|
211
|
-
const schema = numberSchema(first, false);
|
|
212
|
-
schema.minimum ??= 0;
|
|
213
|
-
schema.maximum ??= 100;
|
|
214
|
-
return withDescription(schema, form, controls);
|
|
215
|
-
}
|
|
216
|
-
if (first.type === "checkbox") return withDescription({ type: "boolean" }, form, controls);
|
|
217
|
-
if (first.type === "radio") return withDescription({
|
|
218
|
-
type: "string",
|
|
219
|
-
...groupChoiceSchemas([first])
|
|
220
|
-
}, form, controls);
|
|
221
|
-
if (first.type === "date") return withDescription({
|
|
222
|
-
type: "string",
|
|
223
|
-
format: "date"
|
|
224
|
-
}, form, controls, "Dates MUST be provided in 'YYYY-MM-DD' format.");
|
|
225
|
-
if (first.type === "month") return withDescription({
|
|
226
|
-
type: "string",
|
|
227
|
-
format: "^[0-9]{4}-(0[1-9]|1[0-2])$"
|
|
228
|
-
}, form, controls);
|
|
229
|
-
if (first.type === "week") return withDescription({
|
|
230
|
-
type: "string",
|
|
231
|
-
format: "^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$"
|
|
232
|
-
}, form, controls);
|
|
233
|
-
if (first.type === "time") return withDescription({
|
|
234
|
-
type: "string",
|
|
235
|
-
format: temporalFormat(first, "^([01][0-9]|2[0-3]):[0-5][0-9]")
|
|
236
|
-
}, form, controls);
|
|
237
|
-
if (first.type === "datetime-local") return withDescription({
|
|
238
|
-
type: "string",
|
|
239
|
-
format: temporalFormat(first, "^[0-9]{4}-(0[1-9]|1[0-2])-[0-9]{2}T([01][0-9]|2[0-3]):[0-5][0-9]")
|
|
240
|
-
}, form, controls);
|
|
241
|
-
if (first.type === "color") return withDescription({
|
|
242
|
-
type: "string",
|
|
243
|
-
format: "^#[0-9a-zA-Z]{6}$"
|
|
244
|
-
}, form, controls);
|
|
695
|
+
};
|
|
696
|
+
} else schema = inputSchema(first);
|
|
697
|
+
const dateHint = first.type === "date" ? "Dates MUST be provided in 'YYYY-MM-DD' format." : void 0;
|
|
698
|
+
return schema && withDescription(schema, form, controls, dateHint);
|
|
245
699
|
}
|
|
246
700
|
function synthesizeSchema(form) {
|
|
247
701
|
const properties = {};
|
|
@@ -300,31 +754,19 @@ function inputAcceptsValue(input, value) {
|
|
|
300
754
|
probe.value = value;
|
|
301
755
|
return probe.value !== "";
|
|
302
756
|
}
|
|
303
|
-
function
|
|
304
|
-
const first = controls
|
|
757
|
+
function isFormParameterValue(form, controls, value) {
|
|
758
|
+
const [first] = controls;
|
|
305
759
|
if (!first || !parameterSchema(form, controls)) return false;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (controls.every((control) => control.type === "radio")) {
|
|
310
|
-
const string = toFormString(value);
|
|
311
|
-
return string !== void 0 && controls.some((control) => control.value === string);
|
|
312
|
-
}
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
760
|
+
const string = toFormString(value);
|
|
761
|
+
if (controls.every((control) => isInput(control, "radio"))) return controls.some((control) => control.value === string);
|
|
762
|
+
if (controls.length > 1) return hasUniqueAllowedValues(value, new Set(controls.map((control) => control.value)));
|
|
315
763
|
if (first instanceof HTMLSelectElement) {
|
|
316
764
|
const allowed = new Set([...first.options].map((option) => option.value));
|
|
317
765
|
if (first.multiple) return hasUniqueAllowedValues(value, allowed);
|
|
318
|
-
const string = toFormString(value);
|
|
319
766
|
return string !== void 0 && allowed.has(string);
|
|
320
767
|
}
|
|
321
|
-
if (first instanceof HTMLTextAreaElement) return
|
|
768
|
+
if (first instanceof HTMLTextAreaElement) return string !== void 0;
|
|
322
769
|
if (first.type === "checkbox") return toFormBoolean(value) !== void 0;
|
|
323
|
-
if (first.type === "radio") {
|
|
324
|
-
const string = toFormString(value);
|
|
325
|
-
return string !== void 0 && first.value === string;
|
|
326
|
-
}
|
|
327
|
-
const string = toFormString(value);
|
|
328
770
|
return string !== void 0 && inputAcceptsValue(first, string);
|
|
329
771
|
}
|
|
330
772
|
function dispatchInputAndChange(control) {
|
|
@@ -334,111 +776,78 @@ function dispatchInputAndChange(control) {
|
|
|
334
776
|
}));
|
|
335
777
|
control.dispatchEvent(new Event("change", { bubbles: true }));
|
|
336
778
|
}
|
|
337
|
-
function
|
|
779
|
+
function setValue(control, value) {
|
|
780
|
+
if (control.value === value) return;
|
|
338
781
|
const prototype = control instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
|
|
339
782
|
Object.getOwnPropertyDescriptor(prototype, "value")?.set?.call(control, value);
|
|
783
|
+
dispatchInputAndChange(control);
|
|
340
784
|
}
|
|
341
|
-
function
|
|
785
|
+
function setChecked(control, checked) {
|
|
786
|
+
if (control.checked === checked) return;
|
|
342
787
|
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set?.call(control, checked);
|
|
788
|
+
dispatchInputAndChange(control);
|
|
343
789
|
}
|
|
344
790
|
function fillParameter(controls, value) {
|
|
345
|
-
const first = controls
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
const selected = toFormString(value);
|
|
359
|
-
const control = controls.find((candidate) => candidate.value === selected);
|
|
360
|
-
if (control && !control.checked) {
|
|
361
|
-
setNativeChecked(control, true);
|
|
362
|
-
dispatchInputAndChange(control);
|
|
363
|
-
}
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
if (first instanceof HTMLSelectElement) {
|
|
367
|
-
if (first.multiple && Array.isArray(value)) {
|
|
368
|
-
const selected = new Set(value.map(toFormString));
|
|
791
|
+
const [first] = controls;
|
|
792
|
+
const string = toFormString(value);
|
|
793
|
+
const values = new Set(Array.isArray(value) ? value.map(toFormString) : []);
|
|
794
|
+
if (controls.every((control) => isInput(control, "radio"))) {
|
|
795
|
+
const control = controls.find((candidate) => candidate.value === string);
|
|
796
|
+
if (control) setChecked(control, true);
|
|
797
|
+
} else if (controls.length > 1) {
|
|
798
|
+
for (const control of controls) if (control instanceof HTMLInputElement) setChecked(control, values.has(control.value));
|
|
799
|
+
} else if (first instanceof HTMLSelectElement) {
|
|
800
|
+
if (first.multiple) {
|
|
369
801
|
let changed = false;
|
|
370
802
|
for (const option of first.options) {
|
|
371
|
-
const next =
|
|
803
|
+
const next = values.has(option.value);
|
|
372
804
|
if (option.selected === next) continue;
|
|
373
805
|
option.selected = next;
|
|
374
806
|
changed = true;
|
|
375
807
|
}
|
|
376
808
|
if (changed) dispatchInputAndChange(first);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const next = toFormString(value);
|
|
380
|
-
if (next !== void 0 && first.value !== next) {
|
|
381
|
-
first.value = next;
|
|
809
|
+
} else if (string !== void 0 && first.value !== string) {
|
|
810
|
+
first.value = string;
|
|
382
811
|
dispatchInputAndChange(first);
|
|
383
812
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (next !== void 0 && first.checked !== next) {
|
|
389
|
-
setNativeChecked(first, next);
|
|
390
|
-
dispatchInputAndChange(first);
|
|
391
|
-
}
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (first instanceof HTMLInputElement && first.type === "radio") {
|
|
395
|
-
if (toFormString(value) === first.value && !first.checked) {
|
|
396
|
-
setNativeChecked(first, true);
|
|
397
|
-
dispatchInputAndChange(first);
|
|
398
|
-
}
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
const next = toFormString(value);
|
|
402
|
-
if (next !== void 0 && first.value !== next) {
|
|
403
|
-
setNativeValue(first, next);
|
|
404
|
-
dispatchInputAndChange(first);
|
|
405
|
-
}
|
|
813
|
+
} else if (first && isInput(first, "checkbox")) {
|
|
814
|
+
const checked = toFormBoolean(value);
|
|
815
|
+
if (checked !== void 0) setChecked(first, checked);
|
|
816
|
+
} else if (first && string !== void 0) setValue(first, string);
|
|
406
817
|
}
|
|
407
818
|
function fillForm(form, input) {
|
|
408
|
-
if (Array.isArray(input)) throw
|
|
819
|
+
if (Array.isArray(input)) throw executionError();
|
|
409
820
|
const groups = controlGroups(form);
|
|
821
|
+
const parameters = [];
|
|
410
822
|
for (const [name, value] of Object.entries(input)) {
|
|
411
823
|
const controls = groups.get(name);
|
|
412
|
-
if (!controls || !
|
|
824
|
+
if (!controls || !isFormParameterValue(form, controls, value)) throw executionError();
|
|
825
|
+
parameters.push({
|
|
826
|
+
controls,
|
|
827
|
+
value
|
|
828
|
+
});
|
|
413
829
|
}
|
|
414
|
-
for (const
|
|
830
|
+
for (const { controls, value } of parameters) fillParameter(controls, value);
|
|
415
831
|
}
|
|
416
832
|
function findSubmitter(form) {
|
|
417
833
|
return [...getFormControls(form)].find((element) => !element.matches(":disabled") && (element instanceof HTMLButtonElement && element.type === "submit" || element instanceof HTMLInputElement && ["image", "submit"].includes(element.type)));
|
|
418
834
|
}
|
|
419
|
-
function
|
|
420
|
-
const
|
|
421
|
-
return new DOMException(`Form validation failed: ${failures}`, "UnknownError");
|
|
422
|
-
}
|
|
423
|
-
function toolActivatedEvent(toolName) {
|
|
424
|
-
const event = new Event("toolactivated");
|
|
835
|
+
function lifecycleEvent(type, toolName) {
|
|
836
|
+
const event = new Event(type);
|
|
425
837
|
Object.defineProperty(event, "toolName", {
|
|
426
838
|
enumerable: true,
|
|
427
839
|
value: toolName
|
|
428
840
|
});
|
|
429
841
|
return event;
|
|
430
842
|
}
|
|
431
|
-
function waitForSubmission(registration,
|
|
843
|
+
function waitForSubmission(context, registration, definition, submitter, signal) {
|
|
432
844
|
const { form } = registration;
|
|
433
|
-
registration.cancelPending?.(
|
|
845
|
+
registration.cancelPending?.(executionError());
|
|
434
846
|
return new Promise((resolve, reject) => {
|
|
435
847
|
let settled = false;
|
|
436
848
|
const cleanup = () => {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
onInvalid,
|
|
440
|
-
true
|
|
441
|
-
]);
|
|
849
|
+
EventTarget.prototype.removeEventListener.call(form, "invalid", onInvalid, true);
|
|
850
|
+
signal.removeEventListener("abort", onAbort);
|
|
442
851
|
activeSubmissions.delete(form);
|
|
443
852
|
if (registration.cancelPending === cancel) delete registration.cancelPending;
|
|
444
853
|
};
|
|
@@ -450,25 +859,32 @@ function waitForSubmission(registration, toolName, autosubmit, submitter) {
|
|
|
450
859
|
};
|
|
451
860
|
const cancel = (reason) => finish(() => reject(reason));
|
|
452
861
|
const settleResponse = (response) => {
|
|
453
|
-
|
|
862
|
+
if (registration.cancelPending === cancel) delete registration.cancelPending;
|
|
863
|
+
response.then((value) => finish(() => resolve(value ?? null)), (cause) => finish(() => reject(cause)));
|
|
454
864
|
};
|
|
455
865
|
const onInvalid = (event) => {
|
|
456
866
|
if (!event.isTrusted) return;
|
|
457
867
|
queueMicrotask(() => {
|
|
458
|
-
if (!checkFormValidity(form)) cancel(
|
|
868
|
+
if (!checkFormValidity(form)) cancel(executionError());
|
|
869
|
+
});
|
|
870
|
+
};
|
|
871
|
+
const onAbort = () => {
|
|
872
|
+
finish(() => {
|
|
873
|
+
context.dispatchEvent(lifecycleEvent("toolcancel", definition.name));
|
|
874
|
+
reject(signal.reason);
|
|
459
875
|
});
|
|
460
876
|
};
|
|
461
877
|
activeSubmissions.set(form, {
|
|
462
878
|
complete(event) {
|
|
463
|
-
|
|
879
|
+
setTimeout(() => {
|
|
464
880
|
const response = agentResponses.get(event);
|
|
465
881
|
if (response) settleResponse(response);
|
|
466
|
-
else if (event.defaultPrevented) cancel(
|
|
467
|
-
else finish(() => resolve(
|
|
882
|
+
else if (event.defaultPrevented) cancel(executionError());
|
|
883
|
+
else finish(() => resolve(null));
|
|
468
884
|
});
|
|
469
885
|
},
|
|
470
886
|
direct() {
|
|
471
|
-
finish(() => resolve(
|
|
887
|
+
finish(() => resolve(null));
|
|
472
888
|
},
|
|
473
889
|
respond(event) {
|
|
474
890
|
queueMicrotask(() => {
|
|
@@ -478,27 +894,40 @@ function waitForSubmission(registration, toolName, autosubmit, submitter) {
|
|
|
478
894
|
}
|
|
479
895
|
});
|
|
480
896
|
registration.cancelPending = cancel;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
true
|
|
485
|
-
]);
|
|
486
|
-
if (!autosubmit) {
|
|
897
|
+
EventTarget.prototype.addEventListener.call(form, "invalid", onInvalid, true);
|
|
898
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
899
|
+
if (!definition.autosubmit) {
|
|
487
900
|
submitter?.focus();
|
|
488
|
-
|
|
901
|
+
context.dispatchEvent(lifecycleEvent("toolactivated", definition.name));
|
|
489
902
|
return;
|
|
490
903
|
}
|
|
491
904
|
try {
|
|
492
905
|
requestFormSubmit(form, submitter);
|
|
493
|
-
|
|
906
|
+
context.dispatchEvent(lifecycleEvent("toolactivated", definition.name));
|
|
494
907
|
} catch (error) {
|
|
495
908
|
cancel(error);
|
|
496
909
|
}
|
|
497
910
|
});
|
|
498
911
|
}
|
|
499
|
-
/**
|
|
500
|
-
function
|
|
501
|
-
|
|
912
|
+
/** Adds declarative tools until the vendored upstream implements them. */
|
|
913
|
+
function installWebMCPDeclarativeExtensions(context) {
|
|
914
|
+
const prototype = SubmitEvent.prototype;
|
|
915
|
+
if ("agentInvoked" in prototype && "respondWith" in prototype) return;
|
|
916
|
+
if (!("agentInvoked" in prototype)) Object.defineProperty(prototype, "agentInvoked", {
|
|
917
|
+
configurable: true,
|
|
918
|
+
enumerable: true,
|
|
919
|
+
get() {
|
|
920
|
+
return isAgentInvokedSubmitEvent(this);
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
if (!("respondWith" in prototype)) Object.defineProperty(prototype, "respondWith", {
|
|
924
|
+
configurable: true,
|
|
925
|
+
enumerable: true,
|
|
926
|
+
writable: true,
|
|
927
|
+
value(agentResponse) {
|
|
928
|
+
respondWithAgentSubmitEvent(this, agentResponse);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
502
931
|
const registrations = /* @__PURE__ */ new Map();
|
|
503
932
|
const blockedDefinitions = /* @__PURE__ */ new Map();
|
|
504
933
|
const observers = /* @__PURE__ */ new Map();
|
|
@@ -512,9 +941,9 @@ function installDeclarativeForms(document, context) {
|
|
|
512
941
|
const onReset = (event) => {
|
|
513
942
|
if (!event.isTrusted || !(event.target instanceof HTMLFormElement)) return;
|
|
514
943
|
const form = event.target;
|
|
515
|
-
|
|
944
|
+
setTimeout(() => {
|
|
516
945
|
if (event.defaultPrevented) return;
|
|
517
|
-
registrations.get(form)?.cancelPending?.(
|
|
946
|
+
registrations.get(form)?.cancelPending?.(executionError());
|
|
518
947
|
});
|
|
519
948
|
};
|
|
520
949
|
function stopObservingRoot(root) {
|
|
@@ -538,33 +967,30 @@ function installDeclarativeForms(document, context) {
|
|
|
538
967
|
}
|
|
539
968
|
function sync() {
|
|
540
969
|
observeRoot(document);
|
|
541
|
-
for (const root of observers.keys()) if (root instanceof ShadowRoot && !
|
|
970
|
+
for (const root of observers.keys()) if (root instanceof ShadowRoot && !root.host.isConnected) stopObservingRoot(root);
|
|
542
971
|
const candidates = /* @__PURE__ */ new Set();
|
|
543
972
|
const selected = /* @__PURE__ */ new Map();
|
|
544
973
|
const selectedByName = /* @__PURE__ */ new Map();
|
|
545
974
|
for (const root of observers.keys()) for (const element of root.querySelectorAll("*")) {
|
|
546
975
|
const shadowRoot = getOpenShadowRoot(element);
|
|
547
976
|
if (shadowRoot) observeRoot(shadowRoot);
|
|
548
|
-
if (!(element instanceof HTMLFormElement) || !formHasAttribute(element, "toolname") || !formHasAttribute(element, "tooldescription") || !
|
|
977
|
+
if (!(element instanceof HTMLFormElement) || !formHasAttribute(element, "toolname") || !formHasAttribute(element, "tooldescription") || !isConnected(element)) continue;
|
|
549
978
|
const form = element;
|
|
550
979
|
candidates.add(form);
|
|
551
980
|
const definition = toolDefinition(form);
|
|
552
981
|
const fingerprint = JSON.stringify(definition);
|
|
553
982
|
const blockedFingerprint = blockedDefinitions.get(form);
|
|
554
983
|
if (blockedFingerprint === fingerprint) continue;
|
|
555
|
-
const retryingChangedDefinition = blockedFingerprint !== void 0;
|
|
556
984
|
blockedDefinitions.delete(form);
|
|
557
985
|
const existingForm = selectedByName.get(definition.name);
|
|
558
|
-
if (existingForm && !retryingChangedDefinition) {
|
|
559
|
-
blockedDefinitions.set(form, fingerprint);
|
|
560
|
-
continue;
|
|
561
|
-
}
|
|
562
986
|
if (existingForm) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
selected.delete(existingForm);
|
|
987
|
+
if (blockedFingerprint === void 0) {
|
|
988
|
+
blockedDefinitions.set(form, fingerprint);
|
|
989
|
+
continue;
|
|
567
990
|
}
|
|
991
|
+
const existingSelection = selected.get(existingForm);
|
|
992
|
+
if (existingSelection) blockedDefinitions.set(existingForm, existingSelection.fingerprint);
|
|
993
|
+
selected.delete(existingForm);
|
|
568
994
|
}
|
|
569
995
|
selectedByName.set(definition.name, form);
|
|
570
996
|
selected.set(form, {
|
|
@@ -574,7 +1000,7 @@ function installDeclarativeForms(document, context) {
|
|
|
574
1000
|
}
|
|
575
1001
|
for (const form of blockedDefinitions.keys()) if (!candidates.has(form)) blockedDefinitions.delete(form);
|
|
576
1002
|
for (const [form, registration] of registrations) if (selected.get(form)?.fingerprint !== registration.fingerprint) {
|
|
577
|
-
registration.cancelPending?.(
|
|
1003
|
+
registration.cancelPending?.(executionError());
|
|
578
1004
|
registration.controller.abort();
|
|
579
1005
|
registrations.delete(form);
|
|
580
1006
|
}
|
|
@@ -592,427 +1018,455 @@ function installDeclarativeForms(document, context) {
|
|
|
592
1018
|
title: definition.title,
|
|
593
1019
|
description: definition.description,
|
|
594
1020
|
inputSchema: definition.inputSchema,
|
|
595
|
-
execute(input) {
|
|
1021
|
+
execute(input, options) {
|
|
596
1022
|
const submitter = findSubmitter(form);
|
|
597
|
-
if (!definition.autosubmit && !submitter) throw
|
|
1023
|
+
if (!definition.autosubmit && !submitter) throw executionError();
|
|
598
1024
|
fillForm(form, input);
|
|
599
|
-
return waitForSubmission(registration, definition
|
|
1025
|
+
return waitForSubmission(context, registration, definition, submitter, options.signal);
|
|
600
1026
|
}
|
|
601
|
-
}, { signal: controller.signal }).catch((
|
|
1027
|
+
}, { signal: controller.signal }).catch((cause) => {
|
|
602
1028
|
controller.abort();
|
|
603
|
-
if (
|
|
604
|
-
console.error(`[webmcp] declarative form tool "${definition.name}" was not registered:`,
|
|
1029
|
+
if (cause instanceof DOMException && cause.name === "AbortError") return;
|
|
1030
|
+
console.error(`[webmcp] declarative form tool "${definition.name}" was not registered:`, cause);
|
|
605
1031
|
});
|
|
606
1032
|
}
|
|
607
1033
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if (elementPrototype && attachShadowDescriptor?.configurable) {
|
|
612
|
-
const nativeAttachShadow = elementPrototype.attachShadow;
|
|
1034
|
+
const attachShadowDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, "attachShadow");
|
|
1035
|
+
if (attachShadowDescriptor?.configurable) {
|
|
1036
|
+
const nativeAttachShadow = Element.prototype.attachShadow;
|
|
613
1037
|
const attachShadow = function(init) {
|
|
614
1038
|
const root = nativeAttachShadow.call(this, init);
|
|
615
|
-
if (
|
|
1039
|
+
if (root.mode === "open" && Object.getOwnPropertyDescriptor(Node.prototype, "ownerDocument")?.get?.call(this) === document && isConnected(this)) observeRoot(root);
|
|
616
1040
|
return root;
|
|
617
1041
|
};
|
|
618
|
-
Object.defineProperty(
|
|
1042
|
+
Object.defineProperty(Element.prototype, "attachShadow", {
|
|
619
1043
|
...attachShadowDescriptor,
|
|
620
1044
|
value: attachShadow
|
|
621
1045
|
});
|
|
622
|
-
restoreAttachShadow = () => {
|
|
623
|
-
if (elementPrototype.attachShadow === attachShadow) Object.defineProperty(elementPrototype, "attachShadow", attachShadowDescriptor);
|
|
624
|
-
};
|
|
625
1046
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (formPrototype && submitDescriptor?.configurable) {
|
|
630
|
-
const nativeSubmit = formPrototype.submit;
|
|
1047
|
+
const submitDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, "submit");
|
|
1048
|
+
if (submitDescriptor?.configurable) {
|
|
1049
|
+
const nativeSubmit = HTMLFormElement.prototype.submit;
|
|
631
1050
|
const submit = function() {
|
|
632
1051
|
nativeSubmit.call(this);
|
|
633
|
-
|
|
1052
|
+
activeSubmissions.get(this)?.direct();
|
|
634
1053
|
};
|
|
635
|
-
Object.defineProperty(
|
|
1054
|
+
Object.defineProperty(HTMLFormElement.prototype, "submit", {
|
|
636
1055
|
...submitDescriptor,
|
|
637
1056
|
value: submit
|
|
638
1057
|
});
|
|
639
|
-
restoreFormSubmit = () => {
|
|
640
|
-
if (formPrototype.submit === submit) Object.defineProperty(formPrototype, "submit", submitDescriptor);
|
|
641
|
-
};
|
|
642
1058
|
}
|
|
643
1059
|
sync();
|
|
644
|
-
return () => {
|
|
645
|
-
active = false;
|
|
646
|
-
restoreFormSubmit();
|
|
647
|
-
restoreAttachShadow();
|
|
648
|
-
for (const root of observers.keys()) stopObservingRoot(root);
|
|
649
|
-
blockedDefinitions.clear();
|
|
650
|
-
for (const registration of registrations.values()) {
|
|
651
|
-
registration.cancelPending?.(new DOMException("Tool execution cancelled", "UnknownError"));
|
|
652
|
-
registration.controller.abort();
|
|
653
|
-
}
|
|
654
|
-
registrations.clear();
|
|
655
|
-
};
|
|
656
1060
|
}
|
|
657
1061
|
|
|
658
1062
|
//#endregion
|
|
659
|
-
//#region src/index.ts
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
1063
|
+
//#region src/upstream/index.ts
|
|
1064
|
+
/*!
|
|
1065
|
+
* Copyright (c) 2026 WebMCP polyfill contributors
|
|
1066
|
+
* SPDX-License-Identifier: MIT
|
|
1067
|
+
*/
|
|
1068
|
+
const NativeDOMException = globalThis.DOMException;
|
|
1069
|
+
const getWindow = Object.getOwnPropertyDescriptor(globalThis, "window")?.get;
|
|
1070
|
+
const contexts = /* @__PURE__ */ new WeakMap();
|
|
1071
|
+
/**
|
|
1072
|
+
* Install WebMCP in the current window.
|
|
1073
|
+
*
|
|
1074
|
+
* Does nothing outside a secure browser context or when `document.modelContext`
|
|
1075
|
+
* already exists, including partial native implementations. Call before registering
|
|
1076
|
+
* tools in each frame; repeated calls preserve existing contexts and registrations.
|
|
1077
|
+
*
|
|
1078
|
+
* Installing joins cross-frame discovery: the window listens for the polyfill's messages and
|
|
1079
|
+
* announces itself to the other frames of its tree.
|
|
1080
|
+
*
|
|
1081
|
+
* @throws {TypeError} If the window or Document prototype prevents installation.
|
|
1082
|
+
* @example
|
|
1083
|
+
* import { installWebMCP } from "webmcp-polyfill";
|
|
1084
|
+
* installWebMCP();
|
|
1085
|
+
*
|
|
1086
|
+
* @see https://webmachinelearning.github.io/webmcp/#document-extension
|
|
1087
|
+
* @see https://github.com/webmachinelearning/webmcp-polyfill/blob/main/TESTING.md
|
|
1088
|
+
*/
|
|
1089
|
+
function installWebMCP$1() {
|
|
1090
|
+
if (typeof document === "undefined" || !globalThis.isSecureContext) return;
|
|
1091
|
+
if ("modelContext" in document) return;
|
|
1092
|
+
const documentPrototype = Document.prototype;
|
|
1093
|
+
const getDefaultView = Object.getOwnPropertyDescriptor(documentPrototype, "defaultView").get;
|
|
1094
|
+
const constructorDescriptor = Object.getOwnPropertyDescriptor(globalThis, "ModelContext");
|
|
1095
|
+
if (!Object.isExtensible(documentPrototype) || constructorDescriptor && !constructorDescriptor.configurable || !constructorDescriptor && !Object.isExtensible(globalThis)) throw new TypeError("Cannot install WebMCP on this realm");
|
|
1096
|
+
Object.defineProperty(globalThis, "ModelContext", {
|
|
1097
|
+
value: modelContextConstructor,
|
|
1098
|
+
configurable: true,
|
|
1099
|
+
writable: true
|
|
682
1100
|
});
|
|
1101
|
+
const { getModelContext } = { getModelContext() {
|
|
1102
|
+
getDefaultView.call(this);
|
|
1103
|
+
let context = contexts.get(this);
|
|
1104
|
+
if (!context) {
|
|
1105
|
+
context = new ModelContextPolyfill(this);
|
|
1106
|
+
contexts.set(this, context);
|
|
1107
|
+
}
|
|
1108
|
+
return context;
|
|
1109
|
+
} };
|
|
1110
|
+
Object.defineProperty(getModelContext, "name", { value: "get modelContext" });
|
|
1111
|
+
Object.defineProperty(documentPrototype, "modelContext", {
|
|
1112
|
+
configurable: true,
|
|
1113
|
+
enumerable: true,
|
|
1114
|
+
get: getModelContext
|
|
1115
|
+
});
|
|
1116
|
+
document.modelContext;
|
|
683
1117
|
}
|
|
684
|
-
var
|
|
1118
|
+
var ModelContextPolyfill = class extends EventTarget {
|
|
1119
|
+
#document;
|
|
685
1120
|
#tools = /* @__PURE__ */ new Map();
|
|
686
|
-
#
|
|
687
|
-
#
|
|
688
|
-
#
|
|
689
|
-
this.#
|
|
1121
|
+
#frames;
|
|
1122
|
+
#toolchangeHandler = null;
|
|
1123
|
+
#toolchangeListener = (event) => {
|
|
1124
|
+
const handler = this.#toolchangeHandler;
|
|
1125
|
+
if (typeof handler !== "function") return;
|
|
1126
|
+
if (Reflect.apply(handler, this, [event]) === false) Event.prototype.preventDefault.call(event);
|
|
690
1127
|
};
|
|
691
|
-
|
|
692
|
-
#ownerDocument;
|
|
693
|
-
constructor(ownerDocument) {
|
|
1128
|
+
constructor(owner) {
|
|
694
1129
|
super();
|
|
695
|
-
this.#
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
1130
|
+
this.#document = owner;
|
|
1131
|
+
const view = activeWindow(owner);
|
|
1132
|
+
if (!view) return;
|
|
1133
|
+
this.#frames = new FrameBridge(view, {
|
|
1134
|
+
getTools: (callerOrigin) => this.#exposedTools(callerOrigin),
|
|
1135
|
+
execute: (callerOrigin, name, serializedInput, signal) => {
|
|
1136
|
+
const ownerOrigin = requireActiveWindow(owner).origin;
|
|
1137
|
+
return this.#executeLocal(name, serializedInput, ownerOrigin, callerOrigin, signal);
|
|
1138
|
+
},
|
|
1139
|
+
changed: () => this.#queueToolChange()
|
|
702
1140
|
});
|
|
703
1141
|
}
|
|
704
1142
|
get ontoolchange() {
|
|
705
|
-
return this.#
|
|
1143
|
+
return this.#toolchangeHandler;
|
|
706
1144
|
}
|
|
707
1145
|
set ontoolchange(handler) {
|
|
708
|
-
const
|
|
709
|
-
if (
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
if (this.#ontoolchangeHandler === null) super.addEventListener("toolchange", this.#ontoolchangeListener);
|
|
715
|
-
this.#ontoolchangeHandler = listener;
|
|
1146
|
+
const nextHandler = isObject(handler) ? handler : null;
|
|
1147
|
+
if (!this.#toolchangeHandler && nextHandler) this.addEventListener("toolchange", this.#toolchangeListener);
|
|
1148
|
+
if (this.#toolchangeHandler && !nextHandler) this.removeEventListener("toolchange", this.#toolchangeListener);
|
|
1149
|
+
this.#toolchangeHandler = nextHandler;
|
|
716
1150
|
}
|
|
717
1151
|
async registerTool(tool, options = {}) {
|
|
718
|
-
|
|
719
|
-
const
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1152
|
+
const ownerDocument = this.#document;
|
|
1153
|
+
const { name, title, description, annotations, inputSchema, execute } = readToolDefinition(tool);
|
|
1154
|
+
const settings = readDictionary(options);
|
|
1155
|
+
const exposedTo = readOriginSequence(settings.exposedTo);
|
|
1156
|
+
const registrationSignal = readAbortSignal(settings.signal);
|
|
1157
|
+
requireActiveWindow(ownerDocument);
|
|
1158
|
+
const requireUnusedName = () => {
|
|
1159
|
+
if (this.#tools.has(name)) throw new NativeDOMException(`A tool named ${name} is already registered`, "InvalidStateError");
|
|
1160
|
+
};
|
|
1161
|
+
requireUnusedName();
|
|
1162
|
+
if (!/^[A-Za-z0-9_.-]{1,128}$/u.test(name)) throw new NativeDOMException(`Tool names are 1 to 128 characters of ASCII alphanumerics, "_", "-" or ".": ${name}`, "InvalidStateError");
|
|
1163
|
+
if (!description) throw new NativeDOMException("A tool description cannot be empty", "InvalidStateError");
|
|
1164
|
+
const serializedSchema = inputSchema === void 0 ? void 0 : serializeJSON(inputSchema);
|
|
1165
|
+
registrationSignal?.throwIfAborted();
|
|
1166
|
+
const exposedOrigins = parseOrigins(exposedTo);
|
|
1167
|
+
const storedTool = {
|
|
1168
|
+
metadata: {
|
|
1169
|
+
name,
|
|
1170
|
+
title,
|
|
1171
|
+
description,
|
|
1172
|
+
annotations,
|
|
1173
|
+
serializedSchema
|
|
1174
|
+
},
|
|
1175
|
+
execute,
|
|
1176
|
+
exposedTo: exposedOrigins
|
|
1177
|
+
};
|
|
1178
|
+
const frames = await this.#requireFrames();
|
|
1179
|
+
registrationSignal?.throwIfAborted();
|
|
1180
|
+
requireUnusedName();
|
|
1181
|
+
return new Promise((resolve, reject) => {
|
|
1182
|
+
registrationSignal?.addEventListener("abort", () => {
|
|
1183
|
+
this.#tools.delete(name);
|
|
1184
|
+
frames.notify(exposedOrigins);
|
|
1185
|
+
reject(registrationSignal.reason);
|
|
1186
|
+
}, { once: true });
|
|
1187
|
+
this.#tools.set(name, storedTool);
|
|
1188
|
+
frames.notify(exposedOrigins).then(resolve, reject);
|
|
1189
|
+
});
|
|
736
1190
|
}
|
|
737
1191
|
async getTools(options = {}) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
1192
|
+
const ownerDocument = this.#document;
|
|
1193
|
+
const fromOrigins = readOriginSequence(readDictionary(options).fromOrigins);
|
|
1194
|
+
requireActiveWindow(ownerDocument);
|
|
1195
|
+
const frames = await this.#requireFrames();
|
|
1196
|
+
const requestedOrigins = parseOrigins(fromOrigins);
|
|
1197
|
+
const tools = await frames.getTools(requestedOrigins);
|
|
1198
|
+
tools.sort((left, right) => {
|
|
1199
|
+
if (left.name === right.name) return 0;
|
|
1200
|
+
return left.name < right.name ? -1 : 1;
|
|
1201
|
+
});
|
|
1202
|
+
return new Promise((resolve) => {
|
|
1203
|
+
queueTask(() => resolve(tools));
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
async executeTool(tool, inputObject = {}, options = {}) {
|
|
1207
|
+
const ownerDocument = this.#document;
|
|
1208
|
+
const target = readExecutionTarget(tool);
|
|
1209
|
+
const callerSignal = readAbortSignal(readDictionary(options).signal);
|
|
1210
|
+
requireActiveWindow(ownerDocument);
|
|
1211
|
+
const expectedOrigin = URL.parse(target.origin)?.origin;
|
|
1212
|
+
if (!expectedOrigin || expectedOrigin === "null") throw new NativeDOMException("Invalid or opaque origin", "NotSupportedError");
|
|
1213
|
+
if (!isObject(inputObject)) throw new TypeError("inputObject must be an object");
|
|
1214
|
+
const serializedInput = serializeJSON(inputObject);
|
|
1215
|
+
callerSignal?.throwIfAborted();
|
|
1216
|
+
const frames = await this.#requireFrames();
|
|
1217
|
+
callerSignal?.throwIfAborted();
|
|
1218
|
+
const callerWindow = requireActiveWindow(ownerDocument);
|
|
1219
|
+
if (target.window !== callerWindow) return frames.execute(target.window, expectedOrigin, target.name, serializedInput, callerSignal);
|
|
1220
|
+
return this.#executeLocal(target.name, serializedInput, expectedOrigin, callerWindow.origin, callerSignal);
|
|
1221
|
+
}
|
|
1222
|
+
#executeLocal(name, serializedInput, expectedOrigin, callerOrigin, callerSignal) {
|
|
1223
|
+
return new Promise((resolve, reject) => {
|
|
1224
|
+
callerSignal?.throwIfAborted();
|
|
1225
|
+
const callbackController = new AbortController();
|
|
1226
|
+
let callbackFinished = false;
|
|
1227
|
+
const onCallerAbort = () => {
|
|
1228
|
+
reject(callerSignal.reason);
|
|
1229
|
+
queueTask(() => {
|
|
1230
|
+
if (!callbackFinished) callbackController.abort();
|
|
1231
|
+
});
|
|
752
1232
|
};
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
try {
|
|
811
|
-
const registrationSignal = tool[REGISTRATION_SIGNAL_SYMBOL];
|
|
812
|
-
rawResult = await withAbortSignal(withAbortSignal(Promise.resolve(tool.execute(args)), registrationSignal, () => createUnknownError("Tool unregistered")), options?.signal);
|
|
813
|
-
} catch (error) {
|
|
814
|
-
if (options?.signal?.aborted && error === options.signal.reason) throw error;
|
|
815
|
-
if (tool[REGISTRATION_SIGNAL_SYMBOL]?.aborted) throw error;
|
|
816
|
-
throw createToolInvocationFailedError(error);
|
|
1233
|
+
const rejectExecution = () => {
|
|
1234
|
+
callbackFinished = true;
|
|
1235
|
+
queueTask(() => {
|
|
1236
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
1237
|
+
reject(executionError());
|
|
1238
|
+
});
|
|
1239
|
+
};
|
|
1240
|
+
const completeExecution = (value) => {
|
|
1241
|
+
callbackFinished = true;
|
|
1242
|
+
if (callerSignal?.aborted) return;
|
|
1243
|
+
try {
|
|
1244
|
+
const serializedResult = serializeJSON(value);
|
|
1245
|
+
queueTask(() => {
|
|
1246
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
1247
|
+
resolve(serializedResult);
|
|
1248
|
+
});
|
|
1249
|
+
} catch {
|
|
1250
|
+
rejectExecution();
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
const dispatchTool = () => {
|
|
1254
|
+
if (callerSignal?.aborted) return;
|
|
1255
|
+
try {
|
|
1256
|
+
const ownerWindow = requireActiveWindow(this.#document);
|
|
1257
|
+
const storedTool = this.#tools.get(name);
|
|
1258
|
+
if (!storedTool || expectedOrigin !== ownerWindow.origin || !isExposedTo(storedTool, ownerWindow.origin, callerOrigin)) {
|
|
1259
|
+
rejectExecution();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const input = JSON.parse(serializedInput);
|
|
1263
|
+
if (!isObject(input)) {
|
|
1264
|
+
rejectExecution();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const execute = storedTool.execute;
|
|
1268
|
+
const callbackResult = execute(input, { signal: callbackController.signal });
|
|
1269
|
+
Promise.resolve(callbackResult).then(completeExecution, rejectExecution);
|
|
1270
|
+
} catch {
|
|
1271
|
+
rejectExecution();
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
1275
|
+
queueTask(dispatchTool);
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
async #requireFrames() {
|
|
1279
|
+
const frames = this.#frames;
|
|
1280
|
+
if (!frames || !await frames.allowed()) throw new NativeDOMException("WebMCP is disabled by Permissions Policy", "NotAllowedError");
|
|
1281
|
+
requireActiveWindow(this.#document);
|
|
1282
|
+
return frames;
|
|
1283
|
+
}
|
|
1284
|
+
#exposedTools(callerOrigin) {
|
|
1285
|
+
const ownerOrigin = requireActiveWindow(this.#document).origin;
|
|
1286
|
+
const exposed = [];
|
|
1287
|
+
for (const storedTool of this.#tools.values()) {
|
|
1288
|
+
if (!isExposedTo(storedTool, ownerOrigin, callerOrigin)) continue;
|
|
1289
|
+
exposed.push(storedTool.metadata);
|
|
817
1290
|
}
|
|
818
|
-
return
|
|
1291
|
+
return exposed;
|
|
819
1292
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1293
|
+
#queueToolChange() {
|
|
1294
|
+
return new Promise((resolve) => {
|
|
1295
|
+
queueTask(() => {
|
|
1296
|
+
this.dispatchEvent(new Event("toolchange"));
|
|
1297
|
+
resolve();
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
824
1300
|
}
|
|
825
1301
|
};
|
|
1302
|
+
function isExposedTo(tool, ownerOrigin, callerOrigin) {
|
|
1303
|
+
return ownerOrigin === callerOrigin || tool.exposedTo.includes(callerOrigin);
|
|
1304
|
+
}
|
|
826
1305
|
const modelContextConstructor = function ModelContext() {
|
|
827
1306
|
throw new TypeError("Illegal constructor");
|
|
828
1307
|
};
|
|
829
1308
|
Object.defineProperty(modelContextConstructor, "name", { value: "ModelContext" });
|
|
830
1309
|
Object.defineProperty(modelContextConstructor, "prototype", {
|
|
831
|
-
value:
|
|
1310
|
+
value: ModelContextPolyfill.prototype,
|
|
832
1311
|
writable: false
|
|
833
1312
|
});
|
|
834
1313
|
Object.setPrototypeOf(modelContextConstructor, EventTarget);
|
|
835
|
-
Object.
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1314
|
+
Object.defineProperties(ModelContextPolyfill.prototype, {
|
|
1315
|
+
constructor: {
|
|
1316
|
+
value: modelContextConstructor,
|
|
1317
|
+
configurable: true,
|
|
1318
|
+
writable: true
|
|
1319
|
+
},
|
|
1320
|
+
[Symbol.toStringTag]: {
|
|
1321
|
+
value: "ModelContext",
|
|
1322
|
+
configurable: true
|
|
1323
|
+
},
|
|
1324
|
+
registerTool: { enumerable: true },
|
|
1325
|
+
getTools: { enumerable: true },
|
|
1326
|
+
executeTool: { enumerable: true },
|
|
1327
|
+
ontoolchange: { enumerable: true }
|
|
843
1328
|
});
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
"
|
|
848
|
-
"
|
|
849
|
-
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
var PolyfillTestingShim = class extends EventTarget {
|
|
862
|
-
_ontoolchange = null;
|
|
863
|
-
ontoolchangeListener = (event) => {
|
|
864
|
-
this._ontoolchange?.call(this, event);
|
|
1329
|
+
function readToolDefinition(value) {
|
|
1330
|
+
const descriptor = readDictionary(value);
|
|
1331
|
+
const annotations = readAnnotations(descriptor.annotations);
|
|
1332
|
+
const description = toDOMString(requireMember(descriptor.description, "description"));
|
|
1333
|
+
const callback = requireMember(descriptor.execute, "execute");
|
|
1334
|
+
if (typeof callback !== "function") throw new TypeError("execute must be a function");
|
|
1335
|
+
const execute = callback;
|
|
1336
|
+
const inputSchema = readInputSchema(descriptor.inputSchema);
|
|
1337
|
+
const name = toDOMString(requireMember(descriptor.name, "name"));
|
|
1338
|
+
const rawTitle = descriptor.title;
|
|
1339
|
+
return {
|
|
1340
|
+
name,
|
|
1341
|
+
title: rawTitle === void 0 ? "" : toUSVString(rawTitle),
|
|
1342
|
+
description,
|
|
1343
|
+
annotations,
|
|
1344
|
+
inputSchema,
|
|
1345
|
+
execute
|
|
865
1346
|
};
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
set ontoolchange(handler) {
|
|
881
|
-
const listener = typeof handler === "function" ? handler : null;
|
|
882
|
-
if (listener === null) {
|
|
883
|
-
this._ontoolchange = null;
|
|
884
|
-
super.removeEventListener("toolchange", this.ontoolchangeListener);
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
if (this._ontoolchange === null) super.addEventListener("toolchange", this.ontoolchangeListener);
|
|
888
|
-
this._ontoolchange = listener;
|
|
889
|
-
}
|
|
890
|
-
/** @internal Called by StrictWebMCPContext when tools change. */
|
|
891
|
-
dispatchToolChange() {
|
|
892
|
-
this.dispatchEvent(new Event("toolchange"));
|
|
893
|
-
}
|
|
894
|
-
dispose() {
|
|
895
|
-
this.ontoolchange = null;
|
|
896
|
-
}
|
|
897
|
-
};
|
|
898
|
-
function normalizeToolDescriptor(tool, existing) {
|
|
899
|
-
if (!tool || typeof tool !== "object") throw new TypeError("registerTool(tool) requires a tool object");
|
|
900
|
-
const coerced = coerceWebMcpToolDescriptor(tool);
|
|
901
|
-
validateWebMcpToolDescriptor(coerced);
|
|
902
|
-
if (existing.has(coerced.name)) throw createInvalidStateError(`Tool already registered: ${coerced.name}`);
|
|
903
|
-
const registeredInputSchema = coerced.inputSchema === void 0 ? void 0 : serializeInputSchema(coerced.inputSchema);
|
|
1347
|
+
}
|
|
1348
|
+
function readExecutionTarget(value) {
|
|
1349
|
+
const descriptor = readDictionary(value);
|
|
1350
|
+
readAnnotations(descriptor.annotations);
|
|
1351
|
+
toDOMString(requireMember(descriptor.description, "description"));
|
|
1352
|
+
readInputSchema(descriptor.inputSchema);
|
|
1353
|
+
const name = toDOMString(requireMember(descriptor.name, "name"));
|
|
1354
|
+
const origin = toUSVString(requireMember(descriptor.origin, "origin"));
|
|
1355
|
+
const title = descriptor.title;
|
|
1356
|
+
if (title !== void 0) toDOMString(title);
|
|
1357
|
+
const targetWindow = requireMember(descriptor.window, "window");
|
|
1358
|
+
if (!isObject(targetWindow)) throw new TypeError("window must be a Window");
|
|
1359
|
+
getWindow.call(targetWindow);
|
|
904
1360
|
return {
|
|
905
|
-
name
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
...coerced.inputSchema === void 0 ? {} : { inputSchema: coerced.inputSchema },
|
|
909
|
-
...coerced.annotations === void 0 ? {} : { annotations: coerced.annotations },
|
|
910
|
-
execute: (input) => Reflect.apply(coerced.execute, void 0, [input]),
|
|
911
|
-
...registeredInputSchema !== void 0 ? { [REGISTERED_INPUT_SCHEMA_SYMBOL]: registeredInputSchema } : {}
|
|
1361
|
+
name,
|
|
1362
|
+
origin,
|
|
1363
|
+
window: targetWindow
|
|
912
1364
|
};
|
|
913
1365
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
*
|
|
918
|
-
* WebIDL puts `[SameObject] readonly attribute ModelContext modelContext` on
|
|
919
|
-
* the Document *interface prototype object*, not on the document instance, and
|
|
920
|
-
* Chrome matches that. One shared accessor therefore needs somewhere to keep
|
|
921
|
-
* the per-document value, and keying it by document is what makes repeated
|
|
922
|
-
* reads return the identical object, as `[SameObject]` requires.
|
|
923
|
-
*/
|
|
924
|
-
const documentModelContexts = /* @__PURE__ */ new WeakMap();
|
|
925
|
-
const { modelContext: documentModelContextGetter } = { modelContext() {
|
|
926
|
-
if (!(this instanceof Document)) throw new TypeError("Illegal invocation");
|
|
927
|
-
return documentModelContexts.get(this);
|
|
928
|
-
} };
|
|
929
|
-
Object.defineProperty(documentModelContextGetter, "name", { value: "get modelContext" });
|
|
930
|
-
function defineDocumentModelContext(target, value) {
|
|
931
|
-
documentModelContexts.set(target, value);
|
|
932
|
-
if (Object.hasOwn(Document.prototype, "modelContext")) return;
|
|
933
|
-
installProperty(Document.prototype, "modelContext", {
|
|
934
|
-
configurable: true,
|
|
935
|
-
enumerable: true,
|
|
936
|
-
get: documentModelContextGetter
|
|
937
|
-
});
|
|
1366
|
+
function readInputSchema(value) {
|
|
1367
|
+
if (value !== void 0 && !isObject(value)) throw new TypeError("inputSchema must be an object");
|
|
1368
|
+
return value;
|
|
938
1369
|
}
|
|
939
|
-
function
|
|
940
|
-
|
|
941
|
-
configurable: true,
|
|
942
|
-
enumerable: true,
|
|
943
|
-
get() {
|
|
944
|
-
if (!navigatorModelContextDeprecationWarned) {
|
|
945
|
-
navigatorModelContextDeprecationWarned = true;
|
|
946
|
-
console.warn("[WebMCPPolyfill] navigator.modelContext is deprecated. The May 27, 2026 WebMCP draft moved the modelContext getter from Navigator to Document — use document.modelContext instead. See https://github.com/webmachinelearning/webmcp/pull/184.");
|
|
947
|
-
}
|
|
948
|
-
return value;
|
|
949
|
-
}
|
|
950
|
-
});
|
|
1370
|
+
function isObject(value) {
|
|
1371
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
951
1372
|
}
|
|
952
|
-
function
|
|
953
|
-
|
|
954
|
-
if (!(
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1373
|
+
function readDictionary(value) {
|
|
1374
|
+
if (value == null) return {};
|
|
1375
|
+
if (!isObject(value)) throw new TypeError("Expected a dictionary");
|
|
1376
|
+
return value;
|
|
1377
|
+
}
|
|
1378
|
+
function readAnnotations(value) {
|
|
1379
|
+
if (value === void 0) return;
|
|
1380
|
+
const dictionary = readDictionary(value);
|
|
1381
|
+
const annotations = {};
|
|
1382
|
+
for (const name of annotationNames) annotations[name] = Boolean(dictionary[name]);
|
|
1383
|
+
return annotations;
|
|
1384
|
+
}
|
|
1385
|
+
function toDOMString(value) {
|
|
1386
|
+
if (typeof value === "symbol") throw new TypeError("Cannot convert a Symbol to a string");
|
|
1387
|
+
return String(value);
|
|
1388
|
+
}
|
|
1389
|
+
function toUSVString(value) {
|
|
1390
|
+
return toDOMString(value).toWellFormed();
|
|
1391
|
+
}
|
|
1392
|
+
function requireMember(value, name) {
|
|
1393
|
+
if (value === void 0) throw new TypeError(`${name} is required`);
|
|
1394
|
+
return value;
|
|
1395
|
+
}
|
|
1396
|
+
function serializeJSON(value) {
|
|
1397
|
+
const result = JSON.stringify(value);
|
|
1398
|
+
if (result === void 0) throw new TypeError("Value is not JSON-serializable");
|
|
1399
|
+
return result;
|
|
1400
|
+
}
|
|
1401
|
+
function readAbortSignal(value) {
|
|
1402
|
+
if (value === void 0) return;
|
|
1403
|
+
return AbortSignal.any([value]);
|
|
1404
|
+
}
|
|
1405
|
+
function readOriginSequence(value) {
|
|
1406
|
+
if (value === void 0) return [];
|
|
1407
|
+
if (!isObject(value)) throw new TypeError("Origins must be a sequence");
|
|
1408
|
+
const getIterator = readDictionary(value)[Symbol.iterator];
|
|
1409
|
+
if (typeof getIterator !== "function") throw new TypeError("Origins must be a sequence");
|
|
1410
|
+
const iterable = { [Symbol.iterator]() {
|
|
1411
|
+
const iterator = Reflect.apply(getIterator, value, []);
|
|
1412
|
+
if (!isObject(iterator)) throw new TypeError("Iterator must be an object");
|
|
1413
|
+
const next = readDictionary(iterator).next;
|
|
1414
|
+
if (typeof next !== "function") throw new TypeError("Iterator next must be a function");
|
|
1415
|
+
return { next() {
|
|
1416
|
+
return Reflect.apply(next, iterator, []);
|
|
1417
|
+
} };
|
|
1418
|
+
} };
|
|
1419
|
+
return Array.from(iterable, toUSVString);
|
|
1420
|
+
}
|
|
1421
|
+
function parseOrigins(origins) {
|
|
1422
|
+
return origins.map((value) => {
|
|
1423
|
+
const origin = URL.parse(value)?.origin;
|
|
1424
|
+
if (origin === void 0) throw new NativeDOMException("Invalid origin", "SecurityError");
|
|
1425
|
+
const url = URL.parse(origin);
|
|
1426
|
+
if (!url || !isPotentiallyTrustworthy(url)) throw new NativeDOMException("Origin is not potentially trustworthy", "SecurityError");
|
|
1427
|
+
return origin;
|
|
968
1428
|
});
|
|
969
1429
|
}
|
|
970
|
-
function
|
|
971
|
-
if (
|
|
972
|
-
const
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
if (
|
|
979
|
-
|
|
980
|
-
|
|
1430
|
+
function isPotentiallyTrustworthy({ protocol, hostname }) {
|
|
1431
|
+
if (["https:", "wss:"].includes(protocol)) return true;
|
|
1432
|
+
const isLoopback = hostname === "[::1]" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname);
|
|
1433
|
+
const isLocalhost = hostname === "localhost" || hostname === "localhost." || hostname.endsWith(".localhost") || hostname.endsWith(".localhost.");
|
|
1434
|
+
return ["http:", "ws:"].includes(protocol) && (isLoopback || isLocalhost);
|
|
1435
|
+
}
|
|
1436
|
+
function requireActiveWindow(owner) {
|
|
1437
|
+
const view = activeWindow(owner);
|
|
1438
|
+
if (!view) throw new NativeDOMException("The document is not fully active", "InvalidStateError");
|
|
1439
|
+
if (readToolsPolicy(owner) === false) throw new NativeDOMException("WebMCP is disabled by Permissions Policy", "NotAllowedError");
|
|
1440
|
+
return view;
|
|
1441
|
+
}
|
|
1442
|
+
let taskPort;
|
|
1443
|
+
const queuedTasks = [];
|
|
1444
|
+
function queueTask(callback) {
|
|
1445
|
+
if (!taskPort) {
|
|
1446
|
+
const channel = new MessageChannel();
|
|
1447
|
+
channel.port1.onmessage = () => queuedTasks.shift()?.();
|
|
1448
|
+
taskPort = channel.port2;
|
|
981
1449
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
writable: false,
|
|
1001
|
-
value: StrictWebMCPContext.getTestingShim(context)
|
|
1002
|
-
});
|
|
1003
|
-
cleanupDeclarativeForms = installDeclarativeForms(doc, context);
|
|
1004
|
-
}
|
|
1005
|
-
function cleanupWebMCPPolyfill() {
|
|
1006
|
-
cleanupDeclarativeForms?.();
|
|
1007
|
-
cleanupDeclarativeForms = null;
|
|
1008
|
-
if (installedContext) StrictWebMCPContext.dispose(installedContext);
|
|
1009
|
-
installedContext = null;
|
|
1010
|
-
for (const { target, key, previous } of [...installedProperties].reverse()) if (previous) Object.defineProperty(target, key, previous);
|
|
1011
|
-
else Reflect.deleteProperty(target, key);
|
|
1012
|
-
installedProperties.length = 0;
|
|
1013
|
-
navigatorModelContextDeprecationWarned = false;
|
|
1450
|
+
queuedTasks.push(callback);
|
|
1451
|
+
taskPort.postMessage(void 0);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
//#endregion
|
|
1455
|
+
//#region src/index.ts
|
|
1456
|
+
/**
|
|
1457
|
+
* Installs upstream WebMCP and the temporary declarative tools layer.
|
|
1458
|
+
* Engines missing an API the vendored core calls are left untouched: upstream defines
|
|
1459
|
+
* the document getter before it constructs the context, so a failed construction would
|
|
1460
|
+
* leave `document.modelContext` throwing.
|
|
1461
|
+
*/
|
|
1462
|
+
function installWebMCP() {
|
|
1463
|
+
if (typeof document === "undefined" || !globalThis.isSecureContext) return;
|
|
1464
|
+
if (typeof Promise.withResolvers !== "function" || typeof AbortSignal.any !== "function" || typeof String.prototype.toWellFormed !== "function" || typeof URL.parse !== "function") return;
|
|
1465
|
+
installWebMCP$1();
|
|
1466
|
+
const context = document.modelContext;
|
|
1467
|
+
if (context) installWebMCPDeclarativeExtensions(context);
|
|
1014
1468
|
}
|
|
1015
1469
|
|
|
1016
1470
|
//#endregion
|
|
1017
|
-
export {
|
|
1471
|
+
export { installWebMCP };
|
|
1018
1472
|
//# sourceMappingURL=index.js.map
|