aboutsummaryrefslogtreecommitdiff
path: root/src/domains/form.ts
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmail.com>2026-07-28 22:00:33 +0530
committerAman Kumar <145339995+amankumarsingh77@users.noreply.github.com>2026-08-02 19:12:46 +0530
commit86045450c208bc51670dd9554e581f2a62b181c9 (patch)
tree6e1ee7617afd829549ade41e527f07155921a47e /src/domains/form.ts
parent632600ec0a0a68d9327e19bd419a1ae72047955f (diff)
refactor: helpers live in helper modules, not in tool files
form.ts exported resolveAndCall and detailsOf to form-batch.ts, snapshot.ts exported buildTree and collectInteractiveTargets to ref-resolve.ts, and screenshot.ts exported captureWithCrosshair to click.ts — so importing one tool pulled in another tool's whole module. The shared halves move out: ax-tree.ts (the AX tree model, its construction and the queries over it), element-call.ts (calling a function on a resolved element, and the PageResult it returns), screenshot-capture.ts (capture, resize, crosshair overlay). Each tool file now holds its tools and its own presentation, nothing else. NavOutcome stops being exported; nothing outside navigate.ts read it. target.ts stays a module rather than folding into click.ts as planned: it is already helper-only and has its own test suite, and moving a tested pure function into a tool file is the thing this commit removes. Having one consumer is not the defect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'src/domains/form.ts')
-rw-r--r--src/domains/form.ts117
1 files changed, 2 insertions, 115 deletions
diff --git a/src/domains/form.ts b/src/domains/form.ts
index 8af43c3..577eb5d 100644
--- a/src/domains/form.ts
+++ b/src/domains/form.ts
@@ -1,11 +1,9 @@
import { Type } from "typebox";
-import type { BrowserClient } from "../client";
-import { safeJs } from "../util/js-template";
import { type Result, err, ok } from "../util/result";
import { defineBrowserTool, type ToolErr, type ToolOk } from "../util/tool";
-import { cdpCall, evalJs } from "./cdp-call";
+import { detailsOf, isPageResult, type PageResult, resolveAndCall, runOnElement } from "./element-call";
import { fillBody } from "./fill-engine";
-import { interactiveDiff, resolveRefToObjectId } from "./ref-resolve";
+import { interactiveDiff } from "./ref-resolve";
const FillArgs = Type.Object({
ref: Type.Optional(
@@ -18,51 +16,6 @@ const FillArgs = Type.Object({
value: Type.String({ description: "Value to set in the field" }),
});
-const runOnElement = async (
- client: BrowserClient,
- opts: {
- ref?: string | undefined;
- selector?: string | undefined;
- fnBody: string;
- arg: unknown;
- notFound?: unknown;
- },
-): Promise<Result<unknown, ToolErr>> => {
- if (opts.ref !== undefined) {
- const objectId = await resolveRefToObjectId(client, opts.ref);
- if (!objectId.success) return objectId;
- const r = await cdpCall(client, "Runtime.callFunctionOn", {
- objectId: objectId.data,
- functionDeclaration: `function (arg) { ${opts.fnBody} }`,
- arguments: [{ value: opts.arg }],
- returnByValue: true,
- });
- if (!r.success) return r;
- if (r.data.exceptionDetails !== undefined) {
- return err({
- kind: "cdp_error",
- message: `page function threw: ${r.data.exceptionDetails.text}`,
- details: { ref: opts.ref },
- });
- }
- return ok(r.data.result.value);
- }
- if (opts.selector === undefined) {
- return err({ kind: "invalid_state", message: "Provide either `ref` or `selector`." });
- }
- // fnBody is harness-authored, not user input; only the selector and value are interpolated, and those go through safeJs.
- const prelude = safeJs`
- (() => {
- const el = document.querySelector(${opts.selector});
- if (!el) return ${opts.notFound ?? { status: "not_found" }};
- const __arg = ${opts.arg};
- return (function (arg) {`;
- const expr = `${prelude} ${opts.fnBody} }).call(el, __arg); })()`;
- const r = await evalJs(client, expr);
- if (!r.success) return r;
- return ok(r.data);
-};
-
const FILL_BODY = `const value = arg;\n${fillBody({ rejectSelect: true, focusFirst: false })}`;
const NOT_FOUND: PageResult = { ok: false, reason: "not_found" };
@@ -281,72 +234,6 @@ export const selectOptionTool = defineBrowserTool({
},
});
-export type PageResult = {
- readonly ok: boolean;
- readonly reason?: string;
- readonly kind?: string;
- readonly value?: unknown;
- readonly text?: string;
- readonly checked?: boolean;
- readonly changed?: boolean;
- readonly tag?: string;
- readonly options?: ReadonlyArray<{ value: string; text: string }>;
-};
-
-const isOption = (v: unknown): v is { readonly value: string; readonly text: string } =>
- typeof v === "object" && v !== null
- && "value" in v && typeof v.value === "string"
- && "text" in v && typeof v.text === "string";
-
-const isPageResult = (v: unknown): v is PageResult => {
- if (typeof v !== "object" || v === null) return false;
- if (!("ok" in v) || typeof v.ok !== "boolean") return false;
- if ("reason" in v && typeof v.reason !== "string") return false;
- if ("kind" in v && typeof v.kind !== "string") return false;
- if ("text" in v && typeof v.text !== "string") return false;
- if ("checked" in v && typeof v.checked !== "boolean") return false;
- if ("changed" in v && typeof v.changed !== "boolean") return false;
- if ("tag" in v && typeof v.tag !== "string") return false;
- if ("options" in v && !(Array.isArray(v.options) && v.options.every(isOption))) return false;
- return true;
-};
-
-export const resolveAndCall = async (
- client: BrowserClient,
- ref: string,
- functionDeclaration: string,
- args: ReadonlyArray<unknown>,
-): Promise<Result<PageResult, ToolErr>> => {
- const objectId = await resolveRefToObjectId(client, ref);
- if (!objectId.success) return objectId;
- const handle = objectId.data;
- try {
- const called = await cdpCall(client, "Runtime.callFunctionOn", {
- objectId: handle,
- functionDeclaration,
- arguments: args.map((v) => ({ value: v })),
- returnByValue: true,
- awaitPromise: true,
- });
- if (!called.success) return err({ ...called.error, details: { ref } });
- if (called.data.exceptionDetails !== undefined) {
- return err({ kind: "cdp_error", message: `page function threw: ${JSON.stringify(called.data.exceptionDetails)}`, details: { ref } });
- }
- const value = called.data.result.value;
- if (!isPageResult(value)) {
- return err({ kind: "internal", message: "page function returned no result object", details: { ref } });
- }
- return ok(value);
- } finally {
- await cdpCall(client, "Runtime.releaseObject", { objectId: handle });
- }
-};
-
-export const detailsOf = (ref: string, res: PageResult, extra?: Readonly<Record<string, unknown>>): Readonly<Record<string, unknown>> => {
- const { ok: _ok, ...rest } = res;
- return { ref, ...rest, ...(extra ?? {}) };
-};
-
const CHECK_FN = `
function(checked) {
const el = this;