diff --git a/index.bs b/index.bs index 47f85a7..8cd4180 100644 --- a/index.bs +++ b/index.bs @@ -173,7 +173,8 @@ A tool definition is a [=struct=] with the following [=struct/items=] : execute steps :: an algorithm that takes a {{Document}} targetDocument, a [=string=] inputArguments, an algorithm completionSteps that takes a - [=string=]-or-null and a [=boolean=], and a [=unique internal value=] uuid. + [=string=]-or-null and a [=tool execution error=]-or-null, and a [=unique internal value=] + uuid. Note: These steps [=invoke the execute callback=] of the {{ModelContextTool}} passed to {{ModelContext/registerTool()}}. @@ -208,6 +209,50 @@ An annotations is a [=struct=] with the following [=struct/items=]: :: a [=boolean=], initially false. +A tool execution error represents the reason that a tool execution failed. It is one of +the values in the following table, each of which has a corresponding +exception name. + +
| Value + | Description + | [=tool execution error/Corresponding exception name=] + |
|---|---|---|
| tool not found + | The tool being executed is not registered in the target {{Document}}. + | "{{NotFoundError}}" + |
| invalid input + | The input arguments could not be parsed into a JavaScript Object. + | "{{DataError}}" + |
| tool rejected + | The tool's {{ModelContextTool/execute}} callback returned a rejected {{Promise}}. + | "{{OperationError}}" + |
| unserializable result + | The tool's result could not be serialized to a JSON string. + | "{{OperationError}}" + |
| target document unloaded + | The target {{Document}} was unloaded before the execution completed. + | "{{AbortError}}" + |
[=Tool execution errors=] are not exposed to the web. They exist as a spec concept to give tool + execution algorithms a realm-agnostic set of failure reasons that correspond to {{DOMException}} + types that do get exposed to the caller; the caller only ever observes the [=tool + execution error/corresponding exception name=].
+ +Also note that this table only tracks the list of platform-defined + failures. Tool execution failures defined by tool authors can also be reported + to the tool caller in the response JSON string.
+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.
+ and [=tool execution error/target document unloaded=]. Note: This removes |execution| from the [=traversable navigable/pending tool executions map=]. @@ -400,7 +445,7 @@ a [=list=] of [=origins=] |exposed origins|, and an [=origin=] |accessing origin The tool execute steps, given a [=string=] |toolName|, a {{Document}} |targetDocument|, a [=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. +and a [=tool execution error=]-or-null error. 1. [=Assert=]: these steps are running on |targetDocument|'s [=relevant agent=]'s [=agent/event loop=]. @@ -408,11 +453,8 @@ and a [=boolean=] success. 1. Let |toolMap| be |targetDocument|'s [=Document/associated ModelContext|associatedModelContext=]'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.
-
- Issue: Support the plumbing of more granular errors back to the invoker; this should result in a
- "{{NotFoundError}}" in the calling document.
+1. If |toolMap|[|toolName|] does not [=map/exist=], then run |completionSteps| given null and
+ [=tool execution error/tool not found=], and abort these steps.
This protects us against a race between tool unregistration and execution. While tool
@@ -464,13 +506,10 @@ internal value=] |uuid|, run these steps:
1. Let |inputObject| be the result of [=parse a JSON string to a JavaScript value=] given
|inputArguments|. If 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}}.
+ |completionSteps| given null and [=tool execution error/invalid input=], and abort these steps.
1. If |inputObject| [=Object type|is not an Object=], then run |completionSteps| given null
- and false, and abort these steps.
+ and [=tool execution error/invalid input=], and abort these steps.
1. [=Fire an event=] named {{ModelContext/toolactivated}} at |targetDocument|'s [=Document/associated
ModelContext|associated ModelContext=], using {{ToolActivatedEvent}}, with its
@@ -513,9 +552,9 @@ internal value=] |uuid|, run these steps:
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.
+ [=tool execution error/unserializable result=], and abort these steps.
- 1. Run |completionSteps| given |serializedResult| and true.
+ 1. Run |completionSteps| given |serializedResult| and null.
- If |toolPromise| was rejected with reason |r|, then:
@@ -529,7 +568,7 @@ internal value=] |uuid|, run these steps:
1. [=map/Remove=] |localExecutions|[|uuid|].
- 1. Run |completionSteps| given null and false.
+ 1. Run |completionSteps| given null and [=tool execution error/tool rejected=].
ModelContext=]'s [=ModelContext/internal context=]'s [=model context/tool map=].
@@ -993,21 +1028,25 @@ The executeTool(tool, inputObject
1. If |targetToolMap|[|toolName|] does not [=map/exist=], then [=queue a global task=] on the
[=webmcp task source=] given |callerDocument|'s [=relevant global object=] to [=reject=]
- |promise| with an "{{UnknownError}}" {{DOMException}}, and abort these steps.
-
- Issue: Support more granular errors than "{{UnknownError}}", based on each failure case.
+ |promise| with a "{{NotFoundError}}" {{DOMException}}, and abort these steps.
1. Let |tool definition| be |targetToolMap|[|toolName|].
1. If [=tool is exposed to an origin=] given |targetOrigin|, |tool definition|'s [=tool
definition/exposed origins=], and |callerOrigin| returns false, then [=queue a global task=]
on the [=webmcp task source=] given |callerDocument|'s [=relevant global object=] to
- [=reject=] |promise| with an "{{UnknownError}}" {{DOMException}}, and abort these steps.
+ [=reject=] |promise| with a "{{NotFoundError}}" {{DOMException}}, and abort these steps.
- Issue: Support more granular errors than "{{UnknownError}}", based on each failure case.
+ Note: Rejecting with "{{NotFoundError}}" across all three checks above—when |targetOrigin| does
+ not match |expectedTargetOrigin|, when |toolName| does not exist, and when |tool definition|
+ is not exposed to |callerOrigin|—prevents a cross-origin caller from using fabricated
+ {{RegisteredTool}} dictionaries to probe |targetDocument|'s current origin or enumerate
+ private tools not exposed to |callerOrigin|.
1. Let |completionSteps| be an algorithm that takes a [=string=]-or-null |result| and a
- [=boolean=] |success|, and runs the following steps [=in parallel=]:
+ [=tool execution error=]-or-null |error|, and runs the following steps [=in parallel=]:
+
+ 1. [=Assert=]: exactly one of |result| and |error| is null.
1. If |targetDocument|'s [=node navigable=]'s [=navigable/traversable navigable=]'s
[=traversable navigable/pending tool executions map=][|uuid|] does not [=map/exist=],
@@ -1026,12 +1065,16 @@ The executeTool(tool, inputObject
1. [=map/Remove=] |targetDocument|'s [=node navigable=]'s [=navigable/traversable
navigable=]'s [=traversable navigable/pending tool executions map=][|uuid|].
- 1. If |success| is true, then [=queue a global task=] on the [=webmcp task source=] given
+ 1. If |error| is null, then [=queue a global task=] on the [=webmcp task source=] given
|callerDocument|'s [=relevant global object=] to [=resolve=] |promise| with |result|.
1. Otherwise, [=queue a global task=] on the [=webmcp task source=] given |callerDocument|'s
- [=relevant global object=] to [=reject=] |promise| with an "{{UnknownError}}"
- {{DOMException}}.
+ [=relevant global object=] to [=reject=] |promise| with a {{DOMException}} whose name is
+ |error|'s [=tool execution error/corresponding exception name=].
+
+ Note: Passing only a [=tool execution error=] (rather than any internal JavaScript
+ exception object or message thrown in |targetDocument|) prevents leaking provider
+ implementation details or stack traces across origins.
1. Let |execution| be a new [=pending tool execution=], with the following [=struct/items=]: