aboutsummaryrefslogtreecommitdiff
path: root/src/util/tool.ts
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/tool.ts
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/tool.ts')
-rw-r--r--src/util/tool.ts27
1 files changed, 11 insertions, 16 deletions
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,