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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/dist/entrypoints/cli.js +59 -38
  2. package/dist/entrypoints/cli.js.map +3 -3
  3. package/dist/index.js +5 -26
  4. package/dist/package.json +4 -1
  5. package/package.json +11 -104
  6. package/dist/test/testAdapters.js +0 -88
  7. package/dist/test/testAdapters.js.map +0 -1
  8. package/src/ProjectOnboarding.tsx +0 -198
  9. package/src/Tool.ts +0 -83
  10. package/src/commands/agents.tsx +0 -3416
  11. package/src/commands/approvedTools.ts +0 -53
  12. package/src/commands/bug.tsx +0 -20
  13. package/src/commands/clear.ts +0 -43
  14. package/src/commands/compact.ts +0 -120
  15. package/src/commands/config.tsx +0 -19
  16. package/src/commands/cost.ts +0 -18
  17. package/src/commands/ctx_viz.ts +0 -209
  18. package/src/commands/doctor.ts +0 -24
  19. package/src/commands/help.tsx +0 -19
  20. package/src/commands/init.ts +0 -37
  21. package/src/commands/listen.ts +0 -42
  22. package/src/commands/login.tsx +0 -51
  23. package/src/commands/logout.tsx +0 -40
  24. package/src/commands/mcp.ts +0 -41
  25. package/src/commands/model.tsx +0 -40
  26. package/src/commands/modelstatus.tsx +0 -20
  27. package/src/commands/onboarding.tsx +0 -34
  28. package/src/commands/pr_comments.ts +0 -59
  29. package/src/commands/refreshCommands.ts +0 -54
  30. package/src/commands/release-notes.ts +0 -34
  31. package/src/commands/resume.tsx +0 -31
  32. package/src/commands/review.ts +0 -49
  33. package/src/commands/terminalSetup.ts +0 -221
  34. package/src/commands.ts +0 -139
  35. package/src/components/ApproveApiKey.tsx +0 -93
  36. package/src/components/AsciiLogo.tsx +0 -13
  37. package/src/components/AutoUpdater.tsx +0 -148
  38. package/src/components/Bug.tsx +0 -367
  39. package/src/components/Config.tsx +0 -293
  40. package/src/components/ConsoleOAuthFlow.tsx +0 -327
  41. package/src/components/Cost.tsx +0 -23
  42. package/src/components/CostThresholdDialog.tsx +0 -46
  43. package/src/components/CustomSelect/option-map.ts +0 -42
  44. package/src/components/CustomSelect/select-option.tsx +0 -78
  45. package/src/components/CustomSelect/select.tsx +0 -152
  46. package/src/components/CustomSelect/theme.ts +0 -45
  47. package/src/components/CustomSelect/use-select-state.ts +0 -414
  48. package/src/components/CustomSelect/use-select.ts +0 -35
  49. package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
  50. package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
  51. package/src/components/Help.tsx +0 -215
  52. package/src/components/HighlightedCode.tsx +0 -33
  53. package/src/components/InvalidConfigDialog.tsx +0 -113
  54. package/src/components/Link.tsx +0 -32
  55. package/src/components/LogSelector.tsx +0 -86
  56. package/src/components/Logo.tsx +0 -170
  57. package/src/components/MCPServerApprovalDialog.tsx +0 -100
  58. package/src/components/MCPServerDialogCopy.tsx +0 -25
  59. package/src/components/MCPServerMultiselectDialog.tsx +0 -109
  60. package/src/components/Message.tsx +0 -221
  61. package/src/components/MessageResponse.tsx +0 -15
  62. package/src/components/MessageSelector.tsx +0 -211
  63. package/src/components/ModeIndicator.tsx +0 -88
  64. package/src/components/ModelConfig.tsx +0 -301
  65. package/src/components/ModelListManager.tsx +0 -227
  66. package/src/components/ModelSelector.tsx +0 -3387
  67. package/src/components/ModelStatusDisplay.tsx +0 -230
  68. package/src/components/Onboarding.tsx +0 -274
  69. package/src/components/PressEnterToContinue.tsx +0 -11
  70. package/src/components/PromptInput.tsx +0 -760
  71. package/src/components/SentryErrorBoundary.ts +0 -39
  72. package/src/components/Spinner.tsx +0 -129
  73. package/src/components/StickerRequestForm.tsx +0 -16
  74. package/src/components/StructuredDiff.tsx +0 -191
  75. package/src/components/TextInput.tsx +0 -259
  76. package/src/components/TodoItem.tsx +0 -47
  77. package/src/components/TokenWarning.tsx +0 -31
  78. package/src/components/ToolUseLoader.tsx +0 -40
  79. package/src/components/TrustDialog.tsx +0 -106
  80. package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
  81. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
  82. package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
  83. package/src/components/binary-feedback/utils.ts +0 -220
  84. package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
  85. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
  86. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
  87. package/src/components/messages/AssistantTextMessage.tsx +0 -144
  88. package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
  89. package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
  90. package/src/components/messages/TaskProgressMessage.tsx +0 -32
  91. package/src/components/messages/TaskToolMessage.tsx +0 -58
  92. package/src/components/messages/UserBashInputMessage.tsx +0 -28
  93. package/src/components/messages/UserCommandMessage.tsx +0 -30
  94. package/src/components/messages/UserKodingInputMessage.tsx +0 -28
  95. package/src/components/messages/UserPromptMessage.tsx +0 -35
  96. package/src/components/messages/UserTextMessage.tsx +0 -39
  97. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
  98. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
  99. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
  100. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
  101. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
  102. package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
  103. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
  104. package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
  105. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
  106. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
  107. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
  108. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
  109. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
  110. package/src/components/permissions/PermissionRequest.tsx +0 -101
  111. package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
  112. package/src/components/permissions/hooks.ts +0 -44
  113. package/src/components/permissions/toolUseOptions.ts +0 -59
  114. package/src/components/permissions/utils.ts +0 -23
  115. package/src/constants/betas.ts +0 -5
  116. package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
  117. package/src/constants/figures.ts +0 -4
  118. package/src/constants/keys.ts +0 -3
  119. package/src/constants/macros.ts +0 -11
  120. package/src/constants/modelCapabilities.ts +0 -179
  121. package/src/constants/models.ts +0 -1025
  122. package/src/constants/oauth.ts +0 -18
  123. package/src/constants/product.ts +0 -17
  124. package/src/constants/prompts.ts +0 -168
  125. package/src/constants/releaseNotes.ts +0 -7
  126. package/src/context/PermissionContext.tsx +0 -149
  127. package/src/context.ts +0 -278
  128. package/src/cost-tracker.ts +0 -84
  129. package/src/entrypoints/cli.tsx +0 -1561
  130. package/src/entrypoints/mcp.ts +0 -175
  131. package/src/history.ts +0 -25
  132. package/src/hooks/useApiKeyVerification.ts +0 -59
  133. package/src/hooks/useArrowKeyHistory.ts +0 -55
  134. package/src/hooks/useCanUseTool.ts +0 -138
  135. package/src/hooks/useCancelRequest.ts +0 -39
  136. package/src/hooks/useDoublePress.ts +0 -41
  137. package/src/hooks/useExitOnCtrlCD.ts +0 -31
  138. package/src/hooks/useInterval.ts +0 -25
  139. package/src/hooks/useLogMessages.ts +0 -16
  140. package/src/hooks/useLogStartupTime.ts +0 -12
  141. package/src/hooks/useNotifyAfterTimeout.ts +0 -65
  142. package/src/hooks/usePermissionRequestLogging.ts +0 -44
  143. package/src/hooks/useTerminalSize.ts +0 -49
  144. package/src/hooks/useTextInput.ts +0 -317
  145. package/src/hooks/useUnifiedCompletion.ts +0 -1405
  146. package/src/index.ts +0 -34
  147. package/src/messages.ts +0 -38
  148. package/src/permissions.ts +0 -268
  149. package/src/query.ts +0 -720
  150. package/src/screens/ConfigureNpmPrefix.tsx +0 -197
  151. package/src/screens/Doctor.tsx +0 -219
  152. package/src/screens/LogList.tsx +0 -68
  153. package/src/screens/REPL.tsx +0 -813
  154. package/src/screens/ResumeConversation.tsx +0 -68
  155. package/src/services/adapters/base.ts +0 -38
  156. package/src/services/adapters/chatCompletions.ts +0 -90
  157. package/src/services/adapters/responsesAPI.ts +0 -170
  158. package/src/services/browserMocks.ts +0 -66
  159. package/src/services/claude.ts +0 -2197
  160. package/src/services/customCommands.ts +0 -704
  161. package/src/services/fileFreshness.ts +0 -377
  162. package/src/services/gpt5ConnectionTest.ts +0 -340
  163. package/src/services/mcpClient.ts +0 -564
  164. package/src/services/mcpServerApproval.tsx +0 -50
  165. package/src/services/mentionProcessor.ts +0 -273
  166. package/src/services/modelAdapterFactory.ts +0 -69
  167. package/src/services/notifier.ts +0 -40
  168. package/src/services/oauth.ts +0 -357
  169. package/src/services/openai.ts +0 -1359
  170. package/src/services/responseStateManager.ts +0 -90
  171. package/src/services/sentry.ts +0 -3
  172. package/src/services/statsig.ts +0 -172
  173. package/src/services/statsigStorage.ts +0 -86
  174. package/src/services/systemReminder.ts +0 -507
  175. package/src/services/vcr.ts +0 -161
  176. package/src/test/testAdapters.ts +0 -96
  177. package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
  178. package/src/tools/ArchitectTool/prompt.ts +0 -15
  179. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
  180. package/src/tools/BashTool/BashTool.tsx +0 -243
  181. package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
  182. package/src/tools/BashTool/OutputLine.tsx +0 -49
  183. package/src/tools/BashTool/prompt.ts +0 -174
  184. package/src/tools/BashTool/utils.ts +0 -56
  185. package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
  186. package/src/tools/FileEditTool/prompt.ts +0 -51
  187. package/src/tools/FileEditTool/utils.ts +0 -58
  188. package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
  189. package/src/tools/FileReadTool/prompt.ts +0 -7
  190. package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
  191. package/src/tools/FileWriteTool/prompt.ts +0 -10
  192. package/src/tools/GlobTool/GlobTool.tsx +0 -119
  193. package/src/tools/GlobTool/prompt.ts +0 -8
  194. package/src/tools/GrepTool/GrepTool.tsx +0 -147
  195. package/src/tools/GrepTool/prompt.ts +0 -11
  196. package/src/tools/MCPTool/MCPTool.tsx +0 -107
  197. package/src/tools/MCPTool/prompt.ts +0 -3
  198. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
  199. package/src/tools/MemoryReadTool/prompt.ts +0 -3
  200. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
  201. package/src/tools/MemoryWriteTool/prompt.ts +0 -3
  202. package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
  203. package/src/tools/MultiEditTool/prompt.ts +0 -45
  204. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
  205. package/src/tools/NotebookEditTool/prompt.ts +0 -3
  206. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
  207. package/src/tools/NotebookReadTool/prompt.ts +0 -3
  208. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
  209. package/src/tools/StickerRequestTool/prompt.ts +0 -19
  210. package/src/tools/TaskTool/TaskTool.tsx +0 -438
  211. package/src/tools/TaskTool/constants.ts +0 -1
  212. package/src/tools/TaskTool/prompt.ts +0 -92
  213. package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
  214. package/src/tools/ThinkTool/prompt.ts +0 -12
  215. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
  216. package/src/tools/TodoWriteTool/prompt.ts +0 -63
  217. package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
  218. package/src/tools/URLFetcherTool/cache.ts +0 -55
  219. package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
  220. package/src/tools/URLFetcherTool/prompt.ts +0 -17
  221. package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
  222. package/src/tools/WebSearchTool/prompt.ts +0 -13
  223. package/src/tools/WebSearchTool/searchProviders.ts +0 -66
  224. package/src/tools/lsTool/lsTool.tsx +0 -272
  225. package/src/tools/lsTool/prompt.ts +0 -2
  226. package/src/tools.ts +0 -67
  227. package/src/types/PermissionMode.ts +0 -120
  228. package/src/types/RequestContext.ts +0 -72
  229. package/src/types/common.d.ts +0 -2
  230. package/src/types/conversation.ts +0 -51
  231. package/src/types/logs.ts +0 -58
  232. package/src/types/modelCapabilities.ts +0 -64
  233. package/src/types/notebook.ts +0 -87
  234. package/src/utils/Cursor.ts +0 -436
  235. package/src/utils/PersistentShell.ts +0 -552
  236. package/src/utils/advancedFuzzyMatcher.ts +0 -290
  237. package/src/utils/agentLoader.ts +0 -278
  238. package/src/utils/agentStorage.ts +0 -97
  239. package/src/utils/array.ts +0 -3
  240. package/src/utils/ask.tsx +0 -99
  241. package/src/utils/auth.ts +0 -13
  242. package/src/utils/autoCompactCore.ts +0 -223
  243. package/src/utils/autoUpdater.ts +0 -458
  244. package/src/utils/betas.ts +0 -20
  245. package/src/utils/browser.ts +0 -14
  246. package/src/utils/cleanup.ts +0 -72
  247. package/src/utils/commands.ts +0 -261
  248. package/src/utils/commonUnixCommands.ts +0 -161
  249. package/src/utils/config.ts +0 -945
  250. package/src/utils/conversationRecovery.ts +0 -55
  251. package/src/utils/debugLogger.ts +0 -1235
  252. package/src/utils/diff.ts +0 -42
  253. package/src/utils/env.ts +0 -57
  254. package/src/utils/errors.ts +0 -21
  255. package/src/utils/exampleCommands.ts +0 -109
  256. package/src/utils/execFileNoThrow.ts +0 -51
  257. package/src/utils/expertChatStorage.ts +0 -136
  258. package/src/utils/file.ts +0 -405
  259. package/src/utils/fileRecoveryCore.ts +0 -71
  260. package/src/utils/format.tsx +0 -44
  261. package/src/utils/fuzzyMatcher.ts +0 -328
  262. package/src/utils/generators.ts +0 -62
  263. package/src/utils/git.ts +0 -92
  264. package/src/utils/globalLogger.ts +0 -77
  265. package/src/utils/http.ts +0 -10
  266. package/src/utils/imagePaste.ts +0 -38
  267. package/src/utils/json.ts +0 -13
  268. package/src/utils/log.ts +0 -382
  269. package/src/utils/markdown.ts +0 -213
  270. package/src/utils/messageContextManager.ts +0 -294
  271. package/src/utils/messages.tsx +0 -945
  272. package/src/utils/model.ts +0 -914
  273. package/src/utils/permissions/filesystem.ts +0 -127
  274. package/src/utils/responseState.ts +0 -23
  275. package/src/utils/ripgrep.ts +0 -167
  276. package/src/utils/secureFile.ts +0 -564
  277. package/src/utils/sessionState.ts +0 -49
  278. package/src/utils/state.ts +0 -25
  279. package/src/utils/style.ts +0 -29
  280. package/src/utils/terminal.ts +0 -50
  281. package/src/utils/theme.ts +0 -127
  282. package/src/utils/thinking.ts +0 -144
  283. package/src/utils/todoStorage.ts +0 -431
  284. package/src/utils/tokens.ts +0 -43
  285. package/src/utils/toolExecutionController.ts +0 -163
  286. package/src/utils/unaryLogging.ts +0 -26
  287. package/src/utils/user.ts +0 -37
  288. package/src/utils/validate.ts +0 -165
@@ -1,945 +0,0 @@
1
- import { randomUUID, UUID } from 'crypto'
2
- import { Box } from 'ink'
3
- import {
4
- AssistantMessage,
5
- Message,
6
- ProgressMessage,
7
- UserMessage,
8
- } from '../query.js'
9
- import { getCommand, hasCommand } from '../commands'
10
- import { MalformedCommandError } from './errors'
11
- import { logError } from './log'
12
- import { resolve } from 'path'
13
- import { last, memoize } from 'lodash-es'
14
- import { logEvent } from '../services/statsig'
15
- import type { SetToolJSXFn, Tool, ToolUseContext } from '../Tool'
16
- import { lastX } from '../utils/generators'
17
- import { NO_CONTENT_MESSAGE } from '../services/claude'
18
- import {
19
- ImageBlockParam,
20
- TextBlockParam,
21
- ToolResultBlockParam,
22
- ToolUseBlockParam,
23
- Message as APIMessage,
24
- ContentBlockParam,
25
- ContentBlock,
26
- } from '@anthropic-ai/sdk/resources/index.mjs'
27
- import { setCwd } from './state'
28
- import { getCwd } from './state'
29
- import chalk from 'chalk'
30
- import * as React from 'react'
31
- import { UserBashInputMessage } from '../components/messages/UserBashInputMessage'
32
- import { Spinner } from '../components/Spinner'
33
- import { BashTool } from '../tools/BashTool/BashTool'
34
- import { ToolUseBlock } from '@anthropic-ai/sdk/resources/index.mjs'
35
-
36
- // NOTE: Dynamic content processing for custom commands has been moved to
37
- // src/services/customCommands.ts for better organization and reusability.
38
- // The functions executeBashCommands and resolveFileReferences are no longer
39
- // duplicated here but are imported when needed for custom command processing.
40
-
41
- export const INTERRUPT_MESSAGE = '[Request interrupted by user]'
42
- export const INTERRUPT_MESSAGE_FOR_TOOL_USE =
43
- '[Request interrupted by user for tool use]'
44
- export const CANCEL_MESSAGE =
45
- "The user doesn't want to take this action right now. STOP what you are doing and wait for the user to tell you how to proceed."
46
- export const REJECT_MESSAGE =
47
- "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed."
48
- export const NO_RESPONSE_REQUESTED = 'No response requested.'
49
-
50
- export const SYNTHETIC_ASSISTANT_MESSAGES = new Set([
51
- INTERRUPT_MESSAGE,
52
- INTERRUPT_MESSAGE_FOR_TOOL_USE,
53
- CANCEL_MESSAGE,
54
- REJECT_MESSAGE,
55
- NO_RESPONSE_REQUESTED,
56
- ])
57
-
58
- function baseCreateAssistantMessage(
59
- content: ContentBlock[],
60
- extra?: Partial<AssistantMessage>,
61
- ): AssistantMessage {
62
- return {
63
- type: 'assistant',
64
- costUSD: 0,
65
- durationMs: 0,
66
- uuid: randomUUID(),
67
- message: {
68
- id: randomUUID(),
69
- model: '<synthetic>',
70
- role: 'assistant',
71
- stop_reason: 'stop_sequence',
72
- stop_sequence: '',
73
- type: 'message',
74
- usage: {
75
- input_tokens: 0,
76
- output_tokens: 0,
77
- cache_creation_input_tokens: 0,
78
- cache_read_input_tokens: 0,
79
- },
80
- content,
81
- },
82
- ...extra,
83
- }
84
- }
85
-
86
- export function createAssistantMessage(content: string): AssistantMessage {
87
- return baseCreateAssistantMessage([
88
- {
89
- type: 'text' as const,
90
- text: content === '' ? NO_CONTENT_MESSAGE : content,
91
- citations: [],
92
- },
93
- ])
94
- }
95
-
96
- export function createAssistantAPIErrorMessage(
97
- content: string,
98
- ): AssistantMessage {
99
- return baseCreateAssistantMessage(
100
- [
101
- {
102
- type: 'text' as const,
103
- text: content === '' ? NO_CONTENT_MESSAGE : content,
104
- citations: [],
105
- },
106
- ],
107
- { isApiErrorMessage: true },
108
- )
109
- }
110
-
111
- export type FullToolUseResult = {
112
- data: unknown // Matches tool's `Output` type
113
- resultForAssistant: ToolResultBlockParam['content']
114
- }
115
-
116
- export function createUserMessage(
117
- content: string | ContentBlockParam[],
118
- toolUseResult?: FullToolUseResult,
119
- ): UserMessage {
120
- const m: UserMessage = {
121
- type: 'user',
122
- message: {
123
- role: 'user',
124
- content,
125
- },
126
- uuid: randomUUID(),
127
- toolUseResult,
128
- }
129
- return m
130
- }
131
-
132
- export function createProgressMessage(
133
- toolUseID: string,
134
- siblingToolUseIDs: Set<string>,
135
- content: AssistantMessage,
136
- normalizedMessages: NormalizedMessage[],
137
- tools: Tool[],
138
- ): ProgressMessage {
139
- return {
140
- type: 'progress',
141
- content,
142
- normalizedMessages,
143
- siblingToolUseIDs,
144
- tools,
145
- toolUseID,
146
- uuid: randomUUID(),
147
- }
148
- }
149
-
150
- export function createToolResultStopMessage(
151
- toolUseID: string,
152
- ): ToolResultBlockParam {
153
- return {
154
- type: 'tool_result',
155
- content: CANCEL_MESSAGE,
156
- is_error: true,
157
- tool_use_id: toolUseID,
158
- }
159
- }
160
-
161
- export async function processUserInput(
162
- input: string,
163
- mode: 'bash' | 'prompt' | 'koding',
164
- setToolJSX: SetToolJSXFn,
165
- context: ToolUseContext & {
166
- setForkConvoWithMessagesOnTheNextRender: (
167
- forkConvoWithMessages: Message[],
168
- ) => void
169
- options?: {
170
- isKodingRequest?: boolean
171
- kodingContext?: string
172
- }
173
- },
174
- pastedImage: string | null,
175
- ): Promise<Message[]> {
176
- // Bash commands
177
- if (mode === 'bash') {
178
- logEvent('tengu_input_bash', {})
179
-
180
- const userMessage = createUserMessage(`<bash-input>${input}</bash-input>`)
181
-
182
- // Special case: cd
183
- if (input.startsWith('cd ')) {
184
- const oldCwd = getCwd()
185
- const newCwd = resolve(oldCwd, input.slice(3))
186
- try {
187
- await setCwd(newCwd)
188
- return [
189
- userMessage,
190
- createAssistantMessage(
191
- `<bash-stdout>Changed directory to ${chalk.bold(`${newCwd}/`)}</bash-stdout>`,
192
- ),
193
- ]
194
- } catch (e) {
195
- logError(e)
196
- return [
197
- userMessage,
198
- createAssistantMessage(
199
- `<bash-stderr>cwd error: ${e instanceof Error ? e.message : String(e)}</bash-stderr>`,
200
- ),
201
- ]
202
- }
203
- }
204
-
205
- // All other bash commands
206
- setToolJSX({
207
- jsx: (
208
- <Box flexDirection="column" marginTop={1}>
209
- <UserBashInputMessage
210
- addMargin={false}
211
- param={{ text: `<bash-input>${input}</bash-input>`, type: 'text' }}
212
- />
213
- <Spinner />
214
- </Box>
215
- ),
216
- shouldHidePromptInput: false,
217
- })
218
- try {
219
- const validationResult = await BashTool.validateInput({
220
- command: input,
221
- })
222
- if (!validationResult.result) {
223
- return [userMessage, createAssistantMessage(validationResult.message)]
224
- }
225
- const { data } = await lastX(BashTool.call({ command: input }, context))
226
- return [
227
- userMessage,
228
- createAssistantMessage(
229
- `<bash-stdout>${data.stdout}</bash-stdout><bash-stderr>${data.stderr}</bash-stderr>`,
230
- ),
231
- ]
232
- } catch (e) {
233
- return [
234
- userMessage,
235
- createAssistantMessage(
236
- `<bash-stderr>Command failed: ${e instanceof Error ? e.message : String(e)}</bash-stderr>`,
237
- ),
238
- ]
239
- } finally {
240
- setToolJSX(null)
241
- }
242
- }
243
- // Koding mode - special wrapper for display
244
- else if (mode === 'koding') {
245
- logEvent('tengu_input_koding', {})
246
-
247
- const userMessage = createUserMessage(
248
- `<koding-input>${input}</koding-input>`,
249
- )
250
- // Add the Koding flag to the message
251
- userMessage.options = {
252
- ...userMessage.options,
253
- isKodingRequest: true,
254
- }
255
-
256
- // Rest of koding processing is handled separately to capture assistant response
257
- return [userMessage]
258
- }
259
-
260
- // Slash commands
261
- if (input.startsWith('/')) {
262
- const words = input.slice(1).split(' ')
263
- let commandName = words[0]
264
- if (words.length > 1 && words[1] === '(MCP)') {
265
- commandName = commandName + ' (MCP)'
266
- }
267
- if (!commandName) {
268
- logEvent('tengu_input_slash_missing', { input })
269
- return [
270
- createAssistantMessage('Commands are in the form `/command [args]`'),
271
- ]
272
- }
273
-
274
- // Check if it's a real command before processing
275
- if (!hasCommand(commandName, context.options.commands)) {
276
- // If not a real command, treat it as a regular user input
277
- logEvent('tengu_input_prompt', {})
278
- return [createUserMessage(input)]
279
- }
280
-
281
- const args = input.slice(commandName.length + 2)
282
- const newMessages = await getMessagesForSlashCommand(
283
- commandName,
284
- args,
285
- setToolJSX,
286
- context,
287
- )
288
-
289
- // Local JSX commands
290
- if (newMessages.length === 0) {
291
- logEvent('tengu_input_command', { input })
292
- return []
293
- }
294
-
295
- // For invalid commands, preserve both the user message and error
296
- if (
297
- newMessages.length === 2 &&
298
- newMessages[0]!.type === 'user' &&
299
- newMessages[1]!.type === 'assistant' &&
300
- typeof newMessages[1]!.message.content === 'string' &&
301
- newMessages[1]!.message.content.startsWith('Unknown command:')
302
- ) {
303
- logEvent('tengu_input_slash_invalid', { input })
304
- return newMessages
305
- }
306
-
307
- // User-Assistant pair (eg. local commands)
308
- if (newMessages.length === 2) {
309
- logEvent('tengu_input_command', { input })
310
- return newMessages
311
- }
312
-
313
- // A valid command
314
- logEvent('tengu_input_command', { input })
315
- return newMessages
316
- }
317
-
318
- // Regular user prompt
319
- logEvent('tengu_input_prompt', {})
320
-
321
- // Check if this is a Koding request that needs special handling
322
- const isKodingRequest = context.options?.isKodingRequest === true
323
- const kodingContextInfo = context.options?.kodingContext
324
-
325
- // Create base message
326
- let userMessage: UserMessage
327
-
328
- if (pastedImage) {
329
- userMessage = createUserMessage([
330
- {
331
- type: 'image',
332
- source: {
333
- type: 'base64',
334
- media_type: 'image/png',
335
- data: pastedImage,
336
- },
337
- },
338
- {
339
- type: 'text',
340
- text:
341
- isKodingRequest && kodingContextInfo
342
- ? `${kodingContextInfo}\n\n${input}`
343
- : input,
344
- },
345
- ])
346
- } else {
347
- let processedInput =
348
- isKodingRequest && kodingContextInfo
349
- ? `${kodingContextInfo}\n\n${input}`
350
- : input
351
-
352
- // Process dynamic content for custom commands with ! and @ prefixes
353
- // This uses the same processing functions as custom commands to maintain consistency
354
- if (input.includes('!`') || input.includes('@')) {
355
- try {
356
- // Import functions from customCommands service to avoid code duplication
357
- const { executeBashCommands } = await import(
358
- '../services/customCommands'
359
- )
360
-
361
- // Execute bash commands if present
362
- if (input.includes('!`')) {
363
- // Note: This function is not exported from customCommands.ts, so we need to expose it
364
- // For now, we'll keep the local implementation until we refactor the service
365
- processedInput = await executeBashCommands(processedInput)
366
- }
367
-
368
- // Process mentions for system reminder integration
369
- // Note: We don't call resolveFileReferences here anymore -
370
- // @file mentions should trigger Read tool usage via reminders, not embed content
371
- if (input.includes('@')) {
372
- const { processMentions } = await import('../services/mentionProcessor')
373
- await processMentions(input)
374
- }
375
- } catch (error) {
376
- console.warn('Dynamic content processing failed:', error)
377
- // Continue with original input if processing fails
378
- }
379
- }
380
-
381
- userMessage = createUserMessage(processedInput)
382
- }
383
-
384
- // Add the Koding flag to the message if needed
385
- if (isKodingRequest) {
386
- userMessage.options = {
387
- ...userMessage.options,
388
- isKodingRequest: true,
389
- }
390
- }
391
-
392
- return [userMessage]
393
- }
394
-
395
- async function getMessagesForSlashCommand(
396
- commandName: string,
397
- args: string,
398
- setToolJSX: SetToolJSXFn,
399
- context: ToolUseContext & {
400
- setForkConvoWithMessagesOnTheNextRender: (
401
- forkConvoWithMessages: Message[],
402
- ) => void
403
- },
404
- ): Promise<Message[]> {
405
- try {
406
- const command = getCommand(commandName, context.options.commands)
407
- switch (command.type) {
408
- case 'local-jsx': {
409
- return new Promise(resolve => {
410
- command
411
- .call(r => {
412
- setToolJSX(null)
413
- resolve([
414
- createUserMessage(`<command-name>${command.userFacingName()}</command-name>
415
- <command-message>${command.userFacingName()}</command-message>
416
- <command-args>${args}</command-args>`),
417
- r
418
- ? createAssistantMessage(r)
419
- : createAssistantMessage(NO_RESPONSE_REQUESTED),
420
- ])
421
- }, context)
422
- .then(jsx => {
423
- setToolJSX({
424
- jsx,
425
- shouldHidePromptInput: true,
426
- })
427
- })
428
- })
429
- }
430
- case 'local': {
431
- const userMessage =
432
- createUserMessage(`<command-name>${command.userFacingName()}</command-name>
433
- <command-message>${command.userFacingName()}</command-message>
434
- <command-args>${args}</command-args>`)
435
-
436
- try {
437
- // Use the context's abortController for local commands
438
- const result = await command.call(args, {
439
- ...context,
440
- options: {
441
- commands: context.options.commands || [],
442
- tools: context.options.tools || [],
443
- slowAndCapableModel: context.options.slowAndCapableModel || 'main'
444
- }
445
- })
446
-
447
- return [
448
- userMessage,
449
- createAssistantMessage(
450
- `<local-command-stdout>${result}</local-command-stdout>`,
451
- ),
452
- ]
453
- } catch (e) {
454
- logError(e)
455
- return [
456
- userMessage,
457
- createAssistantMessage(
458
- `<local-command-stderr>${String(e)}</local-command-stderr>`,
459
- ),
460
- ]
461
- }
462
- }
463
- case 'prompt': {
464
- // For custom commands, process them naturally instead of wrapping in command-contents
465
- const prompt = await command.getPromptForCommand(args)
466
- return prompt.map(msg => {
467
- // Create a normal user message from the custom command content
468
- const userMessage = createUserMessage(
469
- typeof msg.content === 'string'
470
- ? msg.content
471
- : msg.content
472
- .map(block => (block.type === 'text' ? block.text : ''))
473
- .join('\n'),
474
- )
475
-
476
- // Add metadata for tracking but don't wrap in special tags
477
- userMessage.options = {
478
- ...userMessage.options,
479
- isCustomCommand: true,
480
- commandName: command.userFacingName(),
481
- commandArgs: args,
482
- }
483
-
484
- return userMessage
485
- })
486
- }
487
- }
488
- } catch (e) {
489
- if (e instanceof MalformedCommandError) {
490
- return [createAssistantMessage(e.message)]
491
- }
492
- throw e
493
- }
494
- }
495
-
496
- export function extractTagFromMessage(
497
- message: Message,
498
- tagName: string,
499
- ): string | null {
500
- if (message.type === 'progress') {
501
- return null
502
- }
503
- if (typeof message.message.content !== 'string') {
504
- return null
505
- }
506
- return extractTag(message.message.content, tagName)
507
- }
508
-
509
- export function extractTag(html: string, tagName: string): string | null {
510
- if (!html.trim() || !tagName.trim()) {
511
- return null
512
- }
513
-
514
- // Escape special characters in the tag name
515
- const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
516
-
517
- // Create regex pattern that handles:
518
- // 1. Self-closing tags
519
- // 2. Tags with attributes
520
- // 3. Nested tags of the same type
521
- // 4. Multiline content
522
- const pattern = new RegExp(
523
- `<${escapedTag}(?:\\s+[^>]*)?>` + // Opening tag with optional attributes
524
- '([\\s\\S]*?)' + // Content (non-greedy match)
525
- `<\\/${escapedTag}>`, // Closing tag
526
- 'gi',
527
- )
528
-
529
- let match
530
- let depth = 0
531
- let lastIndex = 0
532
- const openingTag = new RegExp(`<${escapedTag}(?:\\s+[^>]*?)?>`, 'gi')
533
- const closingTag = new RegExp(`<\\/${escapedTag}>`, 'gi')
534
-
535
- while ((match = pattern.exec(html)) !== null) {
536
- // Check for nested tags
537
- const content = match[1]
538
- const beforeMatch = html.slice(lastIndex, match.index)
539
-
540
- // Reset depth counter
541
- depth = 0
542
-
543
- // Count opening tags before this match
544
- openingTag.lastIndex = 0
545
- while (openingTag.exec(beforeMatch) !== null) {
546
- depth++
547
- }
548
-
549
- // Count closing tags before this match
550
- closingTag.lastIndex = 0
551
- while (closingTag.exec(beforeMatch) !== null) {
552
- depth--
553
- }
554
-
555
- // Only include content if we're at the correct nesting level
556
- if (depth === 0 && content) {
557
- return content
558
- }
559
-
560
- lastIndex = match.index + match[0].length
561
- }
562
-
563
- return null
564
- }
565
-
566
- export function isNotEmptyMessage(message: Message): boolean {
567
- if (message.type === 'progress') {
568
- return true
569
- }
570
-
571
- if (typeof message.message.content === 'string') {
572
- return message.message.content.trim().length > 0
573
- }
574
-
575
- if (message.message.content.length === 0) {
576
- return false
577
- }
578
-
579
- // Skip multi-block messages for now
580
- if (message.message.content.length > 1) {
581
- return true
582
- }
583
-
584
- if (message.message.content[0]!.type !== 'text') {
585
- return true
586
- }
587
-
588
- return (
589
- message.message.content[0]!.text.trim().length > 0 &&
590
- message.message.content[0]!.text !== NO_CONTENT_MESSAGE &&
591
- message.message.content[0]!.text !== INTERRUPT_MESSAGE_FOR_TOOL_USE
592
- )
593
- }
594
-
595
- // TODO: replace this with plain UserMessage if/when PR #405 lands
596
- type NormalizedUserMessage = {
597
- message: {
598
- content: [
599
- | TextBlockParam
600
- | ImageBlockParam
601
- | ToolUseBlockParam
602
- | ToolResultBlockParam,
603
- ]
604
- role: 'user'
605
- }
606
- type: 'user'
607
- uuid: UUID
608
- }
609
-
610
- export type NormalizedMessage =
611
- | NormalizedUserMessage
612
- | AssistantMessage
613
- | ProgressMessage
614
-
615
- // Split messages, so each content block gets its own message
616
- export function normalizeMessages(messages: Message[]): NormalizedMessage[] {
617
- return messages.flatMap(message => {
618
- if (message.type === 'progress') {
619
- return [message] as NormalizedMessage[]
620
- }
621
- if (typeof message.message.content === 'string') {
622
- return [message] as NormalizedMessage[]
623
- }
624
- return message.message.content.map(_ => {
625
- switch (message.type) {
626
- case 'assistant':
627
- return {
628
- type: 'assistant',
629
- uuid: randomUUID(),
630
- message: {
631
- ...message.message,
632
- content: [_],
633
- },
634
- costUSD:
635
- (message as AssistantMessage).costUSD /
636
- message.message.content.length,
637
- durationMs: (message as AssistantMessage).durationMs,
638
- } as NormalizedMessage
639
- case 'user':
640
- // It seems like the line below was a no-op before, but I'm not sure.
641
- // To check, we could throw an error if any of the following are true:
642
- // - message `role` does isn't `user` -- this possibility is allowed by MCP tools,
643
- // though isn't supposed to happen in practice (we should fix this)
644
- // - message `content` is not an array -- this one is more concerning because it's
645
- // not allowed by the `NormalizedUserMessage` type, but if it's happening that was
646
- // probably a bug before.
647
- // Maybe I'm missing something? -(ab)
648
- // return createUserMessage([_]) as NormalizedMessage
649
- return message as NormalizedUserMessage
650
- }
651
- })
652
- })
653
- }
654
-
655
- type ToolUseRequestMessage = AssistantMessage & {
656
- message: { content: ToolUseBlock[] }
657
- }
658
-
659
- function isToolUseRequestMessage(
660
- message: Message,
661
- ): message is ToolUseRequestMessage {
662
- return (
663
- message.type === 'assistant' &&
664
- 'costUSD' in message &&
665
- // Note: stop_reason === 'tool_use' is unreliable -- it's not always set correctly
666
- message.message.content.some(_ => _.type === 'tool_use')
667
- )
668
- }
669
-
670
- // Re-order, to move result messages to be after their tool use messages
671
- export function reorderMessages(
672
- messages: NormalizedMessage[],
673
- ): NormalizedMessage[] {
674
- const ms: NormalizedMessage[] = []
675
- const toolUseMessages: ToolUseRequestMessage[] = []
676
-
677
- for (const message of messages) {
678
- // track tool use messages we've seen
679
- if (isToolUseRequestMessage(message)) {
680
- toolUseMessages.push(message)
681
- }
682
-
683
- // if it's a tool progress message...
684
- if (message.type === 'progress') {
685
- // replace any existing progress messages with this one
686
- const existingProgressMessage = ms.find(
687
- _ => _.type === 'progress' && _.toolUseID === message.toolUseID,
688
- )
689
- if (existingProgressMessage) {
690
- ms[ms.indexOf(existingProgressMessage)] = message
691
- continue
692
- }
693
- // otherwise, insert it after its tool use
694
- const toolUseMessage = toolUseMessages.find(
695
- _ => _.message.content[0]?.id === message.toolUseID,
696
- )
697
- if (toolUseMessage) {
698
- ms.splice(ms.indexOf(toolUseMessage) + 1, 0, message)
699
- continue
700
- }
701
- }
702
-
703
- // if it's a tool result, insert it after its tool use and progress messages
704
- if (
705
- message.type === 'user' &&
706
- Array.isArray(message.message.content) &&
707
- message.message.content[0]?.type === 'tool_result'
708
- ) {
709
- const toolUseID = (message.message.content[0] as ToolResultBlockParam)
710
- ?.tool_use_id
711
-
712
- // First check for progress messages
713
- const lastProgressMessage = ms.find(
714
- _ => _.type === 'progress' && _.toolUseID === toolUseID,
715
- )
716
- if (lastProgressMessage) {
717
- ms.splice(ms.indexOf(lastProgressMessage) + 1, 0, message)
718
- continue
719
- }
720
-
721
- // If no progress messages, check for tool use messages
722
- const toolUseMessage = toolUseMessages.find(
723
- _ => _.message.content[0]?.id === toolUseID,
724
- )
725
- if (toolUseMessage) {
726
- ms.splice(ms.indexOf(toolUseMessage) + 1, 0, message)
727
- continue
728
- }
729
- }
730
-
731
- // otherwise, just add it to the list
732
- else {
733
- ms.push(message)
734
- }
735
- }
736
-
737
- return ms
738
- }
739
-
740
- const getToolResultIDs = memoize(
741
- (normalizedMessages: NormalizedMessage[]): { [toolUseID: string]: boolean } =>
742
- Object.fromEntries(
743
- normalizedMessages.flatMap(_ =>
744
- _.type === 'user' && _.message.content[0]?.type === 'tool_result'
745
- ? [
746
- [
747
- _.message.content[0]!.tool_use_id,
748
- _.message.content[0]!.is_error ?? false,
749
- ],
750
- ]
751
- : ([] as [string, boolean][]),
752
- ),
753
- ),
754
- )
755
-
756
- export function getUnresolvedToolUseIDs(
757
- normalizedMessages: NormalizedMessage[],
758
- ): Set<string> {
759
- const toolResults = getToolResultIDs(normalizedMessages)
760
- return new Set(
761
- normalizedMessages
762
- .filter(
763
- (
764
- _,
765
- ): _ is AssistantMessage & {
766
- message: { content: [ToolUseBlockParam] }
767
- } =>
768
- _.type === 'assistant' &&
769
- Array.isArray(_.message.content) &&
770
- _.message.content[0]?.type === 'tool_use' &&
771
- !(_.message.content[0]?.id in toolResults),
772
- )
773
- .map(_ => _.message.content[0].id),
774
- )
775
- }
776
-
777
- /**
778
- * Tool uses are in flight if either:
779
- * 1. They have a corresponding progress message and no result message
780
- * 2. They are the first unresoved tool use
781
- *
782
- * TODO: Find a way to harden this logic to make it more explicit
783
- */
784
- export function getInProgressToolUseIDs(
785
- normalizedMessages: NormalizedMessage[],
786
- ): Set<string> {
787
- const unresolvedToolUseIDs = getUnresolvedToolUseIDs(normalizedMessages)
788
- const toolUseIDsThatHaveProgressMessages = new Set(
789
- normalizedMessages.filter(_ => _.type === 'progress').map(_ => _.toolUseID),
790
- )
791
- return new Set(
792
- (
793
- normalizedMessages.filter(_ => {
794
- if (_.type !== 'assistant') {
795
- return false
796
- }
797
- if (_.message.content[0]?.type !== 'tool_use') {
798
- return false
799
- }
800
- const toolUseID = _.message.content[0].id
801
- if (toolUseID === unresolvedToolUseIDs.values().next().value) {
802
- return true
803
- }
804
-
805
- if (
806
- toolUseIDsThatHaveProgressMessages.has(toolUseID) &&
807
- unresolvedToolUseIDs.has(toolUseID)
808
- ) {
809
- return true
810
- }
811
-
812
- return false
813
- }) as AssistantMessage[]
814
- ).map(_ => (_.message.content[0]! as ToolUseBlockParam).id),
815
- )
816
- }
817
-
818
- export function getErroredToolUseMessages(
819
- normalizedMessages: NormalizedMessage[],
820
- ): AssistantMessage[] {
821
- const toolResults = getToolResultIDs(normalizedMessages)
822
- return normalizedMessages.filter(
823
- _ =>
824
- _.type === 'assistant' &&
825
- Array.isArray(_.message.content) &&
826
- _.message.content[0]?.type === 'tool_use' &&
827
- _.message.content[0]?.id in toolResults &&
828
- toolResults[_.message.content[0]?.id],
829
- ) as AssistantMessage[]
830
- }
831
-
832
- export function normalizeMessagesForAPI(
833
- messages: Message[],
834
- ): (UserMessage | AssistantMessage)[] {
835
- const result: (UserMessage | AssistantMessage)[] = []
836
- messages
837
- .filter(_ => _.type !== 'progress')
838
- .forEach(message => {
839
- switch (message.type) {
840
- case 'user': {
841
- // If the current message is not a tool result, add it to the result
842
- if (
843
- !Array.isArray(message.message.content) ||
844
- message.message.content[0]?.type !== 'tool_result'
845
- ) {
846
- result.push(message)
847
- return
848
- }
849
-
850
- // If the last message is not a tool result, add it to the result
851
- const lastMessage = last(result)
852
- if (
853
- !lastMessage ||
854
- lastMessage?.type === 'assistant' ||
855
- !Array.isArray(lastMessage.message.content) ||
856
- lastMessage.message.content[0]?.type !== 'tool_result'
857
- ) {
858
- result.push(message)
859
- return
860
- }
861
-
862
- // Otherwise, merge the current message with the last message
863
- result[result.indexOf(lastMessage)] = {
864
- ...lastMessage,
865
- message: {
866
- ...lastMessage.message,
867
- content: [
868
- ...lastMessage.message.content,
869
- ...message.message.content,
870
- ],
871
- },
872
- }
873
- return
874
- }
875
- case 'assistant':
876
- result.push(message)
877
- return
878
- }
879
- })
880
- return result
881
- }
882
-
883
- // Sometimes the API returns empty messages (eg. "\n\n"). We need to filter these out,
884
- // otherwise they will give an API error when we send them to the API next time we call query().
885
- export function normalizeContentFromAPI(
886
- content: APIMessage['content'],
887
- ): APIMessage['content'] {
888
- const filteredContent = content.filter(
889
- _ => _.type !== 'text' || _.text.trim().length > 0,
890
- )
891
-
892
- if (filteredContent.length === 0) {
893
- return [{ type: 'text', text: NO_CONTENT_MESSAGE, citations: [] }]
894
- }
895
-
896
- return filteredContent
897
- }
898
-
899
- export function isEmptyMessageText(text: string): boolean {
900
- return (
901
- stripSystemMessages(text).trim() === '' ||
902
- text.trim() === NO_CONTENT_MESSAGE
903
- )
904
- }
905
- const STRIPPED_TAGS = [
906
- 'commit_analysis',
907
- 'context',
908
- 'function_analysis',
909
- 'pr_analysis',
910
- ]
911
-
912
- export function stripSystemMessages(content: string): string {
913
- const regex = new RegExp(`<(${STRIPPED_TAGS.join('|')})>.*?</\\1>\n?`, 'gs')
914
- return content.replace(regex, '').trim()
915
- }
916
-
917
- export function getToolUseID(message: NormalizedMessage): string | null {
918
- switch (message.type) {
919
- case 'assistant':
920
- if (message.message.content[0]?.type !== 'tool_use') {
921
- return null
922
- }
923
- return message.message.content[0].id
924
- case 'user':
925
- if (message.message.content[0]?.type !== 'tool_result') {
926
- return null
927
- }
928
- return message.message.content[0].tool_use_id
929
- case 'progress':
930
- return message.toolUseID
931
- }
932
- }
933
-
934
- export function getLastAssistantMessageId(
935
- messages: Message[],
936
- ): string | undefined {
937
- // Iterate from the end of the array to find the last assistant message
938
- for (let i = messages.length - 1; i >= 0; i--) {
939
- const message = messages[i]
940
- if (message && message.type === 'assistant') {
941
- return message.message.id
942
- }
943
- }
944
- return undefined
945
- }