From e59006a5b92fdbd0e18c83acc5ac3d1aa115af00 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Fri, 14 Aug 2026 16:07:25 +0000 Subject: [PATCH 01/13] Spec abortable tools Spec abortable tools Better note --- index.bs | 206 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 163 insertions(+), 43 deletions(-) diff --git a/index.bs b/index.bs index ed842c5..01c78be 100644 --- a/index.bs +++ b/index.bs @@ -134,6 +134,20 @@ A model context is a [=struct=] with the following [=struct/items=]: : tool map :: a [=map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are [=tool definition=] [=structs=]. + + : pending executions map + :: a [=map=] whose [=map/keys=] are [=unique internal values=] and whose [=map/values=] are [=local + pending tool execution=] [=structs=]. It is initially empty. + + +A local pending tool execution is a [=struct=] with the following [=struct/items=]: + +
+ : tool name + :: a [=string=]. + + : abort controller + :: an {{AbortController}}.
@@ -170,8 +184,8 @@ A tool definition is a [=struct=] with the following [=struct/items=] : execute steps :: an algorithm that takes a {{Document}} targetDocument, a [=string=] inputArguments, and an algorithm completionSteps that takes a - [=string=]-or-null and a [=boolean=]. + ignore>inputArguments, an algorithm completionSteps that takes a + [=string=]-or-null and a [=boolean=], and a [=unique internal value=] uuid. Note: For tools registered imperatively, these steps will simply invoke the [=imperative execute steps=]. For tools registered @@ -223,6 +237,47 @@ authoritative "browser process" that most modern browsers implement, where execu outside any individual Document process's event loop, and is accessed asynchronously via some inter-process communication mechanism. +
+To cancel a pending tool execution given a [=traversable +navigable=] |traversable| and a [=unique internal value=] |uuid|: + +1. [=Assert=]: these steps are running [=in parallel=]. + +1. If |traversable|'s [=traversable navigable/pending tool executions map=][|uuid|] does not + [=map/exist=], then return. + + Note: See this note to learn how a tool's natural + resolution/rejection can race with the caller's cancellation. This might result in the pending + execution entry for |uuid| being removed before we get here. In that case, the + {{ModelContext/executeTool()}} promise will still be rejected with the abort + [=AbortSignal/abort reason=], and will never observe the tool's natural resolution/rejection. + +1. Let |execution| be |traversable|'s [=traversable navigable/pending tool executions map=][|uuid|]. + +1. [=map/Remove=] |traversable|'s [=traversable navigable/pending tool executions map=][|uuid|]. + +1. Let |targetDocument| be |execution|'s [=pending tool execution/target document=]. + +1. [=Queue a global task=] on the [=webmcp task source=] given |targetDocument|'s [=relevant global + object=] to run the following steps: + + 1. Let |localExecutions| be |targetDocument|'s [=Document/associated ModelContext|associated + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending + executions map=]. + + 1. If |localExecutions|[|uuid|] does not [=map/exist=], then return. + + 1. Let |localExecution| be |localExecutions|[|uuid|]. + + 1. [=map/Remove=] |localExecutions|[|uuid|]. + + 1. [=AbortController/signal abort|Signal abort=] on |localExecution|'s [=local pending tool + execution/abort controller=]. + + Issue(#146): Fire the "toolcanceled" event at |targetDocument|'s relevant global object. + +
+
@@ -254,13 +309,9 @@ follows: Note: This removes |execution| from the [=traversable navigable/pending tool executions map=]. - 1. If |document| is |execution|'s [=pending tool execution/caller document=], then: - - 1. [=map/Remove=] |traversable|'s [=traversable navigable/pending tool executions - map=][|uuid|]. - - 1. Issue(#48): Abort the `AbortSignal` in |execution|'s [=pending tool execution/target - document=], so the tool can abort early now that the caller is dead. + 1. If |document| is |execution|'s [=pending tool execution/caller document=], then + [=pending tool execution/cancel a pending tool execution|cancel=] given |traversable| and + |uuid|. 1. [=Assert=]: |traversable|'s [=traversable navigable/pending tool executions map=][|uuid|] does not [=map/exist=]. @@ -334,20 +385,59 @@ a [=list=] of [=origins=] |exposed origins|, and an [=origin=] |accessing origin
+
+To finish a tool execution given a {{Document}} |targetDocument|, a +[=unique internal value=] |uuid|, a [=string=]-or-null |result|, a [=boolean=] |success|, and an +algorithm |completionSteps|: + +1. [=Assert=]: these steps are running on |targetDocument|'s [=relevant agent=]'s [=agent/event + loop=]. + +1. [=map/Remove=] |targetDocument|'s [=Document/associated ModelContext|associated + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending + executions map=][|uuid|]. + +1. Run |completionSteps| given |result| and |success|. + +
+
The tool execute steps, given a [=string=] |toolName|, a {{Document}} |targetDocument|, a -[=string=] |inputArguments|, and an algorithm |completionSteps|, are as follows. The -|completionSteps| algorithm takes a [=string=]-or-null result and a [=boolean=] -success. +[=string=] |inputArguments|, an algorithm |completionSteps|, and a [=unique internal value=] |uuid|, +are as follows. The |completionSteps| algorithm takes a [=string=]-or-null result +and a [=boolean=] success. 1. [=Assert=]: these steps are running on |targetDocument|'s [=relevant agent=]'s [=agent/event loop=]. +1. Let |traversable| be |targetDocument|'s [=node navigable=]'s [=navigable/traversable navigable=]. + +1. If |traversable|'s [=traversable navigable/pending tool executions map=][|uuid|] does not + [=map/exist=], then return. + +1. Let |controller| be a [=new=] {{AbortController}} created in |targetDocument|'s [=relevant + realm=]. + +1. Let |signal| be |controller|'s [=AbortController/signal=]. + +1. Let |localExecution| be a new [=local pending tool execution=] with the following [=struct/items=]: + + : [=local pending tool execution/tool name=] + :: |toolName| + + : [=local pending tool execution/abort controller=] + :: |controller| + +1. Set |targetDocument|'s [=Document/associated ModelContext|associated + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending + executions map=][|uuid|] to |localExecution|. + 1. Let |toolMap| be |targetDocument|'s [=Document/associated ModelContext|associated ModelContext=]'s [=ModelContext/internal context=]'s [=model context/tool map=]. -1. If |toolMap|[|toolName|] does not [=map/exist=], then run |completionSteps| given null and false, - and abort these steps. +1. If |toolMap|[|toolName|] does not [=map/exist=], then [=model context/finish a tool + execution|finish=] given |targetDocument|, |uuid|, null, false, and |completionSteps|, and abort + these steps. Issue: Support the plumbing of more granular errors back to the invoker; this should result in a "{{NotFoundError}}" in the calling document. @@ -387,8 +477,8 @@ The tool execute steps, given a [=string=] |toolName|, a {{Document}} 1. Let |tool| be |toolMap|[|toolName|]. -1. Run |tool|'s [=tool definition/execute steps=] given |targetDocument|, |inputArguments|, and - |completionSteps|. +1. Run |tool|'s [=tool definition/execute steps=] given |targetDocument|, |inputArguments|, + |completionSteps|, and |uuid|. Note: This is the point where we branch into either the [=imperative execute steps=] or the [=declarative execute steps=]. @@ -397,42 +487,52 @@ The tool execute steps, given a [=string=] |toolName|, a {{Document}}
The imperative execute steps, given a {{ModelContextTool}} |tool|, a {{Document}} -|targetDocument|, a [=string=] |inputArguments|, and an algorithm |completionSteps|, are as follows: +|targetDocument|, a [=string=] |inputArguments|, an algorithm |completionSteps|, an {{AbortSignal}} +|signal|, and a [=unique internal value=] |uuid|, are as follows: 1. [=Assert=]: these steps are running on |targetDocument|'s [=relevant agent=]'s [=agent/event loop=]. 1. Let |inputObject| be the result of [=parse a JSON string to a JavaScript value=] given |inputArguments| and |targetDocument|'s [=relevant realm=]. If exception was thrown, then run |completionSteps| given null and false, and abort - these steps. + thrown">exception was thrown, then [=model context/finish a tool execution|finish=] given + |targetDocument|, |uuid|, null, false, and |completionSteps|, and abort these steps. Issue: Support more granular errors; here we should return something that prompts the caller to reject its {{Promise}} with a "{{DataError}}" {{DOMException}}. -1. If |inputObject| [=Object type|is not an Object=] is false, then run |completionSteps| given null - and false, and abort these steps. +1. If |inputObject| [=Object type|is not an Object=] is false, then [=model context/finish a tool + execution|finish=] given |targetDocument|, |uuid|, null, false, and |completionSteps|, and abort + these steps. Issue(#146): Specify and fire the "toolactivated" event. +1. Let |options| be a new {{ToolExecuteCallbackOptions}} dictionary, with the following fields: + + : {{ToolExecuteCallbackOptions/signal}} + :: |signal| + 1. Let |toolPromise| be the result of [=invoke|invoking=] |tool|'s {{ModelContextTool/execute}} with - |inputObject|. + |inputObject| and |options|. 1. [=promise/React=] to |toolPromise|: - If |toolPromise| was fulfilled with value |v|: 1. Let |serializedResult| be the result of [=serializing a JavaScript value to a JSON - string=] given |v|. If this throws an exception, run |completionSteps| given null and - false, and abort these steps. + string=] given |v|. If this throws an exception, [=model context/finish a tool + execution|finish=] given |targetDocument|, |uuid|, null, false, and |completionSteps|, and + abort these steps. - 1. Run |completionSteps| given |serializedResult| and true. + 1. [=model context/finish a tool execution|finish=] given |targetDocument|, |uuid|, + |serializedResult|, true, and |completionSteps|. - If |toolPromise| was rejected with reason |r|, then: 1. Optionally [=report a warning to the console=] describing |r|. - 1. Run |completionSteps| given null and false. + 1. [=model context/finish a tool execution|finish=] given |targetDocument|, |uuid|, null, + false, and |completionSteps|.
@@ -664,9 +764,10 @@ The registerTool(tool, optionsexecuteTool(tool, inputObjectDocument=]. + +1. Let |uuid| be a new [=unique internal value=]. + 1. If |options|'s {{ModelContextExecuteToolOptions/signal}} [=map/exists=], then: 1. Let |signal| be |options|'s {{ModelContextExecuteToolOptions/signal}}. @@ -851,19 +959,17 @@ The executeTool(tool, inputObjecttoolcanceled" event in the target document. See also pull request #146. + 1. Run the following steps [=in parallel=]: -1. Let |targetWindow| be |tool|'s {{RegisteredTool/window}}. - -1. Let |targetDocument| be |targetWindow|'s [=associated Document|associated - Document=]. + 1. Run [=pending tool execution/cancel a pending tool execution|cancel=] given |traversable| + and |uuid|. 1. Run the following steps [=in parallel=]: @@ -908,8 +1014,6 @@ The executeTool(tool, inputObjectexecuteTool(tool, inputObject

It is possible that a pending execution identified by |uuid| no longer exists. This can happen due to a race between (a) tool cancellation when the caller document gets destroyed; and (b) tool promise resolution. Both + href=#caller-destroyed-cleanup>gets destroyed or when the caller aborts the + execution via the options signal; and (b) tool promise resolution. Both of these race to invoke |completionSteps|, and the first invocation will remove the pending execution by its key |uuid|, this check protects subsequent racing invocations.

@@ -979,7 +1084,8 @@ The executeTool(tool, inputObject (object inputObject); +dictionary ToolExecuteCallbackOptions { + required AbortSignal signal; +}; + +callback ToolExecuteCallback = Promise (object inputObject, ToolExecuteCallbackOptions options);
@@ -1041,7 +1151,8 @@ callback ToolExecuteCallback = Promise (object inputObject);
tool["{{ModelContextTool/execute}}"]
-

A callback function that is invoked when an [=agent=] calls the tool. The function receives the input parameters. +

A callback function that is invoked when an [=agent=] calls the tool. The function receives + the input parameters and execution options.

The function can be asynchronous and return a promise, in which case the [=agent=] will receive the result once the promise is resolved.

@@ -1062,6 +1173,15 @@ The {{ToolAnnotations}} dictionary provides optional metadata about a tool: :: If true, indicates that the tool's output contains data that is untrusted, from the perspective of the author registering the tool.
+

ToolExecuteCallbackOptions Dictionary

+ +The {{ToolExecuteCallbackOptions}} dictionary carries options passed to a tool's +{{ToolExecuteCallback}} when the tool is executed. + +
+ : options["{{ToolExecuteCallbackOptions/signal}}"] + :: An {{AbortSignal}} that communicates when the execution of the tool has been aborted. +

ModelContextRegisterToolOptions Dictionary

From 8d4f2cb5399de3abe3195edb18ed8776b286bd38 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Fri, 14 Aug 2026 16:41:59 +0000 Subject: [PATCH 02/13] Simpler cancellation --- index.bs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 01c78be..2392540 100644 --- a/index.bs +++ b/index.bs @@ -238,8 +238,8 @@ outside any individual Document process's event loop, and is accessed asynchrono inter-process communication mechanism.
-To cancel a pending tool execution given a [=traversable -navigable=] |traversable| and a [=unique internal value=] |uuid|: +To cancel a pending tool execution given a [=traversable navigable=] |traversable| and a +[=unique internal value=] |uuid|: 1. [=Assert=]: these steps are running [=in parallel=]. @@ -309,9 +309,8 @@ follows: Note: This removes |execution| from the [=traversable navigable/pending tool executions map=]. - 1. If |document| is |execution|'s [=pending tool execution/caller document=], then - [=pending tool execution/cancel a pending tool execution|cancel=] given |traversable| and - |uuid|. + 1. If |document| is |execution|'s [=pending tool execution/caller document=], then [=cancel a + pending tool execution=] given |traversable| and |uuid|. 1. [=Assert=]: |traversable|'s [=traversable navigable/pending tool executions map=][|uuid|] does not [=map/exist=]. @@ -968,8 +967,7 @@ The executeTool(tool, inputObject Date: Fri, 14 Aug 2026 18:01:03 +0000 Subject: [PATCH 03/13] Match impl destruction semantics --- index.bs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index 2392540..da06f07 100644 --- a/index.bs +++ b/index.bs @@ -258,6 +258,12 @@ To cancel a pending tool execution given a [=traversable navigable=] 1. Let |targetDocument| be |execution|'s [=pending tool execution/target document=]. + Note: |targetDocument| is guaranteed to still exist (i.e., not be unloaded or destroyed) when + these steps run, because if |targetDocument| had been destroyed, then this specification's unloading document cleanup steps would + have already removed |execution| from the map, and we'd have ended up in the early return path + above. + 1. [=Queue a global task=] on the [=webmcp task source=] given |targetDocument|'s [=relevant global object=] to run the following steps: @@ -302,15 +308,20 @@ follows: 1. Let |execution| be |traversable|'s [=traversable navigable/pending tool executions map=][|uuid|]. - 1.

If |document| is |execution|'s [=pending tool - execution/target document=], then run |execution|'s [=pending tool execution/completion - steps=] given null and false.

+ 1.

If |document| is |execution|'s [=pending tool + execution/target document=] and is not |execution|'s [=pending tool execution/caller + document=], then run |execution|'s [=pending tool execution/completion steps=] given null + and false.

Note: This removes |execution| from the [=traversable navigable/pending tool executions map=]. - 1. If |document| is |execution|'s [=pending tool execution/caller document=], then [=cancel a - pending tool execution=] given |traversable| and |uuid|. + 1.

Otherwise, if |document| is |execution|'s [=pending tool + execution/caller document=] and is not |execution|'s [=pending tool execution/target + document=], then [=cancel a pending tool execution=] given |traversable| and |uuid|.

+ + 1. Otherwise, [=map/Remove=] |traversable|'s [=traversable navigable/pending tool executions + map=][|uuid|]. 1. [=Assert=]: |traversable|'s [=traversable navigable/pending tool executions map=][|uuid|] does not [=map/exist=]. From bb8bd397d4952d9fe8b47e90ccafd60394259b3b Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Mon, 17 Aug 2026 17:45:51 +0000 Subject: [PATCH 04/13] Remove small stuff --- index.bs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/index.bs b/index.bs index da06f07..6276a45 100644 --- a/index.bs +++ b/index.bs @@ -143,9 +143,6 @@ A model context is a [=struct=] with the following [=struct/items=]: A local pending tool execution is a [=struct=] with the following [=struct/items=]:
- : tool name - :: a [=string=]. - : abort controller :: an {{AbortController}}.
@@ -420,11 +417,6 @@ and a [=boolean=] success. 1. [=Assert=]: these steps are running on |targetDocument|'s [=relevant agent=]'s [=agent/event loop=]. -1. Let |traversable| be |targetDocument|'s [=node navigable=]'s [=navigable/traversable navigable=]. - -1. If |traversable|'s [=traversable navigable/pending tool executions map=][|uuid|] does not - [=map/exist=], then return. - 1. Let |controller| be a [=new=] {{AbortController}} created in |targetDocument|'s [=relevant realm=]. @@ -432,9 +424,6 @@ and a [=boolean=] success. 1. Let |localExecution| be a new [=local pending tool execution=] with the following [=struct/items=]: - : [=local pending tool execution/tool name=] - :: |toolName| - : [=local pending tool execution/abort controller=] :: |controller| From c84c3c8a8f126ff74bfaa04187f2c7fdca559e92 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Mon, 17 Aug 2026 18:16:20 +0000 Subject: [PATCH 05/13] Move toolMap checks back in tool execute steps --- index.bs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 6276a45..5dbc9a4 100644 --- a/index.bs +++ b/index.bs @@ -417,6 +417,12 @@ and a [=boolean=] success. 1. [=Assert=]: these steps are running on |targetDocument|'s [=relevant agent=]'s [=agent/event loop=]. +1. Let |toolMap| be |targetDocument|'s [=Document/associated ModelContext|associated + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/tool map=]. + +1. If |toolMap|[|toolName|] does not [=map/exist=], then run |completionSteps| given null and false, + and abort these steps. + 1. Let |controller| be a [=new=] {{AbortController}} created in |targetDocument|'s [=relevant realm=]. @@ -431,13 +437,6 @@ and a [=boolean=] success. ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending executions map=][|uuid|] to |localExecution|. -1. Let |toolMap| be |targetDocument|'s [=Document/associated ModelContext|associated - ModelContext=]'s [=ModelContext/internal context=]'s [=model context/tool map=]. - -1. If |toolMap|[|toolName|] does not [=map/exist=], then [=model context/finish a tool - execution|finish=] given |targetDocument|, |uuid|, null, false, and |completionSteps|, and abort - these steps. - Issue: Support the plumbing of more granular errors back to the invoker; this should result in a "{{NotFoundError}}" in the calling document. From 9813c15de9200ae8ff755400662a38d650a1ca82 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Mon, 17 Aug 2026 18:19:24 +0000 Subject: [PATCH 06/13] Move issue too --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 5dbc9a4..c0ba6a5 100644 --- a/index.bs +++ b/index.bs @@ -423,6 +423,9 @@ and a [=boolean=] success. 1. If |toolMap|[|toolName|] does not [=map/exist=], then run |completionSteps| given null and false, and abort these steps. + Issue: Support the plumbing of more granular errors back to the invoker; this should result in a + "{{NotFoundError}}" in the calling document. + 1. Let |controller| be a [=new=] {{AbortController}} created in |targetDocument|'s [=relevant realm=]. @@ -437,9 +440,6 @@ and a [=boolean=] success. ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending executions map=][|uuid|] to |localExecution|. - Issue: Support the plumbing of more granular errors back to the invoker; this should result in a - "{{NotFoundError}}" in the calling document. -

This protects us against a race between tool unregistration and execution. While tool existence is protected from this race, tool unregistration followed by a quick From 20c1f952e6167cd27478a0641d504241f75aa882 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Mon, 17 Aug 2026 18:20:42 +0000 Subject: [PATCH 07/13] Move example too --- index.bs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/index.bs b/index.bs index c0ba6a5..6728eda 100644 --- a/index.bs +++ b/index.bs @@ -426,20 +426,6 @@ and a [=boolean=] success. Issue: Support the plumbing of more granular errors back to the invoker; this should result in a "{{NotFoundError}}" in the calling document. -1. Let |controller| be a [=new=] {{AbortController}} created in |targetDocument|'s [=relevant - realm=]. - -1. Let |signal| be |controller|'s [=AbortController/signal=]. - -1. Let |localExecution| be a new [=local pending tool execution=] with the following [=struct/items=]: - - : [=local pending tool execution/abort controller=] - :: |controller| - -1. Set |targetDocument|'s [=Document/associated ModelContext|associated - ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending - executions map=][|uuid|] to |localExecution|. -

This protects us against a race between tool unregistration and execution. While tool existence is protected from this race, tool unregistration followed by a quick @@ -473,6 +459,20 @@ and a [=boolean=] success.

+1. Let |controller| be a [=new=] {{AbortController}} created in |targetDocument|'s [=relevant + realm=]. + +1. Let |signal| be |controller|'s [=AbortController/signal=]. + +1. Let |localExecution| be a new [=local pending tool execution=] with the following [=struct/items=]: + + : [=local pending tool execution/abort controller=] + :: |controller| + +1. Set |targetDocument|'s [=Document/associated ModelContext|associated + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending + executions map=][|uuid|] to |localExecution|. + 1. Let |tool| be |toolMap|[|toolName|]. 1. Run |tool|'s [=tool definition/execute steps=] given |targetDocument|, |inputArguments|, From 0800586f404e9d6930f811ae501c41dc6577645f Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Mon, 17 Aug 2026 18:54:40 +0000 Subject: [PATCH 08/13] Reorder and remove the separate finish algorithm --- index.bs | 87 +++++++++++++++++++++++++++----------------------------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/index.bs b/index.bs index 6728eda..dd75485 100644 --- a/index.bs +++ b/index.bs @@ -392,22 +392,6 @@ a [=list=] of [=origins=] |exposed origins|, and an [=origin=] |accessing origin
-
-To finish a tool execution given a {{Document}} |targetDocument|, a -[=unique internal value=] |uuid|, a [=string=]-or-null |result|, a [=boolean=] |success|, and an -algorithm |completionSteps|: - -1. [=Assert=]: these steps are running on |targetDocument|'s [=relevant agent=]'s [=agent/event - loop=]. - -1. [=map/Remove=] |targetDocument|'s [=Document/associated ModelContext|associated - ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending - executions map=][|uuid|]. - -1. Run |completionSteps| given |result| and |success|. - -
-
The tool execute steps, given a [=string=] |toolName|, a {{Document}} |targetDocument|, a [=string=] |inputArguments|, an algorithm |completionSteps|, and a [=unique internal value=] |uuid|, @@ -459,20 +443,6 @@ and a [=boolean=] success.
-1. Let |controller| be a [=new=] {{AbortController}} created in |targetDocument|'s [=relevant - realm=]. - -1. Let |signal| be |controller|'s [=AbortController/signal=]. - -1. Let |localExecution| be a new [=local pending tool execution=] with the following [=struct/items=]: - - : [=local pending tool execution/abort controller=] - :: |controller| - -1. Set |targetDocument|'s [=Document/associated ModelContext|associated - ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending - executions map=][|uuid|] to |localExecution|. - 1. Let |tool| be |toolMap|[|toolName|]. 1. Run |tool|'s [=tool definition/execute steps=] given |targetDocument|, |inputArguments|, @@ -485,30 +455,40 @@ and a [=boolean=] success.
The imperative execute steps, given a {{ModelContextTool}} |tool|, a {{Document}} -|targetDocument|, a [=string=] |inputArguments|, an algorithm |completionSteps|, an {{AbortSignal}} -|signal|, and a [=unique internal value=] |uuid|, are as follows: +|targetDocument|, a [=string=] |inputArguments|, an algorithm |completionSteps|, and a [=unique +internal value=] |uuid|, are as follows: 1. [=Assert=]: these steps are running on |targetDocument|'s [=relevant agent=]'s [=agent/event loop=]. 1. Let |inputObject| be the result of [=parse a JSON string to a JavaScript value=] given |inputArguments| and |targetDocument|'s [=relevant realm=]. If exception was thrown, then [=model context/finish a tool execution|finish=] given - |targetDocument|, |uuid|, null, false, and |completionSteps|, and abort these steps. + thrown">exception was thrown, then run |completionSteps| given null and false, and abort + these steps. Issue: Support more granular errors; here we should return something that prompts the caller to reject its {{Promise}} with a "{{DataError}}" {{DOMException}}. -1. If |inputObject| [=Object type|is not an Object=] is false, then [=model context/finish a tool - execution|finish=] given |targetDocument|, |uuid|, null, false, and |completionSteps|, and abort - these steps. +1. If |inputObject| [=Object type|is not an Object=] is false, then run |completionSteps| given null + and false, and abort these steps. Issue(#146): Specify and fire the "toolactivated" event. +1. Let |controller| be a [=new=] {{AbortController}} created in |targetDocument|'s [=relevant + realm=]. + +1. Let |localExecution| be a new [=local pending tool execution=] with the following [=struct/items=]: + + : [=local pending tool execution/abort controller=] + :: |controller| + +1. Set |targetDocument|'s [=Document/associated ModelContext|associated + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending + executions map=][|uuid|] to |localExecution|. 1. Let |options| be a new {{ToolExecuteCallbackOptions}} dictionary, with the following fields: : {{ToolExecuteCallbackOptions/signal}} - :: |signal| + :: |controller|'s [=AbortController/signal=] 1. Let |toolPromise| be the result of [=invoke|invoking=] |tool|'s {{ModelContextTool/execute}} with |inputObject| and |options|. @@ -517,20 +497,37 @@ The imperative execute steps, given a {{ModelContextTool}} |tool|, a - If |toolPromise| was fulfilled with value |v|: + 1. Let |localExecutions| be |targetDocument|'s [=Document/associated ModelContext|associated + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending + executions map=]. + + 1. If |localExecutions|[|uuid|] does not [=map/exist=], then return. + + Note: The entry corresponding to |uuid| will not exist if the execution was [=cancel a + pending tool execution|cancelled=] (and thus the corresponding entry was removed) before + the developer's |toolPromise| settles. + + 1. [=map/Remove=] |localExecutions|[|uuid|]. + 1. Let |serializedResult| be the result of [=serializing a JavaScript value to a JSON - string=] given |v|. If this throws an exception, [=model context/finish a tool - execution|finish=] given |targetDocument|, |uuid|, null, false, and |completionSteps|, and - abort these steps. + string=] given |v|. If this throws an exception, run |completionSteps| given null and + false, and abort these steps. - 1. [=model context/finish a tool execution|finish=] given |targetDocument|, |uuid|, - |serializedResult|, true, and |completionSteps|. + 1. Run |completionSteps| given |serializedResult| and true. - If |toolPromise| was rejected with reason |r|, then: + 1. Let |localExecutions| be |targetDocument|'s [=Document/associated ModelContext|associated + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending + executions map=]. + + 1. If |localExecutions|[|uuid|] does not [=map/exist=], then return. + + 1. [=map/Remove=] |localExecutions|[|uuid|]. + 1. Optionally [=report a warning to the console=] describing |r|. - 1. [=model context/finish a tool execution|finish=] given |targetDocument|, |uuid|, null, - false, and |completionSteps|. + 1. Run |completionSteps| given null and false.
From ae03ea2111640d80007a2bacc09639d7c072a647 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Mon, 17 Aug 2026 19:03:04 +0000 Subject: [PATCH 09/13] Use full algorithm name --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index dd75485..155100a 100644 --- a/index.bs +++ b/index.bs @@ -963,7 +963,7 @@ The executeTool(tool, inputObject Date: Mon, 17 Aug 2026 19:04:11 +0000 Subject: [PATCH 10/13] One less nesting level --- index.bs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 155100a..c8fa873 100644 --- a/index.bs +++ b/index.bs @@ -961,9 +961,7 @@ The executeTool(tool, inputObject Date: Tue, 18 Aug 2026 13:03:52 -0400 Subject: [PATCH 11/13] Address some review comments --- index.bs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/index.bs b/index.bs index c8fa873..d30ef10 100644 --- a/index.bs +++ b/index.bs @@ -135,19 +135,11 @@ A model context is a [=struct=] with the following [=struct/items=]: :: a [=map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are [=tool definition=] [=structs=]. - : pending executions map + : local pending tool executions map :: a [=map=] whose [=map/keys=] are [=unique internal values=] and whose [=map/values=] are [=local pending tool execution=] [=structs=]. It is initially empty. -A local pending tool execution is a [=struct=] with the following [=struct/items=]: - -
- : abort controller - :: an {{AbortController}}. -
- - A tool definition is a [=struct=] with the following [=struct/items=]:
@@ -197,6 +189,13 @@ A tool definition is a [=struct=] with the following [=struct/items=] :: a [=list=] or [=origins=], initially [=list/empty=].
+A local pending tool execution is a [=struct=] with the following [=struct/items=]: + +
+ : abort controller + :: an {{AbortController}}. +
+ An annotations is a [=struct=] with the following [=struct/items=]:
@@ -265,8 +264,8 @@ To cancel a pending tool execution given a [=traversable navigable=] object=] to run the following steps: 1. Let |localExecutions| be |targetDocument|'s [=Document/associated ModelContext|associated - ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending - executions map=]. + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/local + pending tool executions map=]. 1. If |localExecutions|[|uuid|] does not [=map/exist=], then return. @@ -483,8 +482,8 @@ internal value=] |uuid|, are as follows: :: |controller| 1. Set |targetDocument|'s [=Document/associated ModelContext|associated - ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending - executions map=][|uuid|] to |localExecution|. + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/local pending + tool executions map=][|uuid|] to |localExecution|. 1. Let |options| be a new {{ToolExecuteCallbackOptions}} dictionary, with the following fields: : {{ToolExecuteCallbackOptions/signal}} @@ -498,8 +497,8 @@ internal value=] |uuid|, are as follows: - If |toolPromise| was fulfilled with value |v|: 1. Let |localExecutions| be |targetDocument|'s [=Document/associated ModelContext|associated - ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending - executions map=]. + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/local + pending tool executions map=]. 1. If |localExecutions|[|uuid|] does not [=map/exist=], then return. @@ -518,8 +517,8 @@ internal value=] |uuid|, are as follows: - If |toolPromise| was rejected with reason |r|, then: 1. Let |localExecutions| be |targetDocument|'s [=Document/associated ModelContext|associated - ModelContext=]'s [=ModelContext/internal context=]'s [=model context/pending - executions map=]. + ModelContext=]'s [=ModelContext/internal context=]'s [=model context/local + pending tool executions map=]. 1. If |localExecutions|[|uuid|] does not [=map/exist=], then return. @@ -1172,7 +1171,7 @@ The {{ToolExecuteCallbackOptions}} dictionary carries options passed to a tool's
: options["{{ToolExecuteCallbackOptions/signal}}"] - :: An {{AbortSignal}} that communicates when the execution of the tool has been aborted. + :: An {{AbortSignal}} that communicates when the execution of the tool has been cancelled.

ModelContextRegisterToolOptions Dictionary

From a2cdf190e311ba53e8ab0fc4181ae7306cbf7008 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Tue, 18 Aug 2026 13:11:51 -0400 Subject: [PATCH 12/13] Note --- index.bs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index d30ef10..5ddcc62 100644 --- a/index.bs +++ b/index.bs @@ -136,8 +136,15 @@ A model context is a [=struct=] with the following [=struct/items=]: [=structs=]. : local pending tool executions map - :: a [=map=] whose [=map/keys=] are [=unique internal values=] and whose [=map/values=] are [=local - pending tool execution=] [=structs=]. It is initially empty. + :: a [=map=] whose [=map/keys=] are [=unique internal values=] and whose [=map/values=] are + [=local pending tool execution=] [=structs=]. It is initially empty. + + Note: this map is similar to a [=traversable navigable=]'s [=traversable navigable/pending tool + executions map=], but it only contains pending execution information for tools under a single + {{ModelContext}} object. It is used to store objects that can only be accessed from that + object's event loop, and because it is event-loop-local, it can get out of sync from the + [=traversable navigable=]'s more "global" map. +
A tool definition is a [=struct=] with the following [=struct/items=]: From 3bab85866e2ef9fa47d68eb42034424676e38e18 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Tue, 18 Aug 2026 13:22:39 -0400 Subject: [PATCH 13/13] Move warning --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 5ddcc62..0fa178e 100644 --- a/index.bs +++ b/index.bs @@ -523,6 +523,8 @@ internal value=] |uuid|, are as follows: - If |toolPromise| was rejected with reason |r|, then: + 1. Optionally [=report a warning to the console=] describing |r|. + 1. Let |localExecutions| be |targetDocument|'s [=Document/associated ModelContext|associated ModelContext=]'s [=ModelContext/internal context=]'s [=model context/local pending tool executions map=]. @@ -531,8 +533,6 @@ internal value=] |uuid|, are as follows: 1. [=map/Remove=] |localExecutions|[|uuid|]. - 1. Optionally [=report a warning to the console=] describing |r|. - 1. Run |completionSteps| given null and false.