@shareai-lab/kode 1.1.14 → 1.1.16-dev.2

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 (289) hide show
  1. package/cli.js +77 -82
  2. package/dist/entrypoints/cli.js +59 -38
  3. package/dist/entrypoints/cli.js.map +3 -3
  4. package/dist/index.js +5 -26
  5. package/dist/package.json +4 -1
  6. package/package.json +11 -104
  7. package/dist/test/testAdapters.js +0 -88
  8. package/dist/test/testAdapters.js.map +0 -1
  9. package/src/ProjectOnboarding.tsx +0 -198
  10. package/src/Tool.ts +0 -83
  11. package/src/commands/agents.tsx +0 -3416
  12. package/src/commands/approvedTools.ts +0 -53
  13. package/src/commands/bug.tsx +0 -20
  14. package/src/commands/clear.ts +0 -43
  15. package/src/commands/compact.ts +0 -120
  16. package/src/commands/config.tsx +0 -19
  17. package/src/commands/cost.ts +0 -18
  18. package/src/commands/ctx_viz.ts +0 -209
  19. package/src/commands/doctor.ts +0 -24
  20. package/src/commands/help.tsx +0 -19
  21. package/src/commands/init.ts +0 -37
  22. package/src/commands/listen.ts +0 -42
  23. package/src/commands/login.tsx +0 -51
  24. package/src/commands/logout.tsx +0 -40
  25. package/src/commands/mcp.ts +0 -41
  26. package/src/commands/model.tsx +0 -40
  27. package/src/commands/modelstatus.tsx +0 -20
  28. package/src/commands/onboarding.tsx +0 -34
  29. package/src/commands/pr_comments.ts +0 -59
  30. package/src/commands/refreshCommands.ts +0 -54
  31. package/src/commands/release-notes.ts +0 -34
  32. package/src/commands/resume.tsx +0 -31
  33. package/src/commands/review.ts +0 -49
  34. package/src/commands/terminalSetup.ts +0 -221
  35. package/src/commands.ts +0 -139
  36. package/src/components/ApproveApiKey.tsx +0 -93
  37. package/src/components/AsciiLogo.tsx +0 -13
  38. package/src/components/AutoUpdater.tsx +0 -148
  39. package/src/components/Bug.tsx +0 -367
  40. package/src/components/Config.tsx +0 -293
  41. package/src/components/ConsoleOAuthFlow.tsx +0 -327
  42. package/src/components/Cost.tsx +0 -23
  43. package/src/components/CostThresholdDialog.tsx +0 -46
  44. package/src/components/CustomSelect/option-map.ts +0 -42
  45. package/src/components/CustomSelect/select-option.tsx +0 -78
  46. package/src/components/CustomSelect/select.tsx +0 -152
  47. package/src/components/CustomSelect/theme.ts +0 -45
  48. package/src/components/CustomSelect/use-select-state.ts +0 -414
  49. package/src/components/CustomSelect/use-select.ts +0 -35
  50. package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
  51. package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
  52. package/src/components/Help.tsx +0 -215
  53. package/src/components/HighlightedCode.tsx +0 -33
  54. package/src/components/InvalidConfigDialog.tsx +0 -113
  55. package/src/components/Link.tsx +0 -32
  56. package/src/components/LogSelector.tsx +0 -86
  57. package/src/components/Logo.tsx +0 -170
  58. package/src/components/MCPServerApprovalDialog.tsx +0 -100
  59. package/src/components/MCPServerDialogCopy.tsx +0 -25
  60. package/src/components/MCPServerMultiselectDialog.tsx +0 -109
  61. package/src/components/Message.tsx +0 -221
  62. package/src/components/MessageResponse.tsx +0 -15
  63. package/src/components/MessageSelector.tsx +0 -211
  64. package/src/components/ModeIndicator.tsx +0 -88
  65. package/src/components/ModelConfig.tsx +0 -301
  66. package/src/components/ModelListManager.tsx +0 -227
  67. package/src/components/ModelSelector.tsx +0 -3387
  68. package/src/components/ModelStatusDisplay.tsx +0 -230
  69. package/src/components/Onboarding.tsx +0 -274
  70. package/src/components/PressEnterToContinue.tsx +0 -11
  71. package/src/components/PromptInput.tsx +0 -760
  72. package/src/components/SentryErrorBoundary.ts +0 -39
  73. package/src/components/Spinner.tsx +0 -129
  74. package/src/components/StickerRequestForm.tsx +0 -16
  75. package/src/components/StructuredDiff.tsx +0 -191
  76. package/src/components/TextInput.tsx +0 -259
  77. package/src/components/TodoItem.tsx +0 -47
  78. package/src/components/TokenWarning.tsx +0 -31
  79. package/src/components/ToolUseLoader.tsx +0 -40
  80. package/src/components/TrustDialog.tsx +0 -106
  81. package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
  82. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
  83. package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
  84. package/src/components/binary-feedback/utils.ts +0 -220
  85. package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
  86. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
  87. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
  88. package/src/components/messages/AssistantTextMessage.tsx +0 -144
  89. package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
  90. package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
  91. package/src/components/messages/TaskProgressMessage.tsx +0 -32
  92. package/src/components/messages/TaskToolMessage.tsx +0 -58
  93. package/src/components/messages/UserBashInputMessage.tsx +0 -28
  94. package/src/components/messages/UserCommandMessage.tsx +0 -30
  95. package/src/components/messages/UserKodingInputMessage.tsx +0 -28
  96. package/src/components/messages/UserPromptMessage.tsx +0 -35
  97. package/src/components/messages/UserTextMessage.tsx +0 -39
  98. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
  99. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
  100. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
  101. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
  102. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
  103. package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
  104. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
  105. package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
  106. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
  107. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
  108. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
  109. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
  110. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
  111. package/src/components/permissions/PermissionRequest.tsx +0 -101
  112. package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
  113. package/src/components/permissions/hooks.ts +0 -44
  114. package/src/components/permissions/toolUseOptions.ts +0 -59
  115. package/src/components/permissions/utils.ts +0 -23
  116. package/src/constants/betas.ts +0 -5
  117. package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
  118. package/src/constants/figures.ts +0 -4
  119. package/src/constants/keys.ts +0 -3
  120. package/src/constants/macros.ts +0 -11
  121. package/src/constants/modelCapabilities.ts +0 -179
  122. package/src/constants/models.ts +0 -1025
  123. package/src/constants/oauth.ts +0 -18
  124. package/src/constants/product.ts +0 -17
  125. package/src/constants/prompts.ts +0 -168
  126. package/src/constants/releaseNotes.ts +0 -7
  127. package/src/context/PermissionContext.tsx +0 -149
  128. package/src/context.ts +0 -278
  129. package/src/cost-tracker.ts +0 -84
  130. package/src/entrypoints/cli.tsx +0 -1561
  131. package/src/entrypoints/mcp.ts +0 -175
  132. package/src/history.ts +0 -25
  133. package/src/hooks/useApiKeyVerification.ts +0 -59
  134. package/src/hooks/useArrowKeyHistory.ts +0 -55
  135. package/src/hooks/useCanUseTool.ts +0 -138
  136. package/src/hooks/useCancelRequest.ts +0 -39
  137. package/src/hooks/useDoublePress.ts +0 -41
  138. package/src/hooks/useExitOnCtrlCD.ts +0 -31
  139. package/src/hooks/useInterval.ts +0 -25
  140. package/src/hooks/useLogMessages.ts +0 -16
  141. package/src/hooks/useLogStartupTime.ts +0 -12
  142. package/src/hooks/useNotifyAfterTimeout.ts +0 -65
  143. package/src/hooks/usePermissionRequestLogging.ts +0 -44
  144. package/src/hooks/useTerminalSize.ts +0 -49
  145. package/src/hooks/useTextInput.ts +0 -317
  146. package/src/hooks/useUnifiedCompletion.ts +0 -1405
  147. package/src/index.ts +0 -34
  148. package/src/messages.ts +0 -38
  149. package/src/permissions.ts +0 -268
  150. package/src/query.ts +0 -720
  151. package/src/screens/ConfigureNpmPrefix.tsx +0 -197
  152. package/src/screens/Doctor.tsx +0 -219
  153. package/src/screens/LogList.tsx +0 -68
  154. package/src/screens/REPL.tsx +0 -813
  155. package/src/screens/ResumeConversation.tsx +0 -68
  156. package/src/services/adapters/base.ts +0 -38
  157. package/src/services/adapters/chatCompletions.ts +0 -90
  158. package/src/services/adapters/responsesAPI.ts +0 -170
  159. package/src/services/browserMocks.ts +0 -66
  160. package/src/services/claude.ts +0 -2197
  161. package/src/services/customCommands.ts +0 -704
  162. package/src/services/fileFreshness.ts +0 -377
  163. package/src/services/gpt5ConnectionTest.ts +0 -340
  164. package/src/services/mcpClient.ts +0 -564
  165. package/src/services/mcpServerApproval.tsx +0 -50
  166. package/src/services/mentionProcessor.ts +0 -273
  167. package/src/services/modelAdapterFactory.ts +0 -69
  168. package/src/services/notifier.ts +0 -40
  169. package/src/services/oauth.ts +0 -357
  170. package/src/services/openai.ts +0 -1359
  171. package/src/services/responseStateManager.ts +0 -90
  172. package/src/services/sentry.ts +0 -3
  173. package/src/services/statsig.ts +0 -172
  174. package/src/services/statsigStorage.ts +0 -86
  175. package/src/services/systemReminder.ts +0 -507
  176. package/src/services/vcr.ts +0 -161
  177. package/src/test/testAdapters.ts +0 -96
  178. package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
  179. package/src/tools/ArchitectTool/prompt.ts +0 -15
  180. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
  181. package/src/tools/BashTool/BashTool.tsx +0 -243
  182. package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
  183. package/src/tools/BashTool/OutputLine.tsx +0 -49
  184. package/src/tools/BashTool/prompt.ts +0 -174
  185. package/src/tools/BashTool/utils.ts +0 -56
  186. package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
  187. package/src/tools/FileEditTool/prompt.ts +0 -51
  188. package/src/tools/FileEditTool/utils.ts +0 -58
  189. package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
  190. package/src/tools/FileReadTool/prompt.ts +0 -7
  191. package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
  192. package/src/tools/FileWriteTool/prompt.ts +0 -10
  193. package/src/tools/GlobTool/GlobTool.tsx +0 -119
  194. package/src/tools/GlobTool/prompt.ts +0 -8
  195. package/src/tools/GrepTool/GrepTool.tsx +0 -147
  196. package/src/tools/GrepTool/prompt.ts +0 -11
  197. package/src/tools/MCPTool/MCPTool.tsx +0 -107
  198. package/src/tools/MCPTool/prompt.ts +0 -3
  199. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
  200. package/src/tools/MemoryReadTool/prompt.ts +0 -3
  201. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
  202. package/src/tools/MemoryWriteTool/prompt.ts +0 -3
  203. package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
  204. package/src/tools/MultiEditTool/prompt.ts +0 -45
  205. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
  206. package/src/tools/NotebookEditTool/prompt.ts +0 -3
  207. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
  208. package/src/tools/NotebookReadTool/prompt.ts +0 -3
  209. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
  210. package/src/tools/StickerRequestTool/prompt.ts +0 -19
  211. package/src/tools/TaskTool/TaskTool.tsx +0 -438
  212. package/src/tools/TaskTool/constants.ts +0 -1
  213. package/src/tools/TaskTool/prompt.ts +0 -92
  214. package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
  215. package/src/tools/ThinkTool/prompt.ts +0 -12
  216. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
  217. package/src/tools/TodoWriteTool/prompt.ts +0 -63
  218. package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
  219. package/src/tools/URLFetcherTool/cache.ts +0 -55
  220. package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
  221. package/src/tools/URLFetcherTool/prompt.ts +0 -17
  222. package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
  223. package/src/tools/WebSearchTool/prompt.ts +0 -13
  224. package/src/tools/WebSearchTool/searchProviders.ts +0 -66
  225. package/src/tools/lsTool/lsTool.tsx +0 -272
  226. package/src/tools/lsTool/prompt.ts +0 -2
  227. package/src/tools.ts +0 -67
  228. package/src/types/PermissionMode.ts +0 -120
  229. package/src/types/RequestContext.ts +0 -72
  230. package/src/types/common.d.ts +0 -2
  231. package/src/types/conversation.ts +0 -51
  232. package/src/types/logs.ts +0 -58
  233. package/src/types/modelCapabilities.ts +0 -64
  234. package/src/types/notebook.ts +0 -87
  235. package/src/utils/Cursor.ts +0 -436
  236. package/src/utils/PersistentShell.ts +0 -552
  237. package/src/utils/advancedFuzzyMatcher.ts +0 -290
  238. package/src/utils/agentLoader.ts +0 -278
  239. package/src/utils/agentStorage.ts +0 -97
  240. package/src/utils/array.ts +0 -3
  241. package/src/utils/ask.tsx +0 -99
  242. package/src/utils/auth.ts +0 -13
  243. package/src/utils/autoCompactCore.ts +0 -223
  244. package/src/utils/autoUpdater.ts +0 -458
  245. package/src/utils/betas.ts +0 -20
  246. package/src/utils/browser.ts +0 -14
  247. package/src/utils/cleanup.ts +0 -72
  248. package/src/utils/commands.ts +0 -261
  249. package/src/utils/commonUnixCommands.ts +0 -161
  250. package/src/utils/config.ts +0 -945
  251. package/src/utils/conversationRecovery.ts +0 -55
  252. package/src/utils/debugLogger.ts +0 -1235
  253. package/src/utils/diff.ts +0 -42
  254. package/src/utils/env.ts +0 -57
  255. package/src/utils/errors.ts +0 -21
  256. package/src/utils/exampleCommands.ts +0 -109
  257. package/src/utils/execFileNoThrow.ts +0 -51
  258. package/src/utils/expertChatStorage.ts +0 -136
  259. package/src/utils/file.ts +0 -405
  260. package/src/utils/fileRecoveryCore.ts +0 -71
  261. package/src/utils/format.tsx +0 -44
  262. package/src/utils/fuzzyMatcher.ts +0 -328
  263. package/src/utils/generators.ts +0 -62
  264. package/src/utils/git.ts +0 -92
  265. package/src/utils/globalLogger.ts +0 -77
  266. package/src/utils/http.ts +0 -10
  267. package/src/utils/imagePaste.ts +0 -38
  268. package/src/utils/json.ts +0 -13
  269. package/src/utils/log.ts +0 -382
  270. package/src/utils/markdown.ts +0 -213
  271. package/src/utils/messageContextManager.ts +0 -294
  272. package/src/utils/messages.tsx +0 -945
  273. package/src/utils/model.ts +0 -914
  274. package/src/utils/permissions/filesystem.ts +0 -127
  275. package/src/utils/responseState.ts +0 -23
  276. package/src/utils/ripgrep.ts +0 -167
  277. package/src/utils/secureFile.ts +0 -564
  278. package/src/utils/sessionState.ts +0 -49
  279. package/src/utils/state.ts +0 -25
  280. package/src/utils/style.ts +0 -29
  281. package/src/utils/terminal.ts +0 -50
  282. package/src/utils/theme.ts +0 -127
  283. package/src/utils/thinking.ts +0 -144
  284. package/src/utils/todoStorage.ts +0 -431
  285. package/src/utils/tokens.ts +0 -43
  286. package/src/utils/toolExecutionController.ts +0 -163
  287. package/src/utils/unaryLogging.ts +0 -26
  288. package/src/utils/user.ts +0 -37
  289. 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
- }