@shareai-lab/kode 1.1.13 → 1.1.16-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/dist/entrypoints/cli.js +59 -38
  2. package/dist/entrypoints/cli.js.map +3 -3
  3. package/dist/index.js +5 -26
  4. package/dist/package.json +4 -1
  5. package/package.json +11 -104
  6. package/dist/test/testAdapters.js +0 -88
  7. package/dist/test/testAdapters.js.map +0 -1
  8. package/src/ProjectOnboarding.tsx +0 -198
  9. package/src/Tool.ts +0 -83
  10. package/src/commands/agents.tsx +0 -3416
  11. package/src/commands/approvedTools.ts +0 -53
  12. package/src/commands/bug.tsx +0 -20
  13. package/src/commands/clear.ts +0 -43
  14. package/src/commands/compact.ts +0 -120
  15. package/src/commands/config.tsx +0 -19
  16. package/src/commands/cost.ts +0 -18
  17. package/src/commands/ctx_viz.ts +0 -209
  18. package/src/commands/doctor.ts +0 -24
  19. package/src/commands/help.tsx +0 -19
  20. package/src/commands/init.ts +0 -37
  21. package/src/commands/listen.ts +0 -42
  22. package/src/commands/login.tsx +0 -51
  23. package/src/commands/logout.tsx +0 -40
  24. package/src/commands/mcp.ts +0 -41
  25. package/src/commands/model.tsx +0 -40
  26. package/src/commands/modelstatus.tsx +0 -20
  27. package/src/commands/onboarding.tsx +0 -34
  28. package/src/commands/pr_comments.ts +0 -59
  29. package/src/commands/refreshCommands.ts +0 -54
  30. package/src/commands/release-notes.ts +0 -34
  31. package/src/commands/resume.tsx +0 -31
  32. package/src/commands/review.ts +0 -49
  33. package/src/commands/terminalSetup.ts +0 -221
  34. package/src/commands.ts +0 -139
  35. package/src/components/ApproveApiKey.tsx +0 -93
  36. package/src/components/AsciiLogo.tsx +0 -13
  37. package/src/components/AutoUpdater.tsx +0 -148
  38. package/src/components/Bug.tsx +0 -367
  39. package/src/components/Config.tsx +0 -293
  40. package/src/components/ConsoleOAuthFlow.tsx +0 -327
  41. package/src/components/Cost.tsx +0 -23
  42. package/src/components/CostThresholdDialog.tsx +0 -46
  43. package/src/components/CustomSelect/option-map.ts +0 -42
  44. package/src/components/CustomSelect/select-option.tsx +0 -78
  45. package/src/components/CustomSelect/select.tsx +0 -152
  46. package/src/components/CustomSelect/theme.ts +0 -45
  47. package/src/components/CustomSelect/use-select-state.ts +0 -414
  48. package/src/components/CustomSelect/use-select.ts +0 -35
  49. package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
  50. package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
  51. package/src/components/Help.tsx +0 -215
  52. package/src/components/HighlightedCode.tsx +0 -33
  53. package/src/components/InvalidConfigDialog.tsx +0 -113
  54. package/src/components/Link.tsx +0 -32
  55. package/src/components/LogSelector.tsx +0 -86
  56. package/src/components/Logo.tsx +0 -170
  57. package/src/components/MCPServerApprovalDialog.tsx +0 -100
  58. package/src/components/MCPServerDialogCopy.tsx +0 -25
  59. package/src/components/MCPServerMultiselectDialog.tsx +0 -109
  60. package/src/components/Message.tsx +0 -221
  61. package/src/components/MessageResponse.tsx +0 -15
  62. package/src/components/MessageSelector.tsx +0 -211
  63. package/src/components/ModeIndicator.tsx +0 -88
  64. package/src/components/ModelConfig.tsx +0 -301
  65. package/src/components/ModelListManager.tsx +0 -227
  66. package/src/components/ModelSelector.tsx +0 -3387
  67. package/src/components/ModelStatusDisplay.tsx +0 -230
  68. package/src/components/Onboarding.tsx +0 -274
  69. package/src/components/PressEnterToContinue.tsx +0 -11
  70. package/src/components/PromptInput.tsx +0 -760
  71. package/src/components/SentryErrorBoundary.ts +0 -39
  72. package/src/components/Spinner.tsx +0 -129
  73. package/src/components/StickerRequestForm.tsx +0 -16
  74. package/src/components/StructuredDiff.tsx +0 -191
  75. package/src/components/TextInput.tsx +0 -259
  76. package/src/components/TodoItem.tsx +0 -47
  77. package/src/components/TokenWarning.tsx +0 -31
  78. package/src/components/ToolUseLoader.tsx +0 -40
  79. package/src/components/TrustDialog.tsx +0 -106
  80. package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
  81. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
  82. package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
  83. package/src/components/binary-feedback/utils.ts +0 -220
  84. package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
  85. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
  86. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
  87. package/src/components/messages/AssistantTextMessage.tsx +0 -144
  88. package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
  89. package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
  90. package/src/components/messages/TaskProgressMessage.tsx +0 -32
  91. package/src/components/messages/TaskToolMessage.tsx +0 -58
  92. package/src/components/messages/UserBashInputMessage.tsx +0 -28
  93. package/src/components/messages/UserCommandMessage.tsx +0 -30
  94. package/src/components/messages/UserKodingInputMessage.tsx +0 -28
  95. package/src/components/messages/UserPromptMessage.tsx +0 -35
  96. package/src/components/messages/UserTextMessage.tsx +0 -39
  97. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
  98. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
  99. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
  100. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
  101. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
  102. package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
  103. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
  104. package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
  105. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
  106. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
  107. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
  108. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
  109. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
  110. package/src/components/permissions/PermissionRequest.tsx +0 -101
  111. package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
  112. package/src/components/permissions/hooks.ts +0 -44
  113. package/src/components/permissions/toolUseOptions.ts +0 -59
  114. package/src/components/permissions/utils.ts +0 -23
  115. package/src/constants/betas.ts +0 -5
  116. package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
  117. package/src/constants/figures.ts +0 -4
  118. package/src/constants/keys.ts +0 -3
  119. package/src/constants/macros.ts +0 -11
  120. package/src/constants/modelCapabilities.ts +0 -179
  121. package/src/constants/models.ts +0 -1025
  122. package/src/constants/oauth.ts +0 -18
  123. package/src/constants/product.ts +0 -17
  124. package/src/constants/prompts.ts +0 -168
  125. package/src/constants/releaseNotes.ts +0 -7
  126. package/src/context/PermissionContext.tsx +0 -149
  127. package/src/context.ts +0 -278
  128. package/src/cost-tracker.ts +0 -84
  129. package/src/entrypoints/cli.tsx +0 -1561
  130. package/src/entrypoints/mcp.ts +0 -175
  131. package/src/history.ts +0 -25
  132. package/src/hooks/useApiKeyVerification.ts +0 -59
  133. package/src/hooks/useArrowKeyHistory.ts +0 -55
  134. package/src/hooks/useCanUseTool.ts +0 -138
  135. package/src/hooks/useCancelRequest.ts +0 -39
  136. package/src/hooks/useDoublePress.ts +0 -41
  137. package/src/hooks/useExitOnCtrlCD.ts +0 -31
  138. package/src/hooks/useInterval.ts +0 -25
  139. package/src/hooks/useLogMessages.ts +0 -16
  140. package/src/hooks/useLogStartupTime.ts +0 -12
  141. package/src/hooks/useNotifyAfterTimeout.ts +0 -65
  142. package/src/hooks/usePermissionRequestLogging.ts +0 -44
  143. package/src/hooks/useTerminalSize.ts +0 -49
  144. package/src/hooks/useTextInput.ts +0 -317
  145. package/src/hooks/useUnifiedCompletion.ts +0 -1405
  146. package/src/index.ts +0 -34
  147. package/src/messages.ts +0 -38
  148. package/src/permissions.ts +0 -268
  149. package/src/query.ts +0 -720
  150. package/src/screens/ConfigureNpmPrefix.tsx +0 -197
  151. package/src/screens/Doctor.tsx +0 -219
  152. package/src/screens/LogList.tsx +0 -68
  153. package/src/screens/REPL.tsx +0 -813
  154. package/src/screens/ResumeConversation.tsx +0 -68
  155. package/src/services/adapters/base.ts +0 -38
  156. package/src/services/adapters/chatCompletions.ts +0 -90
  157. package/src/services/adapters/responsesAPI.ts +0 -170
  158. package/src/services/browserMocks.ts +0 -66
  159. package/src/services/claude.ts +0 -2197
  160. package/src/services/customCommands.ts +0 -704
  161. package/src/services/fileFreshness.ts +0 -377
  162. package/src/services/gpt5ConnectionTest.ts +0 -340
  163. package/src/services/mcpClient.ts +0 -564
  164. package/src/services/mcpServerApproval.tsx +0 -50
  165. package/src/services/mentionProcessor.ts +0 -273
  166. package/src/services/modelAdapterFactory.ts +0 -69
  167. package/src/services/notifier.ts +0 -40
  168. package/src/services/oauth.ts +0 -357
  169. package/src/services/openai.ts +0 -1359
  170. package/src/services/responseStateManager.ts +0 -90
  171. package/src/services/sentry.ts +0 -3
  172. package/src/services/statsig.ts +0 -172
  173. package/src/services/statsigStorage.ts +0 -86
  174. package/src/services/systemReminder.ts +0 -507
  175. package/src/services/vcr.ts +0 -161
  176. package/src/test/testAdapters.ts +0 -96
  177. package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
  178. package/src/tools/ArchitectTool/prompt.ts +0 -15
  179. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
  180. package/src/tools/BashTool/BashTool.tsx +0 -243
  181. package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
  182. package/src/tools/BashTool/OutputLine.tsx +0 -49
  183. package/src/tools/BashTool/prompt.ts +0 -174
  184. package/src/tools/BashTool/utils.ts +0 -56
  185. package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
  186. package/src/tools/FileEditTool/prompt.ts +0 -51
  187. package/src/tools/FileEditTool/utils.ts +0 -58
  188. package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
  189. package/src/tools/FileReadTool/prompt.ts +0 -7
  190. package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
  191. package/src/tools/FileWriteTool/prompt.ts +0 -10
  192. package/src/tools/GlobTool/GlobTool.tsx +0 -119
  193. package/src/tools/GlobTool/prompt.ts +0 -8
  194. package/src/tools/GrepTool/GrepTool.tsx +0 -147
  195. package/src/tools/GrepTool/prompt.ts +0 -11
  196. package/src/tools/MCPTool/MCPTool.tsx +0 -107
  197. package/src/tools/MCPTool/prompt.ts +0 -3
  198. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
  199. package/src/tools/MemoryReadTool/prompt.ts +0 -3
  200. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
  201. package/src/tools/MemoryWriteTool/prompt.ts +0 -3
  202. package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
  203. package/src/tools/MultiEditTool/prompt.ts +0 -45
  204. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
  205. package/src/tools/NotebookEditTool/prompt.ts +0 -3
  206. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
  207. package/src/tools/NotebookReadTool/prompt.ts +0 -3
  208. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
  209. package/src/tools/StickerRequestTool/prompt.ts +0 -19
  210. package/src/tools/TaskTool/TaskTool.tsx +0 -438
  211. package/src/tools/TaskTool/constants.ts +0 -1
  212. package/src/tools/TaskTool/prompt.ts +0 -92
  213. package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
  214. package/src/tools/ThinkTool/prompt.ts +0 -12
  215. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
  216. package/src/tools/TodoWriteTool/prompt.ts +0 -63
  217. package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
  218. package/src/tools/URLFetcherTool/cache.ts +0 -55
  219. package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
  220. package/src/tools/URLFetcherTool/prompt.ts +0 -17
  221. package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
  222. package/src/tools/WebSearchTool/prompt.ts +0 -13
  223. package/src/tools/WebSearchTool/searchProviders.ts +0 -66
  224. package/src/tools/lsTool/lsTool.tsx +0 -272
  225. package/src/tools/lsTool/prompt.ts +0 -2
  226. package/src/tools.ts +0 -67
  227. package/src/types/PermissionMode.ts +0 -120
  228. package/src/types/RequestContext.ts +0 -72
  229. package/src/types/common.d.ts +0 -2
  230. package/src/types/conversation.ts +0 -51
  231. package/src/types/logs.ts +0 -58
  232. package/src/types/modelCapabilities.ts +0 -64
  233. package/src/types/notebook.ts +0 -87
  234. package/src/utils/Cursor.ts +0 -436
  235. package/src/utils/PersistentShell.ts +0 -552
  236. package/src/utils/advancedFuzzyMatcher.ts +0 -290
  237. package/src/utils/agentLoader.ts +0 -278
  238. package/src/utils/agentStorage.ts +0 -97
  239. package/src/utils/array.ts +0 -3
  240. package/src/utils/ask.tsx +0 -99
  241. package/src/utils/auth.ts +0 -13
  242. package/src/utils/autoCompactCore.ts +0 -223
  243. package/src/utils/autoUpdater.ts +0 -458
  244. package/src/utils/betas.ts +0 -20
  245. package/src/utils/browser.ts +0 -14
  246. package/src/utils/cleanup.ts +0 -72
  247. package/src/utils/commands.ts +0 -261
  248. package/src/utils/commonUnixCommands.ts +0 -161
  249. package/src/utils/config.ts +0 -945
  250. package/src/utils/conversationRecovery.ts +0 -55
  251. package/src/utils/debugLogger.ts +0 -1235
  252. package/src/utils/diff.ts +0 -42
  253. package/src/utils/env.ts +0 -57
  254. package/src/utils/errors.ts +0 -21
  255. package/src/utils/exampleCommands.ts +0 -109
  256. package/src/utils/execFileNoThrow.ts +0 -51
  257. package/src/utils/expertChatStorage.ts +0 -136
  258. package/src/utils/file.ts +0 -405
  259. package/src/utils/fileRecoveryCore.ts +0 -71
  260. package/src/utils/format.tsx +0 -44
  261. package/src/utils/fuzzyMatcher.ts +0 -328
  262. package/src/utils/generators.ts +0 -62
  263. package/src/utils/git.ts +0 -92
  264. package/src/utils/globalLogger.ts +0 -77
  265. package/src/utils/http.ts +0 -10
  266. package/src/utils/imagePaste.ts +0 -38
  267. package/src/utils/json.ts +0 -13
  268. package/src/utils/log.ts +0 -382
  269. package/src/utils/markdown.ts +0 -213
  270. package/src/utils/messageContextManager.ts +0 -294
  271. package/src/utils/messages.tsx +0 -945
  272. package/src/utils/model.ts +0 -914
  273. package/src/utils/permissions/filesystem.ts +0 -127
  274. package/src/utils/responseState.ts +0 -23
  275. package/src/utils/ripgrep.ts +0 -167
  276. package/src/utils/secureFile.ts +0 -564
  277. package/src/utils/sessionState.ts +0 -49
  278. package/src/utils/state.ts +0 -25
  279. package/src/utils/style.ts +0 -29
  280. package/src/utils/terminal.ts +0 -50
  281. package/src/utils/theme.ts +0 -127
  282. package/src/utils/thinking.ts +0 -144
  283. package/src/utils/todoStorage.ts +0 -431
  284. package/src/utils/tokens.ts +0 -43
  285. package/src/utils/toolExecutionController.ts +0 -163
  286. package/src/utils/unaryLogging.ts +0 -26
  287. package/src/utils/user.ts +0 -37
  288. package/src/utils/validate.ts +0 -165
@@ -1,2197 +0,0 @@
1
- import '@anthropic-ai/sdk/shims/node'
2
- import Anthropic, { APIConnectionError, APIError } from '@anthropic-ai/sdk'
3
- import { AnthropicBedrock } from '@anthropic-ai/bedrock-sdk'
4
- import { AnthropicVertex } from '@anthropic-ai/vertex-sdk'
5
- import type { BetaUsage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
6
- import chalk from 'chalk'
7
- import { createHash, randomUUID, UUID } from 'crypto'
8
- import 'dotenv/config'
9
-
10
- import { addToTotalCost } from '../cost-tracker'
11
- import models from '../constants/models'
12
- import type { AssistantMessage, UserMessage } from '../query'
13
- import { Tool } from '../Tool'
14
- import {
15
- getAnthropicApiKey,
16
- getOrCreateUserID,
17
- getGlobalConfig,
18
- ModelProfile,
19
- } from '../utils/config'
20
- import { getProjectDocs } from '../context'
21
- import { logError, SESSION_ID } from '../utils/log'
22
- import { USER_AGENT } from '../utils/http'
23
- import {
24
- createAssistantAPIErrorMessage,
25
- normalizeContentFromAPI,
26
- } from '../utils/messages'
27
- import { countTokens } from '../utils/tokens'
28
- import { logEvent } from './statsig'
29
- import { withVCR } from './vcr'
30
- import {
31
- debug as debugLogger,
32
- markPhase,
33
- getCurrentRequest,
34
- logLLMInteraction,
35
- logSystemPromptConstruction,
36
- logErrorWithDiagnosis,
37
- } from '../utils/debugLogger'
38
- import {
39
- MessageContextManager,
40
- createRetentionStrategy,
41
- } from '../utils/messageContextManager'
42
- import { getModelManager } from '../utils/model'
43
- import { zodToJsonSchema } from 'zod-to-json-schema'
44
- import type { BetaMessageStream } from '@anthropic-ai/sdk/lib/BetaMessageStream.mjs'
45
- import { ModelAdapterFactory } from './modelAdapterFactory'
46
- import { UnifiedRequestParams } from '../types/modelCapabilities'
47
- import { responseStateManager, getConversationId } from './responseStateManager'
48
- import type { ToolUseContext } from '../Tool'
49
- import type {
50
- Message as APIMessage,
51
- MessageParam,
52
- TextBlockParam,
53
- } from '@anthropic-ai/sdk/resources/index.mjs'
54
- import { USE_BEDROCK, USE_VERTEX } from '../utils/model'
55
- import { getCLISyspromptPrefix } from '../constants/prompts'
56
- import { getVertexRegionForModel } from '../utils/model'
57
- import OpenAI from 'openai'
58
- import type { ChatCompletionStream } from 'openai/lib/ChatCompletionStream'
59
- import { ContentBlock } from '@anthropic-ai/sdk/resources/messages/messages'
60
- import { nanoid } from 'nanoid'
61
- import { getCompletionWithProfile, getGPT5CompletionWithProfile } from './openai'
62
- import { getReasoningEffort } from '../utils/thinking'
63
- import { generateSystemReminders } from './systemReminder'
64
-
65
- // Helper function to check if a model is GPT-5
66
- function isGPT5Model(modelName: string): boolean {
67
- return modelName.startsWith('gpt-5')
68
- }
69
-
70
- // Helper function to extract model configuration for debug logging
71
- function getModelConfigForDebug(model: string): {
72
- modelName: string
73
- provider: string
74
- apiKeyStatus: 'configured' | 'missing' | 'invalid'
75
- baseURL?: string
76
- maxTokens?: number
77
- reasoningEffort?: string
78
- isStream?: boolean
79
- temperature?: number
80
- } {
81
- const config = getGlobalConfig()
82
- const modelManager = getModelManager()
83
-
84
-
85
- const modelProfile = modelManager.getModel('main')
86
-
87
- let apiKeyStatus: 'configured' | 'missing' | 'invalid' = 'missing'
88
- let baseURL: string | undefined
89
- let maxTokens: number | undefined
90
- let reasoningEffort: string | undefined
91
-
92
-
93
- if (modelProfile) {
94
- apiKeyStatus = modelProfile.apiKey ? 'configured' : 'missing'
95
- baseURL = modelProfile.baseURL
96
- maxTokens = modelProfile.maxTokens
97
- reasoningEffort = modelProfile.reasoningEffort
98
- } else {
99
- // 🚨 No ModelProfile available - this should not happen in modern system
100
- apiKeyStatus = 'missing'
101
- maxTokens = undefined
102
- reasoningEffort = undefined
103
- }
104
-
105
- return {
106
- modelName: model,
107
- provider: modelProfile?.provider || config.primaryProvider || 'anthropic',
108
- apiKeyStatus,
109
- baseURL,
110
- maxTokens,
111
- reasoningEffort,
112
- isStream: config.stream || false,
113
- temperature: MAIN_QUERY_TEMPERATURE,
114
- }
115
- }
116
-
117
- // KodeContext管理器 - 用于项目文档的同步缓存和访问
118
- class KodeContextManager {
119
- private static instance: KodeContextManager
120
- private projectDocsCache: string = ''
121
- private cacheInitialized: boolean = false
122
- private initPromise: Promise<void> | null = null
123
-
124
- private constructor() {}
125
-
126
- public static getInstance(): KodeContextManager {
127
- if (!KodeContextManager.instance) {
128
- KodeContextManager.instance = new KodeContextManager()
129
- }
130
- return KodeContextManager.instance
131
- }
132
-
133
- public async initialize(): Promise<void> {
134
- if (this.cacheInitialized) return
135
-
136
- if (this.initPromise) {
137
- return this.initPromise
138
- }
139
-
140
- this.initPromise = this.loadProjectDocs()
141
- await this.initPromise
142
- }
143
-
144
- private async loadProjectDocs(): Promise<void> {
145
- try {
146
- const projectDocs = await getProjectDocs()
147
- this.projectDocsCache = projectDocs || ''
148
- this.cacheInitialized = true
149
-
150
- // 在调试模式下记录加载结果
151
- if (process.env.NODE_ENV === 'development') {
152
- console.log(
153
- `[KodeContext] Loaded ${this.projectDocsCache.length} characters from project docs`,
154
- )
155
- }
156
- } catch (error) {
157
- console.warn('[KodeContext] Failed to load project docs:', error)
158
- this.projectDocsCache = ''
159
- this.cacheInitialized = true
160
- }
161
- }
162
-
163
- public getKodeContext(): string {
164
- if (!this.cacheInitialized) {
165
- // 如果未初始化,异步初始化但立即返回空字符串
166
- this.initialize().catch(console.warn)
167
- return ''
168
- }
169
- return this.projectDocsCache
170
- }
171
-
172
- public async refreshCache(): Promise<void> {
173
- this.cacheInitialized = false
174
- this.initPromise = null
175
- await this.initialize()
176
- }
177
- }
178
-
179
- // 导出函数保持向后兼容
180
- const kodeContextManager = KodeContextManager.getInstance()
181
-
182
- // 在模块加载时异步初始化
183
- kodeContextManager.initialize().catch(console.warn)
184
-
185
- export const generateKodeContext = (): string => {
186
- return kodeContextManager.getKodeContext()
187
- }
188
-
189
- export const refreshKodeContext = async (): Promise<void> => {
190
- await kodeContextManager.refreshCache()
191
- }
192
-
193
- interface StreamResponse extends APIMessage {
194
- ttftMs?: number
195
- }
196
-
197
- export const API_ERROR_MESSAGE_PREFIX = 'API Error'
198
- export const PROMPT_TOO_LONG_ERROR_MESSAGE = 'Prompt is too long'
199
- export const CREDIT_BALANCE_TOO_LOW_ERROR_MESSAGE = 'Credit balance is too low'
200
- export const INVALID_API_KEY_ERROR_MESSAGE =
201
- 'Invalid API key · Please run /login'
202
- export const NO_CONTENT_MESSAGE = '(no content)'
203
- const PROMPT_CACHING_ENABLED = !process.env.DISABLE_PROMPT_CACHING
204
-
205
- // @see https://docs.anthropic.com/en/docs/about-claude/models#model-comparison-table
206
- const HAIKU_COST_PER_MILLION_INPUT_TOKENS = 0.8
207
- const HAIKU_COST_PER_MILLION_OUTPUT_TOKENS = 4
208
- const HAIKU_COST_PER_MILLION_PROMPT_CACHE_WRITE_TOKENS = 1
209
- const HAIKU_COST_PER_MILLION_PROMPT_CACHE_READ_TOKENS = 0.08
210
-
211
- const SONNET_COST_PER_MILLION_INPUT_TOKENS = 3
212
- const SONNET_COST_PER_MILLION_OUTPUT_TOKENS = 15
213
- const SONNET_COST_PER_MILLION_PROMPT_CACHE_WRITE_TOKENS = 3.75
214
- const SONNET_COST_PER_MILLION_PROMPT_CACHE_READ_TOKENS = 0.3
215
-
216
- export const MAIN_QUERY_TEMPERATURE = 1 // to get more variation for binary feedback
217
-
218
- function getMetadata() {
219
- return {
220
- user_id: `${getOrCreateUserID()}_${SESSION_ID}`,
221
- }
222
- }
223
-
224
- const MAX_RETRIES = process.env.USER_TYPE === 'SWE_BENCH' ? 100 : 10
225
- const BASE_DELAY_MS = 500
226
-
227
- interface RetryOptions {
228
- maxRetries?: number
229
- signal?: AbortSignal
230
- }
231
-
232
- // Helper function to create an abortable delay
233
- function abortableDelay(delayMs: number, signal?: AbortSignal): Promise<void> {
234
- return new Promise((resolve, reject) => {
235
- // Check if already aborted
236
- if (signal?.aborted) {
237
- reject(new Error('Request was aborted'))
238
- return
239
- }
240
-
241
- const timeoutId = setTimeout(() => {
242
- resolve()
243
- }, delayMs)
244
-
245
- // If signal is provided, listen for abort event
246
- if (signal) {
247
- const abortHandler = () => {
248
- clearTimeout(timeoutId)
249
- reject(new Error('Request was aborted'))
250
- }
251
- signal.addEventListener('abort', abortHandler, { once: true })
252
- }
253
- })
254
- }
255
-
256
- function getRetryDelay(
257
- attempt: number,
258
- retryAfterHeader?: string | null,
259
- ): number {
260
- if (retryAfterHeader) {
261
- const seconds = parseInt(retryAfterHeader, 10)
262
- if (!isNaN(seconds)) {
263
- return seconds * 1000
264
- }
265
- }
266
- return Math.min(BASE_DELAY_MS * Math.pow(2, attempt - 1), 32000) // Max 32s delay
267
- }
268
-
269
- function shouldRetry(error: APIError): boolean {
270
- // Check for overloaded errors first and only retry for SWE_BENCH
271
- if (error.message?.includes('"type":"overloaded_error"')) {
272
- return process.env.USER_TYPE === 'SWE_BENCH'
273
- }
274
-
275
- // Note this is not a standard header.
276
- const shouldRetryHeader = error.headers?.['x-should-retry']
277
-
278
- // If the server explicitly says whether or not to retry, obey.
279
- if (shouldRetryHeader === 'true') return true
280
- if (shouldRetryHeader === 'false') return false
281
-
282
- if (error instanceof APIConnectionError) {
283
- return true
284
- }
285
-
286
- if (!error.status) return false
287
-
288
- // Retry on request timeouts.
289
- if (error.status === 408) return true
290
-
291
- // Retry on lock timeouts.
292
- if (error.status === 409) return true
293
-
294
- // Retry on rate limits.
295
- if (error.status === 429) return true
296
-
297
- // Retry internal errors.
298
- if (error.status && error.status >= 500) return true
299
-
300
- return false
301
- }
302
-
303
- async function withRetry<T>(
304
- operation: (attempt: number) => Promise<T>,
305
- options: RetryOptions = {},
306
- ): Promise<T> {
307
- const maxRetries = options.maxRetries ?? MAX_RETRIES
308
- let lastError: unknown
309
-
310
- for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
311
- try {
312
- return await operation(attempt)
313
- } catch (error) {
314
- lastError = error
315
- // Only retry if the error indicates we should
316
- if (
317
- attempt > maxRetries ||
318
- !(error instanceof APIError) ||
319
- !shouldRetry(error)
320
- ) {
321
- throw error
322
- }
323
-
324
- if (options.signal?.aborted) {
325
- throw new Error('Request cancelled by user')
326
- }
327
-
328
- // Get retry-after header if available
329
- const retryAfter = error.headers?.['retry-after'] ?? null
330
- const delayMs = getRetryDelay(attempt, retryAfter)
331
-
332
- console.log(
333
- ` ⎿ ${chalk.red(`API ${error.name} (${error.message}) · Retrying in ${Math.round(delayMs / 1000)} seconds… (attempt ${attempt}/${maxRetries})`)}`,
334
- )
335
-
336
- logEvent('tengu_api_retry', {
337
- attempt: String(attempt),
338
- delayMs: String(delayMs),
339
- error: error.message,
340
- status: String(error.status),
341
- provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
342
- })
343
-
344
- try {
345
- await abortableDelay(delayMs, options.signal)
346
- } catch (delayError) {
347
- // If aborted during delay, throw the error to stop retrying
348
- if (delayError.message === 'Request was aborted') {
349
- throw new Error('Request cancelled by user')
350
- }
351
- throw delayError
352
- }
353
- }
354
- }
355
-
356
- throw lastError
357
- }
358
-
359
- /**
360
- * Fetch available models from Anthropic API
361
- */
362
- export async function fetchAnthropicModels(
363
- baseURL: string,
364
- apiKey: string,
365
- ): Promise<any[]> {
366
- try {
367
- // Use provided baseURL or default to official Anthropic API
368
- const modelsURL = baseURL
369
- ? `${baseURL.replace(/\/+$/, '')}/v1/models`
370
- : 'https://api.anthropic.com/v1/models'
371
-
372
- const response = await fetch(modelsURL, {
373
- method: 'GET',
374
- headers: {
375
- 'x-api-key': apiKey,
376
- 'anthropic-version': '2023-06-01',
377
- 'User-Agent': USER_AGENT,
378
- },
379
- })
380
-
381
- if (!response.ok) {
382
- // Provide user-friendly error messages based on status code
383
- if (response.status === 401) {
384
- throw new Error(
385
- 'Invalid API key. Please check your Anthropic API key and try again.',
386
- )
387
- } else if (response.status === 403) {
388
- throw new Error(
389
- 'API key does not have permission to access models. Please check your API key permissions.',
390
- )
391
- } else if (response.status === 429) {
392
- throw new Error(
393
- 'Too many requests. Please wait a moment and try again.',
394
- )
395
- } else if (response.status >= 500) {
396
- throw new Error(
397
- 'Anthropic service is temporarily unavailable. Please try again later.',
398
- )
399
- } else {
400
- throw new Error(
401
- `Unable to connect to Anthropic API (${response.status}). Please check your internet connection and API key.`,
402
- )
403
- }
404
- }
405
-
406
- const data = await response.json()
407
- return data.data || []
408
- } catch (error) {
409
- // If it's already our custom error, pass it through
410
- if (
411
- (error instanceof Error && error.message.includes('API key')) ||
412
- (error instanceof Error && error.message.includes('Anthropic'))
413
- ) {
414
- throw error
415
- }
416
-
417
- // For network errors or other issues
418
- console.error('Failed to fetch Anthropic models:', error)
419
- throw new Error(
420
- 'Unable to connect to Anthropic API. Please check your internet connection and try again.',
421
- )
422
- }
423
- }
424
-
425
- export async function verifyApiKey(
426
- apiKey: string,
427
- baseURL?: string,
428
- provider?: string,
429
- ): Promise<boolean> {
430
- if (!apiKey) {
431
- return false
432
- }
433
-
434
- // For non-Anthropic providers, use OpenAI-compatible verification
435
- if (provider && provider !== 'anthropic') {
436
- try {
437
- const headers: Record<string, string> = {
438
- Authorization: `Bearer ${apiKey}`,
439
- 'Content-Type': 'application/json',
440
- }
441
-
442
-
443
- if (!baseURL) {
444
- console.warn(
445
- 'No baseURL provided for non-Anthropic provider verification',
446
- )
447
- return false
448
- }
449
-
450
- const modelsURL = `${baseURL.replace(/\/+$/, '')}/models`
451
-
452
- const response = await fetch(modelsURL, {
453
- method: 'GET',
454
- headers,
455
- })
456
-
457
- return response.ok
458
- } catch (error) {
459
- console.warn('API verification failed for non-Anthropic provider:', error)
460
- return false
461
- }
462
- }
463
-
464
- // For Anthropic and Anthropic-compatible APIs
465
- const clientConfig: any = {
466
- apiKey,
467
- dangerouslyAllowBrowser: true,
468
- maxRetries: 3,
469
- defaultHeaders: {
470
- 'User-Agent': USER_AGENT,
471
- },
472
- }
473
-
474
- // Only add baseURL for true Anthropic-compatible APIs
475
- if (
476
- baseURL &&
477
- (provider === 'anthropic' ||
478
- provider === 'bigdream' ||
479
- provider === 'opendev')
480
- ) {
481
- clientConfig.baseURL = baseURL
482
- }
483
-
484
- const anthropic = new Anthropic(clientConfig)
485
-
486
- try {
487
- await withRetry(
488
- async () => {
489
- const model = 'claude-sonnet-4-20250514'
490
- const messages: MessageParam[] = [{ role: 'user', content: 'test' }]
491
- await anthropic.messages.create({
492
- model,
493
- max_tokens: 1000, // Simple test token limit for API verification
494
- messages,
495
- temperature: 0,
496
- metadata: getMetadata(),
497
- })
498
- return true
499
- },
500
- { maxRetries: 2 }, // Use fewer retries for API key verification
501
- )
502
- return true
503
- } catch (error) {
504
- logError(error)
505
- // Check for authentication error
506
- if (
507
- error instanceof Error &&
508
- error.message.includes(
509
- '{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}',
510
- )
511
- ) {
512
- return false
513
- }
514
- throw error
515
- }
516
- }
517
-
518
- function convertAnthropicMessagesToOpenAIMessages(
519
- messages: (UserMessage | AssistantMessage)[],
520
- ): (
521
- | OpenAI.ChatCompletionMessageParam
522
- | OpenAI.ChatCompletionToolMessageParam
523
- )[] {
524
- const openaiMessages: (
525
- | OpenAI.ChatCompletionMessageParam
526
- | OpenAI.ChatCompletionToolMessageParam
527
- )[] = []
528
-
529
- const toolResults: Record<string, OpenAI.ChatCompletionToolMessageParam> = {}
530
-
531
- for (const message of messages) {
532
- let contentBlocks = []
533
- if (typeof message.message.content === 'string') {
534
- contentBlocks = [
535
- {
536
- type: 'text',
537
- text: message.message.content,
538
- },
539
- ]
540
- } else if (!Array.isArray(message.message.content)) {
541
- contentBlocks = [message.message.content]
542
- } else {
543
- contentBlocks = message.message.content
544
- }
545
-
546
- for (const block of contentBlocks) {
547
- if (block.type === 'text') {
548
- openaiMessages.push({
549
- role: message.message.role,
550
- content: block.text,
551
- })
552
- } else if (block.type === 'tool_use') {
553
- openaiMessages.push({
554
- role: 'assistant',
555
- content: undefined,
556
- tool_calls: [
557
- {
558
- type: 'function',
559
- function: {
560
- name: block.name,
561
- arguments: JSON.stringify(block.input),
562
- },
563
- id: block.id,
564
- },
565
- ],
566
- })
567
- } else if (block.type === 'tool_result') {
568
- // Ensure content is always a string for role:tool messages
569
- let toolContent = block.content
570
- if (typeof toolContent !== 'string') {
571
- // Convert content to string if it's not already
572
- toolContent = JSON.stringify(toolContent)
573
- }
574
-
575
- toolResults[block.tool_use_id] = {
576
- role: 'tool',
577
- content: toolContent,
578
- tool_call_id: block.tool_use_id,
579
- }
580
- }
581
- }
582
- }
583
-
584
- const finalMessages: (
585
- | OpenAI.ChatCompletionMessageParam
586
- | OpenAI.ChatCompletionToolMessageParam
587
- )[] = []
588
-
589
- for (const message of openaiMessages) {
590
- finalMessages.push(message)
591
-
592
- if ('tool_calls' in message && message.tool_calls) {
593
- for (const toolCall of message.tool_calls) {
594
- if (toolResults[toolCall.id]) {
595
- finalMessages.push(toolResults[toolCall.id])
596
- }
597
- }
598
- }
599
- }
600
-
601
- return finalMessages
602
- }
603
-
604
- function messageReducer(
605
- previous: OpenAI.ChatCompletionMessage,
606
- item: OpenAI.ChatCompletionChunk,
607
- ): OpenAI.ChatCompletionMessage {
608
- const reduce = (acc: any, delta: OpenAI.ChatCompletionChunk.Choice.Delta) => {
609
- acc = { ...acc }
610
- for (const [key, value] of Object.entries(delta)) {
611
- if (acc[key] === undefined || acc[key] === null) {
612
- acc[key] = value
613
- // OpenAI.Chat.Completions.ChatCompletionMessageToolCall does not have a key, .index
614
- if (Array.isArray(acc[key])) {
615
- for (const arr of acc[key]) {
616
- delete arr.index
617
- }
618
- }
619
- } else if (typeof acc[key] === 'string' && typeof value === 'string') {
620
- acc[key] += value
621
- } else if (typeof acc[key] === 'number' && typeof value === 'number') {
622
- acc[key] = value
623
- } else if (Array.isArray(acc[key]) && Array.isArray(value)) {
624
- const accArray = acc[key]
625
- for (let i = 0; i < value.length; i++) {
626
- const { index, ...chunkTool } = value[i]
627
- if (index - accArray.length > 1) {
628
- throw new Error(
629
- `Error: An array has an empty value when tool_calls are constructed. tool_calls: ${accArray}; tool: ${value}`,
630
- )
631
- }
632
- accArray[index] = reduce(accArray[index], chunkTool)
633
- }
634
- } else if (typeof acc[key] === 'object' && typeof value === 'object') {
635
- acc[key] = reduce(acc[key], value)
636
- }
637
- }
638
- return acc
639
- }
640
-
641
- const choice = item.choices?.[0]
642
- if (!choice) {
643
- // chunk contains information about usage and token counts
644
- return previous
645
- }
646
- return reduce(previous, choice.delta) as OpenAI.ChatCompletionMessage
647
- }
648
- async function handleMessageStream(
649
- stream: ChatCompletionStream,
650
- signal?: AbortSignal,
651
- ): Promise<OpenAI.ChatCompletion> {
652
- const streamStartTime = Date.now()
653
- let ttftMs: number | undefined
654
- let chunkCount = 0
655
- let errorCount = 0
656
-
657
- debugLogger.api('OPENAI_STREAM_START', {
658
- streamStartTime: String(streamStartTime),
659
- })
660
-
661
- let message = {} as OpenAI.ChatCompletionMessage
662
-
663
- let id, model, created, object, usage
664
- try {
665
- for await (const chunk of stream) {
666
-
667
- if (signal?.aborted) {
668
- debugLogger.flow('OPENAI_STREAM_ABORTED', {
669
- chunkCount,
670
- timestamp: Date.now()
671
- })
672
- throw new Error('Request was cancelled')
673
- }
674
-
675
- chunkCount++
676
-
677
- try {
678
- if (!id) {
679
- id = chunk.id
680
- debugLogger.api('OPENAI_STREAM_ID_RECEIVED', {
681
- id,
682
- chunkNumber: String(chunkCount),
683
- })
684
- }
685
- if (!model) {
686
- model = chunk.model
687
- debugLogger.api('OPENAI_STREAM_MODEL_RECEIVED', {
688
- model,
689
- chunkNumber: String(chunkCount),
690
- })
691
- }
692
- if (!created) {
693
- created = chunk.created
694
- }
695
- if (!object) {
696
- object = chunk.object
697
- }
698
- if (!usage) {
699
- usage = chunk.usage
700
- }
701
-
702
- message = messageReducer(message, chunk)
703
-
704
- if (chunk?.choices?.[0]?.delta?.content) {
705
- if (!ttftMs) {
706
- ttftMs = Date.now() - streamStartTime
707
- debugLogger.api('OPENAI_STREAM_FIRST_TOKEN', {
708
- ttftMs: String(ttftMs),
709
- chunkNumber: String(chunkCount),
710
- })
711
- }
712
- }
713
- } catch (chunkError) {
714
- errorCount++
715
- debugLogger.error('OPENAI_STREAM_CHUNK_ERROR', {
716
- chunkNumber: String(chunkCount),
717
- errorMessage:
718
- chunkError instanceof Error
719
- ? chunkError.message
720
- : String(chunkError),
721
- errorType:
722
- chunkError instanceof Error
723
- ? chunkError.constructor.name
724
- : typeof chunkError,
725
- })
726
- // Continue processing other chunks
727
- }
728
- }
729
-
730
- debugLogger.api('OPENAI_STREAM_COMPLETE', {
731
- totalChunks: String(chunkCount),
732
- errorCount: String(errorCount),
733
- totalDuration: String(Date.now() - streamStartTime),
734
- ttftMs: String(ttftMs || 0),
735
- finalMessageId: id || 'undefined',
736
- })
737
- } catch (streamError) {
738
- debugLogger.error('OPENAI_STREAM_FATAL_ERROR', {
739
- totalChunks: String(chunkCount),
740
- errorCount: String(errorCount),
741
- errorMessage:
742
- streamError instanceof Error
743
- ? streamError.message
744
- : String(streamError),
745
- errorType:
746
- streamError instanceof Error
747
- ? streamError.constructor.name
748
- : typeof streamError,
749
- })
750
- throw streamError
751
- }
752
- return {
753
- id,
754
- created,
755
- model,
756
- object,
757
- choices: [
758
- {
759
- index: 0,
760
- message,
761
- finish_reason: 'stop',
762
- logprobs: undefined,
763
- },
764
- ],
765
- usage,
766
- }
767
- }
768
-
769
- function convertOpenAIResponseToAnthropic(response: OpenAI.ChatCompletion, tools?: Tool[]) {
770
- let contentBlocks: ContentBlock[] = []
771
- const message = response.choices?.[0]?.message
772
- if (!message) {
773
- logEvent('weird_response', {
774
- response: JSON.stringify(response),
775
- })
776
- return {
777
- role: 'assistant',
778
- content: [],
779
- stop_reason: response.choices?.[0]?.finish_reason,
780
- type: 'message',
781
- usage: response.usage,
782
- }
783
- }
784
-
785
- if (message?.tool_calls) {
786
- for (const toolCall of message.tool_calls) {
787
- const tool = toolCall.function
788
- const toolName = tool?.name
789
- let toolArgs = {}
790
- try {
791
- toolArgs = tool?.arguments ? JSON.parse(tool.arguments) : {}
792
- } catch (e) {
793
- // Invalid JSON in tool arguments
794
- }
795
-
796
- contentBlocks.push({
797
- type: 'tool_use',
798
- input: toolArgs,
799
- name: toolName,
800
- id: toolCall.id?.length > 0 ? toolCall.id : nanoid(),
801
- })
802
- }
803
- }
804
-
805
- if ((message as any).reasoning) {
806
- contentBlocks.push({
807
- type: 'thinking',
808
- thinking: (message as any).reasoning,
809
- signature: '',
810
- })
811
- }
812
-
813
- // NOTE: For deepseek api, the key for its returned reasoning process is reasoning_content
814
- if ((message as any).reasoning_content) {
815
- contentBlocks.push({
816
- type: 'thinking',
817
- thinking: (message as any).reasoning_content,
818
- signature: '',
819
- })
820
- }
821
-
822
- if (message.content) {
823
- contentBlocks.push({
824
- type: 'text',
825
- text: message?.content,
826
- citations: [],
827
- })
828
- }
829
-
830
- const finalMessage = {
831
- role: 'assistant',
832
- content: contentBlocks,
833
- stop_reason: response.choices?.[0]?.finish_reason,
834
- type: 'message',
835
- usage: response.usage,
836
- }
837
-
838
-
839
- return finalMessage
840
- }
841
-
842
- let anthropicClient: Anthropic | AnthropicBedrock | AnthropicVertex | null =
843
- null
844
-
845
- /**
846
- * Get the Anthropic client, creating it if it doesn't exist
847
- */
848
- export function getAnthropicClient(
849
- model?: string,
850
- ): Anthropic | AnthropicBedrock | AnthropicVertex {
851
- const config = getGlobalConfig()
852
- const provider = config.primaryProvider
853
-
854
- // Reset client if provider has changed to ensure correct configuration
855
- if (anthropicClient && provider) {
856
- // Always recreate client for provider-specific configurations
857
- anthropicClient = null
858
- }
859
-
860
- if (anthropicClient) {
861
- return anthropicClient
862
- }
863
-
864
- const region = getVertexRegionForModel(model)
865
-
866
- const defaultHeaders: { [key: string]: string } = {
867
- 'x-app': 'cli',
868
- 'User-Agent': USER_AGENT,
869
- }
870
- if (process.env.ANTHROPIC_AUTH_TOKEN) {
871
- defaultHeaders['Authorization'] =
872
- `Bearer ${process.env.ANTHROPIC_AUTH_TOKEN}`
873
- }
874
-
875
- const ARGS = {
876
- defaultHeaders,
877
- maxRetries: 0, // Disabled auto-retry in favor of manual implementation
878
- timeout: parseInt(process.env.API_TIMEOUT_MS || String(60 * 1000), 10),
879
- }
880
- if (USE_BEDROCK) {
881
- const client = new AnthropicBedrock(ARGS)
882
- anthropicClient = client
883
- return client
884
- }
885
- if (USE_VERTEX) {
886
- const vertexArgs = {
887
- ...ARGS,
888
- region: region || process.env.CLOUD_ML_REGION || 'us-east5',
889
- }
890
- const client = new AnthropicVertex(vertexArgs)
891
- anthropicClient = client
892
- return client
893
- }
894
-
895
- // Get appropriate API key and baseURL from ModelProfile
896
- const modelManager = getModelManager()
897
- const modelProfile = modelManager.getModel('main')
898
-
899
- let apiKey: string
900
- let baseURL: string | undefined
901
-
902
- if (modelProfile) {
903
- apiKey = modelProfile.apiKey || ''
904
- baseURL = modelProfile.baseURL
905
- } else {
906
- // Fallback to default anthropic if no ModelProfile
907
- apiKey = getAnthropicApiKey()
908
- baseURL = undefined
909
- }
910
-
911
- if (process.env.USER_TYPE === 'ant' && !apiKey && provider === 'anthropic') {
912
- console.error(
913
- chalk.red(
914
- '[ANT-ONLY] Please set the ANTHROPIC_API_KEY environment variable to use the CLI. To create a new key, go to https://console.anthropic.com/settings/keys.',
915
- ),
916
- )
917
- }
918
-
919
- // Create client with custom baseURL for BigDream/OpenDev
920
- // Anthropic SDK will append the appropriate paths (like /v1/messages)
921
- const clientConfig = {
922
- apiKey,
923
- dangerouslyAllowBrowser: true,
924
- ...ARGS,
925
- ...(baseURL && { baseURL }), // Use baseURL directly, SDK will handle API versioning
926
- }
927
-
928
- anthropicClient = new Anthropic(clientConfig)
929
- return anthropicClient
930
- }
931
-
932
- /**
933
- * Reset the Anthropic client to null, forcing a new client to be created on next use
934
- */
935
- export function resetAnthropicClient(): void {
936
- anthropicClient = null
937
- }
938
-
939
- /**
940
- * Environment variables for different client types:
941
- *
942
- * Direct API:
943
- * - ANTHROPIC_API_KEY: Required for direct API access
944
- *
945
- * AWS Bedrock:
946
- * - AWS credentials configured via aws-sdk defaults
947
- *
948
- * Vertex AI:
949
- * - Model-specific region variables (highest priority):
950
- * - VERTEX_REGION_CLAUDE_3_5_HAIKU: Region for Claude 3.5 Haiku model
951
- * - VERTEX_REGION_CLAUDE_3_5_SONNET: Region for Claude 3.5 Sonnet model
952
- * - VERTEX_REGION_CLAUDE_3_7_SONNET: Region for Claude 3.7 Sonnet model
953
- * - CLOUD_ML_REGION: Optional. The default GCP region to use for all models
954
- * If specific model region not specified above
955
- * - ANTHROPIC_VERTEX_PROJECT_ID: Required. Your GCP project ID
956
- * - Standard GCP credentials configured via google-auth-library
957
- *
958
- * Priority for determining region:
959
- * 1. Hardcoded model-specific environment variables
960
- * 2. Global CLOUD_ML_REGION variable
961
- * 3. Default region from config
962
- * 4. Fallback region (us-east5)
963
- */
964
-
965
- /**
966
- * Manage cache control to ensure it doesn't exceed Claude's 4 cache block limit
967
- * Priority:
968
- * 1. System prompts (high priority)
969
- * 2. Long documents or reference materials (high priority)
970
- * 3. Reusable context (medium priority)
971
- * 4. Short messages or one-time content (no caching)
972
- */
973
- function applyCacheControlWithLimits(
974
- systemBlocks: TextBlockParam[],
975
- messageParams: MessageParam[]
976
- ): { systemBlocks: TextBlockParam[]; messageParams: MessageParam[] } {
977
- if (!PROMPT_CACHING_ENABLED) {
978
- return { systemBlocks, messageParams }
979
- }
980
-
981
- const maxCacheBlocks = 4
982
- let usedCacheBlocks = 0
983
-
984
- // 1. Prioritize adding cache to system prompts (highest priority)
985
- const processedSystemBlocks = systemBlocks.map((block, index) => {
986
- if (usedCacheBlocks < maxCacheBlocks && block.text.length > 1000) {
987
- usedCacheBlocks++
988
- return {
989
- ...block,
990
- cache_control: { type: 'ephemeral' as const }
991
- }
992
- }
993
- const { cache_control, ...blockWithoutCache } = block
994
- return blockWithoutCache
995
- })
996
-
997
- // 2. Add cache to message content based on priority
998
- const processedMessageParams = messageParams.map((message, messageIndex) => {
999
- if (Array.isArray(message.content)) {
1000
- const processedContent = message.content.map((contentBlock, blockIndex) => {
1001
- // Determine whether this content block should be cached
1002
- const shouldCache =
1003
- usedCacheBlocks < maxCacheBlocks &&
1004
- contentBlock.type === 'text' &&
1005
- typeof contentBlock.text === 'string' &&
1006
- (
1007
- // Long documents (over 2000 characters)
1008
- contentBlock.text.length > 2000 ||
1009
- // Last content block of the last message (may be important context)
1010
- (messageIndex === messageParams.length - 1 &&
1011
- blockIndex === message.content.length - 1 &&
1012
- contentBlock.text.length > 500)
1013
- )
1014
-
1015
- if (shouldCache) {
1016
- usedCacheBlocks++
1017
- return {
1018
- ...contentBlock,
1019
- cache_control: { type: 'ephemeral' as const }
1020
- }
1021
- }
1022
-
1023
- // Remove existing cache_control
1024
- const { cache_control, ...blockWithoutCache } = contentBlock as any
1025
- return blockWithoutCache
1026
- })
1027
-
1028
- return {
1029
- ...message,
1030
- content: processedContent
1031
- }
1032
- }
1033
-
1034
- return message
1035
- })
1036
-
1037
- return {
1038
- systemBlocks: processedSystemBlocks,
1039
- messageParams: processedMessageParams
1040
- }
1041
- }
1042
-
1043
- export function userMessageToMessageParam(
1044
- message: UserMessage,
1045
- addCache = false,
1046
- ): MessageParam {
1047
- if (addCache) {
1048
- if (typeof message.message.content === 'string') {
1049
- return {
1050
- role: 'user',
1051
- content: [
1052
- {
1053
- type: 'text',
1054
- text: message.message.content,
1055
- },
1056
- ],
1057
- }
1058
- } else {
1059
- return {
1060
- role: 'user',
1061
- content: message.message.content.map((_) => ({ ..._ })),
1062
- }
1063
- }
1064
- }
1065
- return {
1066
- role: 'user',
1067
- content: message.message.content,
1068
- }
1069
- }
1070
-
1071
- export function assistantMessageToMessageParam(
1072
- message: AssistantMessage,
1073
- addCache = false,
1074
- ): MessageParam {
1075
- if (addCache) {
1076
- if (typeof message.message.content === 'string') {
1077
- return {
1078
- role: 'assistant',
1079
- content: [
1080
- {
1081
- type: 'text',
1082
- text: message.message.content,
1083
- },
1084
- ],
1085
- }
1086
- } else {
1087
- return {
1088
- role: 'assistant',
1089
- content: message.message.content.map((_) => ({ ..._ })),
1090
- }
1091
- }
1092
- }
1093
- return {
1094
- role: 'assistant',
1095
- content: message.message.content,
1096
- }
1097
- }
1098
-
1099
- function splitSysPromptPrefix(systemPrompt: string[]): string[] {
1100
- // split out the first block of the system prompt as the "prefix" for API
1101
- // to match on in https://console.statsig.com/4aF3Ewatb6xPVpCwxb5nA3/dynamic_configs/claude_cli_system_prompt_prefixes
1102
- const systemPromptFirstBlock = systemPrompt[0] || ''
1103
- const systemPromptRest = systemPrompt.slice(1)
1104
- return [systemPromptFirstBlock, systemPromptRest.join('\n')].filter(Boolean)
1105
- }
1106
-
1107
- export async function queryLLM(
1108
- messages: (UserMessage | AssistantMessage)[],
1109
- systemPrompt: string[],
1110
- maxThinkingTokens: number,
1111
- tools: Tool[],
1112
- signal: AbortSignal,
1113
- options: {
1114
- safeMode: boolean
1115
- model: string | import('../utils/config').ModelPointerType
1116
- prependCLISysprompt: boolean
1117
- toolUseContext?: ToolUseContext
1118
- },
1119
- ): Promise<AssistantMessage> {
1120
-
1121
- const modelManager = getModelManager()
1122
- const modelResolution = modelManager.resolveModelWithInfo(options.model)
1123
-
1124
- if (!modelResolution.success || !modelResolution.profile) {
1125
- throw new Error(
1126
- modelResolution.error || `Failed to resolve model: ${options.model}`,
1127
- )
1128
- }
1129
-
1130
- const modelProfile = modelResolution.profile
1131
- const resolvedModel = modelProfile.modelName
1132
-
1133
- // Initialize response state if toolUseContext is provided
1134
- const toolUseContext = options.toolUseContext
1135
- if (toolUseContext && !toolUseContext.responseState) {
1136
- const conversationId = getConversationId(toolUseContext.agentId, toolUseContext.messageId)
1137
- const previousResponseId = responseStateManager.getPreviousResponseId(conversationId)
1138
-
1139
- toolUseContext.responseState = {
1140
- previousResponseId,
1141
- conversationId
1142
- }
1143
- }
1144
-
1145
- debugLogger.api('MODEL_RESOLVED', {
1146
- inputParam: options.model,
1147
- resolvedModelName: resolvedModel,
1148
- provider: modelProfile.provider,
1149
- isPointer: ['main', 'task', 'reasoning', 'quick'].includes(options.model),
1150
- hasResponseState: !!toolUseContext?.responseState,
1151
- conversationId: toolUseContext?.responseState?.conversationId,
1152
- requestId: getCurrentRequest()?.id,
1153
- })
1154
-
1155
- const currentRequest = getCurrentRequest()
1156
- debugLogger.api('LLM_REQUEST_START', {
1157
- messageCount: messages.length,
1158
- systemPromptLength: systemPrompt.join(' ').length,
1159
- toolCount: tools.length,
1160
- model: resolvedModel,
1161
- originalModelParam: options.model,
1162
- requestId: getCurrentRequest()?.id,
1163
- })
1164
-
1165
- markPhase('LLM_CALL')
1166
-
1167
- try {
1168
- const result = await withVCR(messages, () =>
1169
- queryLLMWithPromptCaching(
1170
- messages,
1171
- systemPrompt,
1172
- maxThinkingTokens,
1173
- tools,
1174
- signal,
1175
- { ...options, model: resolvedModel, modelProfile, toolUseContext }, // Pass resolved ModelProfile and toolUseContext
1176
- ),
1177
- )
1178
-
1179
- debugLogger.api('LLM_REQUEST_SUCCESS', {
1180
- costUSD: result.costUSD,
1181
- durationMs: result.durationMs,
1182
- responseLength: result.message.content?.length || 0,
1183
- requestId: getCurrentRequest()?.id,
1184
- })
1185
-
1186
- // Update response state for GPT-5 Responses API continuation
1187
- if (toolUseContext?.responseState?.conversationId && result.responseId) {
1188
- responseStateManager.setPreviousResponseId(
1189
- toolUseContext.responseState.conversationId,
1190
- result.responseId
1191
- )
1192
-
1193
- debugLogger.api('RESPONSE_STATE_UPDATED', {
1194
- conversationId: toolUseContext.responseState.conversationId,
1195
- responseId: result.responseId,
1196
- requestId: getCurrentRequest()?.id,
1197
- })
1198
- }
1199
-
1200
- return result
1201
- } catch (error) {
1202
- // 使用错误诊断系统记录 LLM 相关错误
1203
- logErrorWithDiagnosis(
1204
- error,
1205
- {
1206
- messageCount: messages.length,
1207
- systemPromptLength: systemPrompt.join(' ').length,
1208
- model: options.model,
1209
- toolCount: tools.length,
1210
- phase: 'LLM_CALL',
1211
- },
1212
- currentRequest?.id,
1213
- )
1214
-
1215
- throw error
1216
- }
1217
- }
1218
-
1219
- export function formatSystemPromptWithContext(
1220
- systemPrompt: string[],
1221
- context: { [k: string]: string },
1222
- agentId?: string,
1223
- skipContextReminders = false, // Parameter kept for API compatibility but not used anymore
1224
- ): { systemPrompt: string[]; reminders: string } {
1225
- // 构建增强的系统提示 - 对齐官方 Claude Code 直接注入方式
1226
- const enhancedPrompt = [...systemPrompt]
1227
- let reminders = ''
1228
-
1229
- // Step 0: Add GPT-5 Agent persistence support for coding tasks
1230
- const modelManager = getModelManager()
1231
- const modelProfile = modelManager.getModel('main')
1232
- if (modelProfile && isGPT5Model(modelProfile.modelName)) {
1233
- // Add coding-specific persistence instructions based on GPT-5 documentation
1234
- const persistencePrompts = [
1235
- "\n# Agent Persistence for Long-Running Coding Tasks",
1236
- "You are working on a coding project that may involve multiple steps and iterations. Please maintain context and continuity throughout the session:",
1237
- "- Remember architectural decisions and design patterns established earlier",
1238
- "- Keep track of file modifications and their relationships",
1239
- "- Maintain awareness of the overall project structure and goals",
1240
- "- Reference previous implementations when making related changes",
1241
- "- Ensure consistency with existing code style and conventions",
1242
- "- Build incrementally on previous work rather than starting from scratch"
1243
- ]
1244
- enhancedPrompt.push(...persistencePrompts)
1245
- }
1246
-
1247
- // 只有当上下文存在时才处理
1248
- const hasContext = Object.entries(context).length > 0
1249
-
1250
- if (hasContext) {
1251
- // 步骤1: 直接注入 Kode 上下文到系统提示 - 对齐官方设计
1252
- if (!skipContextReminders) {
1253
- const kodeContext = generateKodeContext()
1254
- if (kodeContext) {
1255
- // 添加分隔符和标识,使项目文档在系统提示中更清晰
1256
- enhancedPrompt.push('\n---\n# 项目上下文\n')
1257
- enhancedPrompt.push(kodeContext)
1258
- enhancedPrompt.push('\n---\n')
1259
- }
1260
- }
1261
-
1262
- // 步骤2: 生成其他动态提醒返回给调用方 - 保持现有动态提醒功能
1263
- const reminderMessages = generateSystemReminders(hasContext, agentId)
1264
- if (reminderMessages.length > 0) {
1265
- reminders = reminderMessages.map(r => r.content).join('\n') + '\n'
1266
- }
1267
-
1268
- // 步骤3: 添加其他上下文到系统提示
1269
- enhancedPrompt.push(
1270
- `\nAs you answer the user's questions, you can use the following context:\n`,
1271
- )
1272
-
1273
- // 过滤掉已经由 Kode 上下文处理的项目文档(避免重复)
1274
- const filteredContext = Object.fromEntries(
1275
- Object.entries(context).filter(
1276
- ([key]) => key !== 'projectDocs' && key !== 'userDocs',
1277
- ),
1278
- )
1279
-
1280
- enhancedPrompt.push(
1281
- ...Object.entries(filteredContext).map(
1282
- ([key, value]) => `<context name="${key}">${value}</context>`,
1283
- ),
1284
- )
1285
- }
1286
-
1287
- return { systemPrompt: enhancedPrompt, reminders }
1288
- }
1289
-
1290
- async function queryLLMWithPromptCaching(
1291
- messages: (UserMessage | AssistantMessage)[],
1292
- systemPrompt: string[],
1293
- maxThinkingTokens: number,
1294
- tools: Tool[],
1295
- signal: AbortSignal,
1296
- options: {
1297
- safeMode: boolean
1298
- model: string
1299
- prependCLISysprompt: boolean
1300
- modelProfile?: ModelProfile | null
1301
- toolUseContext?: ToolUseContext
1302
- },
1303
- ): Promise<AssistantMessage> {
1304
- const config = getGlobalConfig()
1305
- const modelManager = getModelManager()
1306
- const toolUseContext = options.toolUseContext
1307
-
1308
-
1309
- const modelProfile = options.modelProfile || modelManager.getModel('main')
1310
- let provider: string
1311
-
1312
- if (modelProfile) {
1313
- provider = modelProfile.provider || config.primaryProvider || 'anthropic'
1314
- } else {
1315
- provider = config.primaryProvider || 'anthropic'
1316
- }
1317
-
1318
- // Use native Anthropic SDK for Anthropic and some Anthropic-compatible providers
1319
- if (
1320
- provider === 'anthropic' ||
1321
- provider === 'bigdream' ||
1322
- provider === 'opendev'
1323
- ) {
1324
- return queryAnthropicNative(
1325
- messages,
1326
- systemPrompt,
1327
- maxThinkingTokens,
1328
- tools,
1329
- signal,
1330
- { ...options, modelProfile, toolUseContext },
1331
- )
1332
- }
1333
-
1334
- // Use OpenAI-compatible interface for all other providers
1335
- return queryOpenAI(messages, systemPrompt, maxThinkingTokens, tools, signal, {
1336
- ...options,
1337
- modelProfile,
1338
- toolUseContext,
1339
- })
1340
- }
1341
-
1342
- async function queryAnthropicNative(
1343
- messages: (UserMessage | AssistantMessage)[],
1344
- systemPrompt: string[],
1345
- maxThinkingTokens: number,
1346
- tools: Tool[],
1347
- signal: AbortSignal,
1348
- options?: {
1349
- safeMode: boolean
1350
- model: string
1351
- prependCLISysprompt: boolean
1352
- modelProfile?: ModelProfile | null
1353
- toolUseContext?: ToolUseContext
1354
- },
1355
- ): Promise<AssistantMessage> {
1356
- const config = getGlobalConfig()
1357
- const modelManager = getModelManager()
1358
- const toolUseContext = options?.toolUseContext
1359
-
1360
-
1361
- const modelProfile = options?.modelProfile || modelManager.getModel('main')
1362
- let anthropic: Anthropic | AnthropicBedrock | AnthropicVertex
1363
- let model: string
1364
- let provider: string
1365
-
1366
- // 🔍 Debug: 记录模型配置详情
1367
- debugLogger.api('MODEL_CONFIG_ANTHROPIC', {
1368
- modelProfileFound: !!modelProfile,
1369
- modelProfileId: modelProfile?.modelName,
1370
- modelProfileName: modelProfile?.name,
1371
- modelProfileModelName: modelProfile?.modelName,
1372
- modelProfileProvider: modelProfile?.provider,
1373
- modelProfileBaseURL: modelProfile?.baseURL,
1374
- modelProfileApiKeyExists: !!modelProfile?.apiKey,
1375
- optionsModel: options?.model,
1376
- requestId: getCurrentRequest()?.id,
1377
- })
1378
-
1379
- if (modelProfile) {
1380
- // 使用ModelProfile的完整配置
1381
- model = modelProfile.modelName
1382
- provider = modelProfile.provider || config.primaryProvider || 'anthropic'
1383
-
1384
- // 基于ModelProfile创建专用的API客户端
1385
- if (
1386
- modelProfile.provider === 'anthropic' ||
1387
- modelProfile.provider === 'bigdream' ||
1388
- modelProfile.provider === 'opendev'
1389
- ) {
1390
- const clientConfig: any = {
1391
- apiKey: modelProfile.apiKey,
1392
- dangerouslyAllowBrowser: true,
1393
- maxRetries: 0,
1394
- timeout: parseInt(process.env.API_TIMEOUT_MS || String(60 * 1000), 10),
1395
- defaultHeaders: {
1396
- 'x-app': 'cli',
1397
- 'User-Agent': USER_AGENT,
1398
- },
1399
- }
1400
-
1401
- // 使用ModelProfile的baseURL而不是全局配置
1402
- if (modelProfile.baseURL) {
1403
- clientConfig.baseURL = modelProfile.baseURL
1404
- }
1405
-
1406
- anthropic = new Anthropic(clientConfig)
1407
- } else {
1408
- // 其他提供商的处理逻辑
1409
- anthropic = getAnthropicClient(model)
1410
- }
1411
- } else {
1412
- // 🚨 降级:没有有效的ModelProfile时,应该抛出错误
1413
- const errorDetails = {
1414
- modelProfileExists: !!modelProfile,
1415
- modelProfileModelName: modelProfile?.modelName,
1416
- requestedModel: options?.model,
1417
- requestId: getCurrentRequest()?.id,
1418
- }
1419
- debugLogger.error('ANTHROPIC_FALLBACK_ERROR', errorDetails)
1420
- throw new Error(
1421
- `No valid ModelProfile available for Anthropic provider. Please configure model through /model command. Debug: ${JSON.stringify(errorDetails)}`,
1422
- )
1423
- }
1424
-
1425
- // Prepend system prompt block for easy API identification
1426
- if (options?.prependCLISysprompt) {
1427
- // Log stats about first block for analyzing prefix matching config
1428
- const [firstSyspromptBlock] = splitSysPromptPrefix(systemPrompt)
1429
- logEvent('tengu_sysprompt_block', {
1430
- snippet: firstSyspromptBlock?.slice(0, 20),
1431
- length: String(firstSyspromptBlock?.length ?? 0),
1432
- hash: firstSyspromptBlock
1433
- ? createHash('sha256').update(firstSyspromptBlock).digest('hex')
1434
- : '',
1435
- })
1436
-
1437
- systemPrompt = [getCLISyspromptPrefix(), ...systemPrompt]
1438
- }
1439
-
1440
- const system: TextBlockParam[] = splitSysPromptPrefix(systemPrompt).map(
1441
- _ => ({
1442
- text: _,
1443
- type: 'text',
1444
- }),
1445
- )
1446
-
1447
- const toolSchemas = await Promise.all(
1448
- tools.map(async tool =>
1449
- ({
1450
- name: tool.name,
1451
- description: typeof tool.description === 'function'
1452
- ? await tool.description()
1453
- : tool.description,
1454
- input_schema: zodToJsonSchema(tool.inputSchema),
1455
- }) as unknown as Anthropic.Beta.Messages.BetaTool,
1456
- )
1457
- )
1458
-
1459
- const anthropicMessages = addCacheBreakpoints(messages)
1460
-
1461
- // apply cache control
1462
- const { systemBlocks: processedSystem, messageParams: processedMessages } =
1463
- applyCacheControlWithLimits(system, anthropicMessages)
1464
- const startIncludingRetries = Date.now()
1465
-
1466
- // 记录系统提示构建过程
1467
- logSystemPromptConstruction({
1468
- basePrompt: systemPrompt.join('\n'),
1469
- kodeContext: generateKodeContext() || '',
1470
- reminders: [], // 这里可以从 generateSystemReminders 获取
1471
- finalPrompt: systemPrompt.join('\n'),
1472
- })
1473
-
1474
- let start = Date.now()
1475
- let attemptNumber = 0
1476
- let response
1477
-
1478
- try {
1479
- response = await withRetry(async attempt => {
1480
- attemptNumber = attempt
1481
- start = Date.now()
1482
-
1483
- const params: Anthropic.Beta.Messages.MessageCreateParams = {
1484
- model,
1485
- max_tokens: getMaxTokensFromProfile(modelProfile),
1486
- messages: processedMessages,
1487
- system: processedSystem,
1488
- tools: toolSchemas.length > 0 ? toolSchemas : undefined,
1489
- tool_choice: toolSchemas.length > 0 ? { type: 'auto' } : undefined,
1490
- }
1491
-
1492
- if (maxThinkingTokens > 0) {
1493
- ;(params as any).extra_headers = {
1494
- 'anthropic-beta': 'max-tokens-3-5-sonnet-2024-07-15',
1495
- }
1496
- ;(params as any).thinking = { max_tokens: maxThinkingTokens }
1497
- }
1498
-
1499
- // 🔥 REAL-TIME API CALL DEBUG - 使用全局日志系统 (Anthropic Streaming)
1500
- debugLogger.api('ANTHROPIC_API_CALL_START_STREAMING', {
1501
- endpoint: modelProfile?.baseURL || 'DEFAULT_ANTHROPIC',
1502
- model,
1503
- provider,
1504
- apiKeyConfigured: !!modelProfile?.apiKey,
1505
- apiKeyPrefix: modelProfile?.apiKey
1506
- ? modelProfile.apiKey.substring(0, 8)
1507
- : null,
1508
- maxTokens: params.max_tokens,
1509
- temperature: MAIN_QUERY_TEMPERATURE,
1510
- params: params,
1511
- messageCount: params.messages?.length || 0,
1512
- streamMode: true,
1513
- toolsCount: toolSchemas.length,
1514
- thinkingTokens: maxThinkingTokens,
1515
- timestamp: new Date().toISOString(),
1516
- modelProfileId: modelProfile?.modelName,
1517
- modelProfileName: modelProfile?.name,
1518
- })
1519
-
1520
- if (config.stream) {
1521
-
1522
- const stream = await anthropic.beta.messages.create({
1523
- ...params,
1524
- stream: true,
1525
- }, {
1526
- signal: signal // ← CRITICAL: Connect the AbortSignal to API call
1527
- })
1528
-
1529
- let finalResponse: any | null = null
1530
- let messageStartEvent: any = null
1531
- const contentBlocks: any[] = []
1532
- const inputJSONBuffers = new Map<number, string>()
1533
- let usage: any = null
1534
- let stopReason: string | null = null
1535
- let stopSequence: string | null = null
1536
-
1537
- for await (const event of stream) {
1538
-
1539
- if (signal.aborted) {
1540
- debugLogger.flow('STREAM_ABORTED', {
1541
- eventType: event.type,
1542
- timestamp: Date.now()
1543
- })
1544
- throw new Error('Request was cancelled')
1545
- }
1546
-
1547
- switch (event.type) {
1548
- case 'message_start':
1549
- messageStartEvent = event
1550
- finalResponse = {
1551
- ...event.message,
1552
- content: [], // Will be populated from content blocks
1553
- }
1554
- break
1555
-
1556
- case 'content_block_start':
1557
- contentBlocks[event.index] = { ...event.content_block }
1558
- // Initialize JSON buffer for tool_use blocks
1559
- if (event.content_block.type === 'tool_use') {
1560
- inputJSONBuffers.set(event.index, '')
1561
- }
1562
- break
1563
-
1564
- case 'content_block_delta':
1565
- const blockIndex = event.index
1566
-
1567
- // Ensure content block exists
1568
- if (!contentBlocks[blockIndex]) {
1569
- contentBlocks[blockIndex] = {
1570
- type: event.delta.type === 'text_delta' ? 'text' : 'tool_use',
1571
- text: event.delta.type === 'text_delta' ? '' : undefined,
1572
- }
1573
- if (event.delta.type === 'input_json_delta') {
1574
- inputJSONBuffers.set(blockIndex, '')
1575
- }
1576
- }
1577
-
1578
- if (event.delta.type === 'text_delta') {
1579
- contentBlocks[blockIndex].text += event.delta.text
1580
- } else if (event.delta.type === 'input_json_delta') {
1581
- const currentBuffer = inputJSONBuffers.get(blockIndex) || ''
1582
- inputJSONBuffers.set(blockIndex, currentBuffer + event.delta.partial_json)
1583
- }
1584
- break
1585
-
1586
- case 'message_delta':
1587
- if (event.delta.stop_reason) stopReason = event.delta.stop_reason
1588
- if (event.delta.stop_sequence) stopSequence = event.delta.stop_sequence
1589
- if (event.usage) usage = { ...usage, ...event.usage }
1590
- break
1591
-
1592
- case 'content_block_stop':
1593
- const stopIndex = event.index
1594
- const block = contentBlocks[stopIndex]
1595
-
1596
- if (block?.type === 'tool_use' && inputJSONBuffers.has(stopIndex)) {
1597
- const jsonStr = inputJSONBuffers.get(stopIndex)
1598
- if (jsonStr) {
1599
- try {
1600
- block.input = JSON.parse(jsonStr)
1601
- } catch (error) {
1602
- debugLogger.error('JSON_PARSE_ERROR', {
1603
- blockIndex: stopIndex,
1604
- jsonStr,
1605
- error: error instanceof Error ? error.message : String(error)
1606
- })
1607
- block.input = {}
1608
- }
1609
- inputJSONBuffers.delete(stopIndex)
1610
- }
1611
- }
1612
- break
1613
-
1614
- case 'message_stop':
1615
- // Clear any remaining buffers
1616
- inputJSONBuffers.clear()
1617
- break
1618
- }
1619
-
1620
- if (event.type === 'message_stop') {
1621
- break
1622
- }
1623
- }
1624
-
1625
- if (!finalResponse || !messageStartEvent) {
1626
- throw new Error('Stream ended without proper message structure')
1627
- }
1628
-
1629
- // Construct the final response
1630
- finalResponse = {
1631
- ...messageStartEvent.message,
1632
- content: contentBlocks.filter(Boolean),
1633
- stop_reason: stopReason,
1634
- stop_sequence: stopSequence,
1635
- usage: {
1636
- ...messageStartEvent.message.usage,
1637
- ...usage,
1638
- },
1639
- }
1640
-
1641
- return finalResponse
1642
- } else {
1643
- // 🔥 REAL-TIME API CALL DEBUG - 使用全局日志系统 (Anthropic Non-Streaming)
1644
- debugLogger.api('ANTHROPIC_API_CALL_START_NON_STREAMING', {
1645
- endpoint: modelProfile?.baseURL || 'DEFAULT_ANTHROPIC',
1646
- model,
1647
- provider,
1648
- apiKeyConfigured: !!modelProfile?.apiKey,
1649
- apiKeyPrefix: modelProfile?.apiKey
1650
- ? modelProfile.apiKey.substring(0, 8)
1651
- : null,
1652
- maxTokens: params.max_tokens,
1653
- temperature: MAIN_QUERY_TEMPERATURE,
1654
- messageCount: params.messages?.length || 0,
1655
- streamMode: false,
1656
- toolsCount: toolSchemas.length,
1657
- thinkingTokens: maxThinkingTokens,
1658
- timestamp: new Date().toISOString(),
1659
- modelProfileId: modelProfile?.modelName,
1660
- modelProfileName: modelProfile?.name,
1661
- })
1662
-
1663
-
1664
- return await anthropic.beta.messages.create(params, {
1665
- signal: signal // ← CRITICAL: Connect the AbortSignal to API call
1666
- })
1667
- }
1668
- }, { signal })
1669
-
1670
- debugLogger.api('ANTHROPIC_API_CALL_SUCCESS', {
1671
- content: response.content
1672
- })
1673
-
1674
- const ttftMs = start - Date.now()
1675
- const durationMs = Date.now() - startIncludingRetries
1676
-
1677
- const content = response.content.map((block: ContentBlock) => {
1678
- if (block.type === 'text') {
1679
- return {
1680
- type: 'text' as const,
1681
- text: block.text,
1682
- }
1683
- } else if (block.type === 'tool_use') {
1684
- return {
1685
- type: 'tool_use' as const,
1686
- id: block.id,
1687
- name: block.name,
1688
- input: block.input,
1689
- }
1690
- }
1691
- return block
1692
- })
1693
-
1694
- const assistantMessage: AssistantMessage = {
1695
- message: {
1696
- id: response.id,
1697
- content,
1698
- model: response.model,
1699
- role: 'assistant',
1700
- stop_reason: response.stop_reason,
1701
- stop_sequence: response.stop_sequence,
1702
- type: 'message',
1703
- usage: response.usage,
1704
- },
1705
- type: 'assistant',
1706
- uuid: nanoid() as UUID,
1707
- durationMs,
1708
- costUSD: 0, // Will be calculated below
1709
- }
1710
-
1711
- // 记录完整的 LLM 交互调试信息 (Anthropic path)
1712
- // 注意:Anthropic API将system prompt和messages分开,这里重构为完整的API调用视图
1713
- const systemMessages = system.map(block => ({
1714
- role: 'system',
1715
- content: block.text,
1716
- }))
1717
-
1718
- logLLMInteraction({
1719
- systemPrompt: systemPrompt.join('\n'),
1720
- messages: [...systemMessages, ...anthropicMessages],
1721
- response: response,
1722
- usage: response.usage
1723
- ? {
1724
- inputTokens: response.usage.input_tokens,
1725
- outputTokens: response.usage.output_tokens,
1726
- }
1727
- : undefined,
1728
- timing: {
1729
- start: start,
1730
- end: Date.now(),
1731
- },
1732
- apiFormat: 'anthropic',
1733
- })
1734
-
1735
- // Calculate cost using native Anthropic usage data
1736
- const inputTokens = response.usage.input_tokens
1737
- const outputTokens = response.usage.output_tokens
1738
- const cacheCreationInputTokens =
1739
- response.usage.cache_creation_input_tokens ?? 0
1740
- const cacheReadInputTokens = response.usage.cache_read_input_tokens ?? 0
1741
-
1742
- const costUSD =
1743
- (inputTokens / 1_000_000) * getModelInputTokenCostUSD(model) +
1744
- (outputTokens / 1_000_000) * getModelOutputTokenCostUSD(model) +
1745
- (cacheCreationInputTokens / 1_000_000) *
1746
- getModelInputTokenCostUSD(model) +
1747
- (cacheReadInputTokens / 1_000_000) *
1748
- (getModelInputTokenCostUSD(model) * 0.1) // Cache reads are 10% of input cost
1749
-
1750
- assistantMessage.costUSD = costUSD
1751
- addToTotalCost(costUSD, durationMs)
1752
-
1753
- logEvent('api_response_anthropic_native', {
1754
- model,
1755
- input_tokens: String(inputTokens),
1756
- output_tokens: String(outputTokens),
1757
- cache_creation_input_tokens: String(cacheCreationInputTokens),
1758
- cache_read_input_tokens: String(cacheReadInputTokens),
1759
- cost_usd: String(costUSD),
1760
- duration_ms: String(durationMs),
1761
- ttft_ms: String(ttftMs),
1762
- attempt_number: String(attemptNumber),
1763
- })
1764
-
1765
- return assistantMessage
1766
- } catch (error) {
1767
- return getAssistantMessageFromError(error)
1768
- }
1769
- }
1770
-
1771
- function getAssistantMessageFromError(error: unknown): AssistantMessage {
1772
- if (error instanceof Error && error.message.includes('prompt is too long')) {
1773
- return createAssistantAPIErrorMessage(PROMPT_TOO_LONG_ERROR_MESSAGE)
1774
- }
1775
- if (
1776
- error instanceof Error &&
1777
- error.message.includes('Your credit balance is too low')
1778
- ) {
1779
- return createAssistantAPIErrorMessage(CREDIT_BALANCE_TOO_LOW_ERROR_MESSAGE)
1780
- }
1781
- if (
1782
- error instanceof Error &&
1783
- error.message.toLowerCase().includes('x-api-key')
1784
- ) {
1785
- return createAssistantAPIErrorMessage(INVALID_API_KEY_ERROR_MESSAGE)
1786
- }
1787
- if (error instanceof Error) {
1788
- if (process.env.NODE_ENV === 'development') {
1789
- console.log(error)
1790
- }
1791
- return createAssistantAPIErrorMessage(
1792
- `${API_ERROR_MESSAGE_PREFIX}: ${error.message}`,
1793
- )
1794
- }
1795
- return createAssistantAPIErrorMessage(API_ERROR_MESSAGE_PREFIX)
1796
- }
1797
-
1798
- function addCacheBreakpoints(
1799
- messages: (UserMessage | AssistantMessage)[],
1800
- ): MessageParam[] {
1801
- return messages.map((msg, index) => {
1802
- return msg.type === 'user'
1803
- ? userMessageToMessageParam(msg, index > messages.length - 3)
1804
- : assistantMessageToMessageParam(msg, index > messages.length - 3)
1805
- })
1806
- }
1807
-
1808
- async function queryOpenAI(
1809
- messages: (UserMessage | AssistantMessage)[],
1810
- systemPrompt: string[],
1811
- maxThinkingTokens: number,
1812
- tools: Tool[],
1813
- signal: AbortSignal,
1814
- options?: {
1815
- safeMode: boolean
1816
- model: string
1817
- prependCLISysprompt: boolean
1818
- modelProfile?: ModelProfile | null
1819
- toolUseContext?: ToolUseContext
1820
- },
1821
- ): Promise<AssistantMessage> {
1822
- const config = getGlobalConfig()
1823
- const modelManager = getModelManager()
1824
- const toolUseContext = options?.toolUseContext
1825
-
1826
-
1827
- const modelProfile = options?.modelProfile || modelManager.getModel('main')
1828
- let model: string
1829
-
1830
- // 🔍 Debug: 记录模型配置详情
1831
- const currentRequest = getCurrentRequest()
1832
- debugLogger.api('MODEL_CONFIG_OPENAI', {
1833
- modelProfileFound: !!modelProfile,
1834
- modelProfileId: modelProfile?.modelName,
1835
- modelProfileName: modelProfile?.name,
1836
- modelProfileModelName: modelProfile?.modelName,
1837
- modelProfileProvider: modelProfile?.provider,
1838
- modelProfileBaseURL: modelProfile?.baseURL,
1839
- modelProfileApiKeyExists: !!modelProfile?.apiKey,
1840
- optionsModel: options?.model,
1841
- requestId: getCurrentRequest()?.id,
1842
- })
1843
-
1844
- if (modelProfile) {
1845
- model = modelProfile.modelName
1846
- } else {
1847
- model = options?.model || modelProfile?.modelName || ''
1848
- }
1849
- // Prepend system prompt block for easy API identification
1850
- if (options?.prependCLISysprompt) {
1851
- // Log stats about first block for analyzing prefix matching config (see https://console.statsig.com/4aF3Ewatb6xPVpCwxb5nA3/dynamic_configs/claude_cli_system_prompt_prefixes)
1852
- const [firstSyspromptBlock] = splitSysPromptPrefix(systemPrompt)
1853
- logEvent('tengu_sysprompt_block', {
1854
- snippet: firstSyspromptBlock?.slice(0, 20),
1855
- length: String(firstSyspromptBlock?.length ?? 0),
1856
- hash: firstSyspromptBlock
1857
- ? createHash('sha256').update(firstSyspromptBlock).digest('hex')
1858
- : '',
1859
- })
1860
-
1861
- systemPrompt = [getCLISyspromptPrefix() + systemPrompt] // some openai-like providers need the entire system prompt as a single block
1862
- }
1863
-
1864
- const system: TextBlockParam[] = splitSysPromptPrefix(systemPrompt).map(
1865
- _ => ({
1866
- ...(PROMPT_CACHING_ENABLED
1867
- ? { cache_control: { type: 'ephemeral' } }
1868
- : {}),
1869
- text: _,
1870
- type: 'text',
1871
- }),
1872
- )
1873
-
1874
- const toolSchemas = await Promise.all(
1875
- tools.map(
1876
- async _ =>
1877
- ({
1878
- type: 'function',
1879
- function: {
1880
- name: _.name,
1881
- description: await _.prompt({
1882
- safeMode: options?.safeMode,
1883
- }),
1884
- // Use tool's JSON schema directly if provided, otherwise convert Zod schema
1885
- parameters:
1886
- 'inputJSONSchema' in _ && _.inputJSONSchema
1887
- ? _.inputJSONSchema
1888
- : zodToJsonSchema(_.inputSchema),
1889
- },
1890
- }) as OpenAI.ChatCompletionTool,
1891
- ),
1892
- )
1893
-
1894
- const openaiSystem = system.map(
1895
- s =>
1896
- ({
1897
- role: 'system',
1898
- content: s.text,
1899
- }) as OpenAI.ChatCompletionMessageParam,
1900
- )
1901
-
1902
- const openaiMessages = convertAnthropicMessagesToOpenAIMessages(messages)
1903
- const startIncludingRetries = Date.now()
1904
-
1905
- // 记录系统提示构建过程 (OpenAI path)
1906
- logSystemPromptConstruction({
1907
- basePrompt: systemPrompt.join('\n'),
1908
- kodeContext: generateKodeContext() || '',
1909
- reminders: [], // 这里可以从 generateSystemReminders 获取
1910
- finalPrompt: systemPrompt.join('\n'),
1911
- })
1912
-
1913
- let start = Date.now()
1914
- let attemptNumber = 0
1915
- let response
1916
-
1917
- try {
1918
- response = await withRetry(async attempt => {
1919
- attemptNumber = attempt
1920
- start = Date.now()
1921
- // 🔥 GPT-5 Enhanced Parameter Construction
1922
- const maxTokens = getMaxTokensFromProfile(modelProfile)
1923
- const isGPT5 = isGPT5Model(model)
1924
-
1925
- const opts: OpenAI.ChatCompletionCreateParams = {
1926
- model,
1927
-
1928
- ...(isGPT5 ? { max_completion_tokens: maxTokens } : { max_tokens: maxTokens }),
1929
- messages: [...openaiSystem, ...openaiMessages],
1930
-
1931
- temperature: isGPT5 ? 1 : MAIN_QUERY_TEMPERATURE,
1932
- }
1933
- if (config.stream) {
1934
- ;(opts as OpenAI.ChatCompletionCreateParams).stream = true
1935
- opts.stream_options = {
1936
- include_usage: true,
1937
- }
1938
- }
1939
-
1940
- if (toolSchemas.length > 0) {
1941
- opts.tools = toolSchemas
1942
- opts.tool_choice = 'auto'
1943
- }
1944
- const reasoningEffort = await getReasoningEffort(modelProfile, messages)
1945
- if (reasoningEffort) {
1946
- logEvent('debug_reasoning_effort', {
1947
- effort: reasoningEffort,
1948
- })
1949
- opts.reasoning_effort = reasoningEffort
1950
- }
1951
-
1952
-
1953
- if (modelProfile && modelProfile.modelName) {
1954
- debugLogger.api('USING_MODEL_PROFILE_PATH', {
1955
- modelProfileName: modelProfile.modelName,
1956
- modelName: modelProfile.modelName,
1957
- provider: modelProfile.provider,
1958
- baseURL: modelProfile.baseURL,
1959
- apiKeyExists: !!modelProfile.apiKey,
1960
- requestId: getCurrentRequest()?.id,
1961
- })
1962
-
1963
- // Enable new adapter system with environment variable
1964
- const USE_NEW_ADAPTER_SYSTEM = process.env.USE_NEW_ADAPTERS !== 'false'
1965
-
1966
- if (USE_NEW_ADAPTER_SYSTEM) {
1967
- // New adapter system
1968
- const adapter = ModelAdapterFactory.createAdapter(modelProfile)
1969
-
1970
- // Build unified request parameters
1971
- const unifiedParams: UnifiedRequestParams = {
1972
- messages: openaiMessages,
1973
- systemPrompt: openaiSystem.map(s => s.content as string),
1974
- tools: tools,
1975
- maxTokens: getMaxTokensFromProfile(modelProfile),
1976
- stream: config.stream,
1977
- reasoningEffort: reasoningEffort as any,
1978
- temperature: isGPT5Model(model) ? 1 : MAIN_QUERY_TEMPERATURE,
1979
- previousResponseId: toolUseContext?.responseState?.previousResponseId,
1980
- verbosity: 'high' // High verbosity for coding tasks
1981
- }
1982
-
1983
- // Create request using adapter
1984
- const request = adapter.createRequest(unifiedParams)
1985
-
1986
- // Determine which API to use
1987
- if (ModelAdapterFactory.shouldUseResponsesAPI(modelProfile)) {
1988
- // Use Responses API for GPT-5 and similar models
1989
- const { callGPT5ResponsesAPI } = await import('./openai')
1990
- const response = await callGPT5ResponsesAPI(modelProfile, request, signal)
1991
- const unifiedResponse = adapter.parseResponse(response)
1992
-
1993
- // Convert unified response back to Anthropic format
1994
- const apiMessage = {
1995
- role: 'assistant' as const,
1996
- content: unifiedResponse.content,
1997
- tool_calls: unifiedResponse.toolCalls,
1998
- usage: {
1999
- prompt_tokens: unifiedResponse.usage.promptTokens,
2000
- completion_tokens: unifiedResponse.usage.completionTokens,
2001
- }
2002
- }
2003
- const assistantMsg: AssistantMessage = {
2004
- type: 'assistant',
2005
- message: apiMessage as any,
2006
- costUSD: 0, // Will be calculated later
2007
- durationMs: Date.now() - start,
2008
- uuid: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` as any,
2009
- responseId: unifiedResponse.responseId // For state management
2010
- }
2011
- return assistantMsg
2012
- } else {
2013
- // Use existing Chat Completions flow
2014
- const s = await getCompletionWithProfile(modelProfile, request, 0, 10, signal)
2015
- let finalResponse
2016
- if (config.stream) {
2017
- finalResponse = await handleMessageStream(s as ChatCompletionStream, signal)
2018
- } else {
2019
- finalResponse = s
2020
- }
2021
- const r = convertOpenAIResponseToAnthropic(finalResponse, tools)
2022
- return r
2023
- }
2024
- } else {
2025
- // Legacy system (preserved for fallback)
2026
- const completionFunction = isGPT5Model(modelProfile.modelName)
2027
- ? getGPT5CompletionWithProfile
2028
- : getCompletionWithProfile
2029
- const s = await completionFunction(modelProfile, opts, 0, 10, signal)
2030
- let finalResponse
2031
- if (opts.stream) {
2032
- finalResponse = await handleMessageStream(s as ChatCompletionStream, signal)
2033
- } else {
2034
- finalResponse = s
2035
- }
2036
- const r = convertOpenAIResponseToAnthropic(finalResponse, tools)
2037
- return r
2038
- }
2039
- } else {
2040
- // 🚨 警告:ModelProfile不可用,使用旧逻辑路径
2041
- debugLogger.api('USING_LEGACY_PATH', {
2042
- modelProfileExists: !!modelProfile,
2043
- modelProfileId: modelProfile?.modelName,
2044
- modelNameExists: !!modelProfile?.modelName,
2045
- fallbackModel: 'main',
2046
- actualModel: model,
2047
- requestId: getCurrentRequest()?.id,
2048
- })
2049
-
2050
- // 🚨 FALLBACK: 没有有效的ModelProfile时,应该抛出错误而不是使用遗留系统
2051
- const errorDetails = {
2052
- modelProfileExists: !!modelProfile,
2053
- modelProfileId: modelProfile?.modelName,
2054
- modelNameExists: !!modelProfile?.modelName,
2055
- requestedModel: model,
2056
- requestId: getCurrentRequest()?.id,
2057
- }
2058
- debugLogger.error('NO_VALID_MODEL_PROFILE', errorDetails)
2059
- throw new Error(
2060
- `No valid ModelProfile available for model: ${model}. Please configure model through /model command. Debug: ${JSON.stringify(errorDetails)}`,
2061
- )
2062
- }
2063
- }, { signal })
2064
- } catch (error) {
2065
- logError(error)
2066
- return getAssistantMessageFromError(error)
2067
- }
2068
- const durationMs = Date.now() - start
2069
- const durationMsIncludingRetries = Date.now() - startIncludingRetries
2070
-
2071
- const inputTokens = response.usage?.prompt_tokens ?? 0
2072
- const outputTokens = response.usage?.completion_tokens ?? 0
2073
- const cacheReadInputTokens =
2074
- response.usage?.prompt_token_details?.cached_tokens ?? 0
2075
- const cacheCreationInputTokens =
2076
- response.usage?.prompt_token_details?.cached_tokens ?? 0
2077
- const costUSD =
2078
- (inputTokens / 1_000_000) * SONNET_COST_PER_MILLION_INPUT_TOKENS +
2079
- (outputTokens / 1_000_000) * SONNET_COST_PER_MILLION_OUTPUT_TOKENS +
2080
- (cacheReadInputTokens / 1_000_000) *
2081
- SONNET_COST_PER_MILLION_PROMPT_CACHE_READ_TOKENS +
2082
- (cacheCreationInputTokens / 1_000_000) *
2083
- SONNET_COST_PER_MILLION_PROMPT_CACHE_WRITE_TOKENS
2084
-
2085
- addToTotalCost(costUSD, durationMsIncludingRetries)
2086
-
2087
- // 记录完整的 LLM 交互调试信息 (OpenAI path)
2088
- logLLMInteraction({
2089
- systemPrompt: systemPrompt.join('\n'),
2090
- messages: [...openaiSystem, ...openaiMessages],
2091
- response: response,
2092
- usage: {
2093
- inputTokens: inputTokens,
2094
- outputTokens: outputTokens,
2095
- },
2096
- timing: {
2097
- start: start,
2098
- end: Date.now(),
2099
- },
2100
- apiFormat: 'openai',
2101
- })
2102
-
2103
- return {
2104
- message: {
2105
- ...response,
2106
- content: normalizeContentFromAPI(response.content),
2107
- usage: {
2108
- input_tokens: inputTokens,
2109
- output_tokens: outputTokens,
2110
- cache_read_input_tokens: cacheReadInputTokens,
2111
- cache_creation_input_tokens: 0,
2112
- },
2113
- },
2114
- costUSD,
2115
- durationMs,
2116
- type: 'assistant',
2117
- uuid: randomUUID(),
2118
- }
2119
- }
2120
-
2121
- function getMaxTokensFromProfile(modelProfile: any): number {
2122
- // Use ModelProfile maxTokens or reasonable default
2123
- return modelProfile?.maxTokens || 8000
2124
- }
2125
-
2126
- function getModelInputTokenCostUSD(model: string): number {
2127
- // Find the model in the models object
2128
- for (const providerModels of Object.values(models)) {
2129
- const modelInfo = providerModels.find((m: any) => m.model === model)
2130
- if (modelInfo) {
2131
- return modelInfo.input_cost_per_token || 0
2132
- }
2133
- }
2134
- // Default fallback cost for unknown models
2135
- return 0.000003 // Default to Claude 3 Haiku cost
2136
- }
2137
-
2138
- function getModelOutputTokenCostUSD(model: string): number {
2139
- // Find the model in the models object
2140
- for (const providerModels of Object.values(models)) {
2141
- const modelInfo = providerModels.find((m: any) => m.model === model)
2142
- if (modelInfo) {
2143
- return modelInfo.output_cost_per_token || 0
2144
- }
2145
- }
2146
- // Default fallback cost for unknown models
2147
- return 0.000015 // Default to Claude 3 Haiku cost
2148
- }
2149
-
2150
- // New unified query functions for model pointer system
2151
- export async function queryModel(
2152
- modelPointer: import('../utils/config').ModelPointerType,
2153
- messages: (UserMessage | AssistantMessage)[],
2154
- systemPrompt: string[] = [],
2155
- signal?: AbortSignal,
2156
- ): Promise<AssistantMessage> {
2157
- // Use queryLLM with the pointer directly
2158
- return queryLLM(
2159
- messages,
2160
- systemPrompt,
2161
- 0, // maxThinkingTokens
2162
- [], // tools
2163
- signal || new AbortController().signal,
2164
- {
2165
- safeMode: false,
2166
- model: modelPointer,
2167
- prependCLISysprompt: true,
2168
- },
2169
- )
2170
- }
2171
-
2172
- // Note: Use queryModel(pointer, ...) directly instead of these convenience functions
2173
-
2174
- // Simplified query function using quick model pointer
2175
- export async function queryQuick({
2176
- systemPrompt = [],
2177
- userPrompt,
2178
- assistantPrompt,
2179
- enablePromptCaching = false,
2180
- signal,
2181
- }: {
2182
- systemPrompt?: string[]
2183
- userPrompt: string
2184
- assistantPrompt?: string
2185
- enablePromptCaching?: boolean
2186
- signal?: AbortSignal
2187
- }): Promise<AssistantMessage> {
2188
- const messages = [
2189
- {
2190
- message: { role: 'user', content: userPrompt },
2191
- type: 'user',
2192
- uuid: randomUUID(),
2193
- },
2194
- ] as (UserMessage | AssistantMessage)[]
2195
-
2196
- return queryModel('quick', messages, systemPrompt, signal)
2197
- }