fix: remove runtime helper as-any casts

This commit is contained in:
James Feng 2026-06-07 15:45:11 +08:00
parent ba36ca454d
commit ac807aeb08
14 changed files with 102 additions and 46 deletions

View File

@ -10,7 +10,7 @@ import { getGlobalConfig } from '../utils/config.js'
import { getClaudeAIOAuthTokens } from '../utils/auth.js' import { getClaudeAIOAuthTokens } from '../utils/auth.js'
import { getOauthConfig } from '../constants/oauth.js' import { getOauthConfig } from '../constants/oauth.js'
import { getUserAgent } from '../utils/http.js' import { getUserAgent } from '../utils/http.js'
import type { Message } from '../types/message.js' import type { ContentItem, Message } from '../types/message.js'
// ─── Rate limiting ────────────────────────────────── // ─── Rate limiting ──────────────────────────────────
@ -64,6 +64,15 @@ export function triggerCompanionReaction(
// ─── Helpers ──────────────────────────────────────── // ─── Helpers ────────────────────────────────────────
function getMessageContent(
message: Message,
): string | ContentItem[] | undefined {
const content = message.message?.content
return typeof content === 'string' || Array.isArray(content)
? content
: undefined
}
function isAddressed(messages: Message[], name: string): boolean { function isAddressed(messages: Message[], name: string): boolean {
const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'i') const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'i')
for ( for (
@ -73,7 +82,7 @@ function isAddressed(messages: Message[], name: string): boolean {
) { ) {
const m = messages[i] const m = messages[i]
if (m?.type !== 'user') continue if (m?.type !== 'user') continue
const content = (m as any).message?.content const content = getMessageContent(m)
if (typeof content === 'string' && pattern.test(content)) return true if (typeof content === 'string' && pattern.test(content)) return true
} }
return false return false
@ -89,14 +98,17 @@ function buildTranscript(messages: Message[]): string {
.filter(m => m.type === 'user' || m.type === 'assistant') .filter(m => m.type === 'user' || m.type === 'assistant')
.map(m => { .map(m => {
const role = m.type === 'user' ? 'user' : 'claude' const role = m.type === 'user' ? 'user' : 'claude'
const content = (m as any).message?.content const content = getMessageContent(m)
const text = const text =
typeof content === 'string' typeof content === 'string'
? content.slice(0, 300) ? content.slice(0, 300)
: Array.isArray(content) : Array.isArray(content)
? content ? content
.filter((b: any) => b?.type === 'text') .filter(
.map((b: any) => b.text) (b): b is Extract<ContentItem, { type: 'text' }> =>
b.type === 'text',
)
.map(b => b.text)
.join(' ') .join(' ')
.slice(0, 300) .slice(0, 300)
: '' : ''

View File

@ -185,8 +185,8 @@ export async function getOutputStyleConfig(): Promise<OutputStyleConfig | null>
const forcedStyles = Object.values(allStyles).filter( const forcedStyles = Object.values(allStyles).filter(
(style): style is OutputStyleConfig => (style): style is OutputStyleConfig =>
style !== null && style !== null &&
(style as any).source === 'plugin' && style.source === 'plugin' &&
(style as any).forceForPlugin === true, style.forceForPlugin === true,
) )
const firstForcedStyle = forcedStyles[0] const firstForcedStyle = forcedStyles[0]

View File

@ -12,7 +12,7 @@
import { describe, test, expect, mock, beforeEach } from 'bun:test' import { describe, test, expect, mock, beforeEach } from 'bun:test'
// --- MACRO 全局注入 (编译时 define 在测试中不可用) --- // --- MACRO 全局注入 (编译时 define 在测试中不可用) ---
;(globalThis as any).MACRO = { globalThis.MACRO = {
VERSION: '2.1.888', VERSION: '2.1.888',
BUILD_TIME: '2026-04-22T00:00:00Z', BUILD_TIME: '2026-04-22T00:00:00Z',
FEEDBACK_CHANNEL: '', FEEDBACK_CHANNEL: '',
@ -214,7 +214,7 @@ const standardTools: Tools = [
{ name: 'Agent' }, { name: 'Agent' },
{ name: 'AskUserQuestion' }, { name: 'AskUserQuestion' },
{ name: 'TaskCreate' }, { name: 'TaskCreate' },
] as any ] as unknown as Tools
async function getFullPrompt( async function getFullPrompt(
tools: Tools = standardTools, tools: Tools = standardTools,

View File

@ -10,7 +10,7 @@ import { isEnvTruthy } from '../utils/envUtils.js';
// Runtime fallback for MACRO.* when not injected by build/dev defines. // Runtime fallback for MACRO.* when not injected by build/dev defines.
// This happens when running cli.tsx directly (not via `bun run dev` or built dist/). // This happens when running cli.tsx directly (not via `bun run dev` or built dist/).
if (typeof globalThis.MACRO === 'undefined') { if (typeof globalThis.MACRO === 'undefined') {
(globalThis as any).MACRO = { globalThis.MACRO = {
VERSION: process.env.CLAUDE_CODE_VERSION || '2.1.888', VERSION: process.env.CLAUDE_CODE_VERSION || '2.1.888',
BUILD_TIME: new Date().toISOString(), BUILD_TIME: new Date().toISOString(),
FEEDBACK_CHANNEL: '', FEEDBACK_CHANNEL: '',

View File

@ -142,9 +142,9 @@ export async function startMCPServer(
(args as never) ?? {}, (args as never) ?? {},
toolUseContext, toolUseContext,
) )
if (validationResult && !validationResult.result) { if (validationResult?.result === false) {
throw new Error( throw new Error(
`Tool ${name} input is invalid: ${(validationResult as any).message}`, `Tool ${name} input is invalid: ${validationResult.message}`,
) )
} }
const finalResult = await tool.call( const finalResult = await tool.call(

View File

@ -136,7 +136,7 @@ async function isAppleTerminalBellDisabled(): Promise<boolean> {
// Lazy-load plist (~280KB with xmlbuilder+@xmldom) — only hit on // Lazy-load plist (~280KB with xmlbuilder+@xmldom) — only hit on
// Apple_Terminal with auto-channel, which is a small fraction of users. // Apple_Terminal with auto-channel, which is a small fraction of users.
const plist = await import('plist') const plist = await import('plist')
const parsed: Record<string, unknown> = plist.parse(defaultsOutput.stdout) as any const parsed = plist.parse(defaultsOutput.stdout) as Record<string, unknown>
const windowSettings = parsed?.['Window Settings'] as const windowSettings = parsed?.['Window Settings'] as
| Record<string, unknown> | Record<string, unknown>
| undefined | undefined

View File

@ -241,7 +241,7 @@ export async function startSkillDiscoveryPrefetch(
durationMs: Date.now() - startedAt, durationMs: Date.now() - startedAt,
indexSize: index.length, indexSize: index.length,
method: 'tfidf', method: 'tfidf',
} as any }
logForDebugging( logForDebugging(
`[skill-search] prefetch found ${newResults.length} skills in ${signal.durationMs}ms`, `[skill-search] prefetch found ${newResults.length} skills in ${signal.durationMs}ms`,
@ -306,7 +306,7 @@ export async function getTurnZeroSkillDiscovery(
durationMs: Date.now() - startedAt, durationMs: Date.now() - startedAt,
indexSize: index.length, indexSize: index.length,
method: 'tfidf', method: 'tfidf',
} as any }
logForDebugging( logForDebugging(
`[skill-search] turn-zero found ${results.length} skills in ${signal.durationMs}ms`, `[skill-search] turn-zero found ${results.length} skills in ${signal.durationMs}ms`,

View File

@ -1,9 +1,8 @@
// STUB: 待补全 — 见 docs/devlog/02-tsc-stubs.md
// Skill search signal types — used by skill search prefetch and attachment utilities.
// DiscoverySignal represents the signal emitted when a skill is discovered during prefetch.
export type DiscoverySignal = { export type DiscoverySignal = {
type: 'skill_discovery' trigger: 'assistant_turn' | 'user_input'
skillId: string queryText: string
[key: string]: unknown startedAt: number
durationMs: number
indexSize: number
method: 'tfidf'
} }

View File

@ -479,7 +479,13 @@ function roughTokenCountEstimationForBlock(
return 2000 return 2000
} }
if (block.type === 'tool_result') { if (block.type === 'tool_result') {
return roughTokenCountEstimationForContent(block.content as any) return roughTokenCountEstimationForContent(
block.content as
| string
| Array<Anthropic.ContentBlock>
| Array<Anthropic.ContentBlockParam>
| undefined,
)
} }
if (block.type === 'tool_use') { if (block.type === 'tool_use') {
// input is the JSON the model generated — arbitrarily large (bash // input is the JSON the model generated — arbitrarily large (bash

View File

@ -1,4 +1,7 @@
import type { BetaContentBlock, BetaUsage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' import type {
BetaContentBlock,
BetaUsage,
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import { createHash, randomUUID, type UUID } from 'crypto' import { createHash, randomUUID, type UUID } from 'crypto'
import { mkdir, readFile, writeFile } from 'fs/promises' import { mkdir, readFile, writeFile } from 'fs/promises'
import isPlainObject from 'lodash-es/isPlainObject.js' import isPlainObject from 'lodash-es/isPlainObject.js'
@ -9,6 +12,7 @@ import { calculateUSDCost } from 'src/utils/modelCost.js'
import type { import type {
AssistantMessage, AssistantMessage,
Message, Message,
MessageContent,
StreamEvent, StreamEvent,
SystemAPIErrorMessage, SystemAPIErrorMessage,
UserMessage, UserMessage,
@ -252,24 +256,27 @@ function mapAssistantMessage(
message: { message: {
...message.message, ...message.message,
content: (message.message.content as BetaContentBlock[]) content: (message.message.content as BetaContentBlock[])
.map(_ => { .map(block => {
switch (_.type) { switch (block.type) {
case 'text': case 'text':
return { return {
..._, ...block,
text: f(_.text) as string, text: f(block.text) as string,
citations: _.citations || [], citations: block.citations || [],
} // Ensure citations } // Ensure citations
case 'tool_use': case 'tool_use':
return { return {
..._, ...block,
input: mapValuesDeep(_.input as Record<string, unknown>, f), input: mapValuesDeep(block.input as Record<string, unknown>, f),
} }
default: default:
return _ // Handle other block types unchanged return block // Handle other block types unchanged
} }
}) })
.filter(Boolean) as any, .filter(
(block): block is NonNullable<typeof block> =>
block !== null && block !== undefined,
) as unknown as MessageContent,
}, },
type: 'assistant', type: 'assistant',
} }

View File

@ -15,7 +15,7 @@ type SetAppStateFn = (updater: (prev: AppState) => AppState) => void
export function killTask(taskId: string, setAppState: SetAppStateFn): void { export function killTask(taskId: string, setAppState: SetAppStateFn): void {
updateTaskState(taskId, setAppState, task => { updateTaskState(taskId, setAppState, task => {
if ((task as any).status !== 'running' || !isLocalShellTask(task)) { if (!isLocalShellTask(task) || task.status !== 'running') {
return task return task
} }

39
src/types/global.d.ts vendored
View File

@ -16,12 +16,28 @@ declare namespace MACRO {
export const VERSION_CHANGELOG: string export const VERSION_CHANGELOG: string
} }
interface GlobalThis {
MACRO:
| {
VERSION: string
BUILD_TIME: string
FEEDBACK_CHANNEL: string
ISSUES_EXPLAINER: string
NATIVE_PACKAGE_URL: string
PACKAGE_URL: string
VERSION_CHANGELOG: string
}
| undefined
}
// ============================================================================ // ============================================================================
// Internal Anthropic-only identifiers (dead-code eliminated in open-source) // Internal Anthropic-only identifiers (dead-code eliminated in open-source)
// These are referenced inside `MACRO(() => ...)` or `false && ...` blocks. // These are referenced inside `MACRO(() => ...)` or `false && ...` blocks.
// Model resolution (internal) // Model resolution (internal)
declare function resolveAntModel(model: string): import('../utils/model/antModels.js').AntModel | undefined declare function resolveAntModel(
model: string,
): import('../utils/model/antModels.js').AntModel | undefined
declare function getAntModels(): import('../utils/model/antModels.js').AntModel[] declare function getAntModels(): import('../utils/model/antModels.js').AntModel[]
declare function getAntModelOverrideConfig(): { declare function getAntModelOverrideConfig(): {
defaultSystemPromptSuffix?: string defaultSystemPromptSuffix?: string
@ -31,7 +47,13 @@ declare function getAntModelOverrideConfig(): {
// Companion reactions handled by src/buddy/companionReact.ts (direct import) // Companion reactions handled by src/buddy/companionReact.ts (direct import)
// Metrics (internal) // Metrics (internal)
type ApiMetricEntry = { ttftMs: number; firstTokenTime: number; lastTokenTime: number; responseLengthBaseline: number; endResponseLength: number } type ApiMetricEntry = {
ttftMs: number
firstTokenTime: number
lastTokenTime: number
responseLengthBaseline: number
endResponseLength: number
}
declare const apiMetricsRef: React.RefObject<ApiMetricEntry[]> | null declare const apiMetricsRef: React.RefObject<ApiMetricEntry[]> | null
declare function computeTtftText(metrics: ApiMetricEntry[]): string declare function computeTtftText(metrics: ApiMetricEntry[]): string
@ -44,8 +66,12 @@ declare function ExperimentEnrollmentNotice(): JSX.Element | null
declare const HOOK_TIMING_DISPLAY_THRESHOLD_MS: number declare const HOOK_TIMING_DISPLAY_THRESHOLD_MS: number
// Ultraplan (internal) // Ultraplan (internal)
declare function UltraplanChoiceDialog(props: Record<string, unknown>): JSX.Element | null declare function UltraplanChoiceDialog(
declare function UltraplanLaunchDialog(props: Record<string, unknown>): JSX.Element | null props: Record<string, unknown>,
): JSX.Element | null
declare function UltraplanLaunchDialog(
props: Record<string, unknown>,
): JSX.Element | null
declare function launchUltraplan(...args: unknown[]): Promise<string> declare function launchUltraplan(...args: unknown[]): Promise<string>
// T — Generic type parameter leaked from React compiler output // T — Generic type parameter leaked from React compiler output
@ -53,7 +79,10 @@ declare function launchUltraplan(...args: unknown[]): Promise<string>
declare type T = unknown declare type T = unknown
// Tungsten (internal) // Tungsten (internal)
declare function TungstenPill(props?: { key?: string; selected?: boolean }): JSX.Element | null declare function TungstenPill(props?: {
key?: string
selected?: boolean
}): JSX.Element | null
// ============================================================================ // ============================================================================
// Build-time constants BUILD_TARGET/BUILD_ENV/INTERFACE_TYPE — removed (zero runtime usage) // Build-time constants BUILD_TARGET/BUILD_ENV/INTERFACE_TYPE — removed (zero runtime usage)

View File

@ -117,8 +117,8 @@ export function isAnthropicAuthEnabled(): boolean {
isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) || isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) || isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) || isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
(settings as any).modelType === 'openai' || settings.modelType === 'openai' ||
(settings as any).modelType === 'gemini' || settings.modelType === 'gemini' ||
!!process.env.OPENAI_BASE_URL || !!process.env.OPENAI_BASE_URL ||
!!process.env.GEMINI_BASE_URL !!process.env.GEMINI_BASE_URL
const apiKeyHelper = settings.apiKeyHelper const apiKeyHelper = settings.apiKeyHelper

View File

@ -56,7 +56,7 @@ import { TASK_LIST_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TaskL
import { TASK_UPDATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TaskUpdateTool/constants.js' import { TASK_UPDATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TaskUpdateTool/constants.js'
import { TEAM_CREATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TeamCreateTool/constants.js' import { TEAM_CREATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TeamCreateTool/constants.js'
import { TEAM_DELETE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TeamDeleteTool/constants.js' import { TEAM_DELETE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TeamDeleteTool/constants.js'
import type { Message } from '../../types/message.js' import type { ContentItem, Message } from '../../types/message.js'
import type { PermissionDecision } from '../../types/permissions.js' import type { PermissionDecision } from '../../types/permissions.js'
import { import {
createAssistantAPIErrorMessage, createAssistantAPIErrorMessage,
@ -1449,13 +1449,16 @@ export async function runInProcessTeammate(
for (let i = allMessages.length - 1; i >= 0; i--) { for (let i = allMessages.length - 1; i >= 0; i--) {
const m = allMessages[i]! const m = allMessages[i]!
if (m.type === 'assistant') { if (m.type === 'assistant') {
const blocks = (m.message?.content ?? []) as any[] const content = m.message?.content
const blocks: ContentItem[] = Array.isArray(content) ? content : []
for (const b of blocks) { for (const b of blocks) {
if (b?.type === 'tool_use') completionToolUseCount++ if (b.type === 'tool_use') completionToolUseCount++
} }
const textBlocks = blocks.filter((b: any) => b?.type === 'text') const textBlocks = blocks.filter(
(b): b is Extract<ContentItem, { type: 'text' }> => b.type === 'text',
)
if (textBlocks.length > 0 && lastAssistantContent.length === 0) { if (textBlocks.length > 0 && lastAssistantContent.length === 0) {
lastAssistantContent = textBlocks.map((b: any) => ({ lastAssistantContent = textBlocks.map(b => ({
type: 'text' as const, type: 'text' as const,
text: b.text, text: b.text,
})) }))