@shareai-lab/kode 1.1.14 → 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.
- package/cli.js +77 -82
- package/dist/entrypoints/cli.js +59 -38
- package/dist/entrypoints/cli.js.map +3 -3
- package/dist/index.js +5 -26
- package/dist/package.json +4 -1
- package/package.json +11 -104
- package/dist/test/testAdapters.js +0 -88
- package/dist/test/testAdapters.js.map +0 -1
- package/src/ProjectOnboarding.tsx +0 -198
- package/src/Tool.ts +0 -83
- package/src/commands/agents.tsx +0 -3416
- package/src/commands/approvedTools.ts +0 -53
- package/src/commands/bug.tsx +0 -20
- package/src/commands/clear.ts +0 -43
- package/src/commands/compact.ts +0 -120
- package/src/commands/config.tsx +0 -19
- package/src/commands/cost.ts +0 -18
- package/src/commands/ctx_viz.ts +0 -209
- package/src/commands/doctor.ts +0 -24
- package/src/commands/help.tsx +0 -19
- package/src/commands/init.ts +0 -37
- package/src/commands/listen.ts +0 -42
- package/src/commands/login.tsx +0 -51
- package/src/commands/logout.tsx +0 -40
- package/src/commands/mcp.ts +0 -41
- package/src/commands/model.tsx +0 -40
- package/src/commands/modelstatus.tsx +0 -20
- package/src/commands/onboarding.tsx +0 -34
- package/src/commands/pr_comments.ts +0 -59
- package/src/commands/refreshCommands.ts +0 -54
- package/src/commands/release-notes.ts +0 -34
- package/src/commands/resume.tsx +0 -31
- package/src/commands/review.ts +0 -49
- package/src/commands/terminalSetup.ts +0 -221
- package/src/commands.ts +0 -139
- package/src/components/ApproveApiKey.tsx +0 -93
- package/src/components/AsciiLogo.tsx +0 -13
- package/src/components/AutoUpdater.tsx +0 -148
- package/src/components/Bug.tsx +0 -367
- package/src/components/Config.tsx +0 -293
- package/src/components/ConsoleOAuthFlow.tsx +0 -327
- package/src/components/Cost.tsx +0 -23
- package/src/components/CostThresholdDialog.tsx +0 -46
- package/src/components/CustomSelect/option-map.ts +0 -42
- package/src/components/CustomSelect/select-option.tsx +0 -78
- package/src/components/CustomSelect/select.tsx +0 -152
- package/src/components/CustomSelect/theme.ts +0 -45
- package/src/components/CustomSelect/use-select-state.ts +0 -414
- package/src/components/CustomSelect/use-select.ts +0 -35
- package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
- package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
- package/src/components/Help.tsx +0 -215
- package/src/components/HighlightedCode.tsx +0 -33
- package/src/components/InvalidConfigDialog.tsx +0 -113
- package/src/components/Link.tsx +0 -32
- package/src/components/LogSelector.tsx +0 -86
- package/src/components/Logo.tsx +0 -170
- package/src/components/MCPServerApprovalDialog.tsx +0 -100
- package/src/components/MCPServerDialogCopy.tsx +0 -25
- package/src/components/MCPServerMultiselectDialog.tsx +0 -109
- package/src/components/Message.tsx +0 -221
- package/src/components/MessageResponse.tsx +0 -15
- package/src/components/MessageSelector.tsx +0 -211
- package/src/components/ModeIndicator.tsx +0 -88
- package/src/components/ModelConfig.tsx +0 -301
- package/src/components/ModelListManager.tsx +0 -227
- package/src/components/ModelSelector.tsx +0 -3387
- package/src/components/ModelStatusDisplay.tsx +0 -230
- package/src/components/Onboarding.tsx +0 -274
- package/src/components/PressEnterToContinue.tsx +0 -11
- package/src/components/PromptInput.tsx +0 -760
- package/src/components/SentryErrorBoundary.ts +0 -39
- package/src/components/Spinner.tsx +0 -129
- package/src/components/StickerRequestForm.tsx +0 -16
- package/src/components/StructuredDiff.tsx +0 -191
- package/src/components/TextInput.tsx +0 -259
- package/src/components/TodoItem.tsx +0 -47
- package/src/components/TokenWarning.tsx +0 -31
- package/src/components/ToolUseLoader.tsx +0 -40
- package/src/components/TrustDialog.tsx +0 -106
- package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
- package/src/components/binary-feedback/utils.ts +0 -220
- package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
- package/src/components/messages/AssistantTextMessage.tsx +0 -144
- package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
- package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
- package/src/components/messages/TaskProgressMessage.tsx +0 -32
- package/src/components/messages/TaskToolMessage.tsx +0 -58
- package/src/components/messages/UserBashInputMessage.tsx +0 -28
- package/src/components/messages/UserCommandMessage.tsx +0 -30
- package/src/components/messages/UserKodingInputMessage.tsx +0 -28
- package/src/components/messages/UserPromptMessage.tsx +0 -35
- package/src/components/messages/UserTextMessage.tsx +0 -39
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
- package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
- package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
- package/src/components/permissions/PermissionRequest.tsx +0 -101
- package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
- package/src/components/permissions/hooks.ts +0 -44
- package/src/components/permissions/toolUseOptions.ts +0 -59
- package/src/components/permissions/utils.ts +0 -23
- package/src/constants/betas.ts +0 -5
- package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
- package/src/constants/figures.ts +0 -4
- package/src/constants/keys.ts +0 -3
- package/src/constants/macros.ts +0 -11
- package/src/constants/modelCapabilities.ts +0 -179
- package/src/constants/models.ts +0 -1025
- package/src/constants/oauth.ts +0 -18
- package/src/constants/product.ts +0 -17
- package/src/constants/prompts.ts +0 -168
- package/src/constants/releaseNotes.ts +0 -7
- package/src/context/PermissionContext.tsx +0 -149
- package/src/context.ts +0 -278
- package/src/cost-tracker.ts +0 -84
- package/src/entrypoints/cli.tsx +0 -1561
- package/src/entrypoints/mcp.ts +0 -175
- package/src/history.ts +0 -25
- package/src/hooks/useApiKeyVerification.ts +0 -59
- package/src/hooks/useArrowKeyHistory.ts +0 -55
- package/src/hooks/useCanUseTool.ts +0 -138
- package/src/hooks/useCancelRequest.ts +0 -39
- package/src/hooks/useDoublePress.ts +0 -41
- package/src/hooks/useExitOnCtrlCD.ts +0 -31
- package/src/hooks/useInterval.ts +0 -25
- package/src/hooks/useLogMessages.ts +0 -16
- package/src/hooks/useLogStartupTime.ts +0 -12
- package/src/hooks/useNotifyAfterTimeout.ts +0 -65
- package/src/hooks/usePermissionRequestLogging.ts +0 -44
- package/src/hooks/useTerminalSize.ts +0 -49
- package/src/hooks/useTextInput.ts +0 -317
- package/src/hooks/useUnifiedCompletion.ts +0 -1405
- package/src/index.ts +0 -34
- package/src/messages.ts +0 -38
- package/src/permissions.ts +0 -268
- package/src/query.ts +0 -720
- package/src/screens/ConfigureNpmPrefix.tsx +0 -197
- package/src/screens/Doctor.tsx +0 -219
- package/src/screens/LogList.tsx +0 -68
- package/src/screens/REPL.tsx +0 -813
- package/src/screens/ResumeConversation.tsx +0 -68
- package/src/services/adapters/base.ts +0 -38
- package/src/services/adapters/chatCompletions.ts +0 -90
- package/src/services/adapters/responsesAPI.ts +0 -170
- package/src/services/browserMocks.ts +0 -66
- package/src/services/claude.ts +0 -2197
- package/src/services/customCommands.ts +0 -704
- package/src/services/fileFreshness.ts +0 -377
- package/src/services/gpt5ConnectionTest.ts +0 -340
- package/src/services/mcpClient.ts +0 -564
- package/src/services/mcpServerApproval.tsx +0 -50
- package/src/services/mentionProcessor.ts +0 -273
- package/src/services/modelAdapterFactory.ts +0 -69
- package/src/services/notifier.ts +0 -40
- package/src/services/oauth.ts +0 -357
- package/src/services/openai.ts +0 -1359
- package/src/services/responseStateManager.ts +0 -90
- package/src/services/sentry.ts +0 -3
- package/src/services/statsig.ts +0 -172
- package/src/services/statsigStorage.ts +0 -86
- package/src/services/systemReminder.ts +0 -507
- package/src/services/vcr.ts +0 -161
- package/src/test/testAdapters.ts +0 -96
- package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
- package/src/tools/ArchitectTool/prompt.ts +0 -15
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
- package/src/tools/BashTool/BashTool.tsx +0 -243
- package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
- package/src/tools/BashTool/OutputLine.tsx +0 -49
- package/src/tools/BashTool/prompt.ts +0 -174
- package/src/tools/BashTool/utils.ts +0 -56
- package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
- package/src/tools/FileEditTool/prompt.ts +0 -51
- package/src/tools/FileEditTool/utils.ts +0 -58
- package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
- package/src/tools/FileReadTool/prompt.ts +0 -7
- package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
- package/src/tools/FileWriteTool/prompt.ts +0 -10
- package/src/tools/GlobTool/GlobTool.tsx +0 -119
- package/src/tools/GlobTool/prompt.ts +0 -8
- package/src/tools/GrepTool/GrepTool.tsx +0 -147
- package/src/tools/GrepTool/prompt.ts +0 -11
- package/src/tools/MCPTool/MCPTool.tsx +0 -107
- package/src/tools/MCPTool/prompt.ts +0 -3
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
- package/src/tools/MemoryReadTool/prompt.ts +0 -3
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
- package/src/tools/MemoryWriteTool/prompt.ts +0 -3
- package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
- package/src/tools/MultiEditTool/prompt.ts +0 -45
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
- package/src/tools/NotebookEditTool/prompt.ts +0 -3
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
- package/src/tools/NotebookReadTool/prompt.ts +0 -3
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
- package/src/tools/StickerRequestTool/prompt.ts +0 -19
- package/src/tools/TaskTool/TaskTool.tsx +0 -438
- package/src/tools/TaskTool/constants.ts +0 -1
- package/src/tools/TaskTool/prompt.ts +0 -92
- package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
- package/src/tools/ThinkTool/prompt.ts +0 -12
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
- package/src/tools/TodoWriteTool/prompt.ts +0 -63
- package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
- package/src/tools/URLFetcherTool/cache.ts +0 -55
- package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
- package/src/tools/URLFetcherTool/prompt.ts +0 -17
- package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
- package/src/tools/WebSearchTool/prompt.ts +0 -13
- package/src/tools/WebSearchTool/searchProviders.ts +0 -66
- package/src/tools/lsTool/lsTool.tsx +0 -272
- package/src/tools/lsTool/prompt.ts +0 -2
- package/src/tools.ts +0 -67
- package/src/types/PermissionMode.ts +0 -120
- package/src/types/RequestContext.ts +0 -72
- package/src/types/common.d.ts +0 -2
- package/src/types/conversation.ts +0 -51
- package/src/types/logs.ts +0 -58
- package/src/types/modelCapabilities.ts +0 -64
- package/src/types/notebook.ts +0 -87
- package/src/utils/Cursor.ts +0 -436
- package/src/utils/PersistentShell.ts +0 -552
- package/src/utils/advancedFuzzyMatcher.ts +0 -290
- package/src/utils/agentLoader.ts +0 -278
- package/src/utils/agentStorage.ts +0 -97
- package/src/utils/array.ts +0 -3
- package/src/utils/ask.tsx +0 -99
- package/src/utils/auth.ts +0 -13
- package/src/utils/autoCompactCore.ts +0 -223
- package/src/utils/autoUpdater.ts +0 -458
- package/src/utils/betas.ts +0 -20
- package/src/utils/browser.ts +0 -14
- package/src/utils/cleanup.ts +0 -72
- package/src/utils/commands.ts +0 -261
- package/src/utils/commonUnixCommands.ts +0 -161
- package/src/utils/config.ts +0 -945
- package/src/utils/conversationRecovery.ts +0 -55
- package/src/utils/debugLogger.ts +0 -1235
- package/src/utils/diff.ts +0 -42
- package/src/utils/env.ts +0 -57
- package/src/utils/errors.ts +0 -21
- package/src/utils/exampleCommands.ts +0 -109
- package/src/utils/execFileNoThrow.ts +0 -51
- package/src/utils/expertChatStorage.ts +0 -136
- package/src/utils/file.ts +0 -405
- package/src/utils/fileRecoveryCore.ts +0 -71
- package/src/utils/format.tsx +0 -44
- package/src/utils/fuzzyMatcher.ts +0 -328
- package/src/utils/generators.ts +0 -62
- package/src/utils/git.ts +0 -92
- package/src/utils/globalLogger.ts +0 -77
- package/src/utils/http.ts +0 -10
- package/src/utils/imagePaste.ts +0 -38
- package/src/utils/json.ts +0 -13
- package/src/utils/log.ts +0 -382
- package/src/utils/markdown.ts +0 -213
- package/src/utils/messageContextManager.ts +0 -294
- package/src/utils/messages.tsx +0 -945
- package/src/utils/model.ts +0 -914
- package/src/utils/permissions/filesystem.ts +0 -127
- package/src/utils/responseState.ts +0 -23
- package/src/utils/ripgrep.ts +0 -167
- package/src/utils/secureFile.ts +0 -564
- package/src/utils/sessionState.ts +0 -49
- package/src/utils/state.ts +0 -25
- package/src/utils/style.ts +0 -29
- package/src/utils/terminal.ts +0 -50
- package/src/utils/theme.ts +0 -127
- package/src/utils/thinking.ts +0 -144
- package/src/utils/todoStorage.ts +0 -431
- package/src/utils/tokens.ts +0 -43
- package/src/utils/toolExecutionController.ts +0 -163
- package/src/utils/unaryLogging.ts +0 -26
- package/src/utils/user.ts +0 -37
- package/src/utils/validate.ts +0 -165
package/src/utils/messages.tsx
DELETED
|
@@ -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
|
-
}
|