@shareai-lab/kode 1.0.70 → 1.0.73

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 (278) hide show
  1. package/README.md +342 -75
  2. package/README.zh-CN.md +292 -0
  3. package/cli.js +62 -0
  4. package/package.json +49 -25
  5. package/scripts/postinstall.js +56 -0
  6. package/src/ProjectOnboarding.tsx +198 -0
  7. package/src/Tool.ts +82 -0
  8. package/src/commands/agents.tsx +3401 -0
  9. package/src/commands/approvedTools.ts +53 -0
  10. package/src/commands/bug.tsx +20 -0
  11. package/src/commands/clear.ts +43 -0
  12. package/src/commands/compact.ts +120 -0
  13. package/src/commands/config.tsx +19 -0
  14. package/src/commands/cost.ts +18 -0
  15. package/src/commands/ctx_viz.ts +209 -0
  16. package/src/commands/doctor.ts +24 -0
  17. package/src/commands/help.tsx +19 -0
  18. package/src/commands/init.ts +37 -0
  19. package/src/commands/listen.ts +42 -0
  20. package/src/commands/login.tsx +51 -0
  21. package/src/commands/logout.tsx +40 -0
  22. package/src/commands/mcp.ts +41 -0
  23. package/src/commands/model.tsx +40 -0
  24. package/src/commands/modelstatus.tsx +20 -0
  25. package/src/commands/onboarding.tsx +34 -0
  26. package/src/commands/pr_comments.ts +59 -0
  27. package/src/commands/refreshCommands.ts +54 -0
  28. package/src/commands/release-notes.ts +34 -0
  29. package/src/commands/resume.tsx +31 -0
  30. package/src/commands/review.ts +49 -0
  31. package/src/commands/terminalSetup.ts +221 -0
  32. package/src/commands.ts +139 -0
  33. package/src/components/ApproveApiKey.tsx +93 -0
  34. package/src/components/AsciiLogo.tsx +13 -0
  35. package/src/components/AutoUpdater.tsx +148 -0
  36. package/src/components/Bug.tsx +367 -0
  37. package/src/components/Config.tsx +293 -0
  38. package/src/components/ConsoleOAuthFlow.tsx +327 -0
  39. package/src/components/Cost.tsx +23 -0
  40. package/src/components/CostThresholdDialog.tsx +46 -0
  41. package/src/components/CustomSelect/option-map.ts +42 -0
  42. package/src/components/CustomSelect/select-option.tsx +78 -0
  43. package/src/components/CustomSelect/select.tsx +152 -0
  44. package/src/components/CustomSelect/theme.ts +45 -0
  45. package/src/components/CustomSelect/use-select-state.ts +414 -0
  46. package/src/components/CustomSelect/use-select.ts +35 -0
  47. package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
  48. package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
  49. package/src/components/Help.tsx +215 -0
  50. package/src/components/HighlightedCode.tsx +33 -0
  51. package/src/components/InvalidConfigDialog.tsx +113 -0
  52. package/src/components/Link.tsx +32 -0
  53. package/src/components/LogSelector.tsx +86 -0
  54. package/src/components/Logo.tsx +145 -0
  55. package/src/components/MCPServerApprovalDialog.tsx +100 -0
  56. package/src/components/MCPServerDialogCopy.tsx +25 -0
  57. package/src/components/MCPServerMultiselectDialog.tsx +109 -0
  58. package/src/components/Message.tsx +221 -0
  59. package/src/components/MessageResponse.tsx +15 -0
  60. package/src/components/MessageSelector.tsx +211 -0
  61. package/src/components/ModeIndicator.tsx +88 -0
  62. package/src/components/ModelConfig.tsx +301 -0
  63. package/src/components/ModelListManager.tsx +227 -0
  64. package/src/components/ModelSelector.tsx +3386 -0
  65. package/src/components/ModelStatusDisplay.tsx +230 -0
  66. package/src/components/Onboarding.tsx +274 -0
  67. package/src/components/PressEnterToContinue.tsx +11 -0
  68. package/src/components/PromptInput.tsx +740 -0
  69. package/src/components/SentryErrorBoundary.ts +33 -0
  70. package/src/components/Spinner.tsx +129 -0
  71. package/src/components/StickerRequestForm.tsx +16 -0
  72. package/src/components/StructuredDiff.tsx +191 -0
  73. package/src/components/TextInput.tsx +259 -0
  74. package/src/components/TodoItem.tsx +11 -0
  75. package/src/components/TokenWarning.tsx +31 -0
  76. package/src/components/ToolUseLoader.tsx +40 -0
  77. package/src/components/TrustDialog.tsx +106 -0
  78. package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
  79. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
  80. package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
  81. package/src/components/binary-feedback/utils.ts +220 -0
  82. package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
  83. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +49 -0
  84. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
  85. package/src/components/messages/AssistantTextMessage.tsx +144 -0
  86. package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
  87. package/src/components/messages/AssistantToolUseMessage.tsx +133 -0
  88. package/src/components/messages/TaskProgressMessage.tsx +32 -0
  89. package/src/components/messages/TaskToolMessage.tsx +58 -0
  90. package/src/components/messages/UserBashInputMessage.tsx +28 -0
  91. package/src/components/messages/UserCommandMessage.tsx +30 -0
  92. package/src/components/messages/UserKodingInputMessage.tsx +28 -0
  93. package/src/components/messages/UserPromptMessage.tsx +35 -0
  94. package/src/components/messages/UserTextMessage.tsx +39 -0
  95. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
  96. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
  97. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
  98. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
  99. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
  100. package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
  101. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
  102. package/src/components/permissions/FallbackPermissionRequest.tsx +153 -0
  103. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
  104. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +77 -0
  105. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
  106. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +83 -0
  107. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +240 -0
  108. package/src/components/permissions/PermissionRequest.tsx +101 -0
  109. package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
  110. package/src/components/permissions/hooks.ts +44 -0
  111. package/src/components/permissions/toolUseOptions.ts +59 -0
  112. package/src/components/permissions/utils.ts +23 -0
  113. package/src/constants/betas.ts +5 -0
  114. package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
  115. package/src/constants/figures.ts +4 -0
  116. package/src/constants/keys.ts +3 -0
  117. package/src/constants/macros.ts +8 -0
  118. package/src/constants/modelCapabilities.ts +179 -0
  119. package/src/constants/models.ts +1025 -0
  120. package/src/constants/oauth.ts +18 -0
  121. package/src/constants/product.ts +17 -0
  122. package/src/constants/prompts.ts +177 -0
  123. package/src/constants/releaseNotes.ts +7 -0
  124. package/src/context/PermissionContext.tsx +149 -0
  125. package/src/context.ts +278 -0
  126. package/src/cost-tracker.ts +84 -0
  127. package/src/entrypoints/cli.tsx +1518 -0
  128. package/src/entrypoints/mcp.ts +176 -0
  129. package/src/history.ts +25 -0
  130. package/src/hooks/useApiKeyVerification.ts +59 -0
  131. package/src/hooks/useArrowKeyHistory.ts +55 -0
  132. package/src/hooks/useCanUseTool.ts +138 -0
  133. package/src/hooks/useCancelRequest.ts +39 -0
  134. package/src/hooks/useDoublePress.ts +42 -0
  135. package/src/hooks/useExitOnCtrlCD.ts +31 -0
  136. package/src/hooks/useInterval.ts +25 -0
  137. package/src/hooks/useLogMessages.ts +16 -0
  138. package/src/hooks/useLogStartupTime.ts +12 -0
  139. package/src/hooks/useNotifyAfterTimeout.ts +65 -0
  140. package/src/hooks/usePermissionRequestLogging.ts +44 -0
  141. package/src/hooks/useTerminalSize.ts +49 -0
  142. package/src/hooks/useTextInput.ts +318 -0
  143. package/src/hooks/useUnifiedCompletion.ts +1404 -0
  144. package/src/messages.ts +38 -0
  145. package/src/permissions.ts +268 -0
  146. package/src/query.ts +707 -0
  147. package/src/screens/ConfigureNpmPrefix.tsx +197 -0
  148. package/src/screens/Doctor.tsx +219 -0
  149. package/src/screens/LogList.tsx +68 -0
  150. package/src/screens/REPL.tsx +798 -0
  151. package/src/screens/ResumeConversation.tsx +68 -0
  152. package/src/services/adapters/base.ts +38 -0
  153. package/src/services/adapters/chatCompletions.ts +90 -0
  154. package/src/services/adapters/responsesAPI.ts +170 -0
  155. package/src/services/browserMocks.ts +66 -0
  156. package/src/services/claude.ts +2083 -0
  157. package/src/services/customCommands.ts +704 -0
  158. package/src/services/fileFreshness.ts +377 -0
  159. package/src/services/gpt5ConnectionTest.ts +340 -0
  160. package/src/services/mcpClient.ts +564 -0
  161. package/src/services/mcpServerApproval.tsx +50 -0
  162. package/src/services/mentionProcessor.ts +273 -0
  163. package/src/services/modelAdapterFactory.ts +69 -0
  164. package/src/services/notifier.ts +40 -0
  165. package/src/services/oauth.ts +357 -0
  166. package/src/services/openai.ts +1305 -0
  167. package/src/services/responseStateManager.ts +90 -0
  168. package/src/services/sentry.ts +3 -0
  169. package/src/services/statsig.ts +171 -0
  170. package/src/services/statsigStorage.ts +86 -0
  171. package/src/services/systemReminder.ts +507 -0
  172. package/src/services/vcr.ts +161 -0
  173. package/src/test/testAdapters.ts +96 -0
  174. package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
  175. package/src/tools/ArchitectTool/prompt.ts +15 -0
  176. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +569 -0
  177. package/src/tools/BashTool/BashTool.tsx +243 -0
  178. package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
  179. package/src/tools/BashTool/OutputLine.tsx +49 -0
  180. package/src/tools/BashTool/prompt.ts +174 -0
  181. package/src/tools/BashTool/utils.ts +56 -0
  182. package/src/tools/FileEditTool/FileEditTool.tsx +315 -0
  183. package/src/tools/FileEditTool/prompt.ts +51 -0
  184. package/src/tools/FileEditTool/utils.ts +58 -0
  185. package/src/tools/FileReadTool/FileReadTool.tsx +404 -0
  186. package/src/tools/FileReadTool/prompt.ts +7 -0
  187. package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
  188. package/src/tools/FileWriteTool/prompt.ts +10 -0
  189. package/src/tools/GlobTool/GlobTool.tsx +119 -0
  190. package/src/tools/GlobTool/prompt.ts +8 -0
  191. package/src/tools/GrepTool/GrepTool.tsx +147 -0
  192. package/src/tools/GrepTool/prompt.ts +11 -0
  193. package/src/tools/MCPTool/MCPTool.tsx +107 -0
  194. package/src/tools/MCPTool/prompt.ts +3 -0
  195. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
  196. package/src/tools/MemoryReadTool/prompt.ts +3 -0
  197. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
  198. package/src/tools/MemoryWriteTool/prompt.ts +3 -0
  199. package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
  200. package/src/tools/MultiEditTool/prompt.ts +45 -0
  201. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
  202. package/src/tools/NotebookEditTool/prompt.ts +3 -0
  203. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +258 -0
  204. package/src/tools/NotebookReadTool/prompt.ts +3 -0
  205. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
  206. package/src/tools/StickerRequestTool/prompt.ts +19 -0
  207. package/src/tools/TaskTool/TaskTool.tsx +466 -0
  208. package/src/tools/TaskTool/constants.ts +1 -0
  209. package/src/tools/TaskTool/prompt.ts +92 -0
  210. package/src/tools/ThinkTool/ThinkTool.tsx +54 -0
  211. package/src/tools/ThinkTool/prompt.ts +12 -0
  212. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +290 -0
  213. package/src/tools/TodoWriteTool/prompt.ts +63 -0
  214. package/src/tools/lsTool/lsTool.tsx +272 -0
  215. package/src/tools/lsTool/prompt.ts +2 -0
  216. package/src/tools.ts +63 -0
  217. package/src/types/PermissionMode.ts +120 -0
  218. package/src/types/RequestContext.ts +72 -0
  219. package/src/types/conversation.ts +51 -0
  220. package/src/types/logs.ts +58 -0
  221. package/src/types/modelCapabilities.ts +64 -0
  222. package/src/types/notebook.ts +87 -0
  223. package/src/utils/Cursor.ts +436 -0
  224. package/src/utils/PersistentShell.ts +373 -0
  225. package/src/utils/advancedFuzzyMatcher.ts +290 -0
  226. package/src/utils/agentLoader.ts +284 -0
  227. package/src/utils/agentStorage.ts +97 -0
  228. package/src/utils/array.ts +3 -0
  229. package/src/utils/ask.tsx +99 -0
  230. package/src/utils/auth.ts +13 -0
  231. package/src/utils/autoCompactCore.ts +223 -0
  232. package/src/utils/autoUpdater.ts +318 -0
  233. package/src/utils/betas.ts +20 -0
  234. package/src/utils/browser.ts +14 -0
  235. package/src/utils/cleanup.ts +72 -0
  236. package/src/utils/commands.ts +261 -0
  237. package/src/utils/commonUnixCommands.ts +161 -0
  238. package/src/utils/config.ts +942 -0
  239. package/src/utils/conversationRecovery.ts +55 -0
  240. package/src/utils/debugLogger.ts +1123 -0
  241. package/src/utils/diff.ts +42 -0
  242. package/src/utils/env.ts +57 -0
  243. package/src/utils/errors.ts +21 -0
  244. package/src/utils/exampleCommands.ts +109 -0
  245. package/src/utils/execFileNoThrow.ts +51 -0
  246. package/src/utils/expertChatStorage.ts +136 -0
  247. package/src/utils/file.ts +402 -0
  248. package/src/utils/fileRecoveryCore.ts +71 -0
  249. package/src/utils/format.tsx +44 -0
  250. package/src/utils/fuzzyMatcher.ts +328 -0
  251. package/src/utils/generators.ts +62 -0
  252. package/src/utils/git.ts +92 -0
  253. package/src/utils/globalLogger.ts +77 -0
  254. package/src/utils/http.ts +10 -0
  255. package/src/utils/imagePaste.ts +38 -0
  256. package/src/utils/json.ts +13 -0
  257. package/src/utils/log.ts +382 -0
  258. package/src/utils/markdown.ts +213 -0
  259. package/src/utils/messageContextManager.ts +289 -0
  260. package/src/utils/messages.tsx +939 -0
  261. package/src/utils/model.ts +836 -0
  262. package/src/utils/permissions/filesystem.ts +118 -0
  263. package/src/utils/responseState.ts +23 -0
  264. package/src/utils/ripgrep.ts +167 -0
  265. package/src/utils/secureFile.ts +559 -0
  266. package/src/utils/sessionState.ts +49 -0
  267. package/src/utils/state.ts +25 -0
  268. package/src/utils/style.ts +29 -0
  269. package/src/utils/terminal.ts +50 -0
  270. package/src/utils/theme.ts +133 -0
  271. package/src/utils/thinking.ts +144 -0
  272. package/src/utils/todoStorage.ts +431 -0
  273. package/src/utils/tokens.ts +43 -0
  274. package/src/utils/toolExecutionController.ts +163 -0
  275. package/src/utils/unaryLogging.ts +26 -0
  276. package/src/utils/user.ts +37 -0
  277. package/src/utils/validate.ts +165 -0
  278. package/cli.mjs +0 -1803
@@ -0,0 +1,569 @@
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(
26
+ 'COMPLETE SELF-CONTAINED QUESTION: Must include full background context, relevant details, and a clear independent question. The expert model will receive ONLY this content with no access to previous conversation or external context. Structure as: 1) Background/Context 2) Specific situation/problem 3) Clear question. Ensure the expert can fully understand and respond without needing additional information.'
27
+ ),
28
+ expert_model: z
29
+ .string()
30
+ .describe(
31
+ 'The expert model to use (e.g., gpt-5, claude-3-5-sonnet-20241022)',
32
+ ),
33
+ chat_session_id: z
34
+ .string()
35
+ .describe(
36
+ 'Chat session ID: use "new" for new session or existing session ID',
37
+ ),
38
+ })
39
+
40
+ type In = typeof inputSchema
41
+ export type Out = {
42
+ chatSessionId: string
43
+ expertModelName: string
44
+ expertAnswer: string
45
+ }
46
+
47
+ export const AskExpertModelTool = {
48
+ name: 'AskExpertModel',
49
+ async description() {
50
+ return "Consult external AI models for expert opinions and analysis"
51
+ },
52
+ async prompt() {
53
+ return `Ask a question to a specific external AI model for expert analysis.
54
+
55
+ This tool allows you to consult different AI models for their unique perspectives and expertise.
56
+
57
+ CRITICAL REQUIREMENT FOR QUESTION PARAMETER:
58
+ The question MUST be completely self-contained and include:
59
+ 1. FULL BACKGROUND CONTEXT - All relevant information the expert needs
60
+ 2. SPECIFIC SITUATION - Clear description of the current scenario/problem
61
+ 3. INDEPENDENT QUESTION - What exactly you want the expert to analyze/answer
62
+
63
+ The expert model receives ONLY your question content with NO access to:
64
+ - Previous conversation history (unless using existing session)
65
+ - Current codebase or file context
66
+ - User's current task or project details
67
+
68
+ IMPORTANT: This tool is for asking questions to models, not for task execution.
69
+ - Use when you need a specific model's opinion or analysis
70
+ - Use when you want to compare different models' responses
71
+ - Use the @ask-[model] format when available
72
+
73
+ The expert_model parameter accepts:
74
+ - OpenAI: gpt-4, gpt-5, o1-preview
75
+ - Anthropic: claude-3-5-sonnet, claude-3-opus
76
+ - Others: kimi, gemini-pro, mixtral
77
+
78
+ Example of well-structured question:
79
+ "Background: I'm working on a React TypeScript application with performance issues. The app renders a large list of 10,000 items using a simple map() function, causing UI freezing.
80
+
81
+ Current situation: Users report 3-5 second delays when scrolling through the list. The component re-renders the entire list on every state change.
82
+
83
+ Question: What are the most effective React optimization techniques for handling large lists, and how should I prioritize implementing virtualization vs memoization vs other approaches?"`
84
+ },
85
+ isReadOnly() {
86
+ return true
87
+ },
88
+ isConcurrencySafe() {
89
+ return true
90
+ },
91
+ inputSchema,
92
+ userFacingName() {
93
+ return 'AskExpertModel'
94
+ },
95
+ async isEnabled() {
96
+ return true
97
+ },
98
+ needsPermissions(): boolean {
99
+ return false
100
+ },
101
+ async validateInput({
102
+ question,
103
+ expert_model,
104
+ chat_session_id,
105
+ }, context?: any): Promise<ValidationResult> {
106
+ if (!question.trim()) {
107
+ return { result: false, message: 'Question cannot be empty' }
108
+ }
109
+
110
+
111
+ if (!expert_model.trim()) {
112
+ return { result: false, message: 'Expert model must be specified' }
113
+ }
114
+
115
+ if (!chat_session_id.trim()) {
116
+ return {
117
+ result: false,
118
+ message:
119
+ 'Chat session ID must be specified (use "new" for new session)',
120
+ }
121
+ }
122
+
123
+ // Check if trying to consult the same model we're currently running
124
+ try {
125
+ const modelManager = getModelManager()
126
+
127
+ // Get current model based on context
128
+ let currentModel: string
129
+ if (context?.agentId && context?.options?.model) {
130
+ // In subagent context (Task tool)
131
+ currentModel = context.options.model
132
+ } else {
133
+ // In main agent context or after model switch
134
+ currentModel = modelManager.getModelName('main') || ''
135
+ }
136
+
137
+ // Normalize model names for comparison
138
+ const normalizedExpert = expert_model.toLowerCase().replace(/[^a-z0-9]/g, '')
139
+ const normalizedCurrent = currentModel.toLowerCase().replace(/[^a-z0-9]/g, '')
140
+
141
+ if (normalizedExpert === normalizedCurrent) {
142
+ return {
143
+ result: false,
144
+ message: `You are already running as ${currentModel}. Consulting the same model would be redundant. Please choose a different model or handle the task directly.`
145
+ }
146
+ }
147
+ } catch (e) {
148
+ // If we can't determine current model, allow the request
149
+ debugLogger('AskExpertModel', 'Could not determine current model:', e)
150
+ }
151
+
152
+ // Validate that the model exists and is available
153
+ try {
154
+ const modelManager = getModelManager()
155
+ const modelResolution = modelManager.resolveModelWithInfo(expert_model)
156
+
157
+ if (!modelResolution.success) {
158
+ const availableModels = modelManager.getAllAvailableModelNames()
159
+ if (availableModels.length > 0) {
160
+ return {
161
+ result: false,
162
+ 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.`,
163
+ }
164
+ } else {
165
+ return {
166
+ result: false,
167
+ 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.`,
168
+ }
169
+ }
170
+ }
171
+ } catch (error) {
172
+ logError('Model validation error in AskExpertModelTool', error)
173
+ return {
174
+ result: false,
175
+ message: `Failed to validate expert model '${expert_model}'. Please check your model configuration.`,
176
+ }
177
+ }
178
+
179
+ return { result: true }
180
+ },
181
+
182
+ renderToolUseMessage(
183
+ { question, expert_model, chat_session_id },
184
+ { verbose },
185
+ ) {
186
+ if (!question || !expert_model) return null
187
+ const isNewSession = chat_session_id === 'new'
188
+ const sessionDisplay = isNewSession ? 'new session' : `session ${chat_session_id.substring(0, 5)}...`
189
+ const theme = getTheme()
190
+
191
+ if (verbose) {
192
+ return (
193
+ <Box flexDirection="column">
194
+ <Text bold color="yellow">{expert_model}</Text>
195
+ <Text color={theme.secondaryText}>{sessionDisplay}</Text>
196
+ <Box marginTop={1}>
197
+ <Text color={theme.text}>
198
+ {question.length > 300 ? question.substring(0, 300) + '...' : question}
199
+ </Text>
200
+ </Box>
201
+ </Box>
202
+ )
203
+ }
204
+ return (
205
+ <Box flexDirection="column">
206
+ <Text bold color="yellow">{expert_model} </Text>
207
+ <Text color={theme.secondaryText} dimColor>({sessionDisplay})</Text>
208
+ </Box>
209
+ )
210
+ },
211
+
212
+ renderToolResultMessage(content) {
213
+ const verbose = true // Show more content
214
+ const theme = getTheme()
215
+
216
+ if (typeof content === 'object' && content && 'expertAnswer' in content) {
217
+ const expertResult = content as Out
218
+ const isError = expertResult.expertAnswer.startsWith('Error') || expertResult.expertAnswer.includes('failed')
219
+ const isInterrupted = expertResult.chatSessionId === 'interrupted'
220
+
221
+ if (isInterrupted) {
222
+ return (
223
+ <Box flexDirection="row">
224
+ <Text color={theme.secondaryText}>Consultation interrupted</Text>
225
+ </Box>
226
+ )
227
+ }
228
+
229
+ const answerText = verbose
230
+ ? expertResult.expertAnswer.trim()
231
+ : expertResult.expertAnswer.length > 500
232
+ ? expertResult.expertAnswer.substring(0, 500) + '...'
233
+ : expertResult.expertAnswer.trim()
234
+
235
+ if (isError) {
236
+ return (
237
+ <Box flexDirection="column">
238
+ <Text color="red">{answerText}</Text>
239
+ </Box>
240
+ )
241
+ }
242
+
243
+ return (
244
+ <Box flexDirection="column">
245
+ <Text bold color={theme.text}>Response from {expertResult.expertModelName}:</Text>
246
+ <Box marginTop={1}>
247
+ <Text color={theme.text}>
248
+ {applyMarkdown(answerText)}
249
+ </Text>
250
+ </Box>
251
+ <Box marginTop={1}>
252
+ <Text color={theme.secondaryText} dimColor>
253
+ Session: {expertResult.chatSessionId.substring(0, 8)}
254
+ </Text>
255
+ </Box>
256
+ </Box>
257
+ )
258
+ }
259
+
260
+ return (
261
+ <Box flexDirection="row">
262
+ <Text color={theme.secondaryText}>Consultation completed</Text>
263
+ </Box>
264
+ )
265
+ },
266
+
267
+ renderResultForAssistant(output: Out): string {
268
+ return `[Expert consultation completed]
269
+ Expert Model: ${output.expertModelName}
270
+ Session ID: ${output.chatSessionId}
271
+ To continue this conversation with context preservation, use this Session ID in your next AskExpertModel call to maintain the full conversation history and context.
272
+
273
+ ${output.expertAnswer}`
274
+ },
275
+
276
+ renderToolUseRejectedMessage() {
277
+ return <FallbackToolUseRejectedMessage />
278
+ },
279
+
280
+ async *call(
281
+ { question, expert_model, chat_session_id },
282
+ { abortController, readFileTimestamps },
283
+ ) {
284
+ const expertModel = expert_model
285
+
286
+ let sessionId: string
287
+ let isInterrupted = false
288
+
289
+ // Set up abort listener (following TaskTool pattern)
290
+ const abortListener = () => {
291
+ isInterrupted = true
292
+ }
293
+ abortController.signal.addEventListener('abort', abortListener)
294
+
295
+ try {
296
+ // Initial abort check
297
+ if (abortController.signal.aborted) {
298
+ return yield* this.handleInterrupt()
299
+ }
300
+ // Session management with error handling
301
+ if (chat_session_id === 'new') {
302
+ try {
303
+ const session = createExpertChatSession(expertModel)
304
+ sessionId = session.sessionId
305
+ } catch (error) {
306
+ logError('Failed to create new expert chat session', error)
307
+ throw new Error('Failed to create new chat session')
308
+ }
309
+ } else {
310
+ sessionId = chat_session_id
311
+ try {
312
+ const session = loadExpertChatSession(sessionId)
313
+ if (!session) {
314
+ // Session doesn't exist, create new one
315
+ const newSession = createExpertChatSession(expertModel)
316
+ sessionId = newSession.sessionId
317
+ }
318
+ } catch (error) {
319
+ logError('Failed to load expert chat session', error)
320
+ // Fallback: create new session
321
+ try {
322
+ const newSession = createExpertChatSession(expertModel)
323
+ sessionId = newSession.sessionId
324
+ } catch (createError) {
325
+ logError(
326
+ 'Failed to create fallback expert chat session',
327
+ createError,
328
+ )
329
+ throw new Error('Unable to create or load chat session')
330
+ }
331
+ }
332
+ }
333
+
334
+ // Check for cancellation before loading history
335
+ if (isInterrupted || abortController.signal.aborted) {
336
+ return yield* this.handleInterrupt()
337
+ }
338
+
339
+ // Load history and prepare messages with error handling
340
+ let historyMessages: Array<{ role: string; content: string }>
341
+ try {
342
+ historyMessages = getSessionMessages(sessionId)
343
+ } catch (error) {
344
+ logError('Failed to load session messages', error)
345
+ historyMessages = [] // Fallback to empty history
346
+ }
347
+
348
+ const messages = [...historyMessages, { role: 'user', content: question }]
349
+
350
+ let systemMessages
351
+ try {
352
+ systemMessages = messages.map(msg =>
353
+ msg.role === 'user'
354
+ ? createUserMessage(msg.content)
355
+ : createAssistantMessage(msg.content),
356
+ )
357
+ } catch (error) {
358
+ logError('Failed to create system messages', error)
359
+ throw new Error('Failed to prepare conversation messages')
360
+ }
361
+
362
+ // Check for cancellation before model call
363
+ if (isInterrupted || abortController.signal.aborted) {
364
+ return yield* this.handleInterrupt()
365
+ }
366
+
367
+ // Yield progress message to show we're connecting
368
+ yield {
369
+ type: 'progress',
370
+ content: createAssistantMessage(
371
+ `Connecting to ${expertModel}... (timeout: 5 minutes)`
372
+ ),
373
+ }
374
+
375
+ // Call model with comprehensive error handling and timeout
376
+ let response
377
+ try {
378
+ // Debug: Log the model we're trying to use (using global debug logger)
379
+ const modelManager = getModelManager()
380
+ const modelResolution = modelManager.resolveModelWithInfo(expertModel)
381
+
382
+ debugLogger.api('EXPERT_MODEL_RESOLUTION', {
383
+ requestedModel: expertModel,
384
+ success: modelResolution.success,
385
+ profileName: modelResolution.profile?.name,
386
+ profileModelName: modelResolution.profile?.modelName,
387
+ provider: modelResolution.profile?.provider,
388
+ isActive: modelResolution.profile?.isActive,
389
+ error: modelResolution.error,
390
+ })
391
+
392
+ // Create a timeout promise to prevent hanging
393
+ const timeoutMs = 300000 // 300 seconds (5 minutes) timeout for external models
394
+ const timeoutPromise = new Promise((_, reject) => {
395
+ setTimeout(() => {
396
+ reject(new Error(`Expert model query timed out after ${timeoutMs/1000}s`))
397
+ }, timeoutMs)
398
+ })
399
+
400
+ // Race between the query and timeout
401
+ response = await Promise.race([
402
+ queryLLM(
403
+ systemMessages,
404
+ [], // no system prompt - let expert model use its default behavior
405
+ 0, // no thinking tokens needed
406
+ [], // no tools needed
407
+ abortController.signal,
408
+ {
409
+ safeMode: false,
410
+ model: expertModel,
411
+ prependCLISysprompt: false, // KEY: avoid injecting CLI context
412
+ },
413
+ ),
414
+ timeoutPromise
415
+ ])
416
+ } catch (error: any) {
417
+ logError('Expert model query failed', error)
418
+
419
+ // Check for specific error types
420
+ if (
421
+ error.name === 'AbortError' ||
422
+ abortController.signal?.aborted ||
423
+ isInterrupted
424
+ ) {
425
+ return yield* this.handleInterrupt()
426
+ }
427
+
428
+ if (error.message?.includes('timed out')) {
429
+ throw new Error(
430
+ `Expert model '${expertModel}' timed out after 5 minutes.\n\n` +
431
+ `Suggestions:\n` +
432
+ ` - The model might be experiencing high load\n` +
433
+ ` - Try a different model or retry later\n` +
434
+ ` - Consider breaking down your question into smaller parts`,
435
+ )
436
+ }
437
+
438
+ if (error.message?.includes('rate limit')) {
439
+ throw new Error(
440
+ `Rate limit exceeded for ${expertModel}.\n\n` +
441
+ `Please wait a moment and try again, or use a different model.`,
442
+ )
443
+ }
444
+
445
+ if (error.message?.includes('invalid api key')) {
446
+ throw new Error(
447
+ `Invalid API key for ${expertModel}.\n\n` +
448
+ `Please check your model configuration with /model command.`,
449
+ )
450
+ }
451
+
452
+ if (
453
+ error.message?.includes('model not found') ||
454
+ error.message?.includes('Failed to resolve model')
455
+ ) {
456
+ // Provide helpful model guidance in runtime errors too
457
+ try {
458
+ const modelManager = getModelManager()
459
+ const availableModels = modelManager.getAllAvailableModelNames()
460
+ if (availableModels.length > 0) {
461
+ throw new Error(
462
+ `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.`,
463
+ )
464
+ } else {
465
+ throw new Error(
466
+ `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.`,
467
+ )
468
+ }
469
+ } catch (modelError) {
470
+ // If we can't get model list, fall back to simple error
471
+ throw new Error(
472
+ `Model '${expertModel}' not found. Please check model configuration or inform user about the issue.`,
473
+ )
474
+ }
475
+ }
476
+
477
+ // Generic fallback
478
+ throw new Error(
479
+ `Expert model query failed: ${error.message || 'Unknown error'}`,
480
+ )
481
+ }
482
+
483
+ // Extract answer with error handling
484
+ let expertAnswer: string
485
+ try {
486
+ if (!response?.message?.content) {
487
+ throw new Error('No content in expert response')
488
+ }
489
+
490
+ expertAnswer = response.message.content
491
+ .filter(block => block.type === 'text')
492
+ .map(block => (block as any).text)
493
+ .join('\n')
494
+
495
+ if (!expertAnswer.trim()) {
496
+ throw new Error('Expert response was empty')
497
+ }
498
+ } catch (error) {
499
+ logError('Failed to extract expert answer', error)
500
+ throw new Error('Failed to process expert response')
501
+ }
502
+
503
+ // Save conversation with error handling
504
+ try {
505
+ addMessageToSession(sessionId, 'user', question)
506
+ addMessageToSession(sessionId, 'assistant', expertAnswer)
507
+ } catch (error) {
508
+ logError('Failed to save conversation to session', error)
509
+ // Don't throw here - we got a valid response, saving is non-critical
510
+ }
511
+
512
+ const result: Out = {
513
+ chatSessionId: sessionId,
514
+ expertModelName: expertModel,
515
+ expertAnswer: expertAnswer,
516
+ }
517
+
518
+ yield {
519
+ type: 'result',
520
+ data: result,
521
+ resultForAssistant: this.renderResultForAssistant(result),
522
+ }
523
+ } catch (error: any) {
524
+ // Check if error is due to cancellation
525
+ if (
526
+ error.name === 'AbortError' ||
527
+ abortController.signal?.aborted ||
528
+ isInterrupted
529
+ ) {
530
+ return yield* this.handleInterrupt()
531
+ }
532
+
533
+ logError('AskExpertModelTool execution failed', error)
534
+
535
+ // Ensure we have a valid sessionId for error response
536
+ const errorSessionId = sessionId || 'error-session'
537
+
538
+ const errorMessage =
539
+ error.message || 'Expert consultation failed with unknown error'
540
+ const result: Out = {
541
+ chatSessionId: errorSessionId,
542
+ expertModelName: expertModel,
543
+ expertAnswer: `❌ ${errorMessage}`,
544
+ }
545
+
546
+ yield {
547
+ type: 'result',
548
+ data: result,
549
+ resultForAssistant: this.renderResultForAssistant(result),
550
+ }
551
+ } finally {
552
+ // Clean up event listener
553
+ abortController.signal.removeEventListener('abort', abortListener)
554
+ }
555
+ },
556
+
557
+ // Unified interrupt handling method (following TaskTool pattern)
558
+ async *handleInterrupt() {
559
+ yield {
560
+ type: 'result',
561
+ data: {
562
+ chatSessionId: 'interrupted',
563
+ expertModelName: 'cancelled',
564
+ expertAnswer: INTERRUPT_MESSAGE,
565
+ },
566
+ resultForAssistant: INTERRUPT_MESSAGE,
567
+ }
568
+ },
569
+ }