fix(typecheck): reduce error baseline by 89 across 8 files (#1595)

* 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<any, any>` = `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
This commit is contained in:
Bogdan
2026-06-10 21:54:37 +08:00
committed by GitHub
parent b0064575a7
commit 9db9427f29
8 changed files with 59 additions and 61 deletions
+1
View File
@@ -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'
/**
+2 -12
View File
@@ -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,
+11 -7
View File
@@ -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<string, unknown> | 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
+22 -14
View File
@@ -1055,15 +1055,17 @@ async function* anthropicSsePassthrough(
_model: string,
signal?: AbortSignal,
): AsyncGenerator<AnthropicStreamEvent> {
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<Uint8Array> = readerOrNull
const decoder = new TextDecoder()
let buffer = ''
// Read helper that properly cleans up abort listeners (mirrors codexShim.ts pattern).
function readWithAbort(): Promise<ReadableStreamReadResult<Uint8Array>> {
type ReadResult = Awaited<ReturnType<typeof reader.read>>
function readWithAbort(): Promise<ReadResult> {
if (!signal) return reader.read()
return new Promise((resolve, reject) => {
return new Promise<ReadResult>((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<AnthropicStreamEvent> {
const reader = response.body?.getReader()
if (!reader) return
const reader: ReadableStreamDefaultReader<Uint8Array> | 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<ReadableStreamReadResult<Uint8Array>> {
if (!signal) return reader.read()
if (!signal) return reader!.read() as Promise<ReadableStreamReadResult<Uint8Array>>
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<Uint8Array>) },
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<Uint8Array> = 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<ReadableStreamReadResult<Uint8Array>> {
return new Promise((resolve, reject) => {
type ReadResult = Awaited<ReturnType<typeof reader.read>>
async function readWithTimeout(): Promise<ReadResult> {
return new Promise<ReadResult>((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',
+2 -2
View File
@@ -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
+1
View File
@@ -2,6 +2,7 @@ export type ConnectorTextBlock = {
type: 'connector_text'
connector_text: string
connector?: string
signature?: string
}
export type ConnectorTextDelta = {
+1 -1
View File
@@ -10,7 +10,7 @@ import type {
RenderableMessage,
} from '../types/message.js'
export type MessageWithoutProgress = Exclude<NormalizedMessage, ProgressMessage>
export type MessageWithoutProgress = NormalizedMessage
export type GroupingResult = {
messages: RenderableMessage[]
+19 -25
View File
@@ -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<any, any>` = `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<string>()
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
}
}