From 9db9427f2961ff13a0d24c4f3261fd9ad5bfa932 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Wed, 10 Jun 2026 16:54:37 +0300 Subject: [PATCH] fix(typecheck): reduce error baseline by 89 across 8 files (#1595) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: resolve 28 typecheck errors in openaiShim.ts Add null guards for nullable `reader`, `response`, and `responsesResponse` variables, and use type assertions to bridge Node vs Web ReadableStream type mismatches in stream processing helpers. * fix(typecheck): resolve 17 errors in agentSdkTypes.ts - Add @ts-expect-error for settingsTypes.generated.js (generated at build time) - Fix type imports: redirect 5 types from ./sdk/runtimeTypes.js to ./sdk/shared.js - Remove 11 unused type imports that don't exist (AnyZodRawShape, InferShape, etc.) * fix(typecheck): resolve 26 errors in openaiShim.ts — nullable guards, ReadableStream types * fix(typecheck): resolve 23 errors in messages.ts + groupToolUses.ts MessageWithoutProgress resolved to `never` because all message types are `any` stubs, making `Exclude` = `never`. Widen types and use boolean wrappers to avoid type-predicate narrowing. Add missing return in getToolUseID switch statement. * fix(typecheck): resolve 14 errors in toolExecution.ts — fix never[] inference * fix(typecheck): resolve type errors in claude.ts - Cast nested block params to BetaContentBlockParam for SDK type union mismatch - Add missing CACHE_EDITING_BETA_HEADER constant to betas.ts - Type-assert getCachedMCConfig() return for supportedModels access - Add missing imports: getContextWindowForModel, COMPACT_MAX_OUTPUT_TOKENS, getSdkBetas - Fix model variable reference to use options.model in compact context - Add optional signature property to ConnectorTextBlock type * ci: re-trigger checks * fix: address CodeRabbit review feedback - Throw error instead of silent return when response body is not readable - Clamp hybrid context budget to non-negative floor (Math.max(0, ...)) - Remove unused isResult wrapper in messages.ts --- src/constants/betas.ts | 1 + src/entrypoints/agentSdkTypes.ts | 14 ++------- src/services/api/claude.ts | 18 +++++++----- src/services/api/openaiShim.ts | 36 ++++++++++++++--------- src/services/tools/toolExecution.ts | 4 +-- src/types/connectorText.ts | 1 + src/utils/groupToolUses.ts | 2 +- src/utils/messages.ts | 44 +++++++++++++---------------- 8 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/constants/betas.ts b/src/constants/betas.ts index dc1383b07..4b14c2fa5 100644 --- a/src/constants/betas.ts +++ b/src/constants/betas.ts @@ -28,6 +28,7 @@ export const AFK_MODE_BETA_HEADER = feature('TRANSCRIPT_CLASSIFIER') : '' export const CLI_INTERNAL_BETA_HEADER = process.env.USER_TYPE === 'ant' ? 'cli-internal-2026-02-09' : '' +export const CACHE_EDITING_BETA_HEADER = 'cache-editing-2025-12-01' export const ADVISOR_BETA_HEADER = 'advisor-tool-2026-03-01' /** diff --git a/src/entrypoints/agentSdkTypes.ts b/src/entrypoints/agentSdkTypes.ts index a8d2e2dbc..dbe0c3f91 100644 --- a/src/entrypoints/agentSdkTypes.ts +++ b/src/entrypoints/agentSdkTypes.ts @@ -21,6 +21,7 @@ export * from './sdk/coreTypes.js' export * from './sdk/runtimeTypes.js' // Re-export settings types (generated from settings JSON schema) +// @ts-expect-error — generated at build time export type { Settings } from './sdk/settingsTypes.generated.js' // Re-export tool types (all marked @internal until SDK API stabilizes) export * from './sdk/toolTypes.js' @@ -49,23 +50,12 @@ export { // Import types needed for @internal function signatures kept below import type { - AnyZodRawShape, ForkSessionOptions, ForkSessionResult, GetSessionInfoOptions, - GetSessionMessagesOptions, - InferShape, - InternalOptions, - InternalQuery, ListSessionsOptions, - Options, - Query, - SDKSession, - SDKSessionOptions, - SdkMcpToolDefinition, - SessionMessage, SessionMutationOptions, -} from './sdk/runtimeTypes.js' +} from './sdk/shared.js' import type { SDKMessage, diff --git a/src/services/api/claude.ts b/src/services/api/claude.ts index 72f664244..5e3a27cb4 100644 --- a/src/services/api/claude.ts +++ b/src/services/api/claude.ts @@ -119,6 +119,7 @@ import { getCacheEditingHeaderLatched, getFastModeHeaderLatched, getLastApiCompletionTimestamp, + getSdkBetas, getPromptCache1hAllowlist, getPromptCache1hEligible, getSessionId, @@ -164,7 +165,7 @@ import { } from 'src/utils/betas.js' import { CLAUDE_IN_CHROME_MCP_SERVER_NAME } from 'src/utils/claudeInChrome/common.js' import { CHROME_TOOL_SEARCH_INSTRUCTIONS } from 'src/utils/claudeInChrome/prompt.js' -import { getMaxThinkingTokensForModel } from 'src/utils/context.js' +import { COMPACT_MAX_OUTPUT_TOKENS, getContextWindowForModel, getMaxThinkingTokensForModel } from 'src/utils/context.js' import { logForDebugging } from 'src/utils/debug.js' import { logForDiagnosticsNoPII } from 'src/utils/diagLogs.js' import { type EffortValue, modelSupportsEffort } from 'src/utils/effort.js' @@ -978,7 +979,7 @@ export function stripExcessMediaItems( if (isMedia(block)) toRemove++ if (isToolResult(block) && Array.isArray(block.content)) { for (const nested of block.content) { - if (isMedia(nested)) toRemove++ + if (isMedia(nested as BetaContentBlockParam)) toRemove++ } } } @@ -1001,7 +1002,7 @@ export function stripExcessMediaItems( ) return block const filtered = block.content.filter(n => { - if (toRemove > 0 && isMedia(n)) { + if (toRemove > 0 && isMedia(n as BetaContentBlockParam)) { toRemove-- return false } @@ -1214,7 +1215,7 @@ async function* queryModel( cachedMCEnabled = featureEnabled && modelSupported const config = getCachedMCConfig() logForDebugging( - `Cached MC gate: enabled=${featureEnabled} modelSupported=${modelSupported} model=${options.model} supportedModels=${jsonStringify(config?.supportedModels)}`, + `Cached MC gate: enabled=${featureEnabled} modelSupported=${modelSupported} model=${options.model} supportedModels=${jsonStringify((config as Record | null)?.supportedModels)}`, ) } @@ -1287,9 +1288,12 @@ async function* queryModel( const strategyResult = applyHybridStrategy(messagesForAPI, { cacheWeight: 0.4, freshWeight: 0.6, - maxTotalTokens: Math.min( - getContextWindowForModel(model, getSdkBetas()) - COMPACT_MAX_OUTPUT_TOKENS, - 200000 + maxTotalTokens: Math.max( + 0, + Math.min( + getContextWindowForModel(options.model, getSdkBetas()) - COMPACT_MAX_OUTPUT_TOKENS, + 200000, + ), ), }) messagesForAPI = strategyResult.selectedMessages diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index 20a27a9b5..a57b09823 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -1055,15 +1055,17 @@ async function* anthropicSsePassthrough( _model: string, signal?: AbortSignal, ): AsyncGenerator { - const reader = response.body?.getReader() - if (!reader) return + const readerOrNull = response.body?.getReader() + if (!readerOrNull) throw new Error('Response body is not readable') + const reader: ReadableStreamDefaultReader = readerOrNull const decoder = new TextDecoder() let buffer = '' // Read helper that properly cleans up abort listeners (mirrors codexShim.ts pattern). - function readWithAbort(): Promise> { + type ReadResult = Awaited> + function readWithAbort(): Promise { if (!signal) return reader.read() - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const onAbort = () => reject(new DOMException('Aborted', 'AbortError')) signal.addEventListener('abort', onAbort, { once: true }) reader.read().then( @@ -1116,8 +1118,8 @@ async function* geminiSseToAnthropic( model: string, signal?: AbortSignal, ): AsyncGenerator { - const reader = response.body?.getReader() - if (!reader) return + const reader: ReadableStreamDefaultReader | undefined = response.body?.getReader() + if (!reader) throw new Error('Response body is not readable') const decoder = new TextDecoder() let buffer = '' const messageId = makeMessageId() @@ -1129,12 +1131,12 @@ async function* geminiSseToAnthropic( let finishReason: string | undefined function readWithAbort(): Promise> { - if (!signal) return reader.read() + if (!signal) return reader!.read() as Promise> return new Promise((resolve, reject) => { const onAbort = () => reject(new DOMException('Aborted', 'AbortError')) signal.addEventListener('abort', onAbort, { once: true }) - reader.read().then( - result => { signal.removeEventListener('abort', onAbort); resolve(result) }, + reader!.read().then( + result => { signal.removeEventListener('abort', onAbort); resolve(result as ReadableStreamReadResult) }, err => { signal.removeEventListener('abort', onAbort); reject(err) }, ) }) @@ -1332,8 +1334,9 @@ async function* openaiStreamToAnthropic( }, } - const reader = response.body?.getReader() - if (!reader) return + const readerOrNull = response.body?.getReader() + if (!readerOrNull) throw new Error('Response body is not readable') + const reader: ReadableStreamDefaultReader = readerOrNull const decoder = new TextDecoder() let buffer = '' @@ -1348,8 +1351,9 @@ async function* openaiStreamToAnthropic( * Respects the caller's AbortSignal — clears the idle timer on abort * so the rejection reason is AbortError, not a spurious idle timeout. */ - async function readWithTimeout(): Promise> { - return new Promise((resolve, reject) => { + type ReadResult = Awaited> + async function readWithTimeout(): Promise { + return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { const elapsed = Math.round((Date.now() - lastDataTime) / 1000) reject(new Error( @@ -2823,6 +2827,10 @@ class OpenAIShimMessages { throwClassifiedTransportError(error, requestUrl, failure) } + // After the try/catch, response is guaranteed to be defined — the catch + // block always throws (throwClassifiedTransportError returns never). + if (!response) continue + if (response.ok) { let tokensIn = 0 let tokensOut = 0 @@ -2867,7 +2875,7 @@ class OpenAIShimMessages { const responsesUrl = `${request.baseUrl}/responses` const responsesBody = buildResponsesBody() - let responsesResponse: Response + let responsesResponse!: Response try { responsesResponse = await fetchWithProxyRetry(responsesUrl, { method: 'POST', diff --git a/src/services/tools/toolExecution.ts b/src/services/tools/toolExecution.ts index 8ec28923f..7f37695b0 100644 --- a/src/services/tools/toolExecution.ts +++ b/src/services/tools/toolExecution.ts @@ -826,7 +826,7 @@ async function checkPermissionsAndCallTool( ) } - const resultingMessages = [] + const resultingMessages: MessageUpdateLazy[] = [] // Defense-in-depth: strip _simulatedSedEdit from model-provided Bash input. // This field is internal-only — it must only be injected by the permission @@ -1375,7 +1375,7 @@ async function checkPermissionsAndCallTool( // Run PostToolUse hooks let toolOutput = result.data - const hookResults = [] + const hookResults: MessageUpdateLazy[] = [] const toolContextModifier = result.contextModifier const mcpMeta = result.mcpMeta diff --git a/src/types/connectorText.ts b/src/types/connectorText.ts index 602641164..30d610a37 100644 --- a/src/types/connectorText.ts +++ b/src/types/connectorText.ts @@ -2,6 +2,7 @@ export type ConnectorTextBlock = { type: 'connector_text' connector_text: string connector?: string + signature?: string } export type ConnectorTextDelta = { diff --git a/src/utils/groupToolUses.ts b/src/utils/groupToolUses.ts index b0ecf88de..8810cdd43 100644 --- a/src/utils/groupToolUses.ts +++ b/src/utils/groupToolUses.ts @@ -10,7 +10,7 @@ import type { RenderableMessage, } from '../types/message.js' -export type MessageWithoutProgress = Exclude +export type MessageWithoutProgress = NormalizedMessage export type GroupingResult = { messages: RenderableMessage[] diff --git a/src/utils/messages.ts b/src/utils/messages.ts index 6bc190d74..8b2d5eca1 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -861,19 +861,14 @@ export function isToolUseResultMessage( // Re-order, to move result messages to be after their tool use messages export function reorderMessagesInUI( - messages: ( - | NormalizedUserMessage - | NormalizedAssistantMessage - | AttachmentMessage - | SystemMessage - )[], + messages: any[], // eslint-disable-line @typescript-eslint/no-explicit-any syntheticStreamingToolUseMessages: NormalizedAssistantMessage[], -): ( - | NormalizedUserMessage - | NormalizedAssistantMessage - | AttachmentMessage - | SystemMessage -)[] { +): any[] { // eslint-disable-line @typescript-eslint/no-explicit-any + // Boolean wrappers to avoid type-predicate narrowing (all message types are `any` stubs, + // so `Exclude` = `never` after type guards) + const isToolUse = (m: any): boolean => isToolUseRequestMessage(m) // eslint-disable-line @typescript-eslint/no-explicit-any + const isHook = (m: any): boolean => isHookAttachmentMessage(m) // eslint-disable-line @typescript-eslint/no-explicit-any + // Maps tool use ID to its related messages const toolUseGroups = new Map< string, @@ -886,9 +881,10 @@ export function reorderMessagesInUI( >() // First pass: group messages by tool use ID - for (const message of messages) { + for (const _msg of messages) { + const message: any = _msg // eslint-disable-line @typescript-eslint/no-explicit-any // Handle tool use messages - if (isToolUseRequestMessage(message)) { + if (isToolUse(message)) { const toolUseID = message.message.content[0]?.id if (toolUseID) { if (!toolUseGroups.has(toolUseID)) { @@ -906,7 +902,7 @@ export function reorderMessagesInUI( // Handle pre-tool-use hooks if ( - isHookAttachmentMessage(message) && + isHook(message) && message.attachment.hookEvent === 'PreToolUse' ) { const toolUseID = message.attachment.toolUseID @@ -942,7 +938,7 @@ export function reorderMessagesInUI( // Handle post-tool-use hooks if ( - isHookAttachmentMessage(message) && + isHook(message) && message.attachment.hookEvent === 'PostToolUse' ) { const toolUseID = message.attachment.toolUseID @@ -960,17 +956,13 @@ export function reorderMessagesInUI( } // Second pass: reconstruct the message list in the correct order - const result: ( - | NormalizedUserMessage - | NormalizedAssistantMessage - | AttachmentMessage - | SystemMessage - )[] = [] + const result: any[] = [] // eslint-disable-line @typescript-eslint/no-explicit-any const processedToolUses = new Set() - for (const message of messages) { + for (const _msg of messages) { + const message: any = _msg // eslint-disable-line @typescript-eslint/no-explicit-any // Check if this is a tool use - if (isToolUseRequestMessage(message)) { + if (isToolUse(message)) { const toolUseID = message.message.content[0]?.id if (toolUseID && !processedToolUses.has(toolUseID)) { processedToolUses.add(toolUseID) @@ -990,7 +982,7 @@ export function reorderMessagesInUI( // Check if this message is part of a tool use group if ( - isHookAttachmentMessage(message) && + isHook(message) && (message.attachment.hookEvent === 'PreToolUse' || message.attachment.hookEvent === 'PostToolUse') ) { @@ -2863,6 +2855,8 @@ export function getToolUseID(message: NormalizedMessage): string | null { return message.subtype === 'informational' ? (message.toolUseID ?? null) : null + default: + return null } }