Skip to content

Commit a816d2e

Browse files
authored
Spec toolchange event processing and permission policy integration (#179)
1 parent 986f461 commit a816d2e

1 file changed

Lines changed: 152 additions & 6 deletions

File tree

index.bs

Lines changed: 152 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ p + dl.props { margin-top: -0.5em; }
7878
[data-algorithm] [data-algorithm] {
7979
margin: 1em 0;
8080
}
81+
82+
/* Table styling definitions from https://resources.whatwg.org/standard.css */
83+
table { border-collapse: collapse; border-style: hidden hidden none hidden; margin: 1.25em 0; }
84+
table thead, table tbody { border-bottom: solid; }
85+
table tbody th { text-align: left; }
86+
table tbody th:first-child { border-left: solid; }
87+
table td, table th { border-left: solid; border-right: solid; border-bottom: solid thin; vertical-align: top; padding: 0.2em; }
8188
</style>
8289

8390
<pre class="link-defaults">
@@ -94,6 +101,9 @@ spec: html; type: dfn; urlPrefix: https://html.spec.whatwg.org/C
94101
# `url`.
95102
text:is initial about:blank; url: is-initial-about:blank
96103
text: associated Navigator
104+
spec: SECURE-CONTEXTS; urlPrefix: https://w3c.github.io/webappsec-secure-contexts/
105+
type: abstract-op
106+
text: is origin potentially trustworthy?; url: is-origin-trustworthy
97107
</pre>
98108

99109
<h2 id="intro">Introduction</h2>
@@ -168,18 +178,95 @@ A <dfn>tool definition</dfn> is a [=struct=] with the following [=struct/items=]
168178

169179
: <dfn>untrusted content hint</dfn>
170180
:: a [=boolean=], initially false.
181+
182+
: <dfn>exposed origins</dfn>
183+
:: a [=list=] or [=origins=], initially [=list/empty=].
171184
</dl>
172185

186+
<hr>
187+
188+
<div algorithm>
189+
To <dfn>notify documents of a tool change</dfn> given a {{Document}} |tool owner| and a [=list=] of
190+
[=origins=] |exposed origins|, run these steps:
191+
192+
1. [=Assert=]: these steps are running on |tool owner|'s [=relevant agent=]'s [=agent/event loop=].
193+
194+
1. Let |navigablesToNotify| be |tool owner|'s [=node navigable=]'s [=navigable/traversable
195+
navigable=]'s [=Document/descendant navigables=].
196+
197+
1. [=list/For each=] |navigable| of |navigablesToNotify|:
198+
199+
1. Let |targetDocument| be |navigable|'s [=navigable/active document=].
200+
201+
1. If |targetDocument| is not [=allowed to use=] the "{{tools}}" feature, then
202+
[=iteration/continue=].
203+
204+
1. If [=tool is visible to an origin=] given |tool owner|'s [=Document/origin=], |exposed
205+
origins|, and |targetDocument|'s [=Document/origin=], then [=queue a global task=] on the
206+
[=webmcp task source=] given |targetDocument|'s [=relevant global object=] to [=fire an
207+
event=] named {{ModelContext/toolchange}} at |targetDocument|'s [=relevant global object=]'s
208+
[=associated Navigator|associated <code>Navigator</code>=]'s [=Navigator/associated
209+
ModelContext|associated <code>ModelContext</code>=].
210+
211+
<div class=example id=notify-task-ordering>
212+
<p>This algorithm's use of the [=webmcp task source=] means that the timing between firing the
213+
{{ModelContext/toolchange}} event, and other tasks queued after this algorithm, cannot be relied
214+
upon. For example:</p>
215+
216+
<xmp class=language-js>
217+
navigator.modelContext.ontoolchange = e => console.log('Parent toolchange');
218+
iframe.contentWindow.navigator.modelContext.ontoolchange = e => console.log('Child toolchange');
219+
220+
// Queues a task to fire `toolchange`, on the `webmcp task source`.
221+
navigator.modelContext.registerTool({
222+
name: "tool_name",
223+
description: "tool_desc",
224+
execute: async () => {}
225+
});
226+
227+
// Queues a task on the `timer task source`.
228+
setTimeout(() => console.log('Post-register task'));
229+
230+
// `Parent toolchange` will always log before `Child toolchange`.
231+
// But `Post-register task` can log before, in between, or after both.
232+
</xmp>
233+
</div>
234+
235+
</div>
236+
173237
<div algorithm>
174-
To <dfn for="model context">unregister a tool</dfn> for a [=model context=] given a [=string=]
175-
|tool name|, run these steps:
238+
To determine if a <dfn>tool is visible to an origin</dfn> given an [=origin=] |tool owner origin|,
239+
a [=list=] of [=origins=] |exposed origins|, and an [=origin=] |target origin|, run these steps:
176240

177-
1. Let |tool map| be the associated [=model context=]'s [=model context/tool map=].
241+
1. If |tool owner origin| is [=same origin=] with |target origin|, then return true.
242+
243+
1. [=list/For each=] |origin| of |exposed origins|:
244+
245+
1. If |target origin| is [=same origin=] with |origin|, then return true.
246+
247+
1. Return false.
248+
249+
</div>
250+
251+
<div algorithm>
252+
To <dfn for="model context">unregister a tool</dfn> given a {{ModelContext}} |modelContext| and a
253+
[=string=] |tool name|, run these steps:
254+
255+
1. [=Assert=]: these steps are running on |modelContext|'s [=relevant agent=]'s [=agent/event
256+
loop=].
257+
258+
1. Let |tool map| be |modelContext|'s [=ModelContext/internal context=]'s [=model context/tool
259+
map=].
178260

179261
1. [=Assert=] |tool map|[|tool name|] [=map/exists=].
180262

263+
1. Let |exposed origins| be |tool map|[|tool name|]'s [=tool definition/exposed origins=].
264+
181265
1. [=map/Remove=] |tool map|[|tool name|].
182266

267+
1. Run [=notify documents of a tool change=] given |modelContext|'s [=relevant global object=]'s
268+
[=associated Document|associated <code>Document</code>=] and |exposed origins|.
269+
183270
</div>
184271

185272

@@ -242,8 +329,10 @@ The {{ModelContext}} interface provides methods for web applications to register
242329

243330
<xmp class="idl">
244331
[Exposed=Window, SecureContext]
245-
interface ModelContext {
332+
interface ModelContext : EventTarget {
246333
undefined registerTool(ModelContextTool tool, optional ModelContextRegisterToolOptions options = {});
334+
335+
attribute EventHandler ontoolchange;
247336
};
248337
</xmp>
249338

@@ -265,6 +354,15 @@ is a [=model context=] [=struct=] created alongside the {{ModelContext}}.
265354
<div algorithm>
266355
The <dfn method for=ModelContext>registerTool(<var>tool</var>, <var>options</var>)</dfn> method steps are:
267356

357+
1. Let |tool owner| be [=this=]'s [=relevant global object=]'s [=associated Document|associated
358+
<code>Document</code>=].
359+
360+
1. If |tool owner| is not [=Document/fully active=], then [=exception/throw=] an
361+
"{{InvalidStateError}}" {{DOMException}}.
362+
363+
1. If |tool owner| is not [=allowed to use=] the "{{tools}}" feature, then [=exception/throw=] a
364+
"{{NotAllowedError}}" {{DOMException}}.
365+
268366
1. Let |tool map| be [=this=]'s [=ModelContext/internal context=]'s [=model context/tool map=].
269367

270368
1. Let |tool name| be |tool|'s {{ModelContextTool/name}}.
@@ -316,7 +414,21 @@ The <dfn method for=ModelContext>registerTool(<var>tool</var>, <var>options</var
316414
[=AbortSignal/aborted=], and return.
317415

318416
1. [=AbortSignal/add|Add an abort algorithm=] to |signal| that [=model context/unregisters a
319-
tool=] from [=this=] [=ModelContext/internal context=] given |tool name|.
417+
tool=] given [=this=] and |tool name|.
418+
419+
1. Let |exposed origins| be an empty [=list=] of [=origins=].
420+
421+
1. If |options|'s {{ModelContextRegisterToolOptions/exposedTo}} [=map/exists=], then:
422+
423+
1. [=list/For each=] |origin| of |options|'s {{ModelContextRegisterToolOptions/exposedTo}}:
424+
425+
1. Let |parsedURL| be the result of running the [=URL parser=] on |origin|.
426+
427+
1. If |parsedURL| is failure or its [=url/origin=] is not [$is origin potentially
428+
trustworthy?|potentially trustworthy$], then [=exception/throw=] an "{{SecurityError}}"
429+
{{DOMException}}.
430+
431+
1. [=list/Append=] |parsedURL|'s [=url/origin=] to |exposed origins|.
320432

321433
1. Let |tool definition| be a new [=tool definition=], with the following [=struct/items=]:
322434

@@ -341,8 +453,13 @@ The <dfn method for=ModelContext>registerTool(<var>tool</var>, <var>options</var
341453
: [=tool definition/untrusted content hint=]
342454
:: |untrusted content hint|
343455

456+
: [=tool definition/exposed origins=]
457+
:: |exposed origins|
458+
344459
1. Set [=this=]'s [=ModelContext/internal context=][|tool name|] to |tool definition|.
345460

461+
1. Run [=notify documents of a tool change=] given |tool owner| and |exposed origins|.
462+
346463
</div>
347464

348465
<h4 id="model-context-tool">ModelContextTool Dictionary</h4>
@@ -425,6 +542,7 @@ definition itself.
425542
<xmp class="idl">
426543
dictionary ModelContextRegisterToolOptions {
427544
AbortSignal signal;
545+
sequence<USVString> exposedTo;
428546
};
429547
</xmp>
430548

@@ -433,6 +551,12 @@ dictionary ModelContextRegisterToolOptions {
433551
<dd>
434552
<p>An {{AbortSignal}} that unregisters the tool when aborted.
435553
</dd>
554+
555+
<dt><code><var ignore>exposedTo</var>["{{ModelContextRegisterToolOptions/exposedTo}}"]</code></dt>
556+
<dd>
557+
<p>An array of origins that control which documents this tool is exposed to, in the current
558+
document's tree.
559+
</dd>
436560
</dl>
437561

438562

@@ -493,6 +617,28 @@ The <dfn>synthesize a declarative JSON Schema object algorithm</dfn>, given a <{
493617
}
494618
</pre>
495619

620+
<h3 id="events">Events</h3>
621+
622+
The following are the [=event handlers=] (and their corresponding [=event handler event types=])
623+
that must be supported, as [=event handler IDL attributes=], by all {{ModelContext}} objects:
624+
625+
<table>
626+
<thead>
627+
<tr>
628+
<th>[=Event handler=]
629+
<th>[=Event handler event type=]
630+
<tbody>
631+
<tr>
632+
<td><dfn attribute for="ModelContext">ontoolchange</dfn>
633+
<td><dfn event for="ModelContext">toolchange</dfn>
634+
</table>
635+
636+
<h3 id="permissions-policy">Permissions policy integration</h3>
637+
638+
Access to the APIs in this specification is gated behind the [=policy-controlled feature=] "<dfn
639+
permission>tools</dfn>", which has a [=policy-controlled feature/default allowlist=] of
640+
<code>[=default allowlist/'self'=]</code>.
641+
496642
<h2 id="interaction-with-agents">Interaction with agents</h2>
497643

498644
<h3 id="event-loop">Event loop integration</h3>
@@ -506,7 +652,7 @@ queued on its <dfn>AI agent queue</dfn>, which is the result of [=starting a new
506652

507653
Conversely, steps queued *from* the [=browser agent=] onto the [=event loop=] of a given
508654
{{ModelContext}} object (i.e., the "main thread" where JavaScript runs) are queued on its [=relevant
509-
global object=]'s <dfn noexport>tool calling task source</dfn>.
655+
global object=]'s <dfn noexport>webmcp task source</dfn>.
510656

511657
<h3 id="observations">Page observations</h3>
512658

0 commit comments

Comments
 (0)