@shareai-lab/kode 1.0.70 → 1.0.71

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 (253) hide show
  1. package/README.md +202 -76
  2. package/README.zh-CN.md +246 -0
  3. package/cli.js +62 -0
  4. package/package.json +45 -25
  5. package/scripts/postinstall.js +56 -0
  6. package/src/ProjectOnboarding.tsx +180 -0
  7. package/src/Tool.ts +53 -0
  8. package/src/commands/approvedTools.ts +53 -0
  9. package/src/commands/bug.tsx +20 -0
  10. package/src/commands/clear.ts +43 -0
  11. package/src/commands/compact.ts +120 -0
  12. package/src/commands/config.tsx +19 -0
  13. package/src/commands/cost.ts +18 -0
  14. package/src/commands/ctx_viz.ts +209 -0
  15. package/src/commands/doctor.ts +24 -0
  16. package/src/commands/help.tsx +19 -0
  17. package/src/commands/init.ts +37 -0
  18. package/src/commands/listen.ts +42 -0
  19. package/src/commands/login.tsx +51 -0
  20. package/src/commands/logout.tsx +40 -0
  21. package/src/commands/mcp.ts +41 -0
  22. package/src/commands/model.tsx +40 -0
  23. package/src/commands/modelstatus.tsx +20 -0
  24. package/src/commands/onboarding.tsx +34 -0
  25. package/src/commands/pr_comments.ts +59 -0
  26. package/src/commands/refreshCommands.ts +54 -0
  27. package/src/commands/release-notes.ts +34 -0
  28. package/src/commands/resume.tsx +30 -0
  29. package/src/commands/review.ts +49 -0
  30. package/src/commands/terminalSetup.ts +221 -0
  31. package/src/commands.ts +136 -0
  32. package/src/components/ApproveApiKey.tsx +93 -0
  33. package/src/components/AsciiLogo.tsx +13 -0
  34. package/src/components/AutoUpdater.tsx +148 -0
  35. package/src/components/Bug.tsx +367 -0
  36. package/src/components/Config.tsx +289 -0
  37. package/src/components/ConsoleOAuthFlow.tsx +326 -0
  38. package/src/components/Cost.tsx +23 -0
  39. package/src/components/CostThresholdDialog.tsx +46 -0
  40. package/src/components/CustomSelect/option-map.ts +42 -0
  41. package/src/components/CustomSelect/select-option.tsx +52 -0
  42. package/src/components/CustomSelect/select.tsx +143 -0
  43. package/src/components/CustomSelect/use-select-state.ts +414 -0
  44. package/src/components/CustomSelect/use-select.ts +35 -0
  45. package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
  46. package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
  47. package/src/components/Help.tsx +215 -0
  48. package/src/components/HighlightedCode.tsx +33 -0
  49. package/src/components/InvalidConfigDialog.tsx +113 -0
  50. package/src/components/Link.tsx +32 -0
  51. package/src/components/LogSelector.tsx +86 -0
  52. package/src/components/Logo.tsx +145 -0
  53. package/src/components/MCPServerApprovalDialog.tsx +100 -0
  54. package/src/components/MCPServerDialogCopy.tsx +25 -0
  55. package/src/components/MCPServerMultiselectDialog.tsx +109 -0
  56. package/src/components/Message.tsx +219 -0
  57. package/src/components/MessageResponse.tsx +15 -0
  58. package/src/components/MessageSelector.tsx +211 -0
  59. package/src/components/ModeIndicator.tsx +88 -0
  60. package/src/components/ModelConfig.tsx +301 -0
  61. package/src/components/ModelListManager.tsx +223 -0
  62. package/src/components/ModelSelector.tsx +3208 -0
  63. package/src/components/ModelStatusDisplay.tsx +228 -0
  64. package/src/components/Onboarding.tsx +274 -0
  65. package/src/components/PressEnterToContinue.tsx +11 -0
  66. package/src/components/PromptInput.tsx +710 -0
  67. package/src/components/SentryErrorBoundary.ts +33 -0
  68. package/src/components/Spinner.tsx +129 -0
  69. package/src/components/StructuredDiff.tsx +184 -0
  70. package/src/components/TextInput.tsx +246 -0
  71. package/src/components/TokenWarning.tsx +31 -0
  72. package/src/components/ToolUseLoader.tsx +40 -0
  73. package/src/components/TrustDialog.tsx +106 -0
  74. package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
  75. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
  76. package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
  77. package/src/components/binary-feedback/utils.ts +220 -0
  78. package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
  79. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +45 -0
  80. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
  81. package/src/components/messages/AssistantTextMessage.tsx +144 -0
  82. package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
  83. package/src/components/messages/AssistantToolUseMessage.tsx +123 -0
  84. package/src/components/messages/UserBashInputMessage.tsx +28 -0
  85. package/src/components/messages/UserCommandMessage.tsx +30 -0
  86. package/src/components/messages/UserKodingInputMessage.tsx +28 -0
  87. package/src/components/messages/UserPromptMessage.tsx +35 -0
  88. package/src/components/messages/UserTextMessage.tsx +39 -0
  89. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
  90. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
  91. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
  92. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
  93. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
  94. package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
  95. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
  96. package/src/components/permissions/FallbackPermissionRequest.tsx +155 -0
  97. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
  98. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +75 -0
  99. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
  100. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +81 -0
  101. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +242 -0
  102. package/src/components/permissions/PermissionRequest.tsx +103 -0
  103. package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
  104. package/src/components/permissions/hooks.ts +44 -0
  105. package/src/components/permissions/toolUseOptions.ts +59 -0
  106. package/src/components/permissions/utils.ts +23 -0
  107. package/src/constants/betas.ts +5 -0
  108. package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
  109. package/src/constants/figures.ts +4 -0
  110. package/src/constants/keys.ts +3 -0
  111. package/src/constants/macros.ts +6 -0
  112. package/src/constants/models.ts +935 -0
  113. package/src/constants/oauth.ts +18 -0
  114. package/src/constants/product.ts +17 -0
  115. package/src/constants/prompts.ts +177 -0
  116. package/src/constants/releaseNotes.ts +7 -0
  117. package/src/context/PermissionContext.tsx +149 -0
  118. package/src/context.ts +278 -0
  119. package/src/cost-tracker.ts +84 -0
  120. package/src/entrypoints/cli.tsx +1498 -0
  121. package/src/entrypoints/mcp.ts +176 -0
  122. package/src/history.ts +25 -0
  123. package/src/hooks/useApiKeyVerification.ts +59 -0
  124. package/src/hooks/useArrowKeyHistory.ts +55 -0
  125. package/src/hooks/useCanUseTool.ts +138 -0
  126. package/src/hooks/useCancelRequest.ts +39 -0
  127. package/src/hooks/useDoublePress.ts +42 -0
  128. package/src/hooks/useExitOnCtrlCD.ts +31 -0
  129. package/src/hooks/useInterval.ts +25 -0
  130. package/src/hooks/useLogMessages.ts +16 -0
  131. package/src/hooks/useLogStartupTime.ts +12 -0
  132. package/src/hooks/useNotifyAfterTimeout.ts +65 -0
  133. package/src/hooks/usePermissionRequestLogging.ts +44 -0
  134. package/src/hooks/useSlashCommandTypeahead.ts +137 -0
  135. package/src/hooks/useTerminalSize.ts +49 -0
  136. package/src/hooks/useTextInput.ts +315 -0
  137. package/src/messages.ts +37 -0
  138. package/src/permissions.ts +268 -0
  139. package/src/query.ts +704 -0
  140. package/src/screens/ConfigureNpmPrefix.tsx +197 -0
  141. package/src/screens/Doctor.tsx +219 -0
  142. package/src/screens/LogList.tsx +68 -0
  143. package/src/screens/REPL.tsx +792 -0
  144. package/src/screens/ResumeConversation.tsx +68 -0
  145. package/src/services/browserMocks.ts +66 -0
  146. package/src/services/claude.ts +1947 -0
  147. package/src/services/customCommands.ts +683 -0
  148. package/src/services/fileFreshness.ts +377 -0
  149. package/src/services/mcpClient.ts +564 -0
  150. package/src/services/mcpServerApproval.tsx +50 -0
  151. package/src/services/notifier.ts +40 -0
  152. package/src/services/oauth.ts +357 -0
  153. package/src/services/openai.ts +796 -0
  154. package/src/services/sentry.ts +3 -0
  155. package/src/services/statsig.ts +171 -0
  156. package/src/services/statsigStorage.ts +86 -0
  157. package/src/services/systemReminder.ts +406 -0
  158. package/src/services/vcr.ts +161 -0
  159. package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
  160. package/src/tools/ArchitectTool/prompt.ts +15 -0
  161. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +505 -0
  162. package/src/tools/BashTool/BashTool.tsx +270 -0
  163. package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
  164. package/src/tools/BashTool/OutputLine.tsx +48 -0
  165. package/src/tools/BashTool/prompt.ts +174 -0
  166. package/src/tools/BashTool/utils.ts +56 -0
  167. package/src/tools/FileEditTool/FileEditTool.tsx +316 -0
  168. package/src/tools/FileEditTool/prompt.ts +51 -0
  169. package/src/tools/FileEditTool/utils.ts +58 -0
  170. package/src/tools/FileReadTool/FileReadTool.tsx +371 -0
  171. package/src/tools/FileReadTool/prompt.ts +7 -0
  172. package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
  173. package/src/tools/FileWriteTool/prompt.ts +10 -0
  174. package/src/tools/GlobTool/GlobTool.tsx +119 -0
  175. package/src/tools/GlobTool/prompt.ts +8 -0
  176. package/src/tools/GrepTool/GrepTool.tsx +147 -0
  177. package/src/tools/GrepTool/prompt.ts +11 -0
  178. package/src/tools/MCPTool/MCPTool.tsx +106 -0
  179. package/src/tools/MCPTool/prompt.ts +3 -0
  180. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
  181. package/src/tools/MemoryReadTool/prompt.ts +3 -0
  182. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
  183. package/src/tools/MemoryWriteTool/prompt.ts +3 -0
  184. package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
  185. package/src/tools/MultiEditTool/prompt.ts +45 -0
  186. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
  187. package/src/tools/NotebookEditTool/prompt.ts +3 -0
  188. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +266 -0
  189. package/src/tools/NotebookReadTool/prompt.ts +3 -0
  190. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
  191. package/src/tools/StickerRequestTool/prompt.ts +19 -0
  192. package/src/tools/TaskTool/TaskTool.tsx +382 -0
  193. package/src/tools/TaskTool/constants.ts +1 -0
  194. package/src/tools/TaskTool/prompt.ts +56 -0
  195. package/src/tools/ThinkTool/ThinkTool.tsx +56 -0
  196. package/src/tools/ThinkTool/prompt.ts +12 -0
  197. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +289 -0
  198. package/src/tools/TodoWriteTool/prompt.ts +63 -0
  199. package/src/tools/lsTool/lsTool.tsx +269 -0
  200. package/src/tools/lsTool/prompt.ts +2 -0
  201. package/src/tools.ts +63 -0
  202. package/src/types/PermissionMode.ts +120 -0
  203. package/src/types/RequestContext.ts +72 -0
  204. package/src/utils/Cursor.ts +436 -0
  205. package/src/utils/PersistentShell.ts +373 -0
  206. package/src/utils/agentStorage.ts +97 -0
  207. package/src/utils/array.ts +3 -0
  208. package/src/utils/ask.tsx +98 -0
  209. package/src/utils/auth.ts +13 -0
  210. package/src/utils/autoCompactCore.ts +223 -0
  211. package/src/utils/autoUpdater.ts +318 -0
  212. package/src/utils/betas.ts +20 -0
  213. package/src/utils/browser.ts +14 -0
  214. package/src/utils/cleanup.ts +72 -0
  215. package/src/utils/commands.ts +261 -0
  216. package/src/utils/config.ts +771 -0
  217. package/src/utils/conversationRecovery.ts +54 -0
  218. package/src/utils/debugLogger.ts +1123 -0
  219. package/src/utils/diff.ts +42 -0
  220. package/src/utils/env.ts +57 -0
  221. package/src/utils/errors.ts +21 -0
  222. package/src/utils/exampleCommands.ts +108 -0
  223. package/src/utils/execFileNoThrow.ts +51 -0
  224. package/src/utils/expertChatStorage.ts +136 -0
  225. package/src/utils/file.ts +402 -0
  226. package/src/utils/fileRecoveryCore.ts +71 -0
  227. package/src/utils/format.tsx +44 -0
  228. package/src/utils/generators.ts +62 -0
  229. package/src/utils/git.ts +92 -0
  230. package/src/utils/globalLogger.ts +77 -0
  231. package/src/utils/http.ts +10 -0
  232. package/src/utils/imagePaste.ts +38 -0
  233. package/src/utils/json.ts +13 -0
  234. package/src/utils/log.ts +382 -0
  235. package/src/utils/markdown.ts +213 -0
  236. package/src/utils/messageContextManager.ts +289 -0
  237. package/src/utils/messages.tsx +938 -0
  238. package/src/utils/model.ts +836 -0
  239. package/src/utils/permissions/filesystem.ts +118 -0
  240. package/src/utils/ripgrep.ts +167 -0
  241. package/src/utils/sessionState.ts +49 -0
  242. package/src/utils/state.ts +25 -0
  243. package/src/utils/style.ts +29 -0
  244. package/src/utils/terminal.ts +49 -0
  245. package/src/utils/theme.ts +122 -0
  246. package/src/utils/thinking.ts +144 -0
  247. package/src/utils/todoStorage.ts +431 -0
  248. package/src/utils/tokens.ts +43 -0
  249. package/src/utils/toolExecutionController.ts +163 -0
  250. package/src/utils/unaryLogging.ts +26 -0
  251. package/src/utils/user.ts +37 -0
  252. package/src/utils/validate.ts +165 -0
  253. package/cli.mjs +0 -1803
@@ -0,0 +1,505 @@
1
+ import * as React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { z } from 'zod'
4
+ import { Tool, ValidationResult } from '../../Tool'
5
+ import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage'
6
+ import { getModelManager } from '../../utils/model'
7
+ import { getTheme } from '../../utils/theme'
8
+ import {
9
+ createUserMessage,
10
+ createAssistantMessage,
11
+ INTERRUPT_MESSAGE,
12
+ } from '../../utils/messages'
13
+ import { logError } from '../../utils/log'
14
+ import {
15
+ createExpertChatSession,
16
+ loadExpertChatSession,
17
+ getSessionMessages,
18
+ addMessageToSession,
19
+ } from '../../utils/expertChatStorage'
20
+ import { queryLLM } from '../../services/claude'
21
+ import { debug as debugLogger } from '../../utils/debugLogger'
22
+ import { applyMarkdown } from '../../utils/markdown'
23
+
24
+ export const inputSchema = z.strictObject({
25
+ question: z.string().describe('The question to ask the expert model'),
26
+ expert_model: z
27
+ .string()
28
+ .describe(
29
+ 'The expert model to use (e.g., gpt-5, claude-3-5-sonnet-20241022)',
30
+ ),
31
+ chat_session_id: z
32
+ .string()
33
+ .describe(
34
+ 'Chat session ID: use "new" for new session or existing session ID',
35
+ ),
36
+ })
37
+
38
+ type In = typeof inputSchema
39
+ export type Out = {
40
+ chatSessionId: string
41
+ expertModelName: string
42
+ expertAnswer: string
43
+ }
44
+
45
+ export const AskExpertModelTool = {
46
+ name: 'AskExpertModel',
47
+ async description() {
48
+ return 'Consults external AI models for specialized assistance and second opinions'
49
+ },
50
+ async prompt() {
51
+ return `Consults external AI models for specialized assistance and second opinions. Maintains conversation history through persistent sessions.
52
+
53
+ When to use AskExpertModel tool:
54
+ - User explicitly requests a specific model ("use GPT-5 to...", "ask Claude about...", "consult Kimi for...")
55
+ - User seeks second opinions or specialized model expertise
56
+ - User requests comparison between different model responses
57
+ - Complex questions requiring specific model capabilities
58
+
59
+ When NOT to use AskExpertModel tool:
60
+ - General questions that don't specify a particular model
61
+ - Tasks better suited for current model capabilities
62
+ - Simple queries not requiring external expertise
63
+
64
+ Usage notes:
65
+ 1. Use exact model names as specified by the user
66
+ 2. Sessions persist conversation context - use "new" for fresh conversations or provide existing session ID
67
+ 3. External models operate independently without access to current project context
68
+ 4. Tool validates model availability and provides alternatives if model not found
69
+
70
+ IMPORTANT: Always use the precise model name the user requested. The tool will handle model availability and provide guidance for unavailable models.`
71
+ },
72
+ isReadOnly() {
73
+ return true
74
+ },
75
+ isConcurrencySafe() {
76
+ return true
77
+ },
78
+ inputSchema,
79
+ userFacingName() {
80
+ return 'AskExpertModel'
81
+ },
82
+ async isEnabled() {
83
+ return true
84
+ },
85
+ needsPermissions(): boolean {
86
+ return false
87
+ },
88
+ async validateInput({
89
+ question,
90
+ expert_model,
91
+ chat_session_id,
92
+ }): Promise<ValidationResult> {
93
+ if (!question.trim()) {
94
+ return { result: false, message: 'Question cannot be empty' }
95
+ }
96
+
97
+ if (!expert_model.trim()) {
98
+ return { result: false, message: 'Expert model must be specified' }
99
+ }
100
+
101
+ if (!chat_session_id.trim()) {
102
+ return {
103
+ result: false,
104
+ message:
105
+ 'Chat session ID must be specified (use "new" for new session)',
106
+ }
107
+ }
108
+
109
+ // Validate that the model exists and is available
110
+ try {
111
+ const modelManager = getModelManager()
112
+ const modelResolution = modelManager.resolveModelWithInfo(expert_model)
113
+
114
+ if (!modelResolution.success) {
115
+ const availableModels = modelManager.getAllAvailableModelNames()
116
+ if (availableModels.length > 0) {
117
+ return {
118
+ result: false,
119
+ message: `Model '${expert_model}' is not configured. Available models: ${availableModels.join(', ')}. Check if any available model closely matches the user's request (e.g., 'kimi' matches 'kimi-k2-0711-preview'). If there's a strong match, auto retry using the correct model name. If no close match exists, inform the user that '${expert_model}' needs to be configured using /model command.`,
120
+ }
121
+ } else {
122
+ return {
123
+ result: false,
124
+ message: `Model '${expert_model}' not found and no models are currently configured in the system. Inform the user that models need to be configured first using the /model command.`,
125
+ }
126
+ }
127
+ }
128
+ } catch (error) {
129
+ logError('Model validation error in AskExpertModelTool', error)
130
+ return {
131
+ result: false,
132
+ message: `Failed to validate expert model '${expert_model}'. Please check your model configuration.`,
133
+ }
134
+ }
135
+
136
+ return { result: true }
137
+ },
138
+
139
+ renderToolUseMessage(
140
+ { question, expert_model, chat_session_id },
141
+ { verbose },
142
+ ) {
143
+ if (!question || !expert_model) return null
144
+ const isNewSession = chat_session_id === 'new'
145
+ const sessionDisplay = isNewSession ? 'new session' : chat_session_id
146
+
147
+ if (verbose) {
148
+ const theme = getTheme()
149
+ return (
150
+ <Box flexDirection="column">
151
+ <Text bold color={theme.text}>{expert_model}, {sessionDisplay}</Text>
152
+ <Box
153
+ borderStyle="single"
154
+ borderColor="green"
155
+ paddingX={1}
156
+ paddingY={0}
157
+ marginTop={1}
158
+ >
159
+ <Text color={theme.text}>
160
+ {applyMarkdown(question)}
161
+ </Text>
162
+ </Box>
163
+ </Box>
164
+ )
165
+ }
166
+ return `${expert_model}, ${sessionDisplay}`
167
+ },
168
+
169
+ renderToolResultMessage(content, { verbose }) {
170
+ const theme = getTheme()
171
+
172
+ if (typeof content === 'object' && content && 'expertAnswer' in content) {
173
+ const expertResult = content as Out
174
+ const isError = expertResult.expertAnswer.startsWith('❌')
175
+ const isInterrupted = expertResult.chatSessionId === 'interrupted'
176
+
177
+ if (isInterrupted) {
178
+ return (
179
+ <Box flexDirection="row">
180
+ <Text>&nbsp;&nbsp;⎿ &nbsp;</Text>
181
+ <Text color={theme.error}>[Expert consultation interrupted]</Text>
182
+ </Box>
183
+ )
184
+ }
185
+
186
+ const answerText = verbose
187
+ ? expertResult.expertAnswer.trim()
188
+ : expertResult.expertAnswer.length > 120
189
+ ? expertResult.expertAnswer.substring(0, 120) + '...'
190
+ : expertResult.expertAnswer.trim()
191
+
192
+ return (
193
+ <Box flexDirection="column">
194
+ <Box
195
+ borderStyle="single"
196
+ borderColor="green"
197
+ paddingX={1}
198
+ paddingY={0}
199
+ marginTop={1}
200
+ >
201
+ <Text color={isError ? theme.error : theme.text}>
202
+ {isError ? answerText : applyMarkdown(answerText)}
203
+ </Text>
204
+ </Box>
205
+ </Box>
206
+ )
207
+ }
208
+
209
+ return (
210
+ <Box flexDirection="row">
211
+ <Text>&nbsp;&nbsp;⎿ &nbsp;</Text>
212
+ <Text color={theme.secondaryText}>Expert consultation completed</Text>
213
+ </Box>
214
+ )
215
+ },
216
+
217
+ renderResultForAssistant(output: Out): string {
218
+ return `[Expert consultation completed]
219
+ Expert Model: ${output.expertModelName}
220
+ Session ID: ${output.chatSessionId}
221
+ To continue this conversation with context preservation, use this Session ID in your next AskExpertModel call to maintain the full conversation history and context.
222
+
223
+ ${output.expertAnswer}`
224
+ },
225
+
226
+ renderToolUseRejectedMessage() {
227
+ return <FallbackToolUseRejectedMessage />
228
+ },
229
+
230
+ async *call(
231
+ { question, expert_model, chat_session_id },
232
+ { abortController, readFileTimestamps },
233
+ ) {
234
+ const expertModel = expert_model
235
+
236
+ let sessionId: string
237
+ let isInterrupted = false
238
+
239
+ // Set up abort listener (following TaskTool pattern)
240
+ const abortListener = () => {
241
+ isInterrupted = true
242
+ }
243
+ abortController.signal.addEventListener('abort', abortListener)
244
+
245
+ try {
246
+ // Initial abort check
247
+ if (abortController.signal.aborted) {
248
+ return yield* this.handleInterrupt()
249
+ }
250
+ // Session management with error handling
251
+ if (chat_session_id === 'new') {
252
+ try {
253
+ const session = createExpertChatSession(expertModel)
254
+ sessionId = session.sessionId
255
+ } catch (error) {
256
+ logError('Failed to create new expert chat session', error)
257
+ throw new Error('Failed to create new chat session')
258
+ }
259
+ } else {
260
+ sessionId = chat_session_id
261
+ try {
262
+ const session = loadExpertChatSession(sessionId)
263
+ if (!session) {
264
+ // Session doesn't exist, create new one
265
+ const newSession = createExpertChatSession(expertModel)
266
+ sessionId = newSession.sessionId
267
+ }
268
+ } catch (error) {
269
+ logError('Failed to load expert chat session', error)
270
+ // Fallback: create new session
271
+ try {
272
+ const newSession = createExpertChatSession(expertModel)
273
+ sessionId = newSession.sessionId
274
+ } catch (createError) {
275
+ logError(
276
+ 'Failed to create fallback expert chat session',
277
+ createError,
278
+ )
279
+ throw new Error('Unable to create or load chat session')
280
+ }
281
+ }
282
+ }
283
+
284
+ // Check for cancellation before loading history
285
+ if (isInterrupted || abortController.signal.aborted) {
286
+ return yield* this.handleInterrupt()
287
+ }
288
+
289
+ // Load history and prepare messages with error handling
290
+ let historyMessages: Array<{ role: string; content: string }>
291
+ try {
292
+ historyMessages = getSessionMessages(sessionId)
293
+ } catch (error) {
294
+ logError('Failed to load session messages', error)
295
+ historyMessages = [] // Fallback to empty history
296
+ }
297
+
298
+ const messages = [...historyMessages, { role: 'user', content: question }]
299
+
300
+ let systemMessages
301
+ try {
302
+ systemMessages = messages.map(msg =>
303
+ msg.role === 'user'
304
+ ? createUserMessage(msg.content)
305
+ : createAssistantMessage(msg.content),
306
+ )
307
+ } catch (error) {
308
+ logError('Failed to create system messages', error)
309
+ throw new Error('Failed to prepare conversation messages')
310
+ }
311
+
312
+ // Check for cancellation before model call
313
+ if (isInterrupted || abortController.signal.aborted) {
314
+ return yield* this.handleInterrupt()
315
+ }
316
+
317
+ // Call model with comprehensive error handling and timeout
318
+ let response
319
+ try {
320
+ // Debug: Log the model we're trying to use (using global debug logger)
321
+ const modelManager = getModelManager()
322
+ const modelResolution = modelManager.resolveModelWithInfo(expertModel)
323
+
324
+ debugLogger.api('EXPERT_MODEL_RESOLUTION', {
325
+ requestedModel: expertModel,
326
+ success: modelResolution.success,
327
+ profileName: modelResolution.profile?.name,
328
+ profileModelName: modelResolution.profile?.modelName,
329
+ provider: modelResolution.profile?.provider,
330
+ isActive: modelResolution.profile?.isActive,
331
+ error: modelResolution.error,
332
+ })
333
+
334
+ // Create a timeout promise to prevent hanging
335
+ const timeoutMs = 60000 // 60 seconds timeout
336
+ const timeoutPromise = new Promise((_, reject) => {
337
+ setTimeout(() => {
338
+ reject(new Error(`Expert model query timed out after ${timeoutMs/1000}s`))
339
+ }, timeoutMs)
340
+ })
341
+
342
+ // Race between the query and timeout
343
+ response = await Promise.race([
344
+ queryLLM(
345
+ systemMessages,
346
+ [], // no system prompt - let expert model use its default behavior
347
+ 0, // no thinking tokens needed
348
+ [], // no tools needed
349
+ abortController.signal,
350
+ {
351
+ safeMode: false,
352
+ model: expertModel,
353
+ prependCLISysprompt: false, // KEY: avoid injecting CLI context
354
+ },
355
+ ),
356
+ timeoutPromise
357
+ ])
358
+ } catch (error: any) {
359
+ logError('Expert model query failed', error)
360
+
361
+ // Check for specific error types
362
+ if (
363
+ error.name === 'AbortError' ||
364
+ abortController.signal?.aborted ||
365
+ isInterrupted
366
+ ) {
367
+ return yield* this.handleInterrupt()
368
+ }
369
+
370
+ if (error.message?.includes('timed out')) {
371
+ throw new Error(
372
+ `Expert model '${expertModel}' timed out. This often happens with slower APIs. Try again or use a different model.`,
373
+ )
374
+ }
375
+
376
+ if (error.message?.includes('rate limit')) {
377
+ throw new Error(
378
+ 'Rate limit exceeded for expert model. Please try again later.',
379
+ )
380
+ }
381
+
382
+ if (error.message?.includes('invalid api key')) {
383
+ throw new Error(
384
+ 'Invalid API key for expert model. Please check your configuration.',
385
+ )
386
+ }
387
+
388
+ if (
389
+ error.message?.includes('model not found') ||
390
+ error.message?.includes('Failed to resolve model')
391
+ ) {
392
+ // Provide helpful model guidance in runtime errors too
393
+ try {
394
+ const modelManager = getModelManager()
395
+ const availableModels = modelManager.getAllAvailableModelNames()
396
+ if (availableModels.length > 0) {
397
+ throw new Error(
398
+ `Model '${expertModel}' is not configured. Available models: ${availableModels.join(', ')}. Check if any available model closely matches the user's request (e.g., 'kimi' matches 'kimi-k2-0711-preview'). If there's a strong match, auto retry using the correct model name. If no close match exists, inform the user that '${expertModel}' needs to be configured using /model command.`,
399
+ )
400
+ } else {
401
+ throw new Error(
402
+ `Model '${expertModel}' not found and no models are currently configured in the system. Inform the user that models need to be configured first using the /model command.`,
403
+ )
404
+ }
405
+ } catch (modelError) {
406
+ // If we can't get model list, fall back to simple error
407
+ throw new Error(
408
+ `Model '${expertModel}' not found. Please check model configuration or inform user about the issue.`,
409
+ )
410
+ }
411
+ }
412
+
413
+ // Generic fallback
414
+ throw new Error(
415
+ `Expert model query failed: ${error.message || 'Unknown error'}`,
416
+ )
417
+ }
418
+
419
+ // Extract answer with error handling
420
+ let expertAnswer: string
421
+ try {
422
+ if (!response?.message?.content) {
423
+ throw new Error('No content in expert response')
424
+ }
425
+
426
+ expertAnswer = response.message.content
427
+ .filter(block => block.type === 'text')
428
+ .map(block => (block as any).text)
429
+ .join('\n')
430
+
431
+ if (!expertAnswer.trim()) {
432
+ throw new Error('Expert response was empty')
433
+ }
434
+ } catch (error) {
435
+ logError('Failed to extract expert answer', error)
436
+ throw new Error('Failed to process expert response')
437
+ }
438
+
439
+ // Save conversation with error handling
440
+ try {
441
+ addMessageToSession(sessionId, 'user', question)
442
+ addMessageToSession(sessionId, 'assistant', expertAnswer)
443
+ } catch (error) {
444
+ logError('Failed to save conversation to session', error)
445
+ // Don't throw here - we got a valid response, saving is non-critical
446
+ }
447
+
448
+ const result: Out = {
449
+ chatSessionId: sessionId,
450
+ expertModelName: expertModel,
451
+ expertAnswer: expertAnswer,
452
+ }
453
+
454
+ yield {
455
+ type: 'result',
456
+ data: result,
457
+ resultForAssistant: this.renderResultForAssistant(result),
458
+ }
459
+ } catch (error: any) {
460
+ // Check if error is due to cancellation
461
+ if (
462
+ error.name === 'AbortError' ||
463
+ abortController.signal?.aborted ||
464
+ isInterrupted
465
+ ) {
466
+ return yield* this.handleInterrupt()
467
+ }
468
+
469
+ logError('AskExpertModelTool execution failed', error)
470
+
471
+ // Ensure we have a valid sessionId for error response
472
+ const errorSessionId = sessionId || 'error-session'
473
+
474
+ const errorMessage =
475
+ error.message || 'Expert consultation failed with unknown error'
476
+ const result: Out = {
477
+ chatSessionId: errorSessionId,
478
+ expertModelName: expertModel,
479
+ expertAnswer: `❌ ${errorMessage}`,
480
+ }
481
+
482
+ yield {
483
+ type: 'result',
484
+ data: result,
485
+ resultForAssistant: this.renderResultForAssistant(result),
486
+ }
487
+ } finally {
488
+ // Clean up event listener
489
+ abortController.signal.removeEventListener('abort', abortListener)
490
+ }
491
+ },
492
+
493
+ // Unified interrupt handling method (following TaskTool pattern)
494
+ async *handleInterrupt() {
495
+ yield {
496
+ type: 'result',
497
+ data: {
498
+ chatSessionId: 'interrupted',
499
+ expertModelName: 'cancelled',
500
+ expertAnswer: INTERRUPT_MESSAGE,
501
+ },
502
+ resultForAssistant: INTERRUPT_MESSAGE,
503
+ }
504
+ },
505
+ }