blob: 7911fd75ceaca6138728357e9067abf603861a89 [file]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Registers Intelletto's tools with Chrome's experimental WebMCP API
// (document.modelContext.registerTool). Instead of duplicating definitions,
// it reads the already-populated ToolRegistry and exposes each tool.
// Gracefully no-ops when the API is not available.
import {z} from 'zod';
import type {ToolDef, ToolRegistry} from './tools';
// WebMCP type declarations - Chrome's experimental API which is not available
// in lib.dom.d.ts yet.
interface WebMCPToolDefinition {
readonly name: string;
readonly description: string;
readonly inputSchema: Record<string, unknown>;
readonly execute: (args: Record<string, unknown>) => Promise<string>;
readonly annotations?: {
readonly readOnlyHint?: boolean;
readonly untrustedContentHint?: boolean;
};
}
interface WebMCPRegisterOptions {
readonly signal?: AbortSignal;
readonly exposedTo?: string[];
}
interface WebMCPModelContext {
registerTool(
tool: WebMCPToolDefinition,
options?: WebMCPRegisterOptions,
): Promise<void>;
}
interface DocumentWithWebMCP extends Document {
readonly modelContext: WebMCPModelContext;
}
function hasWebMCP(document: Document): document is DocumentWithWebMCP {
return 'modelContext' in document && document.modelContext !== undefined;
}
// Convert a Zod schema to a JSON Schema object suitable for WebMCP's
// inputSchema.
function zodToJSONSchema(schema: z.ZodType<unknown>): Record<string, unknown> {
return {...z.toJSONSchema(schema)};
}
// Register all Intelletto tools with WebMCP by reading the shared registry.
// Returns an AbortController whose .abort() method unregisters all tools.
// No-ops if WebMCP is not available.
export function registerWebMCPTools(
registry: ToolRegistry,
): AbortController | null {
if (!hasWebMCP(document)) {
return null;
}
const controller = new AbortController();
const mc = document.modelContext;
for (const tool of registry.list()) {
registerTool(mc, tool, controller.signal);
}
const listener = registry.addToolRegisteredListener((tool) => {
registerTool(mc, tool, controller.signal);
});
controller.signal.addEventListener(
'abort',
() => listener[Symbol.dispose](),
{once: true},
);
return controller;
}
// Register a single ToolDef with WebMCP.
async function registerTool(
mc: WebMCPModelContext,
tool: ToolDef,
signal: AbortSignal,
): Promise<void> {
try {
return await mc.registerTool(
{
name: `perfetto_${tool.name}`,
description: tool.description,
inputSchema: zodToJSONSchema(tool.inputSchema),
execute: async (rawArgs: Record<string, unknown>): Promise<string> => {
// WebMCP passes raw args; delegate to the original callback.
return await tool.callback(rawArgs);
},
annotations: {readOnlyHint: !tool.mutating},
},
{signal},
);
} catch {}
}