Files
claude-code/src/services/api/gemini/index.ts
claude-code-best fcbc882232 chore: 清理 src 下 113 项未使用导入和死代码
删除未使用的文件(BuiltinStatusLine.tsx、4 个重复的 .ts stub)、
移除约 55 个文件中未使用的 React 导入、
清理约 50 处未使用的导入/变量/参数。
净减少 ~296 行代码,precheck 4077 测试全部通过。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 20:05:15 +08:00

229 lines
7.5 KiB
TypeScript

import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import { randomUUID } from 'crypto'
import type {
AssistantMessage,
Message,
StreamEvent,
SystemAPIErrorMessage,
} from '../../../types/message.js'
import { type Tools } from '../../../Tool.js'
import { toolToAPISchema } from '../../../utils/api.js'
import { logForDebugging } from '../../../utils/debug.js'
import {
createAssistantAPIErrorMessage,
normalizeContentFromAPI,
normalizeMessagesForAPI,
} from '../../../utils/messages.js'
import type { SDKAssistantMessageError } from '../../../entrypoints/agentSdkTypes.js'
import type { SystemPrompt } from '../../../utils/systemPromptType.js'
import type { ThinkingConfig } from '../../../utils/thinking.js'
import type { Options } from '../claude.js'
import { recordLLMObservation } from '../../../services/langfuse/tracing.js'
import {
convertMessagesToLangfuse,
convertOutputToLangfuse,
convertToolsToLangfuse,
} from '../../../services/langfuse/convert.js'
import { streamGeminiGenerateContent } from './client.js'
import {
anthropicMessagesToGemini,
resolveGeminiModel,
adaptGeminiStreamToAnthropic,
anthropicToolsToGemini,
anthropicToolChoiceToGemini,
GEMINI_THOUGHT_SIGNATURE_FIELD,
} from '@ant/model-provider'
export async function* queryModelGemini(
messages: Message[],
systemPrompt: SystemPrompt,
tools: Tools,
signal: AbortSignal,
options: Options,
thinkingConfig: ThinkingConfig,
): AsyncGenerator<
StreamEvent | AssistantMessage | SystemAPIErrorMessage,
void
> {
try {
const geminiModel = resolveGeminiModel(options.model)
const messagesForAPI = normalizeMessagesForAPI(messages, tools)
const toolSchemas = await Promise.all(
tools.map(tool =>
toolToAPISchema(tool, {
getToolPermissionContext: options.getToolPermissionContext,
tools,
agents: options.agents,
allowedAgentTypes: options.allowedAgentTypes,
model: options.model,
}),
),
)
const standardTools = toolSchemas.filter(
(t): t is BetaToolUnion & { type: string } => {
const anyTool = t as unknown as Record<string, unknown>
return (
anyTool.type !== 'advisor_20260301' &&
anyTool.type !== 'computer_20250124'
)
},
)
const { contents, systemInstruction } = anthropicMessagesToGemini(
messagesForAPI,
systemPrompt,
)
const geminiTools = anthropicToolsToGemini(standardTools)
const toolChoice = anthropicToolChoiceToGemini(options.toolChoice)
const stream = streamGeminiGenerateContent({
model: geminiModel,
signal,
fetchOverride: options.fetchOverride as typeof fetch | undefined,
body: {
contents,
...(systemInstruction && { systemInstruction }),
...(geminiTools.length > 0 && { tools: geminiTools }),
...(toolChoice && {
toolConfig: {
functionCallingConfig: toolChoice,
},
}),
generationConfig: {
...(options.temperatureOverride !== undefined && {
temperature: options.temperatureOverride,
}),
...(thinkingConfig.type !== 'disabled' && {
thinkingConfig: {
includeThoughts: true,
...(thinkingConfig.type === 'enabled' && {
thinkingBudget: thinkingConfig.budgetTokens,
}),
},
}),
},
},
})
logForDebugging(
`[Gemini] Calling model=${geminiModel}, messages=${contents.length}, tools=${geminiTools.length}`,
)
const adaptedStream = adaptGeminiStreamToAnthropic(stream, geminiModel)
const contentBlocks: Record<number, any> = {}
const collectedMessages: AssistantMessage[] = []
let partialMessage: any
let ttftMs = 0
const start = Date.now()
for await (const event of adaptedStream) {
switch (event.type) {
case 'message_start':
partialMessage = (event as any).message
ttftMs = Date.now() - start
break
case 'content_block_start': {
const idx = (event as any).index
const cb = (event as any).content_block
if (cb.type === 'tool_use') {
contentBlocks[idx] = { ...cb, input: '' }
} else if (cb.type === 'text') {
contentBlocks[idx] = { ...cb, text: '' }
} else if (cb.type === 'thinking') {
contentBlocks[idx] = { ...cb, thinking: '', signature: '' }
} else {
contentBlocks[idx] = { ...cb }
}
break
}
case 'content_block_delta': {
const idx = (event as any).index
const delta = (event as any).delta
const block = contentBlocks[idx]
if (!block) break
if (delta.type === 'text_delta') {
block.text = (block.text || '') + delta.text
} else if (delta.type === 'input_json_delta') {
block.input = (block.input || '') + delta.partial_json
} else if (delta.type === 'thinking_delta') {
block.thinking = (block.thinking || '') + delta.thinking
} else if (delta.type === 'signature_delta') {
if (block.type === 'thinking') {
block.signature = delta.signature
} else {
block[GEMINI_THOUGHT_SIGNATURE_FIELD] = delta.signature
}
}
break
}
case 'content_block_stop': {
const idx = (event as any).index
const block = contentBlocks[idx]
if (!block || !partialMessage) break
const message: AssistantMessage = {
message: {
...partialMessage,
content: normalizeContentFromAPI([block], tools, options.agentId),
},
requestId: undefined,
type: 'assistant',
uuid: randomUUID(),
timestamp: new Date().toISOString(),
}
collectedMessages.push(message)
yield message
break
}
case 'message_delta':
case 'message_stop':
break
}
yield {
type: 'stream_event',
event,
...(event.type === 'message_start' ? { ttftMs } : undefined),
} as StreamEvent
}
// Record LLM observation in Langfuse (no-op if not configured)
recordLLMObservation(options.langfuseTrace ?? null, {
model: geminiModel,
provider: 'gemini',
input: convertMessagesToLangfuse(messagesForAPI, systemPrompt),
output: convertOutputToLangfuse(collectedMessages),
usage: {
input_tokens: 0,
output_tokens: 0,
},
startTime: new Date(start),
endTime: new Date(),
completionStartTime: ttftMs > 0 ? new Date(start + ttftMs) : undefined,
tools: convertToolsToLangfuse(toolSchemas as unknown[]),
thinking:
thinkingConfig.type !== 'disabled'
? {
type: thinkingConfig.type,
...(thinkingConfig.type === 'enabled' && {
budgetTokens: thinkingConfig.budgetTokens,
}),
}
: undefined,
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
logForDebugging(`[Gemini] Error: ${errorMessage}`, { level: 'error' })
yield createAssistantAPIErrorMessage({
content: `API Error: ${errorMessage}`,
apiError: 'api_error',
error: (error instanceof Error
? error
: new Error(String(error))) as unknown as SDKAssistantMessageError,
})
}
}