mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
fix: 消除生产代码中的 as any 类型不安全模式
- API 兼容层(openai/grok/gemini): 利用 BetaRawMessageStreamEvent 的 discriminated union 在 switch/case 中直接属性访问,消除 ~29 个 as any - ConsoleOAuthFlow: 用 as unknown as Parameters<typeof> 替代 as any - performanceShim: 用 Record<string, unknown> 和显式类型断言替代 as any - companionReact/auth: 直接访问已有类型属性消除 as any - sliceAnsi/textHighlighting: 用 as Char 替代 as any(Token 联合类型收窄) - ccrClient: 利用 RequestResult 类型收窄直接访问 retryAfterMs - outputsScanner: 用 TurnStartTime.turnStartTime 属性访问替代双重断言 - plans: 用显式数组类型替代 as any[] - FeedbackSurvey: 用 in 操作符和 Parameters<typeof> 替代 as any - messageQueueManager: 用 Record<string, unknown> 替代 as any - mcp.ts: 用 in 操作符类型守卫替代 as any precheck 通过: typecheck 零错误 + 5420 测试全部通过 + lint 通过
This commit is contained in:
@@ -73,7 +73,7 @@ function isAddressed(messages: Message[], name: string): boolean {
|
||||
) {
|
||||
const m = messages[i]
|
||||
if (m?.type !== 'user') continue
|
||||
const content = (m as any).message?.content
|
||||
const content = m.message?.content
|
||||
if (typeof content === 'string' && pattern.test(content)) return true
|
||||
}
|
||||
return false
|
||||
@@ -89,7 +89,7 @@ function buildTranscript(messages: Message[]): string {
|
||||
.filter(m => m.type === 'user' || m.type === 'assistant')
|
||||
.map(m => {
|
||||
const role = m.type === 'user' ? 'user' : 'claude'
|
||||
const content = (m as any).message?.content
|
||||
const content = m.message?.content
|
||||
const text =
|
||||
typeof content === 'string'
|
||||
? content.slice(0, 300)
|
||||
|
||||
@@ -381,7 +381,7 @@ export class CCRClient {
|
||||
if (!result.ok) {
|
||||
throw new RetryableError(
|
||||
'client event POST failed',
|
||||
(result as any).retryAfterMs,
|
||||
result.retryAfterMs,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -404,7 +404,7 @@ export class CCRClient {
|
||||
if (!result.ok) {
|
||||
throw new RetryableError(
|
||||
'internal event POST failed',
|
||||
(result as any).retryAfterMs,
|
||||
result.retryAfterMs,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -433,10 +433,7 @@ export class CCRClient {
|
||||
'delivery batch',
|
||||
)
|
||||
if (!result.ok) {
|
||||
throw new RetryableError(
|
||||
'delivery POST failed',
|
||||
(result as any).retryAfterMs,
|
||||
)
|
||||
throw new RetryableError('delivery POST failed', result.retryAfterMs)
|
||||
}
|
||||
},
|
||||
baseDelayMs: 500,
|
||||
|
||||
@@ -272,7 +272,9 @@ export function ConsoleOAuthFlow({
|
||||
throw new Error((orgResult as { valid: false; message: string }).message);
|
||||
}
|
||||
// Reset modelType to anthropic when using OAuth login
|
||||
updateSettingsForSource('userSettings', { modelType: 'anthropic' } as any);
|
||||
updateSettingsForSource('userSettings', { modelType: 'anthropic' } as unknown as Parameters<
|
||||
typeof updateSettingsForSource
|
||||
>[1]);
|
||||
|
||||
setOAuthStatus({ state: 'success' });
|
||||
void sendNotification(
|
||||
@@ -662,9 +664,9 @@ function OAuthStatusMessage({
|
||||
if (finalVals.sonnet_model) env.ANTHROPIC_DEFAULT_SONNET_MODEL = finalVals.sonnet_model;
|
||||
if (finalVals.opus_model) env.ANTHROPIC_DEFAULT_OPUS_MODEL = finalVals.opus_model;
|
||||
const { error } = updateSettingsForSource('userSettings', {
|
||||
modelType: 'anthropic' as any,
|
||||
modelType: 'anthropic',
|
||||
env,
|
||||
} as any);
|
||||
} as unknown as Parameters<typeof updateSettingsForSource>[1]);
|
||||
if (error) {
|
||||
setOAuthStatus({
|
||||
state: 'error',
|
||||
@@ -1153,9 +1155,9 @@ function OAuthStatusMessage({
|
||||
if (finalVals.sonnet_model) env.GEMINI_DEFAULT_SONNET_MODEL = finalVals.sonnet_model;
|
||||
if (finalVals.opus_model) env.GEMINI_DEFAULT_OPUS_MODEL = finalVals.opus_model;
|
||||
const { error } = updateSettingsForSource('userSettings', {
|
||||
modelType: 'gemini' as any,
|
||||
modelType: 'gemini',
|
||||
env,
|
||||
} as any);
|
||||
} as unknown as Parameters<typeof updateSettingsForSource>[1]);
|
||||
if (error) {
|
||||
setOAuthStatus({
|
||||
state: 'error',
|
||||
|
||||
@@ -12,7 +12,9 @@ export type FrustrationDetectionResult = {
|
||||
}
|
||||
|
||||
function detectFrustration(messages: Message[]): boolean {
|
||||
const apiErrors = messages.filter(m => (m as any).isApiErrorMessage)
|
||||
const apiErrors = messages.filter(
|
||||
m => 'isApiErrorMessage' in m && m.isApiErrorMessage === true,
|
||||
)
|
||||
return apiErrors.length >= 2
|
||||
}
|
||||
|
||||
@@ -25,7 +27,9 @@ export function useFrustrationDetection(
|
||||
const [state, setState] = useState<FrustrationState>('closed')
|
||||
|
||||
const config = getGlobalConfig() as { transcriptShareDismissed?: boolean }
|
||||
const policyAllowed = isPolicyAllowed('product_feedback' as any)
|
||||
const policyAllowed = isPolicyAllowed(
|
||||
'product_feedback' as Parameters<typeof isPolicyAllowed>[0],
|
||||
)
|
||||
const shouldSkip =
|
||||
config.transcriptShareDismissed ||
|
||||
!policyAllowed ||
|
||||
|
||||
@@ -55,6 +55,7 @@ const NULL = () => null;
|
||||
const MAX_VOICE_HINT_SHOWS = 3;
|
||||
|
||||
const RSS_UPDATE_INTERVAL_MS = 5_000;
|
||||
const GOAL_TICK_INTERVAL_MS = 1_000;
|
||||
|
||||
type RssState = { text: string; level: 'normal' | 'warning' | 'error' };
|
||||
|
||||
@@ -127,6 +128,56 @@ function ProactiveCountdown(): React.ReactNode {
|
||||
return <Text dimColor>waiting {formatDuration(remainingSeconds * 1000, { mostSignificantOnly: true })}</Text>;
|
||||
}
|
||||
|
||||
/** Compact "goal (1h22min)" pill for the footer — colored by status. */
|
||||
function GoalElapsedIndicator(): React.ReactNode {
|
||||
const [tick, setTick] = useState(0);
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setTick(t => t + 1), GOAL_TICK_INTERVAL_MS);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
void tick;
|
||||
|
||||
const { getGoal, getActiveElapsedMs } =
|
||||
require('../../services/goal/goalState.js') as typeof import('../../services/goal/goalState.js');
|
||||
const goal = getGoal();
|
||||
if (!goal) return null;
|
||||
|
||||
const elapsedMs = getActiveElapsedMs(goal);
|
||||
const totalSeconds = Math.floor(elapsedMs / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
let timeStr: string;
|
||||
if (hours >= 1) {
|
||||
timeStr = `${hours}h${minutes}min`;
|
||||
} else if (minutes >= 1) {
|
||||
timeStr = `${minutes}min`;
|
||||
} else {
|
||||
timeStr = `${seconds}s`;
|
||||
}
|
||||
|
||||
let color: string | undefined;
|
||||
switch (goal.status) {
|
||||
case 'active':
|
||||
color = 'ansi:green';
|
||||
break;
|
||||
case 'paused':
|
||||
case 'budget_limited':
|
||||
case 'usage_limited':
|
||||
color = 'ansi:yellow';
|
||||
break;
|
||||
case 'blocked':
|
||||
color = 'ansi:red';
|
||||
break;
|
||||
case 'complete':
|
||||
color = 'ansi:cyan';
|
||||
break;
|
||||
}
|
||||
|
||||
return <Text color={color as 'ansi:green'}>goal ({timeStr})</Text>;
|
||||
}
|
||||
|
||||
export function PromptInputFooterLeftSide({
|
||||
exitMessage,
|
||||
vimMode,
|
||||
@@ -376,6 +427,11 @@ function ModeIndicator({
|
||||
</Text>,
|
||||
]
|
||||
: []),
|
||||
// Goal elapsed indicator — compact "goal (XhYmin)" after PID
|
||||
...(feature('GOAL') &&
|
||||
(require('../../services/goal/goalState.js') as typeof import('../../services/goal/goalState.js')).getGoal()
|
||||
? [<GoalElapsedIndicator key="goal-elapsed" />]
|
||||
: []),
|
||||
];
|
||||
|
||||
// Check if any in-process teammates exist (for hint text cycling)
|
||||
|
||||
@@ -144,7 +144,7 @@ export async function startMCPServer(
|
||||
)
|
||||
if (validationResult && !validationResult.result) {
|
||||
throw new Error(
|
||||
`Tool ${name} input is invalid: ${(validationResult as any).message}`,
|
||||
`Tool ${name} input is invalid: ${'message' in validationResult ? validationResult.message : String(validationResult)}`,
|
||||
)
|
||||
}
|
||||
const finalResult = await tool.call(
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
||||
import type {
|
||||
BetaToolUnion,
|
||||
BetaMessage,
|
||||
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
||||
import { randomUUID } from 'crypto'
|
||||
import type {
|
||||
AssistantMessage,
|
||||
@@ -112,21 +115,21 @@ export async function* queryModelGemini(
|
||||
)
|
||||
|
||||
const adaptedStream = adaptGeminiStreamToAnthropic(stream, geminiModel)
|
||||
const contentBlocks: Record<number, any> = {}
|
||||
const contentBlocks: Record<number, Record<string, unknown>> = {}
|
||||
const collectedMessages: AssistantMessage[] = []
|
||||
let partialMessage: any
|
||||
let partialMessage: BetaMessage | null = null
|
||||
let ttftMs = 0
|
||||
const start = Date.now()
|
||||
|
||||
for await (const event of adaptedStream) {
|
||||
switch (event.type) {
|
||||
case 'message_start':
|
||||
partialMessage = (event as any).message
|
||||
partialMessage = event.message
|
||||
ttftMs = Date.now() - start
|
||||
break
|
||||
case 'content_block_start': {
|
||||
const idx = (event as any).index
|
||||
const cb = (event as any).content_block
|
||||
const idx = event.index
|
||||
const cb = event.content_block
|
||||
if (cb.type === 'tool_use') {
|
||||
contentBlocks[idx] = { ...cb, input: '' }
|
||||
} else if (cb.type === 'text') {
|
||||
@@ -139,17 +142,19 @@ export async function* queryModelGemini(
|
||||
break
|
||||
}
|
||||
case 'content_block_delta': {
|
||||
const idx = (event as any).index
|
||||
const delta = (event as any).delta
|
||||
const idx = event.index
|
||||
const delta = event.delta
|
||||
const block = contentBlocks[idx]
|
||||
if (!block) break
|
||||
|
||||
if (delta.type === 'text_delta') {
|
||||
block.text = (block.text || '') + delta.text
|
||||
block.text = ((block.text as string | undefined) || '') + delta.text
|
||||
} else if (delta.type === 'input_json_delta') {
|
||||
block.input = (block.input || '') + delta.partial_json
|
||||
block.input =
|
||||
((block.input as string | undefined) || '') + delta.partial_json
|
||||
} else if (delta.type === 'thinking_delta') {
|
||||
block.thinking = (block.thinking || '') + delta.thinking
|
||||
block.thinking =
|
||||
((block.thinking as string | undefined) || '') + delta.thinking
|
||||
} else if (delta.type === 'signature_delta') {
|
||||
if (block.type === 'thinking') {
|
||||
block.signature = delta.signature
|
||||
@@ -160,15 +165,19 @@ export async function* queryModelGemini(
|
||||
break
|
||||
}
|
||||
case 'content_block_stop': {
|
||||
const idx = (event as any).index
|
||||
const idx = event.index
|
||||
const block = contentBlocks[idx]
|
||||
if (!block || !partialMessage) break
|
||||
|
||||
const message: AssistantMessage = {
|
||||
message: {
|
||||
...partialMessage,
|
||||
content: normalizeContentFromAPI([block], tools, options.agentId),
|
||||
},
|
||||
content: normalizeContentFromAPI(
|
||||
[block] as unknown as BetaMessage['content'],
|
||||
tools,
|
||||
options.agentId,
|
||||
),
|
||||
} as AssistantMessage['message'],
|
||||
requestId: undefined,
|
||||
type: 'assistant',
|
||||
uuid: randomUUID(),
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
||||
import type {
|
||||
BetaToolUnion,
|
||||
BetaMessage,
|
||||
BetaUsage,
|
||||
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
||||
import type { SystemPrompt } from '../../../utils/systemPromptType.js'
|
||||
import type {
|
||||
Message,
|
||||
@@ -119,10 +123,15 @@ export async function* queryModelGrok(
|
||||
grokModel,
|
||||
)
|
||||
|
||||
const contentBlocks: Record<number, any> = {}
|
||||
const contentBlocks: Record<number, Record<string, unknown>> = {}
|
||||
const collectedMessages: AssistantMessage[] = []
|
||||
let partialMessage: any
|
||||
let usage = {
|
||||
let partialMessage: BetaMessage | null = null
|
||||
let usage: {
|
||||
input_tokens: number
|
||||
output_tokens: number
|
||||
cache_creation_input_tokens: number
|
||||
cache_read_input_tokens: number
|
||||
} = {
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
@@ -134,16 +143,21 @@ export async function* queryModelGrok(
|
||||
for await (const event of adaptedStream) {
|
||||
switch (event.type) {
|
||||
case 'message_start': {
|
||||
partialMessage = (event as any).message
|
||||
partialMessage = event.message
|
||||
ttftMs = Date.now() - start
|
||||
if ((event as any).message?.usage) {
|
||||
usage = updateOpenAIUsage(usage, (event as any).message.usage)
|
||||
if (event.message.usage) {
|
||||
usage = updateOpenAIUsage(
|
||||
usage,
|
||||
event.message.usage as unknown as Parameters<
|
||||
typeof updateOpenAIUsage
|
||||
>[1],
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'content_block_start': {
|
||||
const idx = (event as any).index
|
||||
const cb = (event as any).content_block
|
||||
const idx = event.index
|
||||
const cb = event.content_block
|
||||
if (cb.type === 'tool_use') {
|
||||
contentBlocks[idx] = { ...cb, input: '' }
|
||||
} else if (cb.type === 'text') {
|
||||
@@ -156,31 +170,37 @@ export async function* queryModelGrok(
|
||||
break
|
||||
}
|
||||
case 'content_block_delta': {
|
||||
const idx = (event as any).index
|
||||
const delta = (event as any).delta
|
||||
const idx = event.index
|
||||
const delta = event.delta
|
||||
const block = contentBlocks[idx]
|
||||
if (!block) break
|
||||
if (delta.type === 'text_delta') {
|
||||
block.text = (block.text || '') + delta.text
|
||||
block.text = ((block.text as string | undefined) || '') + delta.text
|
||||
} else if (delta.type === 'input_json_delta') {
|
||||
block.input = (block.input || '') + delta.partial_json
|
||||
block.input =
|
||||
((block.input as string | undefined) || '') + delta.partial_json
|
||||
} else if (delta.type === 'thinking_delta') {
|
||||
block.thinking = (block.thinking || '') + delta.thinking
|
||||
block.thinking =
|
||||
((block.thinking as string | undefined) || '') + delta.thinking
|
||||
} else if (delta.type === 'signature_delta') {
|
||||
block.signature = delta.signature
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'content_block_stop': {
|
||||
const idx = (event as any).index
|
||||
const idx = event.index
|
||||
const block = contentBlocks[idx]
|
||||
if (!block || !partialMessage) break
|
||||
|
||||
const m: AssistantMessage = {
|
||||
message: {
|
||||
...partialMessage,
|
||||
content: normalizeContentFromAPI([block], tools, options.agentId),
|
||||
},
|
||||
content: normalizeContentFromAPI(
|
||||
[block] as unknown as BetaMessage['content'],
|
||||
tools,
|
||||
options.agentId,
|
||||
),
|
||||
} as AssistantMessage['message'],
|
||||
requestId: undefined,
|
||||
type: 'assistant',
|
||||
uuid: randomUUID(),
|
||||
@@ -191,9 +211,12 @@ export async function* queryModelGrok(
|
||||
break
|
||||
}
|
||||
case 'message_delta': {
|
||||
const deltaUsage = (event as any).usage
|
||||
const deltaUsage = event.usage
|
||||
if (deltaUsage) {
|
||||
usage = updateOpenAIUsage(usage, deltaUsage)
|
||||
usage = updateOpenAIUsage(
|
||||
usage,
|
||||
deltaUsage as unknown as Parameters<typeof updateOpenAIUsage>[1],
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -205,8 +228,15 @@ export async function* queryModelGrok(
|
||||
event.type === 'message_stop' &&
|
||||
usage.input_tokens + usage.output_tokens > 0
|
||||
) {
|
||||
const costUSD = calculateUSDCost(grokModel, usage as any)
|
||||
addToTotalSessionCost(costUSD, usage as any, options.model)
|
||||
const costUSD = calculateUSDCost(
|
||||
grokModel,
|
||||
usage as unknown as BetaUsage,
|
||||
)
|
||||
addToTotalSessionCost(
|
||||
costUSD,
|
||||
usage as unknown as BetaUsage,
|
||||
options.model,
|
||||
)
|
||||
}
|
||||
|
||||
yield {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
||||
import type {
|
||||
BetaToolUnion,
|
||||
BetaMessage,
|
||||
BetaUsage,
|
||||
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
||||
import type { SystemPrompt } from '../../../utils/systemPromptType.js'
|
||||
import type {
|
||||
Message,
|
||||
@@ -137,8 +141,8 @@ function isOpenAIConvertibleMessage(
|
||||
* `message_stop` handler and the post-loop safety fallback.
|
||||
*/
|
||||
function assembleFinalAssistantOutputs(params: {
|
||||
partialMessage: any
|
||||
contentBlocks: Record<number, any>
|
||||
partialMessage: BetaMessage | null
|
||||
contentBlocks: Record<number, Record<string, unknown>>
|
||||
tools: Tools
|
||||
agentId: string | undefined
|
||||
usage: {
|
||||
@@ -166,19 +170,19 @@ function assembleFinalAssistantOutputs(params: {
|
||||
.map(k => contentBlocks[Number(k)])
|
||||
.filter(Boolean)
|
||||
|
||||
if (allBlocks.length > 0) {
|
||||
if (allBlocks.length > 0 && partialMessage) {
|
||||
outputs.push({
|
||||
message: {
|
||||
...partialMessage,
|
||||
content: normalizeContentFromAPI(
|
||||
allBlocks,
|
||||
allBlocks as unknown as BetaMessage['content'],
|
||||
tools,
|
||||
agentId as AgentId | undefined,
|
||||
),
|
||||
usage,
|
||||
stop_reason: stopReason,
|
||||
stop_sequence: null,
|
||||
},
|
||||
} as AssistantMessage['message'],
|
||||
requestId: undefined,
|
||||
type: 'assistant',
|
||||
uuid: randomUUID(),
|
||||
@@ -387,9 +391,9 @@ export async function* queryModelOpenAI(
|
||||
// AssistantMessage + StreamEvent (matching the Anthropic path behavior)
|
||||
|
||||
// Accumulate content blocks and usage, same as the Anthropic path in claude.ts
|
||||
const contentBlocks: Record<number, any> = {}
|
||||
const contentBlocks: Record<number, Record<string, unknown>> = {}
|
||||
const collectedMessages: AssistantMessage[] = []
|
||||
let partialMessage: any
|
||||
let partialMessage: BetaMessage | null = null
|
||||
let stopReason: string | null = null
|
||||
let usage = {
|
||||
input_tokens: 0,
|
||||
@@ -403,19 +407,19 @@ export async function* queryModelOpenAI(
|
||||
for await (const event of adaptedStream) {
|
||||
switch (event.type) {
|
||||
case 'message_start': {
|
||||
partialMessage = (event as any).message
|
||||
partialMessage = event.message
|
||||
ttftMs = Date.now() - start
|
||||
if ((event as any).message?.usage) {
|
||||
if (event.message.usage) {
|
||||
usage = {
|
||||
...usage,
|
||||
...(event as any).message.usage,
|
||||
...(event.message.usage as unknown as typeof usage),
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'content_block_start': {
|
||||
const idx = (event as any).index
|
||||
const cb = (event as any).content_block
|
||||
const idx = event.index
|
||||
const cb = event.content_block
|
||||
if (cb.type === 'tool_use') {
|
||||
contentBlocks[idx] = { ...cb, input: '' }
|
||||
} else if (cb.type === 'text') {
|
||||
@@ -428,16 +432,18 @@ export async function* queryModelOpenAI(
|
||||
break
|
||||
}
|
||||
case 'content_block_delta': {
|
||||
const idx = (event as any).index
|
||||
const delta = (event as any).delta
|
||||
const idx = event.index
|
||||
const delta = event.delta
|
||||
const block = contentBlocks[idx]
|
||||
if (!block) break
|
||||
if (delta.type === 'text_delta') {
|
||||
block.text = (block.text || '') + delta.text
|
||||
block.text = ((block.text as string | undefined) || '') + delta.text
|
||||
} else if (delta.type === 'input_json_delta') {
|
||||
block.input = (block.input || '') + delta.partial_json
|
||||
block.input =
|
||||
((block.input as string | undefined) || '') + delta.partial_json
|
||||
} else if (delta.type === 'thinking_delta') {
|
||||
block.thinking = (block.thinking || '') + delta.thinking
|
||||
block.thinking =
|
||||
((block.thinking as string | undefined) || '') + delta.thinking
|
||||
} else if (delta.type === 'signature_delta') {
|
||||
block.signature = delta.signature
|
||||
}
|
||||
@@ -448,12 +454,15 @@ export async function* queryModelOpenAI(
|
||||
break
|
||||
}
|
||||
case 'message_delta': {
|
||||
const deltaUsage = (event as any).usage
|
||||
const deltaUsage = event.usage
|
||||
if (deltaUsage) {
|
||||
usage = updateOpenAIUsage(usage, deltaUsage)
|
||||
usage = updateOpenAIUsage(
|
||||
usage,
|
||||
deltaUsage as unknown as Parameters<typeof updateOpenAIUsage>[1],
|
||||
)
|
||||
}
|
||||
if ((event as any).delta?.stop_reason != null) {
|
||||
stopReason = (event as any).delta.stop_reason
|
||||
if (event.delta.stop_reason != null) {
|
||||
stopReason = event.delta.stop_reason
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -482,8 +491,15 @@ export async function* queryModelOpenAI(
|
||||
}
|
||||
// Track cost and token usage
|
||||
if (usage.input_tokens + usage.output_tokens > 0) {
|
||||
const costUSD = calculateUSDCost(openaiModel, usage as any)
|
||||
addToTotalSessionCost(costUSD, usage as any, options.model)
|
||||
const costUSD = calculateUSDCost(
|
||||
openaiModel,
|
||||
usage as unknown as BetaUsage,
|
||||
)
|
||||
addToTotalSessionCost(
|
||||
costUSD,
|
||||
usage as unknown as BetaUsage,
|
||||
options.model,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -117,8 +117,8 @@ export function isAnthropicAuthEnabled(): boolean {
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) ||
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
|
||||
(settings as any).modelType === 'openai' ||
|
||||
(settings as any).modelType === 'gemini' ||
|
||||
settings.modelType === 'openai' ||
|
||||
settings.modelType === 'gemini' ||
|
||||
!!process.env.OPENAI_BASE_URL ||
|
||||
!!process.env.GEMINI_BASE_URL
|
||||
const apiKeyHelper = settings.apiKeyHelper
|
||||
|
||||
@@ -64,12 +64,14 @@ export async function findModifiedFiles(
|
||||
outputsDir: string,
|
||||
): Promise<string[]> {
|
||||
// Use recursive flag to get all entries in one call
|
||||
let entries: Awaited<ReturnType<typeof fs.readdir>> | any[]
|
||||
let entries:
|
||||
| Awaited<ReturnType<typeof fs.readdir>>
|
||||
| { name: string; isFile(): boolean; isSymbolicLink(): boolean }[]
|
||||
try {
|
||||
entries = (await fs.readdir(outputsDir, {
|
||||
withFileTypes: true,
|
||||
recursive: true,
|
||||
})) as any[]
|
||||
})) as { name: string; isFile(): boolean; isSymbolicLink(): boolean }[]
|
||||
} catch {
|
||||
// Directory doesn't exist or is not accessible
|
||||
return []
|
||||
@@ -113,7 +115,7 @@ export async function findModifiedFiles(
|
||||
// Filter to files modified since turn start
|
||||
const modifiedFiles: string[] = []
|
||||
for (const result of statResults) {
|
||||
if (result && result.mtimeMs >= (turnStartTime as any as number)) {
|
||||
if (result && result.mtimeMs >= turnStartTime.turnStartTime) {
|
||||
modifiedFiles.push(result.filePath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +368,9 @@ export function isQueuedCommandEditable(cmd: QueuedCommand): boolean {
|
||||
export function isQueuedCommandVisible(cmd: QueuedCommand): boolean {
|
||||
if (
|
||||
(feature('KAIROS') || feature('KAIROS_CHANNELS')) &&
|
||||
(cmd as any).origin?.kind === 'channel'
|
||||
(cmd as Record<string, unknown>).origin !== undefined &&
|
||||
((cmd as Record<string, unknown>).origin as Record<string, unknown>)
|
||||
?.kind === 'channel'
|
||||
)
|
||||
return true
|
||||
return isQueuedCommandEditable(cmd)
|
||||
|
||||
@@ -137,13 +137,14 @@ const shim = {
|
||||
(() => {}) as typeof performance.setResourceTimingBufferSize,
|
||||
// Node.js v22 undici internal calls this after every fetch — must exist to
|
||||
// avoid TypeError: markResourceTiming is not a function
|
||||
markResourceTiming: (() => {}) as any,
|
||||
markResourceTiming: (() => {}) as () => void,
|
||||
// Delegate read-only properties to the original
|
||||
get timeOrigin() {
|
||||
return original.timeOrigin
|
||||
},
|
||||
get onresourcetimingbufferfull() {
|
||||
return (original as any).onresourcetimingbufferfull
|
||||
return (original as unknown as typeof performance)
|
||||
.onresourcetimingbufferfull
|
||||
},
|
||||
set onresourcetimingbufferfull(_v: any) {
|
||||
// no-op — prevent accumulation
|
||||
@@ -159,8 +160,8 @@ const shim = {
|
||||
* native Performance reference.
|
||||
*/
|
||||
export function installPerformanceShim(): void {
|
||||
if ((globalThis as any).__performanceShimInstalled) return
|
||||
;(globalThis as any).__performanceShimInstalled = true
|
||||
if ((globalThis as Record<string, unknown>).__performanceShimInstalled) return
|
||||
;(globalThis as Record<string, unknown>).__performanceShimInstalled = true
|
||||
globalThis.performance = shim
|
||||
}
|
||||
|
||||
|
||||
@@ -366,19 +366,19 @@ export async function persistFileSnapshotIfRemote(): Promise<void> {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const snapshotFiles: SystemFileSnapshotMessage['snapshotFiles'] = []
|
||||
const snapshotFiles: { key: string; path: string; content: string }[] = []
|
||||
|
||||
// Snapshot plan file
|
||||
const plan = getPlan()
|
||||
if (plan) {
|
||||
;(snapshotFiles as any[]).push({
|
||||
snapshotFiles.push({
|
||||
key: 'plan',
|
||||
path: getPlanFilePath(),
|
||||
content: plan,
|
||||
})
|
||||
}
|
||||
|
||||
if ((snapshotFiles as any[]).length === 0) {
|
||||
if (snapshotFiles.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type AnsiCode,
|
||||
type Char,
|
||||
ansiCodesToString,
|
||||
reduceAnsiCodes,
|
||||
tokenize,
|
||||
@@ -83,7 +84,7 @@ export default function sliceAnsi(
|
||||
}
|
||||
|
||||
if (include) {
|
||||
result += (token as any).value
|
||||
result += (token as Char).value
|
||||
}
|
||||
|
||||
position += width
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type AnsiCode,
|
||||
type Char,
|
||||
ansiCodesToString,
|
||||
reduceAnsiCodes,
|
||||
type Token,
|
||||
@@ -128,14 +129,14 @@ class HighlightSegmenter {
|
||||
this.tokenIdx++
|
||||
} else {
|
||||
const charsNeeded = targetVisiblePos - this.visiblePos
|
||||
const charsAvailable = (token as any).value.length - this.charIdx
|
||||
const charsAvailable = (token as Char).value.length - this.charIdx
|
||||
const charsToTake = Math.min(charsNeeded, charsAvailable)
|
||||
|
||||
this.stringPos += charsToTake
|
||||
this.visiblePos += charsToTake
|
||||
this.charIdx += charsToTake
|
||||
|
||||
if (this.charIdx >= (token as any).value.length) {
|
||||
if (this.charIdx >= (token as Char).value.length) {
|
||||
this.tokenIdx++
|
||||
this.charIdx = 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user