@shareai-lab/kode 1.0.71 → 1.0.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +142 -1
  2. package/README.zh-CN.md +47 -1
  3. package/package.json +5 -1
  4. package/src/ProjectOnboarding.tsx +47 -29
  5. package/src/Tool.ts +33 -4
  6. package/src/commands/agents.tsx +3401 -0
  7. package/src/commands/help.tsx +2 -2
  8. package/src/commands/resume.tsx +2 -1
  9. package/src/commands/terminalSetup.ts +4 -4
  10. package/src/commands.ts +3 -0
  11. package/src/components/ApproveApiKey.tsx +1 -1
  12. package/src/components/Config.tsx +10 -6
  13. package/src/components/ConsoleOAuthFlow.tsx +5 -4
  14. package/src/components/CustomSelect/select-option.tsx +28 -2
  15. package/src/components/CustomSelect/select.tsx +14 -5
  16. package/src/components/CustomSelect/theme.ts +45 -0
  17. package/src/components/Help.tsx +4 -4
  18. package/src/components/InvalidConfigDialog.tsx +1 -1
  19. package/src/components/LogSelector.tsx +1 -1
  20. package/src/components/MCPServerApprovalDialog.tsx +1 -1
  21. package/src/components/Message.tsx +2 -0
  22. package/src/components/ModelListManager.tsx +10 -6
  23. package/src/components/ModelSelector.tsx +201 -23
  24. package/src/components/ModelStatusDisplay.tsx +7 -5
  25. package/src/components/PromptInput.tsx +117 -87
  26. package/src/components/SentryErrorBoundary.ts +3 -3
  27. package/src/components/StickerRequestForm.tsx +16 -0
  28. package/src/components/StructuredDiff.tsx +36 -29
  29. package/src/components/TextInput.tsx +13 -0
  30. package/src/components/TodoItem.tsx +11 -0
  31. package/src/components/TrustDialog.tsx +1 -1
  32. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +5 -1
  33. package/src/components/messages/AssistantToolUseMessage.tsx +14 -4
  34. package/src/components/messages/TaskProgressMessage.tsx +32 -0
  35. package/src/components/messages/TaskToolMessage.tsx +58 -0
  36. package/src/components/permissions/FallbackPermissionRequest.tsx +2 -4
  37. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +1 -1
  38. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +5 -3
  39. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +1 -1
  40. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +5 -3
  41. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +2 -4
  42. package/src/components/permissions/PermissionRequest.tsx +3 -5
  43. package/src/constants/macros.ts +2 -0
  44. package/src/constants/modelCapabilities.ts +179 -0
  45. package/src/constants/models.ts +90 -0
  46. package/src/constants/product.ts +1 -1
  47. package/src/context.ts +7 -7
  48. package/src/entrypoints/cli.tsx +23 -3
  49. package/src/entrypoints/mcp.ts +10 -10
  50. package/src/hooks/useCanUseTool.ts +1 -1
  51. package/src/hooks/useTextInput.ts +5 -2
  52. package/src/hooks/useUnifiedCompletion.ts +1404 -0
  53. package/src/messages.ts +1 -0
  54. package/src/query.ts +3 -0
  55. package/src/screens/ConfigureNpmPrefix.tsx +1 -1
  56. package/src/screens/Doctor.tsx +1 -1
  57. package/src/screens/REPL.tsx +15 -9
  58. package/src/services/adapters/base.ts +38 -0
  59. package/src/services/adapters/chatCompletions.ts +90 -0
  60. package/src/services/adapters/responsesAPI.ts +170 -0
  61. package/src/services/claude.ts +198 -62
  62. package/src/services/customCommands.ts +43 -22
  63. package/src/services/gpt5ConnectionTest.ts +340 -0
  64. package/src/services/mcpClient.ts +1 -1
  65. package/src/services/mentionProcessor.ts +273 -0
  66. package/src/services/modelAdapterFactory.ts +69 -0
  67. package/src/services/openai.ts +521 -12
  68. package/src/services/responseStateManager.ts +90 -0
  69. package/src/services/systemReminder.ts +113 -12
  70. package/src/test/testAdapters.ts +96 -0
  71. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +120 -56
  72. package/src/tools/BashTool/BashTool.tsx +4 -31
  73. package/src/tools/BashTool/BashToolResultMessage.tsx +1 -1
  74. package/src/tools/BashTool/OutputLine.tsx +1 -0
  75. package/src/tools/FileEditTool/FileEditTool.tsx +4 -5
  76. package/src/tools/FileReadTool/FileReadTool.tsx +43 -10
  77. package/src/tools/MCPTool/MCPTool.tsx +2 -1
  78. package/src/tools/MultiEditTool/MultiEditTool.tsx +2 -2
  79. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +15 -23
  80. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +1 -1
  81. package/src/tools/TaskTool/TaskTool.tsx +170 -86
  82. package/src/tools/TaskTool/prompt.ts +61 -25
  83. package/src/tools/ThinkTool/ThinkTool.tsx +1 -3
  84. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +11 -10
  85. package/src/tools/lsTool/lsTool.tsx +5 -2
  86. package/src/tools.ts +16 -16
  87. package/src/types/conversation.ts +51 -0
  88. package/src/types/logs.ts +58 -0
  89. package/src/types/modelCapabilities.ts +64 -0
  90. package/src/types/notebook.ts +87 -0
  91. package/src/utils/advancedFuzzyMatcher.ts +290 -0
  92. package/src/utils/agentLoader.ts +284 -0
  93. package/src/utils/ask.tsx +1 -0
  94. package/src/utils/commands.ts +1 -1
  95. package/src/utils/commonUnixCommands.ts +161 -0
  96. package/src/utils/config.ts +173 -2
  97. package/src/utils/conversationRecovery.ts +1 -0
  98. package/src/utils/debugLogger.ts +13 -13
  99. package/src/utils/exampleCommands.ts +1 -0
  100. package/src/utils/fuzzyMatcher.ts +328 -0
  101. package/src/utils/messages.tsx +6 -5
  102. package/src/utils/responseState.ts +23 -0
  103. package/src/utils/secureFile.ts +559 -0
  104. package/src/utils/terminal.ts +1 -0
  105. package/src/utils/theme.ts +11 -0
  106. package/src/hooks/useSlashCommandTypeahead.ts +0 -137
@@ -8,8 +8,8 @@ const help = {
8
8
  description: 'Show help and available commands',
9
9
  isEnabled: true,
10
10
  isHidden: false,
11
- async call(onDone, { options: { commands } }) {
12
- return <Help commands={commands} onClose={onDone} />
11
+ async call(onDone, context) {
12
+ return <Help commands={context.options?.commands || []} onClose={onDone} />
13
13
  },
14
14
  userFacingName() {
15
15
  return 'help'
@@ -13,7 +13,8 @@ export default {
13
13
  userFacingName() {
14
14
  return 'resume'
15
15
  },
16
- async call(onDone, { options: { commands, tools, verbose } }) {
16
+ async call(onDone, context) {
17
+ const { commands = [], tools = [], verbose = false } = context.options || {}
17
18
  const logs = await loadLogList(CACHE_PATHS.messages())
18
19
  render(
19
20
  <ResumeConversation
@@ -52,17 +52,17 @@ export function isShiftEnterKeyBindingInstalled(): boolean {
52
52
  }
53
53
 
54
54
  export function handleHashCommand(interpreted: string): void {
55
- // Appends the AI-interpreted content to both KODE.md and CLAUDE.md (if exists)
55
+ // Appends the AI-interpreted content to both AGENTS.md and CLAUDE.md (if exists)
56
56
  try {
57
57
  const cwd = process.cwd()
58
- const codeContextPath = join(cwd, 'KODE.md')
58
+ const codeContextPath = join(cwd, 'AGENTS.md')
59
59
  const claudePath = join(cwd, 'CLAUDE.md')
60
60
 
61
61
  // Check which files exist and update them
62
62
  const filesToUpdate = []
63
63
 
64
- // Always try to update KODE.md (create if not exists)
65
- filesToUpdate.push({ path: codeContextPath, name: 'KODE.md' })
64
+ // Always try to update AGENTS.md (create if not exists)
65
+ filesToUpdate.push({ path: codeContextPath, name: 'AGENTS.md' })
66
66
 
67
67
  // Update CLAUDE.md only if it exists
68
68
  try {
package/src/commands.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import React from 'react'
1
2
  import bug from './commands/bug'
2
3
  import clear from './commands/clear'
3
4
  import compact from './commands/compact'
@@ -21,6 +22,7 @@ import review from './commands/review'
21
22
  import terminalSetup from './commands/terminalSetup'
22
23
  import { Tool, ToolUseContext } from './Tool'
23
24
  import resume from './commands/resume'
25
+ import agents from './commands/agents'
24
26
  import { getMCPCommands } from './services/mcpClient'
25
27
  import { loadCustomCommands } from './services/customCommands'
26
28
  import type { MessageParam } from '@anthropic-ai/sdk/resources/index.mjs'
@@ -79,6 +81,7 @@ const INTERNAL_ONLY_COMMANDS = [ctx_viz, resume, listen]
79
81
  // Declared as a function so that we don't run this until getCommands is called,
80
82
  // since underlying functions read from config, which can't be read at module initialization time
81
83
  const COMMANDS = memoize((): Command[] => [
84
+ agents,
82
85
  clear,
83
86
  compact,
84
87
  config,
@@ -2,7 +2,7 @@ import React from 'react'
2
2
  import { Box, Text } from 'ink'
3
3
  import { getGlobalConfig, saveGlobalConfig } from '../utils/config'
4
4
  import { getTheme } from '../utils/theme'
5
- import { Select } from '@inkjs/ui'
5
+ import { Select } from './CustomSelect/select'
6
6
  import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
7
7
  import chalk from 'chalk'
8
8
 
@@ -214,13 +214,17 @@ export function Config({ onClose }: Props): React.ReactNode {
214
214
  ) : (
215
215
  <Box flexDirection="column" marginLeft={2}>
216
216
  {activeProfiles.map(profile => (
217
- <Text key={profile.modelName} color={theme.secondaryText}>
218
- • {profile.name} ({profile.provider})
219
- </Text>
217
+ <React.Fragment key={profile.modelName}>
218
+ <Text color={theme.secondaryText}>
219
+ • {profile.name} ({profile.provider})
220
+ </Text>
221
+ </React.Fragment>
220
222
  ))}
221
- <Text color={theme.suggestion} marginTop={1}>
222
- Use /model to manage model configurations
223
- </Text>
223
+ <Box marginTop={1}>
224
+ <Text color={theme.suggestion}>
225
+ Use /model to manage model configurations
226
+ </Text>
227
+ </Box>
224
228
  </Box>
225
229
  )}
226
230
  </Box>
@@ -288,7 +288,7 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
288
288
  // We need to render the copy-able URL statically to prevent Ink <Text> from inserting
289
289
  // newlines in the middle of the URL (this breaks Safari). Because <Static> components are
290
290
  // only rendered once top-to-bottom, we also need to make everything above the URL static.
291
- const staticItems: Record<string, JSX.Element> = {}
291
+ const staticItems: Record<string, React.JSX.Element> = {}
292
292
  if (!isClearing) {
293
293
  staticItems.header = (
294
294
  <Box key="header" flexDirection="column" gap={1}>
@@ -315,9 +315,10 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
315
315
  }
316
316
  return (
317
317
  <Box flexDirection="column" gap={1}>
318
- <Static items={Object.keys(staticItems)}>
319
- {item => staticItems[item]}
320
- </Static>
318
+ <Static
319
+ items={Object.keys(staticItems)}
320
+ children={(item: string) => staticItems[item]}
321
+ />
321
322
  <Box paddingLeft={1} flexDirection="column" gap={1}>
322
323
  {renderStatusMessage()}
323
324
  </Box>
@@ -2,7 +2,7 @@ import figures from 'figures'
2
2
  import { Box, Text } from 'ink'
3
3
  import React, { type ReactNode } from 'react'
4
4
  import { type Theme } from './theme'
5
- import { useComponentTheme } from '@inkjs/ui'
5
+ import { getTheme } from '../../utils/theme'
6
6
 
7
7
  export type SelectOptionProps = {
8
8
  /**
@@ -24,6 +24,11 @@ export type SelectOptionProps = {
24
24
  * Option label.
25
25
  */
26
26
  readonly children: ReactNode
27
+
28
+ /**
29
+ * React key prop (handled internally by React)
30
+ */
31
+ readonly key?: React.Key
27
32
  }
28
33
 
29
34
  export function SelectOption({
@@ -31,8 +36,29 @@ export function SelectOption({
31
36
  isSelected,
32
37
  smallPointer,
33
38
  children,
39
+ ...props
34
40
  }: SelectOptionProps) {
35
- const { styles } = useComponentTheme<Theme>('Select')
41
+ const appTheme = getTheme()
42
+ const styles = {
43
+ option: ({ isFocused }: { isFocused: boolean }) => ({
44
+ paddingLeft: 2,
45
+ paddingRight: 1,
46
+ }),
47
+ focusIndicator: () => ({
48
+ color: appTheme.claude,
49
+ }),
50
+ label: ({ isFocused, isSelected }: { isFocused: boolean; isSelected: boolean }) => ({
51
+ color: isSelected
52
+ ? appTheme.success
53
+ : isFocused
54
+ ? appTheme.claude
55
+ : appTheme.text,
56
+ bold: isSelected,
57
+ }),
58
+ selectedIndicator: () => ({
59
+ color: appTheme.success,
60
+ }),
61
+ }
36
62
 
37
63
  return (
38
64
  <Box {...styles.option({ isFocused })}>
@@ -4,7 +4,8 @@ import { SelectOption } from './select-option'
4
4
  import { type Theme } from './theme'
5
5
  import { useSelectState } from './use-select-state'
6
6
  import { useSelect } from './use-select'
7
- import { Option, useComponentTheme } from '@inkjs/ui'
7
+ import { Option } from '@inkjs/ui'
8
+ import { getTheme } from '../../utils/theme'
8
9
 
9
10
  export type OptionSubtree = {
10
11
  /**
@@ -94,7 +95,16 @@ export function Select({
94
95
 
95
96
  useSelect({ isDisabled, state })
96
97
 
97
- const { styles } = useComponentTheme<Theme>('Select')
98
+ const appTheme = getTheme()
99
+ const styles = {
100
+ container: () => ({
101
+ flexDirection: 'column' as const,
102
+ }),
103
+ highlightedText: () => ({
104
+ color: appTheme.text,
105
+ backgroundColor: appTheme.warning,
106
+ }),
107
+ }
98
108
 
99
109
  return (
100
110
  <Box {...styles.container()}>
@@ -133,9 +143,8 @@ export function Select({
133
143
  isFocused={isFocused}
134
144
  isSelected={isSelected}
135
145
  smallPointer={smallPointer}
136
- >
137
- {label}
138
- </SelectOption>
146
+ children={label}
147
+ />
139
148
  )
140
149
  })}
141
150
  </Box>
@@ -0,0 +1,45 @@
1
+ // Theme type definitions for CustomSelect components
2
+ // Used by select.tsx and select-option.tsx
3
+
4
+ import type { BoxProps, TextProps } from 'ink'
5
+
6
+ /**
7
+ * Theme interface for CustomSelect components
8
+ * Defines the style functions used by the select components
9
+ */
10
+ export interface Theme {
11
+ /**
12
+ * Collection of style functions
13
+ */
14
+ styles: {
15
+ /**
16
+ * Container styles for the select box
17
+ */
18
+ container(): BoxProps
19
+
20
+ /**
21
+ * Styles for individual option containers
22
+ */
23
+ option(props: { isFocused: boolean }): BoxProps
24
+
25
+ /**
26
+ * Styles for the focus indicator (arrow/pointer)
27
+ */
28
+ focusIndicator(): TextProps
29
+
30
+ /**
31
+ * Styles for option labels
32
+ */
33
+ label(props: { isFocused: boolean; isSelected: boolean }): TextProps
34
+
35
+ /**
36
+ * Styles for the selected indicator (checkmark)
37
+ */
38
+ selectedIndicator(): TextProps
39
+
40
+ /**
41
+ * Styles for highlighted text in option labels
42
+ */
43
+ highlightedText(): TextProps
44
+ }
45
+ }
@@ -175,10 +175,10 @@ export function Help({
175
175
  Custom commands loaded from:
176
176
  </Text>
177
177
  <Text color={theme.secondaryText}>
178
- • {getCustomCommandDirectories().user} (user: prefix)
178
+ • {getCustomCommandDirectories().userClaude} (user: prefix)
179
179
  </Text>
180
180
  <Text color={theme.secondaryText}>
181
- • {getCustomCommandDirectories().project} (project: prefix)
181
+ • {getCustomCommandDirectories().projectClaude} (project: prefix)
182
182
  </Text>
183
183
  <Text color={theme.secondaryText}>
184
184
  Use /refresh-commands to reload after changes
@@ -190,10 +190,10 @@ export function Help({
190
190
  Create custom commands by adding .md files to:
191
191
  </Text>
192
192
  <Text color={theme.secondaryText}>
193
- • {getCustomCommandDirectories().user} (user: prefix)
193
+ • {getCustomCommandDirectories().userClaude} (user: prefix)
194
194
  </Text>
195
195
  <Text color={theme.secondaryText}>
196
- • {getCustomCommandDirectories().project} (project: prefix)
196
+ • {getCustomCommandDirectories().projectClaude} (project: prefix)
197
197
  </Text>
198
198
  <Text color={theme.secondaryText}>
199
199
  Use /refresh-commands to reload after creation
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { Box, Newline, Text, useInput } from 'ink'
3
3
  import { getTheme } from '../utils/theme'
4
- import { Select } from '@inkjs/ui'
4
+ import { Select } from './CustomSelect/select'
5
5
  import { render } from 'ink'
6
6
  import { writeFileSync } from 'fs'
7
7
  import { ConfigParseError } from '../utils/errors'
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { Box, Text } from 'ink'
3
- import { Select } from '@inkjs/ui'
3
+ import { Select } from './CustomSelect/select'
4
4
  import type { LogOption } from '../types/logs'
5
5
  import { getTheme } from '../utils/theme'
6
6
  import { useTerminalSize } from '../hooks/useTerminalSize'
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { Box, Text, useInput } from 'ink'
3
3
  import { getTheme } from '../utils/theme'
4
- import { Select } from '@inkjs/ui'
4
+ import { Select } from './CustomSelect/select'
5
5
  import {
6
6
  saveCurrentProjectConfig,
7
7
  getCurrentProjectConfig,
@@ -121,6 +121,7 @@ function UserMessage({
121
121
  options: {
122
122
  verbose: boolean
123
123
  }
124
+ key?: React.Key
124
125
  }): React.ReactNode {
125
126
  const { columns } = useTerminalSize()
126
127
  switch (param.type) {
@@ -176,6 +177,7 @@ function AssistantMessage({
176
177
  shouldAnimate: boolean
177
178
  shouldShowDot: boolean
178
179
  width?: number | string
180
+ key?: React.Key
179
181
  }): React.ReactNode {
180
182
  switch (param.type) {
181
183
  case 'tool_use':
@@ -173,14 +173,18 @@ export function ModelListManager({ onClose }: Props): React.ReactNode {
173
173
  <>
174
174
  <Text color={theme.secondaryText}>({item.provider})</Text>
175
175
  {item.usedBy.length > 0 && (
176
- <Text color={theme.success} marginLeft={1}>
177
- [Active: {item.usedBy.join(', ')}]
178
- </Text>
176
+ <Box marginLeft={1}>
177
+ <Text color={theme.success}>
178
+ [Active: {item.usedBy.join(', ')}]
179
+ </Text>
180
+ </Box>
179
181
  )}
180
182
  {item.usedBy.length === 0 && (
181
- <Text color={theme.secondaryText} marginLeft={1}>
182
- [Available]
183
- </Text>
183
+ <Box marginLeft={1}>
184
+ <Text color={theme.secondaryText}>
185
+ [Available]
186
+ </Text>
187
+ </Box>
184
188
  )}
185
189
  </>
186
190
  )}
@@ -48,13 +48,15 @@ import TextInput from './TextInput'
48
48
  import OpenAI from 'openai'
49
49
  import chalk from 'chalk'
50
50
  import { fetchAnthropicModels, verifyApiKey } from '../services/claude'
51
- import { fetchCustomModels } from '../services/openai'
51
+ import { fetchCustomModels, getModelFeatures } from '../services/openai'
52
+ import { testGPT5Connection, validateGPT5Config } from '../services/gpt5ConnectionTest'
52
53
  type Props = {
53
54
  onDone: () => void
54
55
  abortController?: AbortController
55
56
  targetPointer?: ModelPointerType // NEW: Target pointer for configuration
56
57
  isOnboarding?: boolean // NEW: Whether this is first-time setup
57
58
  onCancel?: () => void // NEW: Cancel callback (different from onDone)
59
+ skipModelType?: boolean // NEW: Skip model type selection
58
60
  }
59
61
 
60
62
  type ModelInfo = {
@@ -154,6 +156,7 @@ export function ModelSelector({
154
156
  targetPointer,
155
157
  isOnboarding = false,
156
158
  onCancel,
159
+ skipModelType = false,
157
160
  }: Props): React.ReactNode {
158
161
  const config = getGlobalConfig()
159
162
  const theme = getTheme()
@@ -1252,7 +1255,7 @@ export function ModelSelector({
1252
1255
  // Transform the response into our ModelInfo format
1253
1256
  const fetchedModels = []
1254
1257
  for (const model of response.data) {
1255
- const modelName = model.modelName || model.id || model.name || model.model || 'unknown'
1258
+ const modelName = (model as any).modelName || (model as any).id || (model as any).name || (model as any).model || 'unknown'
1256
1259
  const modelInfo = models[selectedProvider as keyof typeof models]?.find(
1257
1260
  m => m.model === modelName,
1258
1261
  )
@@ -1477,7 +1480,42 @@ export function ModelSelector({
1477
1480
  ].includes(selectedProvider)
1478
1481
 
1479
1482
  if (isOpenAICompatible) {
1480
- // Define endpoints to try in order of preference
1483
+ // šŸ”„ Use specialized GPT-5 connection test for GPT-5 models
1484
+ const isGPT5 = selectedModel?.toLowerCase().includes('gpt-5')
1485
+
1486
+ if (isGPT5) {
1487
+ console.log(`šŸš€ Using specialized GPT-5 connection test for model: ${selectedModel}`)
1488
+
1489
+ // Validate configuration first
1490
+ const configValidation = validateGPT5Config({
1491
+ model: selectedModel,
1492
+ apiKey: apiKey,
1493
+ baseURL: testBaseURL,
1494
+ maxTokens: parseInt(maxTokens) || 8192,
1495
+ provider: selectedProvider,
1496
+ })
1497
+
1498
+ if (!configValidation.valid) {
1499
+ return {
1500
+ success: false,
1501
+ message: 'āŒ GPT-5 configuration validation failed',
1502
+ details: configValidation.errors.join('\n'),
1503
+ }
1504
+ }
1505
+
1506
+ // Use specialized GPT-5 test service
1507
+ const gpt5Result = await testGPT5Connection({
1508
+ model: selectedModel,
1509
+ apiKey: apiKey,
1510
+ baseURL: testBaseURL,
1511
+ maxTokens: parseInt(maxTokens) || 8192,
1512
+ provider: selectedProvider,
1513
+ })
1514
+
1515
+ return gpt5Result
1516
+ }
1517
+
1518
+ // For non-GPT-5 OpenAI-compatible models, use existing logic
1481
1519
  const endpointsToTry = []
1482
1520
 
1483
1521
  if (selectedProvider === 'minimax') {
@@ -1503,6 +1541,7 @@ export function ModelSelector({
1503
1541
  endpoint.path,
1504
1542
  endpoint.name,
1505
1543
  )
1544
+
1506
1545
  if (testResult.success) {
1507
1546
  return testResult
1508
1547
  }
@@ -1552,7 +1591,7 @@ export function ModelSelector({
1552
1591
  const testURL = `${baseURL.replace(/\/+$/, '')}${endpointPath}`
1553
1592
 
1554
1593
  // Create a test message that expects a specific response
1555
- const testPayload = {
1594
+ const testPayload: any = {
1556
1595
  model: selectedModel,
1557
1596
  messages: [
1558
1597
  {
@@ -1566,6 +1605,24 @@ export function ModelSelector({
1566
1605
  stream: false,
1567
1606
  }
1568
1607
 
1608
+ // GPT-5 parameter compatibility fix
1609
+ if (selectedModel && selectedModel.toLowerCase().includes('gpt-5')) {
1610
+ console.log(`Applying GPT-5 parameter fix for model: ${selectedModel}`)
1611
+
1612
+ // GPT-5 requires max_completion_tokens instead of max_tokens
1613
+ if (testPayload.max_tokens) {
1614
+ testPayload.max_completion_tokens = testPayload.max_tokens
1615
+ delete testPayload.max_tokens
1616
+ console.log(`Transformed max_tokens → max_completion_tokens: ${testPayload.max_completion_tokens}`)
1617
+ }
1618
+
1619
+ // GPT-5 temperature handling - ensure it's 1 or undefined
1620
+ if (testPayload.temperature !== undefined && testPayload.temperature !== 1) {
1621
+ console.log(`Adjusting temperature from ${testPayload.temperature} to 1 for GPT-5`)
1622
+ testPayload.temperature = 1
1623
+ }
1624
+ }
1625
+
1569
1626
  const headers: Record<string, string> = {
1570
1627
  'Content-Type': 'application/json',
1571
1628
  }
@@ -1646,6 +1703,123 @@ export function ModelSelector({
1646
1703
  }
1647
1704
  }
1648
1705
 
1706
+ async function testResponsesEndpoint(
1707
+ baseURL: string,
1708
+ endpointPath: string,
1709
+ endpointName: string,
1710
+ ): Promise<{
1711
+ success: boolean
1712
+ message: string
1713
+ endpoint?: string
1714
+ details?: string
1715
+ }> {
1716
+ const testURL = `${baseURL.replace(/\/+$/, '')}${endpointPath}`
1717
+
1718
+ // šŸ”§ Enhanced GPT-5 Responses API test payload
1719
+ const testPayload: any = {
1720
+ model: selectedModel,
1721
+ input: [
1722
+ {
1723
+ role: 'user',
1724
+ content:
1725
+ 'Please respond with exactly "YES" (in capital letters) to confirm this connection is working.',
1726
+ },
1727
+ ],
1728
+ max_completion_tokens: Math.max(parseInt(maxTokens) || 8192, 8192),
1729
+ temperature: 1, // GPT-5 only supports temperature=1
1730
+ // šŸš€ Add reasoning configuration for better GPT-5 performance
1731
+ reasoning: {
1732
+ effort: 'low', // Fast response for connection test
1733
+ },
1734
+ }
1735
+
1736
+ console.log(`šŸ”§ Testing GPT-5 Responses API for model: ${selectedModel}`)
1737
+ console.log(`šŸ”§ Test URL: ${testURL}`)
1738
+ console.log(`šŸ”§ Test payload:`, JSON.stringify(testPayload, null, 2))
1739
+
1740
+ const headers: Record<string, string> = {
1741
+ 'Content-Type': 'application/json',
1742
+ 'Authorization': `Bearer ${apiKey}`,
1743
+ }
1744
+
1745
+ try {
1746
+ const response = await fetch(testURL, {
1747
+ method: 'POST',
1748
+ headers,
1749
+ body: JSON.stringify(testPayload),
1750
+ })
1751
+
1752
+ if (response.ok) {
1753
+ const data = await response.json()
1754
+ console.log(
1755
+ '[DEBUG] Responses API connection test response:',
1756
+ JSON.stringify(data, null, 2),
1757
+ )
1758
+
1759
+ // Extract content from Responses API format
1760
+ let responseContent = ''
1761
+
1762
+ if (data.output_text) {
1763
+ responseContent = data.output_text
1764
+ } else if (data.output) {
1765
+ responseContent = typeof data.output === 'string' ? data.output : data.output.text || ''
1766
+ }
1767
+
1768
+ console.log('[DEBUG] Extracted response content:', responseContent)
1769
+
1770
+ // Check if response contains "YES" (case insensitive)
1771
+ const containsYes = responseContent.toLowerCase().includes('yes')
1772
+
1773
+ if (containsYes) {
1774
+ return {
1775
+ success: true,
1776
+ message: `āœ… Connection test passed with ${endpointName}`,
1777
+ endpoint: endpointPath,
1778
+ details: `GPT-5 responded correctly via Responses API: "${responseContent.trim()}"`,
1779
+ }
1780
+ } else {
1781
+ return {
1782
+ success: false,
1783
+ message: `āš ļø ${endpointName} connected but model response unexpected`,
1784
+ endpoint: endpointPath,
1785
+ details: `Expected "YES" but got: "${responseContent.trim() || '(empty response)'}"`,
1786
+ }
1787
+ }
1788
+ } else {
1789
+ // šŸ”§ Enhanced error handling with detailed debugging
1790
+ const errorData = await response.json().catch(() => null)
1791
+ const errorMessage =
1792
+ errorData?.error?.message || errorData?.message || response.statusText
1793
+
1794
+ console.log(`🚨 GPT-5 Responses API Error (${response.status}):`, errorData)
1795
+
1796
+ // šŸ”§ Provide specific guidance for common GPT-5 errors
1797
+ let details = `Responses API Error: ${errorMessage}`
1798
+ if (response.status === 400 && errorMessage.includes('max_tokens')) {
1799
+ details += '\nšŸ”§ Note: This appears to be a parameter compatibility issue. The fallback to Chat Completions should handle this.'
1800
+ } else if (response.status === 404) {
1801
+ details += '\nšŸ”§ Note: Responses API endpoint may not be available for this model or provider.'
1802
+ } else if (response.status === 401) {
1803
+ details += '\nšŸ”§ Note: API key authentication failed.'
1804
+ }
1805
+
1806
+ return {
1807
+ success: false,
1808
+ message: `āŒ ${endpointName} failed (${response.status})`,
1809
+ endpoint: endpointPath,
1810
+ details: details,
1811
+ }
1812
+ }
1813
+ } catch (error) {
1814
+ return {
1815
+ success: false,
1816
+ message: `āŒ ${endpointName} connection failed`,
1817
+ endpoint: endpointPath,
1818
+ details: error instanceof Error ? error.message : String(error),
1819
+ }
1820
+ }
1821
+ }
1822
+
1649
1823
  async function testProviderSpecificEndpoint(baseURL: string): Promise<{
1650
1824
  success: boolean
1651
1825
  message: string
@@ -3181,28 +3355,32 @@ export function ModelSelector({
3181
3355
 
3182
3356
  // Render Provider Selection Screen
3183
3357
  return (
3184
- <ScreenContainer title="Provider Selection" exitState={exitState}>
3185
- <Box flexDirection="column" gap={1}>
3186
- <Text bold>
3187
- Select your preferred AI provider for this model profile:
3188
- </Text>
3189
- <Box flexDirection="column" width={70}>
3190
- <Text color={theme.secondaryText}>
3191
- Choose the provider you want to use for this model profile.
3192
- <Newline />
3193
- This will determine which models are available to you.
3358
+ <ScreenContainer
3359
+ title="Provider Selection"
3360
+ exitState={exitState}
3361
+ children={
3362
+ <Box flexDirection="column" gap={1}>
3363
+ <Text bold>
3364
+ Select your preferred AI provider for this model profile:
3194
3365
  </Text>
3195
- </Box>
3366
+ <Box flexDirection="column" width={70}>
3367
+ <Text color={theme.secondaryText}>
3368
+ Choose the provider you want to use for this model profile.
3369
+ <Newline />
3370
+ This will determine which models are available to you.
3371
+ </Text>
3372
+ </Box>
3196
3373
 
3197
- <Select options={providerOptions} onChange={handleProviderSelection} />
3374
+ <Select options={providerOptions} onChange={handleProviderSelection} />
3198
3375
 
3199
- <Box marginTop={1}>
3200
- <Text dimColor>
3201
- You can change this later by running{' '}
3202
- <Text color={theme.suggestion}>/model</Text> again
3203
- </Text>
3376
+ <Box marginTop={1}>
3377
+ <Text dimColor>
3378
+ You can change this later by running{' '}
3379
+ <Text color={theme.suggestion}>/model</Text> again
3380
+ </Text>
3381
+ </Box>
3204
3382
  </Box>
3205
- </Box>
3206
- </ScreenContainer>
3383
+ }
3384
+ />
3207
3385
  )
3208
3386
  }