Spec the executeTool() API#226
Conversation
This CL introduces the `executeTool()` method to the `ModelContext` interface in the WebMCP specification. This aligns the spec with the C++ implementation in Blink. Key changes: - Adds `executeTool()` and `ExecuteToolOptions` IDL definitions. - Specifies the `executeTool()` algorithm, detailing cross-document tool lookup, origin visibility checks, and asynchronous invocation in the target document`s realm. - Clarifies the execution callback arguments and JSON serialization. - Incorporates `AbortSignal` handling for tool cancellation, dispatching a `toolcancel` event in the target document. - Fires `toolactivated` event in the target document. - Updates IDL member access syntax to use idiomatic Bikeshed style. Small things Fix some things Don't serialize Object argument enforcement Add AbortSignal issue Small things
| interface ModelContext : EventTarget { | ||
| Promise<undefined> registerTool(ModelContextTool tool, optional ModelContextRegisterToolOptions options = {}); | ||
| Promise<sequence<RegisteredTool>> getTools(optional ModelContextGetToolOptions options = {}); | ||
| Promise<DOMString?> executeTool(RegisteredTool tool, DOMString inputArguments, optional ExecuteToolOptions options = {}); |
There was a problem hiding this comment.
For consistency with the options naming convention, should we also prepend ModelContext to this dictionary name (e.g., ModelContextExecuteToolOptions)?
| Promise<DOMString?> executeTool(RegisteredTool tool, DOMString inputArguments, optional ExecuteToolOptions options = {}); | |
| Promise<DOMString?> executeTool(RegisteredTool tool, DOMString inputArguments, optional ModelContextExecuteToolOptions options = {}); |
I now wonder if we should have used ModelContextRegisteredTool as well ;)
| 1. If |invokingDocument| is not [=Document/fully active=], then return [=a promise rejected with=] | ||
| an "{{InvalidStateError}}" {{DOMException}}. | ||
|
|
||
| 1. If this's [=surrounding agent=]'s [=agent cluster=]'s [=is origin-keyed=] is false and this's |
There was a problem hiding this comment.
| 1. If this's [=surrounding agent=]'s [=agent cluster=]'s [=is origin-keyed=] is false and this's | |
| 1. If [=this=]'s [=surrounding agent=]'s [=agent cluster=]'s [=is origin-keyed=] is false and [=this=]'s |
This suggestion applies as well to getTools and registerTool.
| 1. Let |expectedTargetOriginURL| be the result of [=URL parser|parsing=] |tool|'s | ||
| {{RegisteredTool/origin}}. | ||
|
|
||
| 1. If |expectedTargetOriginURL| is failure, then return [=a promise rejected with=] a |
There was a problem hiding this comment.
How about using NotSupportedError instead and return a rejected promise in both cases?
- expectedTargetOriginURL is a failure
- expectedTargetOrigin is opaque
| 1. Let |signal| be |options|'s {{ExecuteToolOptions/signal}} if it [=map/exists=]; otherwise null. | ||
|
|
||
| 1. If |signal| is not null, then: |
There was a problem hiding this comment.
| 1. Let |signal| be |options|'s {{ExecuteToolOptions/signal}} if it [=map/exists=]; otherwise null. | |
| 1. If |signal| is not null, then: | |
| 1. Let |signal| be |options|'s {{ExecuteToolOptions/signal}}. | |
| 1. If |signal| [=map/exists=], then: |
| 1. If |targetDocument|'s [=node navigable=]'s [=navigable/traversable navigable=] is not | ||
| |invokingDocument|'s [=node navigable=]'s [=navigable/traversable navigable=], then [=queue a | ||
| global task=] on the [=webmcp task source=] given |invokingDocument|'s [=relevant global | ||
| object=] to [=reject=] |promise| with an "{{UnknownError}}" {{DOMException}}, and abort these |
There was a problem hiding this comment.
UnknownError feels weird to me in these cases. Maybe InvalidStateError?
| <dt><code><var ignore>document</var>.{{Document/modelContext}}.{{ModelContext/executeTool(tool, inputArguments, options)}}</code></dt> | ||
| <dd> | ||
| <p>Executes a tool on the document it was registered on. Returns a promise that resolves to the | ||
| result of the tool's execution.</p> |
There was a problem hiding this comment.
Maybe extends that it's a stringified result only for now.
| The <dfn method for=ModelContext>executeTool(<var>tool</var>, <var>inputArguments</var>, | ||
| <var>options</var>)</dfn> method steps are: | ||
|
|
||
| 1. Let |invokingDocument| be [=this=]'s [=relevant global object=]'s [=associated |
There was a problem hiding this comment.
Nit: Shall we use executingDocument?
| </div> | ||
|
|
||
| <div algorithm> | ||
| The <dfn method for=ModelContext>executeTool(<var>tool</var>, <var>inputArguments</var>, |
There was a problem hiding this comment.
A JS example of how to execute a tool from a cross-origin iframe may come handy in this spec. See previous comment at #223 (comment)
| loop=]. | ||
|
|
||
| 1. Let |inputObject| be the result of [=parse a JSON string to a JavaScript value=] given | ||
| |inputArguments| and |targetWindow|'s [=relevant realm=]. If <a spec=webidl lt="an exception was thrown">exception was thrown</a>, then |
There was a problem hiding this comment.
Why not add this webidl bit to <pre class="link-defaults"> at the top of the file?
| interface ModelContext : EventTarget { | ||
| Promise<undefined> registerTool(ModelContextTool tool, optional ModelContextRegisterToolOptions options = {}); | ||
| Promise<sequence<RegisteredTool>> getTools(optional ModelContextGetToolOptions options = {}); | ||
| Promise<DOMString?> executeTool(RegisteredTool tool, DOMString inputArguments, optional ExecuteToolOptions options = {}); |
There was a problem hiding this comment.
Out of scope for this PR.
Have we consider moving executeTool from document.modelContext to RegisteredTool interface hinted in https://issues.chromium.org/536063275? FWIW This would match what Puppeteer does: https://pptr.dev/guides/webmcp#executing-tools
beaufortfrancois
left a comment
There was a problem hiding this comment.
We should update README as well: https://github.com/webmachinelearning/webmcp#discovering-and-running-tools
|
|
||
| 1. Run the following steps [=in parallel=]: | ||
|
|
||
| 1. If |targetDocument|'s [=node navigable=]'s [=navigable/traversable navigable=] is not |
There was a problem hiding this comment.
Should we check that this document is fully active?
|
|
||
| 1. Let |toolName| be |tool|'s {{RegisteredTool/name}}. | ||
|
|
||
| 1. If |targetToolMap|[|toolName|] does not [=map/exist=], then [=queue a global task=] on the |
There was a problem hiding this comment.
Weird case we could get into here: |targetToolMap|[|toolName|] isn't the same tool as returned from getTools. It may have been modified in between.
There was a problem hiding this comment.
Yep, some modifications are OK, others are not. I'll be carrying over some from our implementation before taking this PR out of draft mode.
This PR specifies the
executeTool()API onModelContext.Closes #57.
Closes #117.
Closes #159.
Closes #160.
Preview | Diff