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.
114 lines
3.5 KiB
TypeScript
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;
|
|
}
|