blob: 0249ca74a1bb5c3c0055c95a37f03f9c61048ef7 [file]
// 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_