From abde5e813acf72327b6ee1c4dacab1490115f9e0 Mon Sep 17 00:00:00 2001 From: James Feng <47167674+GhostDragon124@users.noreply.github.com> Date: Sun, 7 Jun 2026 15:48:33 +0800 Subject: [PATCH] fix: remove final planned as-any casts --- .../src/tools/FileReadTool/FileReadTool.ts | 39 +++++++++++++------ src/utils/execFileNoThrowPortable.ts | 24 ++++++++++-- src/utils/sideQuery.ts | 10 +++-- src/utils/ultraplan/ccrSession.ts | 37 ++++++++++++++---- 4 files changed, 85 insertions(+), 25 deletions(-) diff --git a/packages/builtin-tools/src/tools/FileReadTool/FileReadTool.ts b/packages/builtin-tools/src/tools/FileReadTool/FileReadTool.ts index 6304b345e..d9df69cf1 100644 --- a/packages/builtin-tools/src/tools/FileReadTool/FileReadTool.ts +++ b/packages/builtin-tools/src/tools/FileReadTool/FileReadTool.ts @@ -909,12 +909,12 @@ async function callInner( resolvedFilePath, parsedRange ?? undefined, ) - if (!extractResult.success) { - throw new Error((extractResult as any).error.message) + if (extractResult.success === false) { + throw new Error(extractResult.error.message) } logEvent('tengu_pdf_page_extraction', { success: true, - pageCount: (extractResult as any).data.file.count, + pageCount: extractResult.data.file.count, fileSize: extractResult.data.file.originalSize, hasPageRange: true, }) @@ -981,7 +981,9 @@ async function callInner( } else { logEvent('tengu_pdf_page_extraction', { success: false, - available: (extractResult as any).error.reason !== 'unavailable', + available: + extractResult.success === false && + extractResult.error.reason !== 'unavailable', fileSize: stats.size, }) } @@ -996,8 +998,8 @@ async function callInner( } const readResult = await readPDF(resolvedFilePath) - if (!readResult.success) { - throw new Error((readResult as any).error.message) + if (readResult.success === false) { + throw new Error(readResult.error.message) } const pdfData = readResult.data logFileOperation({ @@ -1167,14 +1169,27 @@ export async function readImageWithTokenBudget( // Fallback: heavily compressed version from the SAME buffer try { const sharpModule = await import('sharp') + type SharpFactory = ( + input: Buffer, + ) => { + resize( + width: number, + height: number, + options: { + fit: 'inside' + withoutEnlargement: boolean + }, + ): { + jpeg(options: { quality: number }): { + toBuffer(): Promise + } + } + } const sharp = - ( - sharpModule as unknown as { - default?: typeof sharpModule - } & typeof sharpModule - ).default || sharpModule + ((sharpModule as unknown as { default?: unknown }).default ?? + sharpModule) as SharpFactory - const fallbackBuffer = await (sharp as any)(imageBuffer) + const fallbackBuffer = await sharp(imageBuffer) .resize(400, 400, { fit: 'inside', withoutEnlargement: true, diff --git a/src/utils/execFileNoThrowPortable.ts b/src/utils/execFileNoThrowPortable.ts index 4ca4c89b9..f505fe6ef 100644 --- a/src/utils/execFileNoThrowPortable.ts +++ b/src/utils/execFileNoThrowPortable.ts @@ -1,4 +1,8 @@ -import { type Options as ExecaOptions, execaSync } from 'execa' +import { + type Options as ExecaOptions, + type ExecaSyncMethod, + execaSync, +} from 'execa' import { getCwd } from '../utils/cwd.js' import { slowLogging } from './slowOperations.js' @@ -12,6 +16,11 @@ type ExecSyncOptions = { stdio?: ExecaOptions['stdio'] } +type ShellCommandExecaSync = ( + command: string, + options: ExecaOptions, +) => ReturnType + /** * @deprecated Use `execa` directly with `{ shell: true, reject: false }` for non-blocking execution. * Sync exec calls block the event loop and cause performance issues. @@ -72,7 +81,8 @@ export function execSyncWithDefaults_DEPRECATED( // DEPRECATED: caller is responsible for sanitizing shell metacharacters // in the command argument. This function uses shell: true which can lead // to command injection if command contains untrusted input. - const result = (execaSync as any)(command, { + const runShellCommand = execaSync as unknown as ShellCommandExecaSync + const result = runShellCommand(command, { env: process.env, maxBuffer: 1_000_000, timeout: finalTimeout, @@ -85,7 +95,15 @@ export function execSyncWithDefaults_DEPRECATED( if (!result.stdout) { return null } - return result.stdout.trim() || null + const stdout = + typeof result.stdout === 'string' + ? result.stdout + : result.stdout instanceof Uint8Array + ? new TextDecoder().decode(result.stdout) + : Array.isArray(result.stdout) + ? result.stdout.join('') + : String(result.stdout) + return stdout.trim() || null } catch { return null } diff --git a/src/utils/sideQuery.ts b/src/utils/sideQuery.ts index dd676a72a..255beaf3c 100644 --- a/src/utils/sideQuery.ts +++ b/src/utils/sideQuery.ts @@ -1,5 +1,6 @@ import type Anthropic from '@anthropic-ai/sdk' import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages.js' +import type { ChatCompletionCreateParamsNonStreaming } from 'openai/resources/chat/completions' import { getLastApiCompletionTimestamp, getSessionId, @@ -210,7 +211,7 @@ async function sideQueryViaOpenAICompatible( const start = Date.now() - const requestParams: Record = { + const requestParams: ChatCompletionCreateParamsNonStreaming = { model: openaiModel, messages: openaiMessages, max_tokens, @@ -218,10 +219,13 @@ async function sideQueryViaOpenAICompatible( if (temperature !== undefined) requestParams.temperature = temperature if (openaiTools && openaiTools.length > 0) { requestParams.tools = openaiTools - if (openaiToolChoice) requestParams.tool_choice = openaiToolChoice + if (openaiToolChoice) { + requestParams.tool_choice = + openaiToolChoice as ChatCompletionCreateParamsNonStreaming['tool_choice'] + } } - const response = await client.chat.completions.create(requestParams as any, { + const response = await client.chat.completions.create(requestParams, { signal, }) diff --git a/src/utils/ultraplan/ccrSession.ts b/src/utils/ultraplan/ccrSession.ts index 7c45465ca..97e7b8e81 100644 --- a/src/utils/ultraplan/ccrSession.ts +++ b/src/utils/ultraplan/ccrSession.ts @@ -5,6 +5,7 @@ // teleportToRemote's CreateSession events array. import type { + ContentBlock, ToolResultBlockParam, ToolUseBlock, } from '@anthropic-ai/sdk/resources' @@ -23,6 +24,27 @@ const POLL_INTERVAL_MS = 3000 // at any nonzero 5xx rate one blip would kill the run. const MAX_CONSECUTIVE_FAILURES = 5 +function getSDKMessageContent(message: SDKMessage): unknown { + const sdkMessage = message.message + if (typeof sdkMessage !== 'object' || sdkMessage === null) return undefined + return (sdkMessage as { content?: unknown }).content +} + +function isToolUseBlock(block: ContentBlock): block is ToolUseBlock { + return block.type === 'tool_use' +} + +function isToolResultBlock(block: unknown): block is ToolResultBlockParam { + return ( + typeof block === 'object' && + block !== null && + 'type' in block && + block.type === 'tool_result' && + 'tool_use_id' in block && + typeof block.tool_use_id === 'string' + ) +} + export type PollFailReason = | 'terminated' | 'timeout_pending' @@ -101,18 +123,19 @@ export class ExitPlanModeScanner { ingest(newEvents: SDKMessage[]): ScanResult { for (const m of newEvents) { if (m.type === 'assistant') { - for (const block of (m as any).message.content) { - if (block.type !== 'tool_use') continue - const tu = block as ToolUseBlock - if (tu.name === EXIT_PLAN_MODE_V2_TOOL_NAME) { - this.exitPlanCalls.push(tu.id) + const content = getSDKMessageContent(m) + if (!Array.isArray(content)) continue + for (const block of content as ContentBlock[]) { + if (!isToolUseBlock(block)) continue + if (block.name === EXIT_PLAN_MODE_V2_TOOL_NAME) { + this.exitPlanCalls.push(block.id) } } } else if (m.type === 'user') { - const content = (m as any).message.content + const content = getSDKMessageContent(m) if (!Array.isArray(content)) continue for (const block of content) { - if (block.type === 'tool_result') { + if (isToolResultBlock(block)) { this.results.set(block.tool_use_id, block) } }