evOo-local/packages/adapters/src/browser-tools.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

114 lines
3.5 KiB
TypeScript

import type {
AdapterContext,
BrowserActKind,
BrowserActStep,
BrowserProvider,
ComputerRef,
} from "@rakazo/adapter-kit";
const MAX_BROWSER_ACTIONS = 24;
export async function browserNavigateFromTool(
browser: BrowserProvider,
computer: ComputerRef,
context: AdapterContext,
args: Record<string, unknown>,
) {
const url = String(args.url ?? "").trim();
try {
const parsed = new URL(url);
if (!/^https?:$/.test(parsed.protocol) || parsed.username || parsed.password) {
return {
error: "An HTTP(S) URL without embedded credentials is required",
fallback: "computer_act" as const,
};
}
const result = await browser.navigate(computer, { url, signal: context.signal }, context);
return formatBrowserResult(result);
} catch (error) {
context.signal.throwIfAborted();
return {
error: error instanceof Error ? error.message : String(error),
fallback: "computer_act" as const,
};
}
}
export async function browserSnapshotFromTool(
browser: BrowserProvider,
computer: ComputerRef,
context: AdapterContext,
args: Record<string, unknown>,
) {
void args;
try {
const result = await browser.snapshot(computer, { signal: context.signal }, context);
return formatBrowserResult(result);
} catch (error) {
context.signal.throwIfAborted();
return {
error: error instanceof Error ? error.message : String(error),
fallback: "computer_act" as const,
};
}
}
export async function browserActFromTool(
browser: BrowserProvider,
computer: ComputerRef,
context: AdapterContext,
args: Record<string, unknown>,
) {
try {
const actions = parseBrowserActions(args.actions);
const result = await browser.act(computer, { actions, signal: context.signal }, context);
return formatBrowserResult(result);
} catch (error) {
context.signal.throwIfAborted();
return {
ok: false,
uncertain: true,
error: error instanceof Error ? error.message : String(error),
fallback: "computer_act" as const,
};
}
}
export function parseBrowserActions(value: unknown): BrowserActStep[] {
if (!Array.isArray(value) || value.length === 0) {
throw new Error("browser_act requires at least one action");
}
if (value.length > MAX_BROWSER_ACTIONS) {
throw new Error(`browser_act accepts at most ${MAX_BROWSER_ACTIONS} actions`);
}
return value.map((raw, index) => {
if (!raw || typeof raw !== "object") {
throw new Error(`browser_act action ${index} must be an object`);
}
const action = raw as Record<string, unknown>;
const kind = String(action.kind ?? "") as BrowserActKind;
if (kind !== "click" && kind !== "fill" && kind !== "type") {
throw new Error(`browser_act action ${index} has unsupported kind`);
}
const ref = String(action.ref ?? "").trim();
if (!ref) throw new Error(`browser_act action ${index} requires ref`);
if (kind === "fill" || kind === "type") {
if (typeof action.text !== "string") {
throw new Error(`browser_act ${kind} requires text`);
}
return { kind, ref, text: String(action.text) };
}
return { kind, ref };
});
}
function formatBrowserResult<T extends { fallback?: "computer_act"; error?: string }>(result: T) {
if (result.fallback === "computer_act") {
return {
...result,
note: "Page browser could not complete this step. Inspect the current state before continuing with computer_act if available, otherwise request_takeover. Do not replay completed or uncertain actions.",
};
}
return result;
}