| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_TOOLS_MODEL_CONTEXT_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_TOOLS_MODEL_CONTEXT_H_ |
| |
| #include <optional> |
| |
| #include "base/functional/callback.h" |
| #include "base/types/pass_key.h" |
| #include "base/unguessable_token.h" |
| #include "third_party/blink/public/mojom/content_extraction/script_tools.mojom-blink.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_model_context.h" |
| #include "third_party/blink/renderer/core/core_export.h" |
| #include "third_party/blink/renderer/core/dom/abort_signal.h" |
| #include "third_party/blink/renderer/core/dom/events/event_target.h" |
| #include "third_party/blink/renderer/core/dom/scoped_abort_state.h" |
| #include "third_party/blink/renderer/core/script_tools/script_tool_types.h" |
| #include "third_party/blink/renderer/platform/allow_discouraged_type.h" |
| #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h" |
| #include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h" |
| #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h" |
| |
| namespace blink { |
| class AbortController; |
| class Document; |
| class Element; |
| class ExecuteToolOptions; |
| class SourceLocation; |
| class ModelContextGetToolOptions; |
| class ModelContextRegisterToolOptions; |
| class ModelContextTool; |
| class RegisteredTool; |
| class V8ToolExecuteCallback; |
| |
| class DeclarativeWebMCPTool : public GarbageCollectedMixin { |
| public: |
| // Executes the associated tool and invokes `done_callback` with the result |
| // when the execution is finished. The callback is invoked with a null string |
| // if the execution resulted in a navigation, or an error if the execution |
| // failed. |
| virtual void ExecuteTool( |
| const base::UnguessableToken& invocation_id, |
| String input_arguments, |
| base::OnceCallback<void(base::expected<String, ScriptToolError>)> |
| done_callback) = 0; |
| |
| virtual String ToolName() const = 0; |
| |
| virtual String ToolDescription() const = 0; |
| |
| virtual String ToolTitle() const = 0; |
| |
| // Returns the input json-schema associated with the tool. |
| virtual String ComputeInputSchema() = 0; |
| |
| // The <form> backing this declarative tool. |
| virtual Element* FormElement() const = 0; |
| }; |
| |
| class CORE_EXPORT ToolData : public GarbageCollected<ToolData> { |
| public: |
| // Creates a JS-backed tool. |
| ToolData(base::PassKey<ModelContext>, |
| mojo::StructPtr<mojom::blink::ScriptTool> script_tool, |
| V8ToolExecuteCallback* v8_tool_function, |
| SourceLocation* source_location, |
| AbortSignal::AlgorithmHandle* abort_handle) |
| : script_tool_(std::move(script_tool)), |
| v8_tool_function_(v8_tool_function), |
| source_location_(source_location), |
| abort_algorithm_handle_(abort_handle) {} |
| |
| // Creates a declarative (<form>-backed) tool. |
| ToolData(base::PassKey<ModelContext>, |
| mojo::StructPtr<mojom::blink::ScriptTool> script_tool, |
| DeclarativeWebMCPTool* declarative_tool) |
| : script_tool_(std::move(script_tool)), |
| declarative_tool_(declarative_tool) {} |
| |
| const String& Name() const; |
| |
| const mojom::blink::ScriptTool& ScriptTool() const { return *script_tool_; } |
| |
| // If this is a JS-provided tool, returns the source location |
| // of the call to registerTool(). Otherwise, returns nullptr. |
| SourceLocation* GetSourceLocation() const; |
| |
| // If this is a declarative tool, returns the <form> element |
| // that provided this tool. Otherwise, returns nullptr. |
| Element* BackingFormElement() const; |
| |
| void Trace(Visitor* visitor) const; |
| |
| private: |
| friend class ModelContext; |
| |
| V8ToolExecuteCallback* GetV8ToolExecuteCallback() const { |
| return v8_tool_function_; |
| } |
| DeclarativeWebMCPTool* DeclarativeTool() const { return declarative_tool_; } |
| |
| bool RefreshDeclarativeInputSchema(); |
| |
| mojo::StructPtr<mojom::blink::ScriptTool> script_tool_; |
| // A JS-provided MCP tool: |
| Member<V8ToolExecuteCallback> v8_tool_function_; |
| // Used for declarative (form-based) MCP tools only: |
| Member<DeclarativeWebMCPTool> declarative_tool_; |
| // For JS-provided MCP tools, the location of the registerTool() call. |
| Member<SourceLocation> source_location_; |
| // Keeps the abort algorithm handle alive as long as the tool is registered. |
| Member<AbortSignal::AlgorithmHandle> abort_algorithm_handle_ = nullptr; |
| }; |
| |
| class CORE_EXPORT ModelContext : public EventTarget, |
| public mojom::blink::ModelContext { |
| DEFINE_WRAPPERTYPEINFO(); |
| |
| public: |
| ModelContext(Document& document); |
| |
| DEFINE_ATTRIBUTE_EVENT_LISTENER(toolchange, kToolchange) |
| |
| const AtomicString& InterfaceName() const override; |
| |
| void ForEachScriptTool( |
| base::FunctionRef<void(const mojom::blink::ScriptTool&)>) const; |
| |
| ScriptPromise<IDLUndefined> registerTool( |
| ScriptState* state, |
| ModelContextTool* tool, |
| ModelContextRegisterToolOptions* options); |
| ScriptPromise<IDLSequence<RegisteredTool>> getTools( |
| ScriptState* script_state, |
| const ModelContextGetToolOptions* options = nullptr); |
| ScriptPromise<IDLNullable<IDLString>> executeTool( |
| ScriptState* script_state, |
| RegisteredTool* tool, |
| String input_arguments, |
| const ExecuteToolOptions* options = nullptr); |
| void UnregisterTool(const String& name); |
| |
| std::optional<ScriptToolDeclaration> GetScriptToolDeclaration( |
| const String& name) const; |
| |
| bool ExecuteTool(const base::UnguessableToken& invocation_id, |
| const String& name, |
| const String& input_arguments, |
| ScriptToolExecutedCallback tool_executed_cb); |
| using CrossDocumentScriptToolResultCallback = |
| base::OnceCallback<void(String)>; |
| void GetCrossDocumentScriptToolResult( |
| const base::UnguessableToken& invocation_id, |
| CrossDocumentScriptToolResultCallback result_callback); |
| |
| bool CancelTool(const base::UnguessableToken& invocation_id); |
| |
| void RegisterDeclarativeTool(DeclarativeWebMCPTool* tool); |
| void PauseExecution(); |
| |
| // mojom::blink::ScriptToolReceiver implementation: |
| void NotifyToolChange() override; |
| void ExecuteScriptTool(const base::UnguessableToken& invocation_id, |
| const String& name, |
| const String& input_arguments, |
| ExecuteScriptToolCallback callback) override; |
| |
| void DidFinishParsing(); |
| |
| // Returns registered tools, sorted by CodeUnitCompareLessThan(). |
| HeapVector<Member<const ToolData>> ListTools() const; |
| |
| ExecutionContext* GetExecutionContext() const override; |
| |
| void OnGetScriptToolsCompleted( |
| ScriptPromiseResolver<IDLSequence<RegisteredTool>>* resolver, |
| Vector<mojom::blink::ScriptToolPtr> tools); |
| |
| void OnExecuteScriptToolCompleted( |
| ScriptPromiseResolver<IDLNullable<IDLString>>* resolver, |
| const String& result, |
| bool success); |
| |
| void Trace(Visitor*) const override; |
| |
| private: |
| class ToolFunctionFinishedCallback; |
| class ToolUnregisterAbortAlgorithm; |
| |
| bool IsModelContextAllowed() const; |
| |
| bool ExecuteV8Tool(V8ToolExecuteCallback* tool_function, |
| const base::UnguessableToken& invocation_id, |
| const String& name, |
| const String& input_arguments, |
| AbortController* abort_controller, |
| ScriptToolExecutedCallback tool_executed_cb); |
| void ExecuteDeclarativeTool(DeclarativeWebMCPTool* tool, |
| const base::UnguessableToken& invocation_id, |
| const String& input_arguments, |
| AbortController* abort_controller, |
| ScriptToolExecutedCallback tool_executed_cb); |
| |
| void OnToolFailed(ScriptToolExecutedCallback callback, |
| const base::UnguessableToken& invocation_id, |
| ScriptToolError&& error); |
| |
| void OnToolExecuted( |
| const base::UnguessableToken& invocation_id, |
| base::expected<String, std::pair<ScriptValue, ScriptState*>> result); |
| |
| void MaybeRecordToolCount(); |
| |
| HeapHashMap<String, Member<ToolData>> tool_map_; |
| |
| struct PendingExecution { |
| DISALLOW_NEW(); |
| |
| public: |
| void Trace(Visitor* visitor) const; |
| String tool_name; |
| ScriptToolExecutedCallback callback; |
| base::UnguessableToken invocation_id; |
| Member<AbortController> abort_controller; |
| // Manages the lifetime of the abort algorithm associated with this |
| // execution's AbortSignal. Storing this here ensures the algorithm is |
| // automatically unregistered when the execution completes (i.e. is removed |
| // from `pending_executions_`). |
| std::unique_ptr<ScopedAbortState> scoped_abort_state; |
| }; |
| HeapHashMap<String, PendingExecution> pending_executions_; |
| |
| Vector<CrossDocumentScriptToolResultCallback> |
| cross_document_result_callbacks_; |
| |
| Member<Document> document_; |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
| |
| HeapMojoRemote<mojom::blink::ScriptToolHost> script_tool_host_remote_; |
| HeapMojoRemote<mojom::blink::ModelContextHost> model_context_host_remote_; |
| HeapMojoReceiver<mojom::blink::ModelContext, ModelContext> |
| model_context_receiver_{this, nullptr}; |
| |
| // true when there is a pending or completed task to record the number |
| // of registered tools. |
| bool will_record_tool_count_ = false; |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_TOOLS_MODEL_CONTEXT_H_ |