@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.
- package/README.md +202 -76
- package/README.zh-CN.md +246 -0
- package/cli.js +62 -0
- package/package.json +45 -25
- package/scripts/postinstall.js +56 -0
- package/src/ProjectOnboarding.tsx +180 -0
- package/src/Tool.ts +53 -0
- package/src/commands/approvedTools.ts +53 -0
- package/src/commands/bug.tsx +20 -0
- package/src/commands/clear.ts +43 -0
- package/src/commands/compact.ts +120 -0
- package/src/commands/config.tsx +19 -0
- package/src/commands/cost.ts +18 -0
- package/src/commands/ctx_viz.ts +209 -0
- package/src/commands/doctor.ts +24 -0
- package/src/commands/help.tsx +19 -0
- package/src/commands/init.ts +37 -0
- package/src/commands/listen.ts +42 -0
- package/src/commands/login.tsx +51 -0
- package/src/commands/logout.tsx +40 -0
- package/src/commands/mcp.ts +41 -0
- package/src/commands/model.tsx +40 -0
- package/src/commands/modelstatus.tsx +20 -0
- package/src/commands/onboarding.tsx +34 -0
- package/src/commands/pr_comments.ts +59 -0
- package/src/commands/refreshCommands.ts +54 -0
- package/src/commands/release-notes.ts +34 -0
- package/src/commands/resume.tsx +30 -0
- package/src/commands/review.ts +49 -0
- package/src/commands/terminalSetup.ts +221 -0
- package/src/commands.ts +136 -0
- package/src/components/ApproveApiKey.tsx +93 -0
- package/src/components/AsciiLogo.tsx +13 -0
- package/src/components/AutoUpdater.tsx +148 -0
- package/src/components/Bug.tsx +367 -0
- package/src/components/Config.tsx +289 -0
- package/src/components/ConsoleOAuthFlow.tsx +326 -0
- package/src/components/Cost.tsx +23 -0
- package/src/components/CostThresholdDialog.tsx +46 -0
- package/src/components/CustomSelect/option-map.ts +42 -0
- package/src/components/CustomSelect/select-option.tsx +52 -0
- package/src/components/CustomSelect/select.tsx +143 -0
- package/src/components/CustomSelect/use-select-state.ts +414 -0
- package/src/components/CustomSelect/use-select.ts +35 -0
- package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
- package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
- package/src/components/Help.tsx +215 -0
- package/src/components/HighlightedCode.tsx +33 -0
- package/src/components/InvalidConfigDialog.tsx +113 -0
- package/src/components/Link.tsx +32 -0
- package/src/components/LogSelector.tsx +86 -0
- package/src/components/Logo.tsx +145 -0
- package/src/components/MCPServerApprovalDialog.tsx +100 -0
- package/src/components/MCPServerDialogCopy.tsx +25 -0
- package/src/components/MCPServerMultiselectDialog.tsx +109 -0
- package/src/components/Message.tsx +219 -0
- package/src/components/MessageResponse.tsx +15 -0
- package/src/components/MessageSelector.tsx +211 -0
- package/src/components/ModeIndicator.tsx +88 -0
- package/src/components/ModelConfig.tsx +301 -0
- package/src/components/ModelListManager.tsx +223 -0
- package/src/components/ModelSelector.tsx +3208 -0
- package/src/components/ModelStatusDisplay.tsx +228 -0
- package/src/components/Onboarding.tsx +274 -0
- package/src/components/PressEnterToContinue.tsx +11 -0
- package/src/components/PromptInput.tsx +710 -0
- package/src/components/SentryErrorBoundary.ts +33 -0
- package/src/components/Spinner.tsx +129 -0
- package/src/components/StructuredDiff.tsx +184 -0
- package/src/components/TextInput.tsx +246 -0
- package/src/components/TokenWarning.tsx +31 -0
- package/src/components/ToolUseLoader.tsx +40 -0
- package/src/components/TrustDialog.tsx +106 -0
- package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
- package/src/components/binary-feedback/utils.ts +220 -0
- package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +45 -0
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
- package/src/components/messages/AssistantTextMessage.tsx +144 -0
- package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
- package/src/components/messages/AssistantToolUseMessage.tsx +123 -0
- package/src/components/messages/UserBashInputMessage.tsx +28 -0
- package/src/components/messages/UserCommandMessage.tsx +30 -0
- package/src/components/messages/UserKodingInputMessage.tsx +28 -0
- package/src/components/messages/UserPromptMessage.tsx +35 -0
- package/src/components/messages/UserTextMessage.tsx +39 -0
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
- package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
- package/src/components/permissions/FallbackPermissionRequest.tsx +155 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +75 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +81 -0
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +242 -0
- package/src/components/permissions/PermissionRequest.tsx +103 -0
- package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
- package/src/components/permissions/hooks.ts +44 -0
- package/src/components/permissions/toolUseOptions.ts +59 -0
- package/src/components/permissions/utils.ts +23 -0
- package/src/constants/betas.ts +5 -0
- package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
- package/src/constants/figures.ts +4 -0
- package/src/constants/keys.ts +3 -0
- package/src/constants/macros.ts +6 -0
- package/src/constants/models.ts +935 -0
- package/src/constants/oauth.ts +18 -0
- package/src/constants/product.ts +17 -0
- package/src/constants/prompts.ts +177 -0
- package/src/constants/releaseNotes.ts +7 -0
- package/src/context/PermissionContext.tsx +149 -0
- package/src/context.ts +278 -0
- package/src/cost-tracker.ts +84 -0
- package/src/entrypoints/cli.tsx +1498 -0
- package/src/entrypoints/mcp.ts +176 -0
- package/src/history.ts +25 -0
- package/src/hooks/useApiKeyVerification.ts +59 -0
- package/src/hooks/useArrowKeyHistory.ts +55 -0
- package/src/hooks/useCanUseTool.ts +138 -0
- package/src/hooks/useCancelRequest.ts +39 -0
- package/src/hooks/useDoublePress.ts +42 -0
- package/src/hooks/useExitOnCtrlCD.ts +31 -0
- package/src/hooks/useInterval.ts +25 -0
- package/src/hooks/useLogMessages.ts +16 -0
- package/src/hooks/useLogStartupTime.ts +12 -0
- package/src/hooks/useNotifyAfterTimeout.ts +65 -0
- package/src/hooks/usePermissionRequestLogging.ts +44 -0
- package/src/hooks/useSlashCommandTypeahead.ts +137 -0
- package/src/hooks/useTerminalSize.ts +49 -0
- package/src/hooks/useTextInput.ts +315 -0
- package/src/messages.ts +37 -0
- package/src/permissions.ts +268 -0
- package/src/query.ts +704 -0
- package/src/screens/ConfigureNpmPrefix.tsx +197 -0
- package/src/screens/Doctor.tsx +219 -0
- package/src/screens/LogList.tsx +68 -0
- package/src/screens/REPL.tsx +792 -0
- package/src/screens/ResumeConversation.tsx +68 -0
- package/src/services/browserMocks.ts +66 -0
- package/src/services/claude.ts +1947 -0
- package/src/services/customCommands.ts +683 -0
- package/src/services/fileFreshness.ts +377 -0
- package/src/services/mcpClient.ts +564 -0
- package/src/services/mcpServerApproval.tsx +50 -0
- package/src/services/notifier.ts +40 -0
- package/src/services/oauth.ts +357 -0
- package/src/services/openai.ts +796 -0
- package/src/services/sentry.ts +3 -0
- package/src/services/statsig.ts +171 -0
- package/src/services/statsigStorage.ts +86 -0
- package/src/services/systemReminder.ts +406 -0
- package/src/services/vcr.ts +161 -0
- package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
- package/src/tools/ArchitectTool/prompt.ts +15 -0
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +505 -0
- package/src/tools/BashTool/BashTool.tsx +270 -0
- package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
- package/src/tools/BashTool/OutputLine.tsx +48 -0
- package/src/tools/BashTool/prompt.ts +174 -0
- package/src/tools/BashTool/utils.ts +56 -0
- package/src/tools/FileEditTool/FileEditTool.tsx +316 -0
- package/src/tools/FileEditTool/prompt.ts +51 -0
- package/src/tools/FileEditTool/utils.ts +58 -0
- package/src/tools/FileReadTool/FileReadTool.tsx +371 -0
- package/src/tools/FileReadTool/prompt.ts +7 -0
- package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
- package/src/tools/FileWriteTool/prompt.ts +10 -0
- package/src/tools/GlobTool/GlobTool.tsx +119 -0
- package/src/tools/GlobTool/prompt.ts +8 -0
- package/src/tools/GrepTool/GrepTool.tsx +147 -0
- package/src/tools/GrepTool/prompt.ts +11 -0
- package/src/tools/MCPTool/MCPTool.tsx +106 -0
- package/src/tools/MCPTool/prompt.ts +3 -0
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
- package/src/tools/MemoryReadTool/prompt.ts +3 -0
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
- package/src/tools/MemoryWriteTool/prompt.ts +3 -0
- package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
- package/src/tools/MultiEditTool/prompt.ts +45 -0
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
- package/src/tools/NotebookEditTool/prompt.ts +3 -0
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +266 -0
- package/src/tools/NotebookReadTool/prompt.ts +3 -0
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
- package/src/tools/StickerRequestTool/prompt.ts +19 -0
- package/src/tools/TaskTool/TaskTool.tsx +382 -0
- package/src/tools/TaskTool/constants.ts +1 -0
- package/src/tools/TaskTool/prompt.ts +56 -0
- package/src/tools/ThinkTool/ThinkTool.tsx +56 -0
- package/src/tools/ThinkTool/prompt.ts +12 -0
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +289 -0
- package/src/tools/TodoWriteTool/prompt.ts +63 -0
- package/src/tools/lsTool/lsTool.tsx +269 -0
- package/src/tools/lsTool/prompt.ts +2 -0
- package/src/tools.ts +63 -0
- package/src/types/PermissionMode.ts +120 -0
- package/src/types/RequestContext.ts +72 -0
- package/src/utils/Cursor.ts +436 -0
- package/src/utils/PersistentShell.ts +373 -0
- package/src/utils/agentStorage.ts +97 -0
- package/src/utils/array.ts +3 -0
- package/src/utils/ask.tsx +98 -0
- package/src/utils/auth.ts +13 -0
- package/src/utils/autoCompactCore.ts +223 -0
- package/src/utils/autoUpdater.ts +318 -0
- package/src/utils/betas.ts +20 -0
- package/src/utils/browser.ts +14 -0
- package/src/utils/cleanup.ts +72 -0
- package/src/utils/commands.ts +261 -0
- package/src/utils/config.ts +771 -0
- package/src/utils/conversationRecovery.ts +54 -0
- package/src/utils/debugLogger.ts +1123 -0
- package/src/utils/diff.ts +42 -0
- package/src/utils/env.ts +57 -0
- package/src/utils/errors.ts +21 -0
- package/src/utils/exampleCommands.ts +108 -0
- package/src/utils/execFileNoThrow.ts +51 -0
- package/src/utils/expertChatStorage.ts +136 -0
- package/src/utils/file.ts +402 -0
- package/src/utils/fileRecoveryCore.ts +71 -0
- package/src/utils/format.tsx +44 -0
- package/src/utils/generators.ts +62 -0
- package/src/utils/git.ts +92 -0
- package/src/utils/globalLogger.ts +77 -0
- package/src/utils/http.ts +10 -0
- package/src/utils/imagePaste.ts +38 -0
- package/src/utils/json.ts +13 -0
- package/src/utils/log.ts +382 -0
- package/src/utils/markdown.ts +213 -0
- package/src/utils/messageContextManager.ts +289 -0
- package/src/utils/messages.tsx +938 -0
- package/src/utils/model.ts +836 -0
- package/src/utils/permissions/filesystem.ts +118 -0
- package/src/utils/ripgrep.ts +167 -0
- package/src/utils/sessionState.ts +49 -0
- package/src/utils/state.ts +25 -0
- package/src/utils/style.ts +29 -0
- package/src/utils/terminal.ts +49 -0
- package/src/utils/theme.ts +122 -0
- package/src/utils/thinking.ts +144 -0
- package/src/utils/todoStorage.ts +431 -0
- package/src/utils/tokens.ts +43 -0
- package/src/utils/toolExecutionController.ts +163 -0
- package/src/utils/unaryLogging.ts +26 -0
- package/src/utils/user.ts +37 -0
- package/src/utils/validate.ts +165 -0
- 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> ⎿ </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> ⎿ </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
|
+
}
|