app/utils/ModelContext.ts
Rama main · réplica de lectura del repositorio original.
import { toError } from "@shared/utils/error";
import Logger from "~/utils/Logger";
/** A content block returned from a WebMCP tool execution. */
export interface ModelContextContent {
type: "text";
text: string;
}
/** The result payload returned from a WebMCP tool execution. */
export interface ModelContextToolResult {
content: ModelContextContent[];
isError?: boolean;
}
/** A tool descriptor for registration with the browser's model context. */
export interface ModelContextTool {
/** Unique tool identifier, e.g. "star_document". */
name: string;
/** Natural language description of the tool for agents. */
description: string;
/** JSON Schema describing the tool arguments. */
inputSchema?: Record<string, unknown>;
/** Callback invoked when an agent runs the tool. */
execute: (
args: Record<string, unknown>,
options?: { signal?: AbortSignal }
) => Promise<ModelContextToolResult>;
}
interface ModelContext {
registerTool: (
tool: ModelContextTool,
options?: { signal?: AbortSignal }
) => void | Promise<void>;
}
declare global {
interface Document {
/** Experimental WebMCP API, shipping behind a flag in Chrome 146+. */
modelContext?: ModelContext;
}
}
/**
* Returns true when the browser supports the WebMCP model context API.
*
* @returns true when `document.modelContext` is available.
*/
export function isModelContextSupported(): boolean {
return typeof window.document.modelContext?.registerTool === "function";
}
/**
* Registers a tool with the browser's model context, skipping registration
* when the API is unavailable or a tool with the same name is already
* registered elsewhere in the app.
*
* @param tool the tool descriptor to register.
* @param signal aborting the signal unregisters the tool.
* @returns true when the tool was registered.
*/
export function registerModelContextTool(
tool: ModelContextTool,
signal: AbortSignal
): boolean {
if (
!isModelContextSupported() ||
signal.aborted ||
registeredToolNames.has(tool.name)
) {
return false;
}
registeredToolNames.add(tool.name);
signal.addEventListener(
"abort",
() => registeredToolNames.delete(tool.name),
{ once: true }
);
try {
const result = window.document.modelContext?.registerTool(tool, {
signal,
});
if (result instanceof Promise) {
result.catch((err: unknown) => {
// An aborted signal rejects a pending registration; this is normal
// teardown, not a failure.
const error = toError(err);
if (signal.aborted || error.name === "AbortError") {
return;
}
Logger.warn("Failed to register WebMCP tool", {
name: tool.name,
error: error.message,
});
registeredToolNames.delete(tool.name);
});
}
} catch (err) {
Logger.warn("Failed to register WebMCP tool", {
name: tool.name,
error: toError(err).message,
});
registeredToolNames.delete(tool.name);
return false;
}
return true;
}
/** Names of tools currently registered, to prevent duplicate registration. */
const registeredToolNames = new Set<string>();