aboutsummaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmail.com>2026-07-28 01:19:58 +0530
committerAman Kumar <145339995+amankumarsingh77@users.noreply.github.com>2026-08-02 19:12:46 +0530
commitf9dcdf7f6dcdf2979693a9f179eda2b9995d57ae (patch)
tree763ca6f879a693f56572ab256d33769d6d6bb0ad /src/util
parentb9c77bb6f9b8b83574489344ef6efd207a611e98 (diff)
refactor: close the remaining unguarded type assertions
Types the event-queue iterator's TReturn as undefined so the terminal result needs no cast; declares the isError field the pi runtime reads by duck-typing; binds socket to a local const in the daemon transport; replaces the @ts-ignore on the optional sharp import with a non-literal specifier so src/ still typechecks where sharp was not installed. Adds src/util/guards.ts as the single home for narrowing over unknown — isRecord plus scalar/array readers — and converts the two private isObject copies in cdp/types.ts and daemon/protocol.ts along with every inline `as Record<string, unknown>` in domains/js.ts and domains/render.ts. Gives Accessibility.getFullAXTree a real result schema, closing the last unvalidated CDP boundary: only nodeId is required, every object carries additionalProperties, and each AX value stays Unknown, so the tree decodes as a whole or fails as a whole rather than being filtered. Guards the five tool-result `details` readers, the daemon attach response, the persisted session state, the browser-namespace flag, and the fs errno narrowing, each feeding an already-present failure path. The daemon-transport socket binding is not a bug fix: send() runs synchronously inside sendWithTimeout's promise executor, so socket cannot be reassigned between the check and the write. It removes the assertion and makes the write immune to a later refactor that adds an await. Two assertions survive by design and are documented in the task report: the never->unknown re-widening in util/tool.ts, which the type-erased tool registry makes unavoidable, and the AsyncFunction constructor lookup in domains/js.ts, which JavaScript exposes no typed binding for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'src/util')
-rw-r--r--src/util/guards.ts30
-rw-r--r--src/util/sharp-shim.ts20
-rw-r--r--src/util/tool.ts27
3 files changed, 51 insertions, 26 deletions
diff --git a/src/util/guards.ts b/src/util/guards.ts
new file mode 100644
index 0000000..2744c68
--- /dev/null
+++ b/src/util/guards.ts
@@ -0,0 +1,30 @@
+export const isRecord = (v: unknown): v is Readonly<Record<string, unknown>> =>
+ typeof v === "object" && v !== null && !Array.isArray(v);
+
+export const asRecord = (v: unknown): Readonly<Record<string, unknown>> | undefined =>
+ isRecord(v) ? v : undefined;
+
+export const asString = (v: unknown): string | undefined => (typeof v === "string" ? v : undefined);
+
+export const asNumber = (v: unknown): number | undefined => (typeof v === "number" ? v : undefined);
+
+export const asBoolean = (v: unknown): boolean | undefined => (typeof v === "boolean" ? v : undefined);
+
+export const asArrayOf = <T>(
+ v: unknown,
+ item: (x: unknown) => T | undefined,
+): ReadonlyArray<T> | undefined => {
+ if (!Array.isArray(v)) return undefined;
+ const out: T[] = [];
+ for (const x of v) {
+ const parsed = item(x);
+ if (parsed === undefined) return undefined;
+ out.push(parsed);
+ }
+ return out;
+};
+
+export const errnoCode = (e: unknown): string | undefined => {
+ if (!(e instanceof Error) || !("code" in e)) return undefined;
+ return asString(e.code);
+};
diff --git a/src/util/sharp-shim.ts b/src/util/sharp-shim.ts
index a9f145e..36dd536 100644
--- a/src/util/sharp-shim.ts
+++ b/src/util/sharp-shim.ts
@@ -20,21 +20,21 @@ export type SharpLoad =
| { readonly kind: "missing" }
| { readonly kind: "error"; readonly message: string };
+const SHARP_SPECIFIER: string = "sharp";
+
+const MISSING_MODULE_MARKERS: ReadonlyArray<string> = [
+ "Cannot find module",
+ "MODULE_NOT_FOUND",
+ "ERR_MODULE_NOT_FOUND",
+];
+
export const loadSharp = async (): Promise<SharpLoad> => {
let mod: unknown;
try {
- // sharp is an optional dependency; it may not be installed.
- // @ts-ignore — optional peer dependency not required at compile time
- mod = await import("sharp");
+ mod = await import(SHARP_SPECIFIER);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
- if (
- msg.includes("Cannot find module") ||
- msg.includes("MODULE_NOT_FOUND") ||
- msg.includes("ERR_MODULE_NOT_FOUND")
- ) {
- return { kind: "missing" };
- }
+ if (MISSING_MODULE_MARKERS.some((marker) => msg.includes(marker))) return { kind: "missing" };
return { kind: "error", message: msg };
}
if (mod === null || mod === undefined) return { kind: "missing" };
diff --git a/src/util/tool.ts b/src/util/tool.ts
index 90a2e15..e4bb5ba 100644
--- a/src/util/tool.ts
+++ b/src/util/tool.ts
@@ -1,7 +1,6 @@
import type { TSchema, Static } from "typebox";
import type {
AgentToolResult,
- AgentToolUpdateCallback,
ExtensionAPI,
ExtensionContext,
Theme,
@@ -75,10 +74,12 @@ export const defineBrowserTool = <S extends TSchema>(
type OkDetails = { readonly ok: true } & Readonly<Record<string, unknown>>;
type ErrDetails = { readonly ok: false; readonly kind: ToolErrKind; readonly message: string } & Readonly<Record<string, unknown>>;
+type ErrorFlagged<D> = AgentToolResult<D> & { readonly isError?: boolean };
+
const toToolResult = (
r: Result<ToolOk, ToolErr>,
toolName: string,
-): AgentToolResult<OkDetails | ErrDetails> => {
+): ErrorFlagged<OkDetails | ErrDetails> => {
if (r.success) {
const details: OkDetails = { ok: true, ...(r.data.details ?? {}) };
return {
@@ -93,10 +94,7 @@ const toToolResult = (
...(r.error.details ?? {}),
};
return {
- // isError is an extension the pi-coding-agent runtime picks up from the result object;
- // AgentToolResult<T> itself doesn't declare it but the runtime reads it via duck-typing.
- // We cast through unknown to avoid widening the return type.
- ...(({ isError: true }) as unknown as object),
+ isError: true,
content: [{ type: "text", text: `${toolName} failed (${r.error.kind}): ${r.error.message}` }],
details,
};
@@ -111,6 +109,8 @@ export const registerBrowserTool = (
// by casting def to the generic form. The cast is safe because defineBrowserTool
// ensures the handler, parameters, and renderCall were all created with the same S.
type S = TSchema;
+ const defRenderCall = def.renderCall;
+ const defRenderResult = def.renderResult;
const td: ToolDefinition<S> = {
name: def.name,
label: def.label,
@@ -118,25 +118,22 @@ export const registerBrowserTool = (
promptSnippet: def.promptSnippet,
promptGuidelines: [...def.promptGuidelines],
parameters: def.parameters,
- ...(def.renderCall
+ ...(defRenderCall
? {
renderCall: (args: Static<S>, theme: Theme, _ctx: Parameters<NonNullable<ToolDefinition<S>["renderCall"]>>[2]) =>
- // Non-null assertion safe: this branch only runs when renderCall was provided.
// Cast from never->Component to unknown->Component: safe because at runtime
// args is always the concrete Static<S> value the tool was defined with.
- (def.renderCall as (args: unknown, theme: Theme) => Component)(args, theme),
+ (defRenderCall as (args: unknown, theme: Theme) => Component)(args, theme),
}
: {}),
- ...(def.renderResult
+ ...(defRenderResult
? {
renderResult: (
result: AgentToolResult<unknown>,
options: ToolRenderResultOptions,
theme: Theme,
_ctx: Parameters<NonNullable<ToolDefinition<S>["renderResult"]>>[3],
- ) =>
- // Non-null assertion safe: this branch only runs when renderResult was provided.
- def.renderResult!(result, options.expanded ?? false, theme),
+ ) => defRenderResult(result, options.expanded ?? false, theme),
}
: {}),
async execute(_toolCallId, args, signal, onUpdate, extensionCtx) {
@@ -184,13 +181,11 @@ export const registerBrowserTool = (
signal,
onUpdate: (u) => {
if (onUpdate) {
- // AgentToolUpdateCallback expects AgentToolResult<TDetails>; we use unknown for TDetails.
const update: AgentToolResult<OkDetails> = {
content: [{ type: "text", text: u.text }],
details: { ok: true, ...(u.details ?? {}) },
};
- // Cast needed: onUpdate is AgentToolUpdateCallback<unknown> (generic TDetails).
- (onUpdate as AgentToolUpdateCallback<OkDetails>)(update);
+ onUpdate(update);
}
},
extensionCtx,