@shareai-lab/kode 1.0.71 → 1.0.75

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 (108) hide show
  1. package/README.md +160 -1
  2. package/README.zh-CN.md +65 -1
  3. package/cli.js +5 -10
  4. package/package.json +6 -2
  5. package/src/ProjectOnboarding.tsx +47 -29
  6. package/src/Tool.ts +33 -4
  7. package/src/commands/agents.tsx +3401 -0
  8. package/src/commands/help.tsx +2 -2
  9. package/src/commands/resume.tsx +2 -1
  10. package/src/commands/terminalSetup.ts +4 -4
  11. package/src/commands.ts +3 -0
  12. package/src/components/ApproveApiKey.tsx +1 -1
  13. package/src/components/Config.tsx +10 -6
  14. package/src/components/ConsoleOAuthFlow.tsx +5 -4
  15. package/src/components/CustomSelect/select-option.tsx +28 -2
  16. package/src/components/CustomSelect/select.tsx +14 -5
  17. package/src/components/CustomSelect/theme.ts +45 -0
  18. package/src/components/Help.tsx +4 -4
  19. package/src/components/InvalidConfigDialog.tsx +1 -1
  20. package/src/components/LogSelector.tsx +1 -1
  21. package/src/components/MCPServerApprovalDialog.tsx +1 -1
  22. package/src/components/Message.tsx +2 -0
  23. package/src/components/ModelListManager.tsx +10 -6
  24. package/src/components/ModelSelector.tsx +201 -23
  25. package/src/components/ModelStatusDisplay.tsx +7 -5
  26. package/src/components/PromptInput.tsx +146 -96
  27. package/src/components/SentryErrorBoundary.ts +9 -3
  28. package/src/components/StickerRequestForm.tsx +16 -0
  29. package/src/components/StructuredDiff.tsx +36 -29
  30. package/src/components/TextInput.tsx +13 -0
  31. package/src/components/TodoItem.tsx +47 -0
  32. package/src/components/TrustDialog.tsx +1 -1
  33. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +5 -1
  34. package/src/components/messages/AssistantToolUseMessage.tsx +14 -4
  35. package/src/components/messages/TaskProgressMessage.tsx +32 -0
  36. package/src/components/messages/TaskToolMessage.tsx +58 -0
  37. package/src/components/permissions/FallbackPermissionRequest.tsx +2 -4
  38. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +1 -1
  39. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +5 -3
  40. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +1 -1
  41. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +5 -3
  42. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +2 -4
  43. package/src/components/permissions/PermissionRequest.tsx +3 -5
  44. package/src/constants/macros.ts +2 -0
  45. package/src/constants/modelCapabilities.ts +179 -0
  46. package/src/constants/models.ts +90 -0
  47. package/src/constants/product.ts +1 -1
  48. package/src/context.ts +7 -7
  49. package/src/entrypoints/cli.tsx +23 -3
  50. package/src/entrypoints/mcp.ts +10 -10
  51. package/src/hooks/useCanUseTool.ts +1 -1
  52. package/src/hooks/useTextInput.ts +5 -2
  53. package/src/hooks/useUnifiedCompletion.ts +1405 -0
  54. package/src/messages.ts +1 -0
  55. package/src/query.ts +3 -0
  56. package/src/screens/ConfigureNpmPrefix.tsx +1 -1
  57. package/src/screens/Doctor.tsx +1 -1
  58. package/src/screens/REPL.tsx +11 -12
  59. package/src/services/adapters/base.ts +38 -0
  60. package/src/services/adapters/chatCompletions.ts +90 -0
  61. package/src/services/adapters/responsesAPI.ts +170 -0
  62. package/src/services/claude.ts +198 -62
  63. package/src/services/customCommands.ts +43 -22
  64. package/src/services/gpt5ConnectionTest.ts +340 -0
  65. package/src/services/mcpClient.ts +1 -1
  66. package/src/services/mentionProcessor.ts +273 -0
  67. package/src/services/modelAdapterFactory.ts +69 -0
  68. package/src/services/openai.ts +534 -14
  69. package/src/services/responseStateManager.ts +90 -0
  70. package/src/services/systemReminder.ts +113 -12
  71. package/src/test/testAdapters.ts +96 -0
  72. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +120 -56
  73. package/src/tools/BashTool/BashTool.tsx +4 -31
  74. package/src/tools/BashTool/BashToolResultMessage.tsx +1 -1
  75. package/src/tools/BashTool/OutputLine.tsx +1 -0
  76. package/src/tools/FileEditTool/FileEditTool.tsx +4 -5
  77. package/src/tools/FileReadTool/FileReadTool.tsx +43 -10
  78. package/src/tools/MCPTool/MCPTool.tsx +2 -1
  79. package/src/tools/MultiEditTool/MultiEditTool.tsx +2 -2
  80. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +15 -23
  81. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +1 -1
  82. package/src/tools/TaskTool/TaskTool.tsx +170 -86
  83. package/src/tools/TaskTool/prompt.ts +61 -25
  84. package/src/tools/ThinkTool/ThinkTool.tsx +1 -3
  85. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +65 -41
  86. package/src/tools/lsTool/lsTool.tsx +5 -2
  87. package/src/tools.ts +16 -16
  88. package/src/types/conversation.ts +51 -0
  89. package/src/types/logs.ts +58 -0
  90. package/src/types/modelCapabilities.ts +64 -0
  91. package/src/types/notebook.ts +87 -0
  92. package/src/utils/advancedFuzzyMatcher.ts +290 -0
  93. package/src/utils/agentLoader.ts +284 -0
  94. package/src/utils/ask.tsx +1 -0
  95. package/src/utils/commands.ts +1 -1
  96. package/src/utils/commonUnixCommands.ts +161 -0
  97. package/src/utils/config.ts +173 -2
  98. package/src/utils/conversationRecovery.ts +1 -0
  99. package/src/utils/debugLogger.ts +13 -13
  100. package/src/utils/exampleCommands.ts +1 -0
  101. package/src/utils/fuzzyMatcher.ts +328 -0
  102. package/src/utils/messages.tsx +6 -5
  103. package/src/utils/model.ts +120 -42
  104. package/src/utils/responseState.ts +23 -0
  105. package/src/utils/secureFile.ts +559 -0
  106. package/src/utils/terminal.ts +1 -0
  107. package/src/utils/theme.ts +11 -0
  108. package/src/hooks/useSlashCommandTypeahead.ts +0 -137
@@ -21,7 +21,7 @@ const execFileAsync = promisify(execFile)
21
21
  * @param content - The custom command content to process
22
22
  * @returns Promise<string> - Content with bash commands replaced by their output
23
23
  */
24
- async function executeBashCommands(content: string): Promise<string> {
24
+ export async function executeBashCommands(content: string): Promise<string> {
25
25
  // Match patterns like !`git status` or !`command here`
26
26
  const bashCommandRegex = /!\`([^`]+)\`/g
27
27
  const matches = [...content.matchAll(bashCommandRegex)]
@@ -75,8 +75,10 @@ async function executeBashCommands(content: string): Promise<string> {
75
75
  * @param content - The custom command content to process
76
76
  * @returns Promise<string> - Content with file references replaced by file contents
77
77
  */
78
- async function resolveFileReferences(content: string): Promise<string> {
78
+ export async function resolveFileReferences(content: string): Promise<string> {
79
79
  // Match patterns like @src/file.js or @path/to/file.txt
80
+ // Use consistent file mention pattern from mentionProcessor
81
+ // Exclude agent and ask-model patterns to avoid conflicts
80
82
  const fileRefRegex = /@([a-zA-Z0-9/._-]+(?:\.[a-zA-Z0-9]+)?)/g
81
83
  const matches = [...content.matchAll(fileRefRegex)]
82
84
 
@@ -90,6 +92,11 @@ async function resolveFileReferences(content: string): Promise<string> {
90
92
  const fullMatch = match[0]
91
93
  const filePath = match[1]
92
94
 
95
+ // Skip agent mentions - these are handled by the mention processor
96
+ if (filePath.startsWith('agent-')) {
97
+ continue
98
+ }
99
+
93
100
  try {
94
101
  // Resolve relative to current working directory
95
102
  // This maintains consistency with how other file operations work
@@ -169,7 +176,27 @@ export interface CustomCommandFrontmatter {
169
176
  * This extends the base Command interface to include scope metadata
170
177
  * for distinguishing between user-level and project-level commands.
171
178
  */
172
- export interface CustomCommandWithScope extends Command {
179
+ export interface CustomCommandWithScope {
180
+ /** Command type - matches PromptCommand */
181
+ type: 'prompt'
182
+ /** Command name */
183
+ name: string
184
+ /** Command description */
185
+ description: string
186
+ /** Whether command is enabled */
187
+ isEnabled: boolean
188
+ /** Whether command is hidden */
189
+ isHidden: boolean
190
+ /** Command aliases */
191
+ aliases?: string[]
192
+ /** Progress message */
193
+ progressMessage: string
194
+ /** Argument names for legacy support */
195
+ argNames?: string[]
196
+ /** User-facing name function */
197
+ userFacingName(): string
198
+ /** Prompt generation function */
199
+ getPromptForCommand(args: string): Promise<MessageParam[]>
173
200
  /** Scope indicates whether this is a user or project command */
174
201
  scope?: 'user' | 'project'
175
202
  }
@@ -239,8 +266,7 @@ export function parseFrontmatter(content: string): {
239
266
  // End array processing when we hit a new key
240
267
  if (inArray && trimmed.includes(':')) {
241
268
  if (currentKey) {
242
- frontmatter[currentKey as keyof CustomCommandFrontmatter] =
243
- arrayItems as any
269
+ ;(frontmatter as any)[currentKey] = arrayItems
244
270
  }
245
271
  inArray = false
246
272
  arrayItems = []
@@ -260,7 +286,7 @@ export function parseFrontmatter(content: string): {
260
286
  .split(',')
261
287
  .map(s => s.trim().replace(/['"]/g, ''))
262
288
  .filter(s => s.length > 0)
263
- frontmatter[key as keyof CustomCommandFrontmatter] = items as any
289
+ ;(frontmatter as any)[key] = items
264
290
  }
265
291
  // Handle multi-line arrays (value is empty or [])
266
292
  else if (value === '' || value === '[]') {
@@ -270,22 +296,17 @@ export function parseFrontmatter(content: string): {
270
296
  }
271
297
  // Handle boolean values
272
298
  else if (value === 'true' || value === 'false') {
273
- frontmatter[key as keyof CustomCommandFrontmatter] = (value ===
274
- 'true') as any
299
+ ;(frontmatter as any)[key] = value === 'true'
275
300
  }
276
301
  // Handle string values (remove quotes)
277
302
  else {
278
- frontmatter[key as keyof CustomCommandFrontmatter] = value.replace(
279
- /['"]/g,
280
- '',
281
- ) as any
303
+ ;(frontmatter as any)[key] = value.replace(/['"]/g, '')
282
304
  }
283
305
  }
284
306
 
285
307
  // Handle final array if we ended in array mode
286
308
  if (inArray && currentKey) {
287
- frontmatter[currentKey as keyof CustomCommandFrontmatter] =
288
- arrayItems as any
309
+ ;(frontmatter as any)[currentKey] = arrayItems
289
310
  }
290
311
 
291
312
  return { frontmatter, content: markdownContent }
@@ -539,10 +560,10 @@ export const loadCustomCommands = memoize(
539
560
  // Log performance metrics for monitoring
540
561
  // This follows the same pattern as other performance-sensitive operations
541
562
  logEvent('tengu_custom_command_scan', {
542
- durationMs: duration,
543
- projectFilesFound: projectFiles.length,
544
- userFilesFound: userFiles.length,
545
- totalFiles: allFiles.length,
563
+ durationMs: duration.toString(),
564
+ projectFilesFound: projectFiles.length.toString(),
565
+ userFilesFound: userFiles.length.toString(),
566
+ totalFiles: allFiles.length.toString(),
546
567
  })
547
568
 
548
569
  // Parse files and create command objects
@@ -599,10 +620,10 @@ export const loadCustomCommands = memoize(
599
620
 
600
621
  // Log loading results for debugging and monitoring
601
622
  logEvent('tengu_custom_commands_loaded', {
602
- totalCommands: commands.length,
603
- enabledCommands: enabledCommands.length,
604
- userCommands: commands.filter(cmd => cmd.scope === 'user').length,
605
- projectCommands: commands.filter(cmd => cmd.scope === 'project').length,
623
+ totalCommands: commands.length.toString(),
624
+ enabledCommands: enabledCommands.length.toString(),
625
+ userCommands: commands.filter(cmd => cmd.scope === 'user').length.toString(),
626
+ projectCommands: commands.filter(cmd => cmd.scope === 'project').length.toString(),
606
627
  })
607
628
 
608
629
  return enabledCommands
@@ -0,0 +1,340 @@
1
+ /**
2
+ * 🔥 GPT-5 Connection Test Service
3
+ *
4
+ * Specialized connection testing for GPT-5 models that supports both
5
+ * Responses API and Chat Completions API with proper fallback handling.
6
+ */
7
+
8
+ import { getModelFeatures } from './openai'
9
+
10
+ export interface ConnectionTestResult {
11
+ success: boolean
12
+ message: string
13
+ endpoint?: string
14
+ details?: string
15
+ apiUsed?: 'responses' | 'chat_completions'
16
+ responseTime?: number
17
+ }
18
+
19
+ export interface GPT5TestConfig {
20
+ model: string
21
+ apiKey: string
22
+ baseURL?: string
23
+ maxTokens?: number
24
+ provider?: string
25
+ }
26
+
27
+ /**
28
+ * Test GPT-5 model connection with intelligent API selection
29
+ */
30
+ export async function testGPT5Connection(config: GPT5TestConfig): Promise<ConnectionTestResult> {
31
+ const startTime = Date.now()
32
+
33
+ // Validate configuration
34
+ if (!config.model || !config.apiKey) {
35
+ return {
36
+ success: false,
37
+ message: 'Invalid configuration',
38
+ details: 'Model name and API key are required',
39
+ }
40
+ }
41
+
42
+ const isGPT5 = config.model.toLowerCase().includes('gpt-5')
43
+ const modelFeatures = getModelFeatures(config.model)
44
+ const baseURL = config.baseURL || 'https://api.openai.com/v1'
45
+ const isOfficialOpenAI = !config.baseURL || config.baseURL.includes('api.openai.com')
46
+
47
+ console.log(`🔧 Testing GPT-5 connection for model: ${config.model}`)
48
+ console.log(`🔧 Base URL: ${baseURL}`)
49
+ console.log(`🔧 Official OpenAI: ${isOfficialOpenAI}`)
50
+ console.log(`🔧 Supports Responses API: ${modelFeatures.supportsResponsesAPI}`)
51
+
52
+ // Try Responses API first for official GPT-5 models
53
+ if (isGPT5 && modelFeatures.supportsResponsesAPI && isOfficialOpenAI) {
54
+ console.log(`🚀 Attempting Responses API for ${config.model}`)
55
+ const responsesResult = await testResponsesAPI(config, baseURL, startTime)
56
+
57
+ if (responsesResult.success) {
58
+ console.log(`✅ Responses API test successful for ${config.model}`)
59
+ return responsesResult
60
+ } else {
61
+ console.log(`⚠️ Responses API failed, falling back to Chat Completions: ${responsesResult.details}`)
62
+ }
63
+ }
64
+
65
+ // Fallback to Chat Completions API
66
+ console.log(`🔄 Using Chat Completions API for ${config.model}`)
67
+ return await testChatCompletionsAPI(config, baseURL, startTime)
68
+ }
69
+
70
+ /**
71
+ * Test using GPT-5 Responses API
72
+ */
73
+ async function testResponsesAPI(
74
+ config: GPT5TestConfig,
75
+ baseURL: string,
76
+ startTime: number
77
+ ): Promise<ConnectionTestResult> {
78
+ const testURL = `${baseURL.replace(/\/+$/, '')}/responses`
79
+
80
+ const testPayload = {
81
+ model: config.model,
82
+ input: [
83
+ {
84
+ role: 'user',
85
+ content: 'Please respond with exactly "YES" (in capital letters) to confirm this connection is working.',
86
+ },
87
+ ],
88
+ max_completion_tokens: Math.max(config.maxTokens || 8192, 8192),
89
+ temperature: 1, // GPT-5 requirement
90
+ reasoning: {
91
+ effort: 'low', // Fast response for connection test
92
+ },
93
+ }
94
+
95
+ const headers = {
96
+ 'Content-Type': 'application/json',
97
+ 'Authorization': `Bearer ${config.apiKey}`,
98
+ }
99
+
100
+ console.log(`🔧 Responses API URL: ${testURL}`)
101
+ console.log(`🔧 Responses API payload:`, JSON.stringify(testPayload, null, 2))
102
+
103
+ try {
104
+ const response = await fetch(testURL, {
105
+ method: 'POST',
106
+ headers,
107
+ body: JSON.stringify(testPayload),
108
+ })
109
+
110
+ const responseTime = Date.now() - startTime
111
+
112
+ if (response.ok) {
113
+ const data = await response.json()
114
+ console.log(`✅ Responses API successful response:`, data)
115
+
116
+ // Extract content from Responses API format
117
+ let responseContent = ''
118
+ if (data.output_text) {
119
+ responseContent = data.output_text
120
+ } else if (data.output && Array.isArray(data.output)) {
121
+ // Extract from structured output format
122
+ const messageOutput = data.output.find(item => item.type === 'message')
123
+ if (messageOutput && messageOutput.content) {
124
+ const textContent = messageOutput.content.find(c => c.type === 'output_text')
125
+ responseContent = textContent?.text || ''
126
+ }
127
+ }
128
+
129
+ const containsYes = responseContent.toLowerCase().includes('yes')
130
+
131
+ if (containsYes) {
132
+ return {
133
+ success: true,
134
+ message: '✅ GPT-5 Responses API connection successful',
135
+ endpoint: '/responses',
136
+ details: `Model responded correctly: "${responseContent.trim()}"`,
137
+ apiUsed: 'responses',
138
+ responseTime,
139
+ }
140
+ } else {
141
+ return {
142
+ success: false,
143
+ message: '⚠️ Responses API connected but unexpected response',
144
+ endpoint: '/responses',
145
+ details: `Expected "YES" but got: "${responseContent.trim() || '(empty response)'}"`,
146
+ apiUsed: 'responses',
147
+ responseTime,
148
+ }
149
+ }
150
+ } else {
151
+ const errorData = await response.json().catch(() => null)
152
+ const errorMessage = errorData?.error?.message || errorData?.message || response.statusText
153
+
154
+ console.log(`❌ Responses API error (${response.status}):`, errorData)
155
+
156
+ return {
157
+ success: false,
158
+ message: `❌ Responses API failed (${response.status})`,
159
+ endpoint: '/responses',
160
+ details: `Error: ${errorMessage}`,
161
+ apiUsed: 'responses',
162
+ responseTime: Date.now() - startTime,
163
+ }
164
+ }
165
+ } catch (error) {
166
+ console.log(`❌ Responses API connection error:`, error)
167
+
168
+ return {
169
+ success: false,
170
+ message: '❌ Responses API connection failed',
171
+ endpoint: '/responses',
172
+ details: error instanceof Error ? error.message : String(error),
173
+ apiUsed: 'responses',
174
+ responseTime: Date.now() - startTime,
175
+ }
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Test using Chat Completions API with GPT-5 compatibility
181
+ */
182
+ async function testChatCompletionsAPI(
183
+ config: GPT5TestConfig,
184
+ baseURL: string,
185
+ startTime: number
186
+ ): Promise<ConnectionTestResult> {
187
+ const testURL = `${baseURL.replace(/\/+$/, '')}/chat/completions`
188
+
189
+ const isGPT5 = config.model.toLowerCase().includes('gpt-5')
190
+
191
+ // Create test payload with GPT-5 compatibility
192
+ const testPayload: any = {
193
+ model: config.model,
194
+ messages: [
195
+ {
196
+ role: 'user',
197
+ content: 'Please respond with exactly "YES" (in capital letters) to confirm this connection is working.',
198
+ },
199
+ ],
200
+ temperature: isGPT5 ? 1 : 0, // GPT-5 requires temperature=1
201
+ stream: false,
202
+ }
203
+
204
+ // 🔧 Apply GPT-5 parameter transformations
205
+ if (isGPT5) {
206
+ testPayload.max_completion_tokens = Math.max(config.maxTokens || 8192, 8192)
207
+ delete testPayload.max_tokens // 🔥 CRITICAL: Remove max_tokens for GPT-5
208
+ console.log(`🔧 GPT-5 mode: Using max_completion_tokens = ${testPayload.max_completion_tokens}`)
209
+ } else {
210
+ testPayload.max_tokens = Math.max(config.maxTokens || 8192, 8192)
211
+ }
212
+
213
+ const headers = {
214
+ 'Content-Type': 'application/json',
215
+ }
216
+
217
+ // Add provider-specific headers
218
+ if (config.provider === 'azure') {
219
+ headers['api-key'] = config.apiKey
220
+ } else {
221
+ headers['Authorization'] = `Bearer ${config.apiKey}`
222
+ }
223
+
224
+ console.log(`🔧 Chat Completions URL: ${testURL}`)
225
+ console.log(`🔧 Chat Completions payload:`, JSON.stringify(testPayload, null, 2))
226
+
227
+ try {
228
+ const response = await fetch(testURL, {
229
+ method: 'POST',
230
+ headers,
231
+ body: JSON.stringify(testPayload),
232
+ })
233
+
234
+ const responseTime = Date.now() - startTime
235
+
236
+ if (response.ok) {
237
+ const data = await response.json()
238
+ console.log(`✅ Chat Completions successful response:`, data)
239
+
240
+ const responseContent = data.choices?.[0]?.message?.content || ''
241
+ const containsYes = responseContent.toLowerCase().includes('yes')
242
+
243
+ if (containsYes) {
244
+ return {
245
+ success: true,
246
+ message: `✅ ${isGPT5 ? 'GPT-5' : 'Model'} Chat Completions connection successful`,
247
+ endpoint: '/chat/completions',
248
+ details: `Model responded correctly: "${responseContent.trim()}"`,
249
+ apiUsed: 'chat_completions',
250
+ responseTime,
251
+ }
252
+ } else {
253
+ return {
254
+ success: false,
255
+ message: '⚠️ Chat Completions connected but unexpected response',
256
+ endpoint: '/chat/completions',
257
+ details: `Expected "YES" but got: "${responseContent.trim() || '(empty response)'}"`,
258
+ apiUsed: 'chat_completions',
259
+ responseTime,
260
+ }
261
+ }
262
+ } else {
263
+ const errorData = await response.json().catch(() => null)
264
+ const errorMessage = errorData?.error?.message || errorData?.message || response.statusText
265
+
266
+ console.log(`❌ Chat Completions error (${response.status}):`, errorData)
267
+
268
+ // 🔧 Provide specific guidance for GPT-5 errors
269
+ let details = `Error: ${errorMessage}`
270
+ if (response.status === 400 && errorMessage.includes('max_tokens') && isGPT5) {
271
+ details += '\n\n🔧 GPT-5 Fix Applied: This error suggests a parameter compatibility issue. Please check if the provider supports GPT-5 with max_completion_tokens.'
272
+ }
273
+
274
+ return {
275
+ success: false,
276
+ message: `❌ Chat Completions failed (${response.status})`,
277
+ endpoint: '/chat/completions',
278
+ details: details,
279
+ apiUsed: 'chat_completions',
280
+ responseTime: Date.now() - startTime,
281
+ }
282
+ }
283
+ } catch (error) {
284
+ console.log(`❌ Chat Completions connection error:`, error)
285
+
286
+ return {
287
+ success: false,
288
+ message: '❌ Chat Completions connection failed',
289
+ endpoint: '/chat/completions',
290
+ details: error instanceof Error ? error.message : String(error),
291
+ apiUsed: 'chat_completions',
292
+ responseTime: Date.now() - startTime,
293
+ }
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Quick validation for GPT-5 configuration
299
+ */
300
+ export function validateGPT5Config(config: GPT5TestConfig): { valid: boolean; errors: string[] } {
301
+ console.log(`🔧 validateGPT5Config called with:`, {
302
+ model: config.model,
303
+ hasApiKey: !!config.apiKey,
304
+ baseURL: config.baseURL,
305
+ provider: config.provider,
306
+ })
307
+
308
+ const errors: string[] = []
309
+
310
+ if (!config.model) {
311
+ errors.push('Model name is required')
312
+ }
313
+
314
+ if (!config.apiKey) {
315
+ errors.push('API key is required')
316
+ }
317
+
318
+ if (config.apiKey && config.apiKey.length < 10) {
319
+ errors.push('API key appears to be invalid (too short)')
320
+ }
321
+
322
+ const isGPT5 = config.model?.toLowerCase().includes('gpt-5')
323
+ if (isGPT5) {
324
+ console.log(`🔧 GPT-5 validation: model=${config.model}, maxTokens=${config.maxTokens}`)
325
+
326
+ if (config.maxTokens && config.maxTokens < 1000) {
327
+ errors.push('GPT-5 models typically require at least 1000 max tokens')
328
+ }
329
+
330
+ // 完全移除第三方provider限制,允许所有代理中转站使用GPT-5
331
+ console.log(`🔧 No third-party restrictions applied for GPT-5`)
332
+ }
333
+
334
+ console.log(`🔧 Validation result:`, { valid: errors.length === 0, errors })
335
+
336
+ return {
337
+ valid: errors.length === 0,
338
+ errors,
339
+ }
340
+ }
@@ -331,7 +331,7 @@ export const getClients = memoize(async (): Promise<WrappedClient[]> => {
331
331
  return await Promise.all(
332
332
  Object.entries(allServers).map(async ([name, serverRef]) => {
333
333
  try {
334
- const client = await connectToServer(name, serverRef)
334
+ const client = await connectToServer(name, serverRef as McpServerConfig)
335
335
  logEvent('tengu_mcp_server_connection_succeeded', {})
336
336
  return { name, client, type: 'connected' as const }
337
337
  } catch (error) {