Repository navigation
Where to fire toolactivated and toolcanceled events? #126
Description
Activity
How about making
navigator.modelContextinherits fromEventTargetand have bothnavigator.modelContext.ontoolactivatedandnavigator.modelContext.ontoolcanceledattribute handlers?Reacted by Alex NahasHaving the
modelContextbe the event target for all tool events has a similar drawback to using theWindowas the target: all of your tool events end up in one place, and you may need a big if statement to handle all the different kinds.Toolas a new interface that extendsEventTargetis an interesting idea.If
modelContext.registerToolreturned aTool, that could serve as both the event target, and a means of unregistering the tool, which would address some of the concerns about unrelated javascript code unregistering tools you registered (#101)We did consider that in discussion today. My first thought was that these events are really only useful for declarative tools, and should probably not fire for imperative tools at all. So they should definitely fire on the
<form>that backs the tool /cc @mfreed7.However, I'm not sold that these events are only useful for declarative tools, especially if we decide (as I suspect we will) that developers are responsible for managing concurrency between tools on their own, just as they are responsible for managing concurrency between JavaScript functions that get triggered by (a) other script, (b) user interactions. Designing for that world, then these events are useful for both imperative and declarative, and I vote we mostly follow the approach @beaufortfrancois mentioned above 👍
- Make
ModelContextinheritEventTarget - Introduce a
ToolActivatedEvent(notWebMCPEventlike Chromium has)- Give it a
toolNameattribute, like Chromium has - Give it an
elementmember that points to the form element, if the event represents a declarative tool
- Give it a
- Introduce a
ToolCanceledEventwith similar properties
This should give developers that need to manage concurrency a good global place to do so, which I suspect is important because concurrency managers might be different from tool authors.
- Make
Oops sorry, I wrote my comment before @bwalderman's, but didn't hit send or see his until way later!... will review in the context of it...
I dumped my thoughts on tool unregistration mechanisms in #130, and after I think I'm convinced that the events should fire on
navigator.modelContext, since it's unlikely that tool authors are going to be the same people adding event listeners for these events.If I'm wrong: (a) we can always reconsider where we fire the events, and (b) there should be enough information on the event I proposed above, to direct the handler to the relevant
<form>, or possiblyToolobject if we decide to introduce it too. Would love your thoughts.RESOLUTION: The toolactivated and toolcanceled events are fired on the ModelContext object. (issue #126)
Looking at #146 , I noticed that
toolactivatedandtoolcanceledevents as defined there may not work if multiple concurrent activations of the same tool are allowed. An easy solution here would be to add a unique "activation ID" on the activation events and include that ID with the cancellation event so that the code listening for the event can match them up.Reacted by Dominic Farolino and Jeffrey YasskinGood call. I like the idea of including a UUID on the event, similar to what the modern Navigation API does with
NavigateEvent.destination.id, which holds a UUID. One slightly unfortunate thing is that we'd be storing state directly on the event though, instead of indirectly referencing state on some more stable object; this cuts against what https://w3ctag.github.io/design-principles/#state-and-subclassing recommends, but I don't see a great way around it. Even if we eventually introduce aToolinterface, I don't think that solves this either, since there would be multiple events with different UUIDs pointing at the sameTool.... this probably isn't too bad though?The upcoming
executeTool()could also hypothetically return aToolExecutionEventTarget interface which would contain a state attribute developers could inspect like the following:const toolExecution = await navigator.modelContext.executeTool({...}); console.log(toolExecution.id); // UUID navigator.modelContext.ontoolactivated = (event) => { console.assert(toolExecution.state === "activated"); } navigator.modelContext.ontoolcancel = (event) => { console.assert(toolExecution.state === "cancel"); }
Nevermind, this approach doesn't follow https://w3ctag.github.io/design-principles/#state-and-subclassing.
What shall we do then?
I think we can't limit these events to firing on anything associated with the
executeTool()API, since tools will mostly be fired outside of that API.but I don't see a great way around it
[...]
What shall we do then?
I think we proceed as planned—storing the UUID on the event itself. I don't think it's as bad as storing tons of other state and execution details on the event. I think we should plan to do this, but let me also ping @jyasskin from the TAG, to see if he's OK with this.
Reacted by François BeaufortSorry for my high latency. If you want to check something with the TAG, it may be more efficient to post to https://github.com/w3ctag/design-reviews/issues/new?template=025-question.md so that any member can answer, and so you're more likely to get a consensus opinion.
I don't have a strong opinion here, but some thoughts:
- My understanding of declarative tools is that they consist of filling in a form and then submitting it. There's mention of the user reviewing the filled form, and of the submit sometimes causing a navigation. In that context, if an agent tries to run a tool twice concurrently, it's likely to yield inconsistent field values, and one of the tool activations might navigate the page and blow away the other activation's context. So I assume we're talking solely about imperative executions.
- For imperative tools, https://www.w3.org/2026/03/05-webmachinelearning-minutes.html#2afd:~:text=if%20imperative%20tools%20need%20to%20do%20anything%20they%20can%20just%20use%20the%20execute%20function seems right, unless I'm missing a circumstance when there would be actions in between the
toolactivatedevent and theexecutecall? - This would be easier to understand with a flow chart.
- If there's still a need to handle concurrent executions, I wonder if the point in https://w3ctag.github.io/design-principles/#state-and-subclassing about "determine the current state" is relevant. That might argue for exposing an object listing all the active calls, which could be a host for the
idattribute.
Reacted by François BeaufortI think we can close #126 now that #245 has been merged @domfarolino
The declarative API explainer introduces the
toolactivatedandtoolcanceledevents, leaves the issue of where to fire the events for future discussion. This is that discussion. The explainer initially mentions firing the events at theWindowobject, however in #76 (comment), @bwalderman mentions that it might make more sense to fire these events at the<form>element itself that is the declarative tool itself.Initially this makes sense, but if we extend these events to imperative tools, the firing location becomes confusing. For declarative, they'd fire at the element itself, but for imperative they'd fire at
Windowwhich feels odd. I think if we had a dedicatedToolinterface that inherited fromEventTargetto represent imperative tools, then we could have a policy of always firing the events on the tool itself. But absent this, it might make sense to fire all events at the Window and call it a day.Thoughts?