@shareai-lab/kode 1.0.69 → 1.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +205 -72
- package/README.zh-CN.md +246 -0
- package/cli.js +62 -0
- package/package.json +45 -25
- package/scripts/postinstall.js +56 -0
- package/src/ProjectOnboarding.tsx +180 -0
- package/src/Tool.ts +53 -0
- package/src/commands/approvedTools.ts +53 -0
- package/src/commands/bug.tsx +20 -0
- package/src/commands/clear.ts +43 -0
- package/src/commands/compact.ts +120 -0
- package/src/commands/config.tsx +19 -0
- package/src/commands/cost.ts +18 -0
- package/src/commands/ctx_viz.ts +209 -0
- package/src/commands/doctor.ts +24 -0
- package/src/commands/help.tsx +19 -0
- package/src/commands/init.ts +37 -0
- package/src/commands/listen.ts +42 -0
- package/src/commands/login.tsx +51 -0
- package/src/commands/logout.tsx +40 -0
- package/src/commands/mcp.ts +41 -0
- package/src/commands/model.tsx +40 -0
- package/src/commands/modelstatus.tsx +20 -0
- package/src/commands/onboarding.tsx +34 -0
- package/src/commands/pr_comments.ts +59 -0
- package/src/commands/refreshCommands.ts +54 -0
- package/src/commands/release-notes.ts +34 -0
- package/src/commands/resume.tsx +30 -0
- package/src/commands/review.ts +49 -0
- package/src/commands/terminalSetup.ts +221 -0
- package/src/commands.ts +136 -0
- package/src/components/ApproveApiKey.tsx +93 -0
- package/src/components/AsciiLogo.tsx +13 -0
- package/src/components/AutoUpdater.tsx +148 -0
- package/src/components/Bug.tsx +367 -0
- package/src/components/Config.tsx +289 -0
- package/src/components/ConsoleOAuthFlow.tsx +326 -0
- package/src/components/Cost.tsx +23 -0
- package/src/components/CostThresholdDialog.tsx +46 -0
- package/src/components/CustomSelect/option-map.ts +42 -0
- package/src/components/CustomSelect/select-option.tsx +52 -0
- package/src/components/CustomSelect/select.tsx +143 -0
- package/src/components/CustomSelect/use-select-state.ts +414 -0
- package/src/components/CustomSelect/use-select.ts +35 -0
- package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
- package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
- package/src/components/Help.tsx +215 -0
- package/src/components/HighlightedCode.tsx +33 -0
- package/src/components/InvalidConfigDialog.tsx +113 -0
- package/src/components/Link.tsx +32 -0
- package/src/components/LogSelector.tsx +86 -0
- package/src/components/Logo.tsx +145 -0
- package/src/components/MCPServerApprovalDialog.tsx +100 -0
- package/src/components/MCPServerDialogCopy.tsx +25 -0
- package/src/components/MCPServerMultiselectDialog.tsx +109 -0
- package/src/components/Message.tsx +219 -0
- package/src/components/MessageResponse.tsx +15 -0
- package/src/components/MessageSelector.tsx +211 -0
- package/src/components/ModeIndicator.tsx +88 -0
- package/src/components/ModelConfig.tsx +301 -0
- package/src/components/ModelListManager.tsx +223 -0
- package/src/components/ModelSelector.tsx +3208 -0
- package/src/components/ModelStatusDisplay.tsx +228 -0
- package/src/components/Onboarding.tsx +274 -0
- package/src/components/PressEnterToContinue.tsx +11 -0
- package/src/components/PromptInput.tsx +710 -0
- package/src/components/SentryErrorBoundary.ts +33 -0
- package/src/components/Spinner.tsx +129 -0
- package/src/components/StructuredDiff.tsx +184 -0
- package/src/components/TextInput.tsx +246 -0
- package/src/components/TokenWarning.tsx +31 -0
- package/src/components/ToolUseLoader.tsx +40 -0
- package/src/components/TrustDialog.tsx +106 -0
- package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
- package/src/components/binary-feedback/utils.ts +220 -0
- package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +45 -0
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
- package/src/components/messages/AssistantTextMessage.tsx +144 -0
- package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
- package/src/components/messages/AssistantToolUseMessage.tsx +123 -0
- package/src/components/messages/UserBashInputMessage.tsx +28 -0
- package/src/components/messages/UserCommandMessage.tsx +30 -0
- package/src/components/messages/UserKodingInputMessage.tsx +28 -0
- package/src/components/messages/UserPromptMessage.tsx +35 -0
- package/src/components/messages/UserTextMessage.tsx +39 -0
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
- package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
- package/src/components/permissions/FallbackPermissionRequest.tsx +155 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +75 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +81 -0
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +242 -0
- package/src/components/permissions/PermissionRequest.tsx +103 -0
- package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
- package/src/components/permissions/hooks.ts +44 -0
- package/src/components/permissions/toolUseOptions.ts +59 -0
- package/src/components/permissions/utils.ts +23 -0
- package/src/constants/betas.ts +5 -0
- package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
- package/src/constants/figures.ts +4 -0
- package/src/constants/keys.ts +3 -0
- package/src/constants/macros.ts +6 -0
- package/src/constants/models.ts +935 -0
- package/src/constants/oauth.ts +18 -0
- package/src/constants/product.ts +17 -0
- package/src/constants/prompts.ts +177 -0
- package/src/constants/releaseNotes.ts +7 -0
- package/src/context/PermissionContext.tsx +149 -0
- package/src/context.ts +278 -0
- package/src/cost-tracker.ts +84 -0
- package/src/entrypoints/cli.tsx +1498 -0
- package/src/entrypoints/mcp.ts +176 -0
- package/src/history.ts +25 -0
- package/src/hooks/useApiKeyVerification.ts +59 -0
- package/src/hooks/useArrowKeyHistory.ts +55 -0
- package/src/hooks/useCanUseTool.ts +138 -0
- package/src/hooks/useCancelRequest.ts +39 -0
- package/src/hooks/useDoublePress.ts +42 -0
- package/src/hooks/useExitOnCtrlCD.ts +31 -0
- package/src/hooks/useInterval.ts +25 -0
- package/src/hooks/useLogMessages.ts +16 -0
- package/src/hooks/useLogStartupTime.ts +12 -0
- package/src/hooks/useNotifyAfterTimeout.ts +65 -0
- package/src/hooks/usePermissionRequestLogging.ts +44 -0
- package/src/hooks/useSlashCommandTypeahead.ts +137 -0
- package/src/hooks/useTerminalSize.ts +49 -0
- package/src/hooks/useTextInput.ts +315 -0
- package/src/messages.ts +37 -0
- package/src/permissions.ts +268 -0
- package/src/query.ts +704 -0
- package/src/screens/ConfigureNpmPrefix.tsx +197 -0
- package/src/screens/Doctor.tsx +219 -0
- package/src/screens/LogList.tsx +68 -0
- package/src/screens/REPL.tsx +792 -0
- package/src/screens/ResumeConversation.tsx +68 -0
- package/src/services/browserMocks.ts +66 -0
- package/src/services/claude.ts +1947 -0
- package/src/services/customCommands.ts +683 -0
- package/src/services/fileFreshness.ts +377 -0
- package/src/services/mcpClient.ts +564 -0
- package/src/services/mcpServerApproval.tsx +50 -0
- package/src/services/notifier.ts +40 -0
- package/src/services/oauth.ts +357 -0
- package/src/services/openai.ts +796 -0
- package/src/services/sentry.ts +3 -0
- package/src/services/statsig.ts +171 -0
- package/src/services/statsigStorage.ts +86 -0
- package/src/services/systemReminder.ts +406 -0
- package/src/services/vcr.ts +161 -0
- package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
- package/src/tools/ArchitectTool/prompt.ts +15 -0
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +505 -0
- package/src/tools/BashTool/BashTool.tsx +270 -0
- package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
- package/src/tools/BashTool/OutputLine.tsx +48 -0
- package/src/tools/BashTool/prompt.ts +174 -0
- package/src/tools/BashTool/utils.ts +56 -0
- package/src/tools/FileEditTool/FileEditTool.tsx +316 -0
- package/src/tools/FileEditTool/prompt.ts +51 -0
- package/src/tools/FileEditTool/utils.ts +58 -0
- package/src/tools/FileReadTool/FileReadTool.tsx +371 -0
- package/src/tools/FileReadTool/prompt.ts +7 -0
- package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
- package/src/tools/FileWriteTool/prompt.ts +10 -0
- package/src/tools/GlobTool/GlobTool.tsx +119 -0
- package/src/tools/GlobTool/prompt.ts +8 -0
- package/src/tools/GrepTool/GrepTool.tsx +147 -0
- package/src/tools/GrepTool/prompt.ts +11 -0
- package/src/tools/MCPTool/MCPTool.tsx +106 -0
- package/src/tools/MCPTool/prompt.ts +3 -0
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
- package/src/tools/MemoryReadTool/prompt.ts +3 -0
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
- package/src/tools/MemoryWriteTool/prompt.ts +3 -0
- package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
- package/src/tools/MultiEditTool/prompt.ts +45 -0
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
- package/src/tools/NotebookEditTool/prompt.ts +3 -0
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +266 -0
- package/src/tools/NotebookReadTool/prompt.ts +3 -0
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
- package/src/tools/StickerRequestTool/prompt.ts +19 -0
- package/src/tools/TaskTool/TaskTool.tsx +382 -0
- package/src/tools/TaskTool/constants.ts +1 -0
- package/src/tools/TaskTool/prompt.ts +56 -0
- package/src/tools/ThinkTool/ThinkTool.tsx +56 -0
- package/src/tools/ThinkTool/prompt.ts +12 -0
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +289 -0
- package/src/tools/TodoWriteTool/prompt.ts +63 -0
- package/src/tools/lsTool/lsTool.tsx +269 -0
- package/src/tools/lsTool/prompt.ts +2 -0
- package/src/tools.ts +63 -0
- package/src/types/PermissionMode.ts +120 -0
- package/src/types/RequestContext.ts +72 -0
- package/src/utils/Cursor.ts +436 -0
- package/src/utils/PersistentShell.ts +373 -0
- package/src/utils/agentStorage.ts +97 -0
- package/src/utils/array.ts +3 -0
- package/src/utils/ask.tsx +98 -0
- package/src/utils/auth.ts +13 -0
- package/src/utils/autoCompactCore.ts +223 -0
- package/src/utils/autoUpdater.ts +318 -0
- package/src/utils/betas.ts +20 -0
- package/src/utils/browser.ts +14 -0
- package/src/utils/cleanup.ts +72 -0
- package/src/utils/commands.ts +261 -0
- package/src/utils/config.ts +771 -0
- package/src/utils/conversationRecovery.ts +54 -0
- package/src/utils/debugLogger.ts +1123 -0
- package/src/utils/diff.ts +42 -0
- package/src/utils/env.ts +57 -0
- package/src/utils/errors.ts +21 -0
- package/src/utils/exampleCommands.ts +108 -0
- package/src/utils/execFileNoThrow.ts +51 -0
- package/src/utils/expertChatStorage.ts +136 -0
- package/src/utils/file.ts +402 -0
- package/src/utils/fileRecoveryCore.ts +71 -0
- package/src/utils/format.tsx +44 -0
- package/src/utils/generators.ts +62 -0
- package/src/utils/git.ts +92 -0
- package/src/utils/globalLogger.ts +77 -0
- package/src/utils/http.ts +10 -0
- package/src/utils/imagePaste.ts +38 -0
- package/src/utils/json.ts +13 -0
- package/src/utils/log.ts +382 -0
- package/src/utils/markdown.ts +213 -0
- package/src/utils/messageContextManager.ts +289 -0
- package/src/utils/messages.tsx +938 -0
- package/src/utils/model.ts +836 -0
- package/src/utils/permissions/filesystem.ts +118 -0
- package/src/utils/ripgrep.ts +167 -0
- package/src/utils/sessionState.ts +49 -0
- package/src/utils/state.ts +25 -0
- package/src/utils/style.ts +29 -0
- package/src/utils/terminal.ts +49 -0
- package/src/utils/theme.ts +122 -0
- package/src/utils/thinking.ts +144 -0
- package/src/utils/todoStorage.ts +431 -0
- package/src/utils/tokens.ts +43 -0
- package/src/utils/toolExecutionController.ts +163 -0
- package/src/utils/unaryLogging.ts +26 -0
- package/src/utils/user.ts +37 -0
- package/src/utils/validate.ts +165 -0
- package/cli.mjs +0 -1803
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Box, Text, useInput } from 'ink'
|
|
3
|
+
import { getTheme } from '../utils/theme'
|
|
4
|
+
import { Select } from '@inkjs/ui'
|
|
5
|
+
import {
|
|
6
|
+
saveCurrentProjectConfig,
|
|
7
|
+
getCurrentProjectConfig,
|
|
8
|
+
} from '../utils/config.js'
|
|
9
|
+
import { PRODUCT_NAME } from '../constants/product'
|
|
10
|
+
import { logEvent } from '../services/statsig'
|
|
11
|
+
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
|
|
12
|
+
import { homedir } from 'os'
|
|
13
|
+
import { getCwd } from '../utils/state'
|
|
14
|
+
import Link from './Link'
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
onDone(): void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function TrustDialog({ onDone }: Props): React.ReactNode {
|
|
21
|
+
const theme = getTheme()
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
// Log when dialog is shown
|
|
24
|
+
logEvent('trust_dialog_shown', {})
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
function onChange(value: 'yes' | 'no') {
|
|
28
|
+
const config = getCurrentProjectConfig()
|
|
29
|
+
switch (value) {
|
|
30
|
+
case 'yes': {
|
|
31
|
+
// Log when user accepts
|
|
32
|
+
const isHomeDir = homedir() === getCwd()
|
|
33
|
+
logEvent('trust_dialog_accept', {
|
|
34
|
+
isHomeDir: String(isHomeDir),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
if (!isHomeDir) {
|
|
38
|
+
saveCurrentProjectConfig({
|
|
39
|
+
...config,
|
|
40
|
+
hasTrustDialogAccepted: true,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
onDone()
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
case 'no': {
|
|
47
|
+
process.exit(1)
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const exitState = useExitOnCtrlCD(() => process.exit(0))
|
|
54
|
+
|
|
55
|
+
useInput((_input, key) => {
|
|
56
|
+
if (key.escape) {
|
|
57
|
+
process.exit(0)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<>
|
|
64
|
+
<Box
|
|
65
|
+
flexDirection="column"
|
|
66
|
+
gap={1}
|
|
67
|
+
padding={1}
|
|
68
|
+
borderStyle="round"
|
|
69
|
+
borderColor={theme.warning}
|
|
70
|
+
>
|
|
71
|
+
<Text bold color={theme.warning}>
|
|
72
|
+
Do you trust the files in this folder?
|
|
73
|
+
</Text>
|
|
74
|
+
<Text bold>{process.cwd()}</Text>
|
|
75
|
+
|
|
76
|
+
<Box flexDirection="column" gap={1}>
|
|
77
|
+
<Text>
|
|
78
|
+
{PRODUCT_NAME} may read files in this folder. Reading untrusted
|
|
79
|
+
files may lead to {PRODUCT_NAME} to behave in an unexpected ways.
|
|
80
|
+
</Text>
|
|
81
|
+
<Text>
|
|
82
|
+
With your permission {PRODUCT_NAME} may execute files in this
|
|
83
|
+
folder. Executing untrusted code is unsafe.
|
|
84
|
+
</Text>
|
|
85
|
+
</Box>
|
|
86
|
+
|
|
87
|
+
<Select
|
|
88
|
+
options={[
|
|
89
|
+
{ label: 'Yes, proceed', value: 'yes' },
|
|
90
|
+
{ label: 'No, exit', value: 'no' },
|
|
91
|
+
]}
|
|
92
|
+
onChange={value => onChange(value as 'yes' | 'no')}
|
|
93
|
+
/>
|
|
94
|
+
</Box>
|
|
95
|
+
<Box marginLeft={3}>
|
|
96
|
+
<Text dimColor>
|
|
97
|
+
{exitState.pending ? (
|
|
98
|
+
<>Press {exitState.keyName} again to exit</>
|
|
99
|
+
) : (
|
|
100
|
+
<>Enter to confirm · Esc to exit</>
|
|
101
|
+
)}
|
|
102
|
+
</Text>
|
|
103
|
+
</Box>
|
|
104
|
+
</>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { default as React, useCallback } from 'react'
|
|
2
|
+
import { useNotifyAfterTimeout } from '../../hooks/useNotifyAfterTimeout'
|
|
3
|
+
import { AssistantMessage, BinaryFeedbackResult } from '../../query'
|
|
4
|
+
import type { Tool } from '../../Tool'
|
|
5
|
+
import type { NormalizedMessage } from '../../utils/messages'
|
|
6
|
+
import { BinaryFeedbackView } from './BinaryFeedbackView'
|
|
7
|
+
import {
|
|
8
|
+
type BinaryFeedbackChoose,
|
|
9
|
+
getBinaryFeedbackResultForChoice,
|
|
10
|
+
logBinaryFeedbackEvent,
|
|
11
|
+
} from './utils.js'
|
|
12
|
+
import { PRODUCT_NAME } from '../../constants/product'
|
|
13
|
+
|
|
14
|
+
type Props = {
|
|
15
|
+
m1: AssistantMessage
|
|
16
|
+
m2: AssistantMessage
|
|
17
|
+
resolve: (result: BinaryFeedbackResult) => void
|
|
18
|
+
debug: boolean
|
|
19
|
+
erroredToolUseIDs: Set<string>
|
|
20
|
+
inProgressToolUseIDs: Set<string>
|
|
21
|
+
normalizedMessages: NormalizedMessage[]
|
|
22
|
+
tools: Tool[]
|
|
23
|
+
unresolvedToolUseIDs: Set<string>
|
|
24
|
+
verbose: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function BinaryFeedback({
|
|
28
|
+
m1,
|
|
29
|
+
m2,
|
|
30
|
+
resolve,
|
|
31
|
+
debug,
|
|
32
|
+
erroredToolUseIDs,
|
|
33
|
+
inProgressToolUseIDs,
|
|
34
|
+
normalizedMessages,
|
|
35
|
+
tools,
|
|
36
|
+
unresolvedToolUseIDs,
|
|
37
|
+
verbose,
|
|
38
|
+
}: Props): React.ReactNode {
|
|
39
|
+
const onChoose = useCallback<BinaryFeedbackChoose>(
|
|
40
|
+
choice => {
|
|
41
|
+
logBinaryFeedbackEvent(m1, m2, choice)
|
|
42
|
+
resolve(getBinaryFeedbackResultForChoice(m1, m2, choice))
|
|
43
|
+
},
|
|
44
|
+
[m1, m2, resolve],
|
|
45
|
+
)
|
|
46
|
+
useNotifyAfterTimeout(
|
|
47
|
+
`${PRODUCT_NAME} needs your input on a response comparison`,
|
|
48
|
+
)
|
|
49
|
+
return (
|
|
50
|
+
<BinaryFeedbackView
|
|
51
|
+
debug={debug}
|
|
52
|
+
erroredToolUseIDs={erroredToolUseIDs}
|
|
53
|
+
inProgressToolUseIDs={inProgressToolUseIDs}
|
|
54
|
+
m1={m1}
|
|
55
|
+
m2={m2}
|
|
56
|
+
normalizedMessages={normalizedMessages}
|
|
57
|
+
tools={tools}
|
|
58
|
+
unresolvedToolUseIDs={unresolvedToolUseIDs}
|
|
59
|
+
verbose={verbose}
|
|
60
|
+
onChoose={onChoose}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { FileEditTool } from '../../tools/FileEditTool/FileEditTool'
|
|
2
|
+
import { FileEditToolDiff } from '../permissions/FileEditPermissionRequest/FileEditToolDiff'
|
|
3
|
+
import { Message } from '../Message'
|
|
4
|
+
import {
|
|
5
|
+
normalizeMessages,
|
|
6
|
+
type NormalizedMessage,
|
|
7
|
+
} from '../../utils/messages.js'
|
|
8
|
+
import type { Tool } from '../../Tool'
|
|
9
|
+
import { useTerminalSize } from '../../hooks/useTerminalSize'
|
|
10
|
+
import { FileWriteTool } from '../../tools/FileWriteTool/FileWriteTool'
|
|
11
|
+
import { FileWriteToolDiff } from '../permissions/FileWritePermissionRequest/FileWriteToolDiff'
|
|
12
|
+
import type { AssistantMessage } from '../../query'
|
|
13
|
+
import * as React from 'react'
|
|
14
|
+
import { Box } from 'ink'
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
debug: boolean
|
|
18
|
+
erroredToolUseIDs: Set<string>
|
|
19
|
+
inProgressToolUseIDs: Set<string>
|
|
20
|
+
message: AssistantMessage
|
|
21
|
+
normalizedMessages: NormalizedMessage[]
|
|
22
|
+
tools: Tool[]
|
|
23
|
+
unresolvedToolUseIDs: Set<string>
|
|
24
|
+
verbose: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function BinaryFeedbackOption({
|
|
28
|
+
debug,
|
|
29
|
+
erroredToolUseIDs,
|
|
30
|
+
inProgressToolUseIDs,
|
|
31
|
+
message,
|
|
32
|
+
normalizedMessages,
|
|
33
|
+
tools,
|
|
34
|
+
unresolvedToolUseIDs,
|
|
35
|
+
verbose,
|
|
36
|
+
}: Props): React.ReactNode {
|
|
37
|
+
const { columns } = useTerminalSize()
|
|
38
|
+
return normalizeMessages([message])
|
|
39
|
+
.filter(_ => _.type !== 'progress')
|
|
40
|
+
.map((_, index) => (
|
|
41
|
+
<Box flexDirection="column" key={index}>
|
|
42
|
+
<Message
|
|
43
|
+
addMargin={false}
|
|
44
|
+
erroredToolUseIDs={erroredToolUseIDs}
|
|
45
|
+
debug={debug}
|
|
46
|
+
inProgressToolUseIDs={inProgressToolUseIDs}
|
|
47
|
+
message={_}
|
|
48
|
+
messages={normalizedMessages}
|
|
49
|
+
shouldAnimate={false}
|
|
50
|
+
shouldShowDot={true}
|
|
51
|
+
tools={tools}
|
|
52
|
+
unresolvedToolUseIDs={unresolvedToolUseIDs}
|
|
53
|
+
verbose={verbose}
|
|
54
|
+
width={columns / 2 - 6}
|
|
55
|
+
/>
|
|
56
|
+
<AdditionalContext message={_} verbose={verbose} />
|
|
57
|
+
</Box>
|
|
58
|
+
))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function AdditionalContext({
|
|
62
|
+
message,
|
|
63
|
+
verbose,
|
|
64
|
+
}: {
|
|
65
|
+
message: NormalizedMessage
|
|
66
|
+
verbose: boolean
|
|
67
|
+
}) {
|
|
68
|
+
const { columns } = useTerminalSize()
|
|
69
|
+
if (message.type !== 'assistant') {
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
const content = message.message.content[0]!
|
|
73
|
+
switch (content.type) {
|
|
74
|
+
case 'tool_use':
|
|
75
|
+
switch (content.name) {
|
|
76
|
+
case FileEditTool.name: {
|
|
77
|
+
const input = FileEditTool.inputSchema.safeParse(content.input)
|
|
78
|
+
if (!input.success) {
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
return (
|
|
82
|
+
<FileEditToolDiff
|
|
83
|
+
file_path={input.data.file_path}
|
|
84
|
+
new_string={input.data.new_string}
|
|
85
|
+
old_string={input.data.old_string}
|
|
86
|
+
verbose={verbose}
|
|
87
|
+
width={columns / 2 - 12}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
case FileWriteTool.name: {
|
|
92
|
+
const input = FileWriteTool.inputSchema.safeParse(content.input)
|
|
93
|
+
if (!input.success) {
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
return (
|
|
97
|
+
<FileWriteToolDiff
|
|
98
|
+
file_path={input.data.file_path}
|
|
99
|
+
content={input.data.content}
|
|
100
|
+
verbose={verbose}
|
|
101
|
+
width={columns / 2 - 12}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
default:
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
default:
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Option, SelectProps } from '@inkjs/ui'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { Box, Text, useInput } from 'ink'
|
|
4
|
+
import Link from 'ink-link'
|
|
5
|
+
import React, { useState } from 'react'
|
|
6
|
+
import { getTheme } from '../../utils/theme'
|
|
7
|
+
import { Select } from '../CustomSelect/select'
|
|
8
|
+
import type { Tool } from '../../Tool'
|
|
9
|
+
import type { NormalizedMessage } from '../../utils/messages'
|
|
10
|
+
import { BinaryFeedbackOption } from './BinaryFeedbackOption'
|
|
11
|
+
import type { AssistantMessage } from '../../query'
|
|
12
|
+
import type { BinaryFeedbackChoose } from './utils'
|
|
13
|
+
import { useExitOnCtrlCD } from '../../hooks/useExitOnCtrlCD'
|
|
14
|
+
import { BinaryFeedbackChoice } from './utils'
|
|
15
|
+
import { PRODUCT_NAME } from '../../constants/product'
|
|
16
|
+
|
|
17
|
+
const HELP_URL = 'https://go/cli-feedback'
|
|
18
|
+
|
|
19
|
+
type BinaryFeedbackOption = Option & { value: BinaryFeedbackChoice }
|
|
20
|
+
|
|
21
|
+
// Make options a function to avoid early theme access during module initialization
|
|
22
|
+
export function getOptions(): BinaryFeedbackOption[] {
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
// This option combines the follow user intents:
|
|
26
|
+
// - The two options look about equally good to me
|
|
27
|
+
// - I don't feel confident enough to choose
|
|
28
|
+
// - I don't want to choose right now
|
|
29
|
+
label: 'Choose for me',
|
|
30
|
+
value: 'no-preference',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'Left option looks better',
|
|
34
|
+
value: 'prefer-left',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: 'Right option looks better',
|
|
38
|
+
value: 'prefer-right',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: `Neither, and tell ${PRODUCT_NAME} what to do differently (${chalk.bold.hex(getTheme().warning)('esc')})`,
|
|
42
|
+
value: 'neither',
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type Props = {
|
|
48
|
+
m1: AssistantMessage
|
|
49
|
+
m2: AssistantMessage
|
|
50
|
+
onChoose?: BinaryFeedbackChoose
|
|
51
|
+
debug: boolean
|
|
52
|
+
erroredToolUseIDs: Set<string>
|
|
53
|
+
inProgressToolUseIDs: Set<string>
|
|
54
|
+
normalizedMessages: NormalizedMessage[]
|
|
55
|
+
tools: Tool[]
|
|
56
|
+
unresolvedToolUseIDs: Set<string>
|
|
57
|
+
verbose: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function BinaryFeedbackView({
|
|
61
|
+
m1,
|
|
62
|
+
m2,
|
|
63
|
+
onChoose,
|
|
64
|
+
debug,
|
|
65
|
+
erroredToolUseIDs,
|
|
66
|
+
inProgressToolUseIDs,
|
|
67
|
+
normalizedMessages,
|
|
68
|
+
tools,
|
|
69
|
+
unresolvedToolUseIDs,
|
|
70
|
+
verbose,
|
|
71
|
+
}: Props) {
|
|
72
|
+
const theme = getTheme()
|
|
73
|
+
const [focused, setFocus] = useState('no-preference')
|
|
74
|
+
const [focusValue, setFocusValue] = useState<string | undefined>(undefined)
|
|
75
|
+
const exitState = useExitOnCtrlCD(() => process.exit(1))
|
|
76
|
+
|
|
77
|
+
useInput((_input, key) => {
|
|
78
|
+
if (key.leftArrow) {
|
|
79
|
+
setFocusValue('prefer-left')
|
|
80
|
+
} else if (key.rightArrow) {
|
|
81
|
+
setFocusValue('prefer-right')
|
|
82
|
+
} else if (key.escape) {
|
|
83
|
+
onChoose?.('neither')
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<>
|
|
89
|
+
<Box
|
|
90
|
+
flexDirection="column"
|
|
91
|
+
height="100%"
|
|
92
|
+
width="100%"
|
|
93
|
+
borderStyle="round"
|
|
94
|
+
borderColor={theme.permission}
|
|
95
|
+
>
|
|
96
|
+
<Box width="100%" justifyContent="space-between" paddingX={1}>
|
|
97
|
+
<Text bold color={theme.permission}>
|
|
98
|
+
[ANT-ONLY] Help train {PRODUCT_NAME}
|
|
99
|
+
</Text>
|
|
100
|
+
<Text>
|
|
101
|
+
<Link url={HELP_URL}>[?]</Link>
|
|
102
|
+
</Text>
|
|
103
|
+
</Box>
|
|
104
|
+
<Box flexDirection="row" width="100%" flexGrow={1} paddingTop={1}>
|
|
105
|
+
<Box
|
|
106
|
+
flexDirection="column"
|
|
107
|
+
flexGrow={1}
|
|
108
|
+
flexBasis={1}
|
|
109
|
+
gap={1}
|
|
110
|
+
borderStyle={focused === 'prefer-left' ? 'bold' : 'single'}
|
|
111
|
+
borderColor={
|
|
112
|
+
focused === 'prefer-left' ? theme.success : theme.secondaryBorder
|
|
113
|
+
}
|
|
114
|
+
marginRight={1}
|
|
115
|
+
padding={1}
|
|
116
|
+
>
|
|
117
|
+
<BinaryFeedbackOption
|
|
118
|
+
erroredToolUseIDs={erroredToolUseIDs}
|
|
119
|
+
debug={debug}
|
|
120
|
+
inProgressToolUseIDs={inProgressToolUseIDs}
|
|
121
|
+
message={m1}
|
|
122
|
+
normalizedMessages={normalizedMessages}
|
|
123
|
+
tools={tools}
|
|
124
|
+
unresolvedToolUseIDs={unresolvedToolUseIDs}
|
|
125
|
+
verbose={verbose}
|
|
126
|
+
/>
|
|
127
|
+
</Box>
|
|
128
|
+
<Box
|
|
129
|
+
flexDirection="column"
|
|
130
|
+
flexGrow={1}
|
|
131
|
+
flexBasis={1}
|
|
132
|
+
gap={1}
|
|
133
|
+
borderStyle={focused === 'prefer-right' ? 'bold' : 'single'}
|
|
134
|
+
borderColor={
|
|
135
|
+
focused === 'prefer-right' ? theme.success : theme.secondaryBorder
|
|
136
|
+
}
|
|
137
|
+
marginLeft={1}
|
|
138
|
+
padding={1}
|
|
139
|
+
>
|
|
140
|
+
<BinaryFeedbackOption
|
|
141
|
+
erroredToolUseIDs={erroredToolUseIDs}
|
|
142
|
+
debug={debug}
|
|
143
|
+
inProgressToolUseIDs={inProgressToolUseIDs}
|
|
144
|
+
message={m2}
|
|
145
|
+
normalizedMessages={normalizedMessages}
|
|
146
|
+
tools={tools}
|
|
147
|
+
unresolvedToolUseIDs={unresolvedToolUseIDs}
|
|
148
|
+
verbose={verbose}
|
|
149
|
+
/>
|
|
150
|
+
</Box>
|
|
151
|
+
</Box>
|
|
152
|
+
<Box flexDirection="column" paddingTop={1} paddingX={1}>
|
|
153
|
+
<Text>How do you want to proceed?</Text>
|
|
154
|
+
<Select
|
|
155
|
+
options={getOptions()}
|
|
156
|
+
onFocus={setFocus}
|
|
157
|
+
focusValue={focusValue}
|
|
158
|
+
onChange={onChoose as SelectProps['onChange']}
|
|
159
|
+
/>
|
|
160
|
+
</Box>
|
|
161
|
+
</Box>
|
|
162
|
+
{exitState.pending ? (
|
|
163
|
+
<Box marginLeft={3}>
|
|
164
|
+
<Text dimColor>Press {exitState.keyName} again to exit</Text>
|
|
165
|
+
</Box>
|
|
166
|
+
) : (
|
|
167
|
+
// Render a blank line so that the UI doesn't reflow when the exit message is shown
|
|
168
|
+
<Text> </Text>
|
|
169
|
+
)}
|
|
170
|
+
</>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { TextBlock, ToolUseBlock } from '@anthropic-ai/sdk/resources/index.mjs'
|
|
2
|
+
import { AssistantMessage, BinaryFeedbackResult } from '../../query'
|
|
3
|
+
import { MAIN_QUERY_TEMPERATURE } from '../../services/claude'
|
|
4
|
+
import { getDynamicConfig, logEvent } from '../../services/statsig'
|
|
5
|
+
|
|
6
|
+
import { isEqual, zip } from 'lodash-es'
|
|
7
|
+
import { getGitState } from '../../utils/git'
|
|
8
|
+
|
|
9
|
+
export type BinaryFeedbackChoice =
|
|
10
|
+
| 'prefer-left'
|
|
11
|
+
| 'prefer-right'
|
|
12
|
+
| 'neither'
|
|
13
|
+
| 'no-preference'
|
|
14
|
+
|
|
15
|
+
export type BinaryFeedbackChoose = (choice: BinaryFeedbackChoice) => void
|
|
16
|
+
|
|
17
|
+
type BinaryFeedbackConfig = {
|
|
18
|
+
sampleFrequency: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function getBinaryFeedbackStatsigConfig(): Promise<BinaryFeedbackConfig> {
|
|
22
|
+
return await getDynamicConfig('tengu-binary-feedback-config', {
|
|
23
|
+
sampleFrequency: 0,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getMessageBlockSequence(m: AssistantMessage) {
|
|
28
|
+
return m.message.content.map(cb => {
|
|
29
|
+
if (cb.type === 'text') return 'text'
|
|
30
|
+
if (cb.type === 'tool_use') return cb.name
|
|
31
|
+
return cb.type // Handle other block types like 'thinking' or 'redacted_thinking'
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function logBinaryFeedbackEvent(
|
|
36
|
+
m1: AssistantMessage,
|
|
37
|
+
m2: AssistantMessage,
|
|
38
|
+
choice: BinaryFeedbackChoice,
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
const modelA = m1.message.model
|
|
41
|
+
const modelB = m2.message.model
|
|
42
|
+
const gitState = await getGitState()
|
|
43
|
+
logEvent('tengu_binary_feedback', {
|
|
44
|
+
msg_id_A: m1.message.id,
|
|
45
|
+
msg_id_B: m2.message.id,
|
|
46
|
+
choice: {
|
|
47
|
+
'prefer-left': m1.message.id,
|
|
48
|
+
'prefer-right': m2.message.id,
|
|
49
|
+
neither: undefined,
|
|
50
|
+
'no-preference': undefined,
|
|
51
|
+
}[choice],
|
|
52
|
+
choiceStr: choice,
|
|
53
|
+
gitHead: gitState?.commitHash,
|
|
54
|
+
gitBranch: gitState?.branchName,
|
|
55
|
+
gitRepoRemoteUrl: gitState?.remoteUrl || undefined,
|
|
56
|
+
gitRepoIsHeadOnRemote: gitState?.isHeadOnRemote?.toString(),
|
|
57
|
+
gitRepoIsClean: gitState?.isClean?.toString(),
|
|
58
|
+
modelA,
|
|
59
|
+
modelB,
|
|
60
|
+
temperatureA: String(MAIN_QUERY_TEMPERATURE),
|
|
61
|
+
temperatureB: String(MAIN_QUERY_TEMPERATURE),
|
|
62
|
+
seqA: String(getMessageBlockSequence(m1)),
|
|
63
|
+
seqB: String(getMessageBlockSequence(m2)),
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function logBinaryFeedbackSamplingDecision(
|
|
68
|
+
decision: boolean,
|
|
69
|
+
reason?: string,
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
logEvent('tengu_binary_feedback_sampling_decision', {
|
|
72
|
+
decision: decision.toString(),
|
|
73
|
+
reason,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function logBinaryFeedbackDisplayDecision(
|
|
78
|
+
decision: boolean,
|
|
79
|
+
m1: AssistantMessage,
|
|
80
|
+
m2: AssistantMessage,
|
|
81
|
+
reason?: string,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
logEvent('tengu_binary_feedback_display_decision', {
|
|
84
|
+
decision: decision.toString(),
|
|
85
|
+
reason,
|
|
86
|
+
msg_id_A: m1.message.id,
|
|
87
|
+
msg_id_B: m2.message.id,
|
|
88
|
+
seqA: String(getMessageBlockSequence(m1)),
|
|
89
|
+
seqB: String(getMessageBlockSequence(m2)),
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function textContentBlocksEqual(cb1: TextBlock, cb2: TextBlock): boolean {
|
|
94
|
+
return cb1.text === cb2.text
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function contentBlocksEqual(
|
|
98
|
+
cb1: TextBlock | ToolUseBlock,
|
|
99
|
+
cb2: TextBlock | ToolUseBlock,
|
|
100
|
+
): boolean {
|
|
101
|
+
if (cb1.type !== cb2.type) {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
if (cb1.type === 'text') {
|
|
105
|
+
return textContentBlocksEqual(cb1, cb2 as TextBlock)
|
|
106
|
+
}
|
|
107
|
+
cb2 = cb2 as ToolUseBlock
|
|
108
|
+
return cb1.name === cb2.name && isEqual(cb1.input, cb2.input)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function allContentBlocksEqual(
|
|
112
|
+
content1: (TextBlock | ToolUseBlock)[],
|
|
113
|
+
content2: (TextBlock | ToolUseBlock)[],
|
|
114
|
+
): boolean {
|
|
115
|
+
if (content1.length !== content2.length) {
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
return zip(content1, content2).every(([cb1, cb2]) =>
|
|
119
|
+
contentBlocksEqual(cb1!, cb2!),
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function shouldUseBinaryFeedback(): Promise<boolean> {
|
|
124
|
+
if (process.env.DISABLE_BINARY_FEEDBACK) {
|
|
125
|
+
logBinaryFeedbackSamplingDecision(false, 'disabled_by_env_var')
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
if (process.env.FORCE_BINARY_FEEDBACK) {
|
|
129
|
+
logBinaryFeedbackSamplingDecision(true, 'forced_by_env_var')
|
|
130
|
+
return true
|
|
131
|
+
}
|
|
132
|
+
if (process.env.USER_TYPE !== 'ant') {
|
|
133
|
+
logBinaryFeedbackSamplingDecision(false, 'not_ant')
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
if (process.env.NODE_ENV === 'test') {
|
|
137
|
+
// Binary feedback breaks a couple tests related to checking for permission,
|
|
138
|
+
// so we have to disable it in tests at the risk of hiding bugs
|
|
139
|
+
logBinaryFeedbackSamplingDecision(false, 'test')
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const config = await getBinaryFeedbackStatsigConfig()
|
|
144
|
+
if (config.sampleFrequency === 0) {
|
|
145
|
+
logBinaryFeedbackSamplingDecision(false, 'top_level_frequency_zero')
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
if (Math.random() > config.sampleFrequency) {
|
|
149
|
+
logBinaryFeedbackSamplingDecision(false, 'top_level_frequency_rng')
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
logBinaryFeedbackSamplingDecision(true)
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function messagePairValidForBinaryFeedback(
|
|
157
|
+
m1: AssistantMessage,
|
|
158
|
+
m2: AssistantMessage,
|
|
159
|
+
): boolean {
|
|
160
|
+
const logPass = () => logBinaryFeedbackDisplayDecision(true, m1, m2)
|
|
161
|
+
const logFail = (reason: string) =>
|
|
162
|
+
logBinaryFeedbackDisplayDecision(false, m1, m2, reason)
|
|
163
|
+
|
|
164
|
+
// Ignore thinking blocks, on the assumption that users don't find them very relevant
|
|
165
|
+
// compared to other content types
|
|
166
|
+
const nonThinkingBlocks1 = m1.message.content.filter(
|
|
167
|
+
b => b.type !== 'thinking' && b.type !== 'redacted_thinking',
|
|
168
|
+
)
|
|
169
|
+
const nonThinkingBlocks2 = m2.message.content.filter(
|
|
170
|
+
b => b.type !== 'thinking' && b.type !== 'redacted_thinking',
|
|
171
|
+
)
|
|
172
|
+
const hasToolUse =
|
|
173
|
+
nonThinkingBlocks1.some(b => b.type === 'tool_use') ||
|
|
174
|
+
nonThinkingBlocks2.some(b => b.type === 'tool_use')
|
|
175
|
+
|
|
176
|
+
// If they're all text blocks, compare those
|
|
177
|
+
if (!hasToolUse) {
|
|
178
|
+
if (allContentBlocksEqual(nonThinkingBlocks1, nonThinkingBlocks2)) {
|
|
179
|
+
logFail('contents_identical')
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
logPass()
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If there are tools, they're the most material difference between the messages.
|
|
187
|
+
// Only show binary feedback if there's a tool use difference, ignoring text.
|
|
188
|
+
if (
|
|
189
|
+
allContentBlocksEqual(
|
|
190
|
+
nonThinkingBlocks1.filter(b => b.type === 'tool_use'),
|
|
191
|
+
nonThinkingBlocks2.filter(b => b.type === 'tool_use'),
|
|
192
|
+
)
|
|
193
|
+
) {
|
|
194
|
+
logFail('contents_identical')
|
|
195
|
+
return false
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
logPass()
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function getBinaryFeedbackResultForChoice(
|
|
203
|
+
m1: AssistantMessage,
|
|
204
|
+
m2: AssistantMessage,
|
|
205
|
+
choice: BinaryFeedbackChoice,
|
|
206
|
+
): BinaryFeedbackResult {
|
|
207
|
+
switch (choice) {
|
|
208
|
+
case 'prefer-left':
|
|
209
|
+
return { message: m1, shouldSkipPermissionCheck: true }
|
|
210
|
+
case 'prefer-right':
|
|
211
|
+
return { message: m2, shouldSkipPermissionCheck: true }
|
|
212
|
+
case 'no-preference':
|
|
213
|
+
return {
|
|
214
|
+
message: Math.random() < 0.5 ? m1 : m2,
|
|
215
|
+
shouldSkipPermissionCheck: false,
|
|
216
|
+
}
|
|
217
|
+
case 'neither':
|
|
218
|
+
return { message: null, shouldSkipPermissionCheck: false }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import BashToolResultMessage from '../../tools/BashTool/BashToolResultMessage'
|
|
3
|
+
import { extractTag } from '../../utils/messages'
|
|
4
|
+
|
|
5
|
+
export function AssistantBashOutputMessage({
|
|
6
|
+
content,
|
|
7
|
+
verbose,
|
|
8
|
+
}: {
|
|
9
|
+
content: string
|
|
10
|
+
verbose?: boolean
|
|
11
|
+
}): React.ReactNode {
|
|
12
|
+
const stdout = extractTag(content, 'bash-stdout') ?? ''
|
|
13
|
+
const stderr = extractTag(content, 'bash-stderr') ?? ''
|
|
14
|
+
const stdoutLines = stdout.split('\n').length
|
|
15
|
+
const stderrLines = stderr.split('\n').length
|
|
16
|
+
return (
|
|
17
|
+
<BashToolResultMessage
|
|
18
|
+
content={{ stdout, stdoutLines, stderr, stderrLines }}
|
|
19
|
+
verbose={!!verbose}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|