@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.
- package/README.md +160 -1
- package/README.zh-CN.md +65 -1
- package/cli.js +5 -10
- package/package.json +6 -2
- package/src/ProjectOnboarding.tsx +47 -29
- package/src/Tool.ts +33 -4
- package/src/commands/agents.tsx +3401 -0
- package/src/commands/help.tsx +2 -2
- package/src/commands/resume.tsx +2 -1
- package/src/commands/terminalSetup.ts +4 -4
- package/src/commands.ts +3 -0
- package/src/components/ApproveApiKey.tsx +1 -1
- package/src/components/Config.tsx +10 -6
- package/src/components/ConsoleOAuthFlow.tsx +5 -4
- package/src/components/CustomSelect/select-option.tsx +28 -2
- package/src/components/CustomSelect/select.tsx +14 -5
- package/src/components/CustomSelect/theme.ts +45 -0
- package/src/components/Help.tsx +4 -4
- package/src/components/InvalidConfigDialog.tsx +1 -1
- package/src/components/LogSelector.tsx +1 -1
- package/src/components/MCPServerApprovalDialog.tsx +1 -1
- package/src/components/Message.tsx +2 -0
- package/src/components/ModelListManager.tsx +10 -6
- package/src/components/ModelSelector.tsx +201 -23
- package/src/components/ModelStatusDisplay.tsx +7 -5
- package/src/components/PromptInput.tsx +146 -96
- package/src/components/SentryErrorBoundary.ts +9 -3
- package/src/components/StickerRequestForm.tsx +16 -0
- package/src/components/StructuredDiff.tsx +36 -29
- package/src/components/TextInput.tsx +13 -0
- package/src/components/TodoItem.tsx +47 -0
- package/src/components/TrustDialog.tsx +1 -1
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +5 -1
- package/src/components/messages/AssistantToolUseMessage.tsx +14 -4
- package/src/components/messages/TaskProgressMessage.tsx +32 -0
- package/src/components/messages/TaskToolMessage.tsx +58 -0
- package/src/components/permissions/FallbackPermissionRequest.tsx +2 -4
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +1 -1
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +5 -3
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +1 -1
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +5 -3
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +2 -4
- package/src/components/permissions/PermissionRequest.tsx +3 -5
- package/src/constants/macros.ts +2 -0
- package/src/constants/modelCapabilities.ts +179 -0
- package/src/constants/models.ts +90 -0
- package/src/constants/product.ts +1 -1
- package/src/context.ts +7 -7
- package/src/entrypoints/cli.tsx +23 -3
- package/src/entrypoints/mcp.ts +10 -10
- package/src/hooks/useCanUseTool.ts +1 -1
- package/src/hooks/useTextInput.ts +5 -2
- package/src/hooks/useUnifiedCompletion.ts +1405 -0
- package/src/messages.ts +1 -0
- package/src/query.ts +3 -0
- package/src/screens/ConfigureNpmPrefix.tsx +1 -1
- package/src/screens/Doctor.tsx +1 -1
- package/src/screens/REPL.tsx +11 -12
- package/src/services/adapters/base.ts +38 -0
- package/src/services/adapters/chatCompletions.ts +90 -0
- package/src/services/adapters/responsesAPI.ts +170 -0
- package/src/services/claude.ts +198 -62
- package/src/services/customCommands.ts +43 -22
- package/src/services/gpt5ConnectionTest.ts +340 -0
- package/src/services/mcpClient.ts +1 -1
- package/src/services/mentionProcessor.ts +273 -0
- package/src/services/modelAdapterFactory.ts +69 -0
- package/src/services/openai.ts +534 -14
- package/src/services/responseStateManager.ts +90 -0
- package/src/services/systemReminder.ts +113 -12
- package/src/test/testAdapters.ts +96 -0
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +120 -56
- package/src/tools/BashTool/BashTool.tsx +4 -31
- package/src/tools/BashTool/BashToolResultMessage.tsx +1 -1
- package/src/tools/BashTool/OutputLine.tsx +1 -0
- package/src/tools/FileEditTool/FileEditTool.tsx +4 -5
- package/src/tools/FileReadTool/FileReadTool.tsx +43 -10
- package/src/tools/MCPTool/MCPTool.tsx +2 -1
- package/src/tools/MultiEditTool/MultiEditTool.tsx +2 -2
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +15 -23
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +1 -1
- package/src/tools/TaskTool/TaskTool.tsx +170 -86
- package/src/tools/TaskTool/prompt.ts +61 -25
- package/src/tools/ThinkTool/ThinkTool.tsx +1 -3
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +65 -41
- package/src/tools/lsTool/lsTool.tsx +5 -2
- package/src/tools.ts +16 -16
- package/src/types/conversation.ts +51 -0
- package/src/types/logs.ts +58 -0
- package/src/types/modelCapabilities.ts +64 -0
- package/src/types/notebook.ts +87 -0
- package/src/utils/advancedFuzzyMatcher.ts +290 -0
- package/src/utils/agentLoader.ts +284 -0
- package/src/utils/ask.tsx +1 -0
- package/src/utils/commands.ts +1 -1
- package/src/utils/commonUnixCommands.ts +161 -0
- package/src/utils/config.ts +173 -2
- package/src/utils/conversationRecovery.ts +1 -0
- package/src/utils/debugLogger.ts +13 -13
- package/src/utils/exampleCommands.ts +1 -0
- package/src/utils/fuzzyMatcher.ts +328 -0
- package/src/utils/messages.tsx +6 -5
- package/src/utils/model.ts +120 -42
- package/src/utils/responseState.ts +23 -0
- package/src/utils/secureFile.ts +559 -0
- package/src/utils/terminal.ts +1 -0
- package/src/utils/theme.ts +11 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) {
|