@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.
- package/README.md +142 -1
- package/README.zh-CN.md +47 -1
- package/package.json +5 -1
- 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 +117 -87
- package/src/components/SentryErrorBoundary.ts +3 -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 +11 -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 +1404 -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 +15 -9
- 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 +521 -12
- 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 +11 -10
- 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/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
package/src/services/openai.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { getGlobalConfig, GlobalConfig } from '../utils/config'
|
|
|
3
3
|
import { ProxyAgent, fetch, Response } from 'undici'
|
|
4
4
|
import { setSessionState, getSessionState } from '../utils/sessionState'
|
|
5
5
|
import { logEvent } from '../services/statsig'
|
|
6
|
-
import { debug as debugLogger } from '../utils/debugLogger'
|
|
6
|
+
import { debug as debugLogger, getCurrentRequest } from '../utils/debugLogger'
|
|
7
7
|
|
|
8
8
|
// Helper function to calculate retry delay with exponential backoff
|
|
9
9
|
function getRetryDelay(attempt: number, retryAfter?: string | null): number {
|
|
@@ -53,6 +53,7 @@ function abortableDelay(delayMs: number, signal?: AbortSignal): Promise<void> {
|
|
|
53
53
|
enum ModelErrorType {
|
|
54
54
|
MaxLength = '1024',
|
|
55
55
|
MaxCompletionTokens = 'max_completion_tokens',
|
|
56
|
+
TemperatureRestriction = 'temperature_restriction',
|
|
56
57
|
StreamOptions = 'stream_options',
|
|
57
58
|
Citations = 'citations',
|
|
58
59
|
RateLimit = 'rate_limit',
|
|
@@ -98,6 +99,49 @@ interface ErrorHandler {
|
|
|
98
99
|
fix: ErrorFixer
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
// GPT-5 specific error handlers with enhanced detection patterns
|
|
103
|
+
const GPT5_ERROR_HANDLERS: ErrorHandler[] = [
|
|
104
|
+
{
|
|
105
|
+
type: ModelErrorType.MaxCompletionTokens,
|
|
106
|
+
detect: errMsg => {
|
|
107
|
+
const lowerMsg = errMsg.toLowerCase()
|
|
108
|
+
return (
|
|
109
|
+
// Exact OpenAI GPT-5 error message
|
|
110
|
+
(lowerMsg.includes("unsupported parameter: 'max_tokens'") && lowerMsg.includes("'max_completion_tokens'")) ||
|
|
111
|
+
// Generic max_tokens error patterns
|
|
112
|
+
(lowerMsg.includes("max_tokens") && lowerMsg.includes("max_completion_tokens")) ||
|
|
113
|
+
(lowerMsg.includes("max_tokens") && lowerMsg.includes("not supported")) ||
|
|
114
|
+
(lowerMsg.includes("max_tokens") && lowerMsg.includes("use max_completion_tokens")) ||
|
|
115
|
+
// Additional patterns for various providers
|
|
116
|
+
(lowerMsg.includes("invalid parameter") && lowerMsg.includes("max_tokens")) ||
|
|
117
|
+
(lowerMsg.includes("parameter error") && lowerMsg.includes("max_tokens"))
|
|
118
|
+
)
|
|
119
|
+
},
|
|
120
|
+
fix: async opts => {
|
|
121
|
+
console.log(`🔧 GPT-5 Fix: Converting max_tokens (${opts.max_tokens}) to max_completion_tokens`)
|
|
122
|
+
if ('max_tokens' in opts) {
|
|
123
|
+
opts.max_completion_tokens = opts.max_tokens
|
|
124
|
+
delete opts.max_tokens
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: ModelErrorType.TemperatureRestriction,
|
|
130
|
+
detect: errMsg => {
|
|
131
|
+
const lowerMsg = errMsg.toLowerCase()
|
|
132
|
+
return (
|
|
133
|
+
lowerMsg.includes("temperature") &&
|
|
134
|
+
(lowerMsg.includes("only supports") || lowerMsg.includes("must be 1") || lowerMsg.includes("invalid temperature"))
|
|
135
|
+
)
|
|
136
|
+
},
|
|
137
|
+
fix: async opts => {
|
|
138
|
+
console.log(`🔧 GPT-5 Fix: Adjusting temperature from ${opts.temperature} to 1`)
|
|
139
|
+
opts.temperature = 1
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
// Add more GPT-5 specific handlers as needed
|
|
143
|
+
]
|
|
144
|
+
|
|
101
145
|
// Standard error handlers
|
|
102
146
|
const ERROR_HANDLERS: ErrorHandler[] = [
|
|
103
147
|
{
|
|
@@ -210,6 +254,11 @@ function isRateLimitError(errMsg: string): boolean {
|
|
|
210
254
|
// Model-specific feature flags - can be extended with more properties as needed
|
|
211
255
|
interface ModelFeatures {
|
|
212
256
|
usesMaxCompletionTokens: boolean
|
|
257
|
+
supportsResponsesAPI?: boolean
|
|
258
|
+
requiresTemperatureOne?: boolean
|
|
259
|
+
supportsVerbosityControl?: boolean
|
|
260
|
+
supportsCustomTools?: boolean
|
|
261
|
+
supportsAllowedTools?: boolean
|
|
213
262
|
}
|
|
214
263
|
|
|
215
264
|
// Map of model identifiers to their specific features
|
|
@@ -220,16 +269,63 @@ const MODEL_FEATURES: Record<string, ModelFeatures> = {
|
|
|
220
269
|
'o1-mini': { usesMaxCompletionTokens: true },
|
|
221
270
|
'o1-pro': { usesMaxCompletionTokens: true },
|
|
222
271
|
'o3-mini': { usesMaxCompletionTokens: true },
|
|
272
|
+
// GPT-5 models
|
|
273
|
+
'gpt-5': {
|
|
274
|
+
usesMaxCompletionTokens: true,
|
|
275
|
+
supportsResponsesAPI: true,
|
|
276
|
+
requiresTemperatureOne: true,
|
|
277
|
+
supportsVerbosityControl: true,
|
|
278
|
+
supportsCustomTools: true,
|
|
279
|
+
supportsAllowedTools: true,
|
|
280
|
+
},
|
|
281
|
+
'gpt-5-mini': {
|
|
282
|
+
usesMaxCompletionTokens: true,
|
|
283
|
+
supportsResponsesAPI: true,
|
|
284
|
+
requiresTemperatureOne: true,
|
|
285
|
+
supportsVerbosityControl: true,
|
|
286
|
+
supportsCustomTools: true,
|
|
287
|
+
supportsAllowedTools: true,
|
|
288
|
+
},
|
|
289
|
+
'gpt-5-nano': {
|
|
290
|
+
usesMaxCompletionTokens: true,
|
|
291
|
+
supportsResponsesAPI: true,
|
|
292
|
+
requiresTemperatureOne: true,
|
|
293
|
+
supportsVerbosityControl: true,
|
|
294
|
+
supportsCustomTools: true,
|
|
295
|
+
supportsAllowedTools: true,
|
|
296
|
+
},
|
|
297
|
+
'gpt-5-chat-latest': {
|
|
298
|
+
usesMaxCompletionTokens: true,
|
|
299
|
+
supportsResponsesAPI: false, // Uses Chat Completions only
|
|
300
|
+
requiresTemperatureOne: true,
|
|
301
|
+
supportsVerbosityControl: true,
|
|
302
|
+
},
|
|
223
303
|
}
|
|
224
304
|
|
|
225
305
|
// Helper to get model features based on model ID/name
|
|
226
306
|
function getModelFeatures(modelName: string): ModelFeatures {
|
|
227
|
-
|
|
307
|
+
if (!modelName || typeof modelName !== 'string') {
|
|
308
|
+
return { usesMaxCompletionTokens: false }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Check for exact matches first (highest priority)
|
|
228
312
|
if (MODEL_FEATURES[modelName]) {
|
|
229
313
|
return MODEL_FEATURES[modelName]
|
|
230
314
|
}
|
|
231
315
|
|
|
232
|
-
//
|
|
316
|
+
// Simple GPT-5 detection: any model name containing 'gpt-5'
|
|
317
|
+
if (modelName.toLowerCase().includes('gpt-5')) {
|
|
318
|
+
return {
|
|
319
|
+
usesMaxCompletionTokens: true,
|
|
320
|
+
supportsResponsesAPI: true,
|
|
321
|
+
requiresTemperatureOne: true,
|
|
322
|
+
supportsVerbosityControl: true,
|
|
323
|
+
supportsCustomTools: true,
|
|
324
|
+
supportsAllowedTools: true,
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check for partial matches (e.g., other reasoning models)
|
|
233
329
|
for (const [key, features] of Object.entries(MODEL_FEATURES)) {
|
|
234
330
|
if (modelName.includes(key)) {
|
|
235
331
|
return features
|
|
@@ -249,15 +345,53 @@ function applyModelSpecificTransformations(
|
|
|
249
345
|
}
|
|
250
346
|
|
|
251
347
|
const features = getModelFeatures(opts.model)
|
|
348
|
+
const isGPT5 = opts.model.toLowerCase().includes('gpt-5')
|
|
252
349
|
|
|
253
|
-
//
|
|
254
|
-
if (
|
|
255
|
-
|
|
256
|
-
'max_tokens' in opts &&
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
350
|
+
// 🔥 Enhanced GPT-5 Detection and Transformation
|
|
351
|
+
if (isGPT5 || features.usesMaxCompletionTokens) {
|
|
352
|
+
// Force max_completion_tokens for all GPT-5 models
|
|
353
|
+
if ('max_tokens' in opts && !('max_completion_tokens' in opts)) {
|
|
354
|
+
console.log(`🔧 Transforming max_tokens (${opts.max_tokens}) to max_completion_tokens for ${opts.model}`)
|
|
355
|
+
opts.max_completion_tokens = opts.max_tokens
|
|
356
|
+
delete opts.max_tokens
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Force temperature = 1 for GPT-5 models
|
|
360
|
+
if (features.requiresTemperatureOne && 'temperature' in opts) {
|
|
361
|
+
if (opts.temperature !== 1 && opts.temperature !== undefined) {
|
|
362
|
+
console.log(
|
|
363
|
+
`🔧 GPT-5 temperature constraint: Adjusting temperature from ${opts.temperature} to 1 for ${opts.model}`
|
|
364
|
+
)
|
|
365
|
+
opts.temperature = 1
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Remove unsupported parameters for GPT-5
|
|
370
|
+
if (isGPT5) {
|
|
371
|
+
// Remove parameters that may not be supported by GPT-5
|
|
372
|
+
delete opts.frequency_penalty
|
|
373
|
+
delete opts.presence_penalty
|
|
374
|
+
delete opts.logit_bias
|
|
375
|
+
delete opts.user
|
|
376
|
+
|
|
377
|
+
// Add reasoning_effort if not present and model supports it
|
|
378
|
+
if (!opts.reasoning_effort && features.supportsVerbosityControl) {
|
|
379
|
+
opts.reasoning_effort = 'medium' // Default reasoning effort for coding tasks
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Apply transformations for non-GPT-5 models
|
|
385
|
+
else {
|
|
386
|
+
// Standard max_tokens to max_completion_tokens conversion for other reasoning models
|
|
387
|
+
if (
|
|
388
|
+
features.usesMaxCompletionTokens &&
|
|
389
|
+
'max_tokens' in opts &&
|
|
390
|
+
!('max_completion_tokens' in opts)
|
|
391
|
+
) {
|
|
392
|
+
opts.max_completion_tokens = opts.max_tokens
|
|
393
|
+
delete opts.max_tokens
|
|
394
|
+
}
|
|
261
395
|
}
|
|
262
396
|
|
|
263
397
|
// Add more transformations here as needed
|
|
@@ -267,7 +401,10 @@ async function applyModelErrorFixes(
|
|
|
267
401
|
opts: OpenAI.ChatCompletionCreateParams,
|
|
268
402
|
baseURL: string,
|
|
269
403
|
) {
|
|
270
|
-
|
|
404
|
+
const isGPT5 = opts.model.startsWith('gpt-5')
|
|
405
|
+
const handlers = isGPT5 ? [...GPT5_ERROR_HANDLERS, ...ERROR_HANDLERS] : ERROR_HANDLERS
|
|
406
|
+
|
|
407
|
+
for (const handler of handlers) {
|
|
271
408
|
if (hasModelError(baseURL, opts.model, handler.type)) {
|
|
272
409
|
await handler.fix(opts)
|
|
273
410
|
return
|
|
@@ -333,6 +470,9 @@ async function tryWithEndpointFallback(
|
|
|
333
470
|
throw lastError || new Error('All endpoints failed')
|
|
334
471
|
}
|
|
335
472
|
|
|
473
|
+
// Export shared utilities for GPT-5 compatibility
|
|
474
|
+
export { getGPT5CompletionWithProfile, getModelFeatures, applyModelSpecificTransformations }
|
|
475
|
+
|
|
336
476
|
export async function getCompletionWithProfile(
|
|
337
477
|
modelProfile: any,
|
|
338
478
|
opts: OpenAI.ChatCompletionCreateParams,
|
|
@@ -465,6 +605,43 @@ export async function getCompletionWithProfile(
|
|
|
465
605
|
throw new Error('Request cancelled by user')
|
|
466
606
|
}
|
|
467
607
|
|
|
608
|
+
// 🔥 NEW: Parse error message to detect and handle specific API errors
|
|
609
|
+
try {
|
|
610
|
+
const errorData = await response.json()
|
|
611
|
+
const errorMessage = errorData?.error?.message || errorData?.message || `HTTP ${response.status}`
|
|
612
|
+
|
|
613
|
+
// Check if this is a parameter error that we can fix
|
|
614
|
+
const isGPT5 = opts.model.startsWith('gpt-5')
|
|
615
|
+
const handlers = isGPT5 ? [...GPT5_ERROR_HANDLERS, ...ERROR_HANDLERS] : ERROR_HANDLERS
|
|
616
|
+
|
|
617
|
+
for (const handler of handlers) {
|
|
618
|
+
if (handler.detect(errorMessage)) {
|
|
619
|
+
console.log(`🔧 Detected ${handler.type} error for ${opts.model}: ${errorMessage}`)
|
|
620
|
+
|
|
621
|
+
// Store this error for future requests
|
|
622
|
+
setModelError(baseURL || '', opts.model, handler.type, errorMessage)
|
|
623
|
+
|
|
624
|
+
// Apply the fix and retry immediately
|
|
625
|
+
await handler.fix(opts)
|
|
626
|
+
console.log(`🔧 Applied fix for ${handler.type}, retrying...`)
|
|
627
|
+
|
|
628
|
+
return getCompletionWithProfile(
|
|
629
|
+
modelProfile,
|
|
630
|
+
opts,
|
|
631
|
+
attempt + 1,
|
|
632
|
+
maxAttempts,
|
|
633
|
+
signal,
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// If no specific handler found, log the error for debugging
|
|
639
|
+
console.log(`⚠️ Unhandled API error (${response.status}): ${errorMessage}`)
|
|
640
|
+
} catch (parseError) {
|
|
641
|
+
// If we can't parse the error, fall back to generic retry
|
|
642
|
+
console.log(`⚠️ Could not parse error response (${response.status})`)
|
|
643
|
+
}
|
|
644
|
+
|
|
468
645
|
const delayMs = getRetryDelay(attempt)
|
|
469
646
|
console.log(
|
|
470
647
|
` ⎿ API error (${response.status}), retrying in ${Math.round(delayMs / 1000)}s... (attempt ${attempt + 1}/${maxAttempts})`,
|
|
@@ -538,6 +715,43 @@ export async function getCompletionWithProfile(
|
|
|
538
715
|
throw new Error('Request cancelled by user')
|
|
539
716
|
}
|
|
540
717
|
|
|
718
|
+
// 🔥 NEW: Parse error message to detect and handle specific API errors
|
|
719
|
+
try {
|
|
720
|
+
const errorData = await response.json()
|
|
721
|
+
const errorMessage = errorData?.error?.message || errorData?.message || `HTTP ${response.status}`
|
|
722
|
+
|
|
723
|
+
// Check if this is a parameter error that we can fix
|
|
724
|
+
const isGPT5 = opts.model.startsWith('gpt-5')
|
|
725
|
+
const handlers = isGPT5 ? [...GPT5_ERROR_HANDLERS, ...ERROR_HANDLERS] : ERROR_HANDLERS
|
|
726
|
+
|
|
727
|
+
for (const handler of handlers) {
|
|
728
|
+
if (handler.detect(errorMessage)) {
|
|
729
|
+
console.log(`🔧 Detected ${handler.type} error for ${opts.model}: ${errorMessage}`)
|
|
730
|
+
|
|
731
|
+
// Store this error for future requests
|
|
732
|
+
setModelError(baseURL || '', opts.model, handler.type, errorMessage)
|
|
733
|
+
|
|
734
|
+
// Apply the fix and retry immediately
|
|
735
|
+
await handler.fix(opts)
|
|
736
|
+
console.log(`🔧 Applied fix for ${handler.type}, retrying...`)
|
|
737
|
+
|
|
738
|
+
return getCompletionWithProfile(
|
|
739
|
+
modelProfile,
|
|
740
|
+
opts,
|
|
741
|
+
attempt + 1,
|
|
742
|
+
maxAttempts,
|
|
743
|
+
signal,
|
|
744
|
+
)
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// If no specific handler found, log the error for debugging
|
|
749
|
+
console.log(`⚠️ Unhandled API error (${response.status}): ${errorMessage}`)
|
|
750
|
+
} catch (parseError) {
|
|
751
|
+
// If we can't parse the error, fall back to generic retry
|
|
752
|
+
console.log(`⚠️ Could not parse error response (${response.status})`)
|
|
753
|
+
}
|
|
754
|
+
|
|
541
755
|
const delayMs = getRetryDelay(attempt)
|
|
542
756
|
console.log(
|
|
543
757
|
` ⎿ API error (${response.status}), retrying in ${Math.round(delayMs / 1000)}s... (attempt ${attempt + 1}/${maxAttempts})`,
|
|
@@ -689,6 +903,301 @@ export function streamCompletion(
|
|
|
689
903
|
return createStreamProcessor(stream)
|
|
690
904
|
}
|
|
691
905
|
|
|
906
|
+
/**
|
|
907
|
+
* Call GPT-5 Responses API with proper parameter handling
|
|
908
|
+
*/
|
|
909
|
+
export async function callGPT5ResponsesAPI(
|
|
910
|
+
modelProfile: any,
|
|
911
|
+
opts: any, // Using 'any' for Responses API params which differ from ChatCompletionCreateParams
|
|
912
|
+
signal?: AbortSignal,
|
|
913
|
+
): Promise<any> {
|
|
914
|
+
const baseURL = modelProfile?.baseURL || 'https://api.openai.com/v1'
|
|
915
|
+
const apiKey = modelProfile?.apiKey
|
|
916
|
+
const proxy = getGlobalConfig().proxy
|
|
917
|
+
? new ProxyAgent(getGlobalConfig().proxy)
|
|
918
|
+
: undefined
|
|
919
|
+
|
|
920
|
+
const headers: Record<string, string> = {
|
|
921
|
+
'Content-Type': 'application/json',
|
|
922
|
+
Authorization: `Bearer ${apiKey}`,
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// 🔥 Enhanced Responses API Parameter Mapping for GPT-5
|
|
926
|
+
const responsesParams: any = {
|
|
927
|
+
model: opts.model,
|
|
928
|
+
input: opts.messages, // Responses API uses 'input' instead of 'messages'
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// 🔧 GPT-5 Token Configuration
|
|
932
|
+
if (opts.max_completion_tokens) {
|
|
933
|
+
responsesParams.max_completion_tokens = opts.max_completion_tokens
|
|
934
|
+
} else if (opts.max_tokens) {
|
|
935
|
+
// Fallback conversion if max_tokens is still present
|
|
936
|
+
responsesParams.max_completion_tokens = opts.max_tokens
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// 🔧 GPT-5 Temperature Handling (only 1 or undefined)
|
|
940
|
+
if (opts.temperature === 1) {
|
|
941
|
+
responsesParams.temperature = 1
|
|
942
|
+
}
|
|
943
|
+
// Note: Do not pass temperature if it's not 1, GPT-5 will use default
|
|
944
|
+
|
|
945
|
+
// 🔧 GPT-5 Reasoning Configuration
|
|
946
|
+
const reasoningEffort = opts.reasoning_effort || 'medium'
|
|
947
|
+
responsesParams.reasoning = {
|
|
948
|
+
effort: reasoningEffort,
|
|
949
|
+
// 🚀 Enable reasoning summaries for transparency in coding tasks
|
|
950
|
+
generate_summary: true,
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// 🔧 GPT-5 Tools Support
|
|
954
|
+
if (opts.tools && opts.tools.length > 0) {
|
|
955
|
+
responsesParams.tools = opts.tools
|
|
956
|
+
|
|
957
|
+
// 🚀 GPT-5 Tool Choice Configuration
|
|
958
|
+
if (opts.tool_choice) {
|
|
959
|
+
responsesParams.tool_choice = opts.tool_choice
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// 🔧 GPT-5 System Instructions (separate from messages)
|
|
964
|
+
const systemMessages = opts.messages.filter(msg => msg.role === 'system')
|
|
965
|
+
const nonSystemMessages = opts.messages.filter(msg => msg.role !== 'system')
|
|
966
|
+
|
|
967
|
+
if (systemMessages.length > 0) {
|
|
968
|
+
responsesParams.instructions = systemMessages.map(msg => msg.content).join('\n\n')
|
|
969
|
+
responsesParams.input = nonSystemMessages
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Handle verbosity (if supported) - optimized for coding tasks
|
|
973
|
+
const features = getModelFeatures(opts.model)
|
|
974
|
+
if (features.supportsVerbosityControl) {
|
|
975
|
+
// High verbosity for coding tasks to get detailed explanations and structured code
|
|
976
|
+
// Based on GPT-5 best practices for agent-like coding environments
|
|
977
|
+
responsesParams.text = {
|
|
978
|
+
verbosity: 'high',
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Apply GPT-5 coding optimizations
|
|
983
|
+
if (opts.model.startsWith('gpt-5')) {
|
|
984
|
+
// Set reasoning effort based on task complexity
|
|
985
|
+
if (!responsesParams.reasoning) {
|
|
986
|
+
responsesParams.reasoning = {
|
|
987
|
+
effort: 'medium', // Balanced for most coding tasks
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Add instructions parameter for coding-specific guidance
|
|
992
|
+
if (!responsesParams.instructions) {
|
|
993
|
+
responsesParams.instructions = `You are an expert programmer working in a terminal-based coding environment. Follow these guidelines:
|
|
994
|
+
- Provide clear, concise code solutions
|
|
995
|
+
- Use proper error handling and validation
|
|
996
|
+
- Follow coding best practices and patterns
|
|
997
|
+
- Explain complex logic when necessary
|
|
998
|
+
- Focus on maintainable, readable code`
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
try {
|
|
1003
|
+
const response = await fetch(`${baseURL}/responses`, {
|
|
1004
|
+
method: 'POST',
|
|
1005
|
+
headers,
|
|
1006
|
+
body: JSON.stringify(responsesParams),
|
|
1007
|
+
dispatcher: proxy,
|
|
1008
|
+
signal: signal,
|
|
1009
|
+
})
|
|
1010
|
+
|
|
1011
|
+
if (!response.ok) {
|
|
1012
|
+
throw new Error(`GPT-5 Responses API error: ${response.status} ${response.statusText}`)
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
const responseData = await response.json()
|
|
1016
|
+
|
|
1017
|
+
// Convert Responses API response back to Chat Completion format for compatibility
|
|
1018
|
+
return convertResponsesAPIToChatCompletion(responseData)
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
if (signal?.aborted) {
|
|
1021
|
+
throw new Error('Request cancelled by user')
|
|
1022
|
+
}
|
|
1023
|
+
throw error
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Convert Responses API response to Chat Completion format for compatibility
|
|
1029
|
+
* 🔥 Enhanced for GPT-5 with reasoning summary support
|
|
1030
|
+
*/
|
|
1031
|
+
function convertResponsesAPIToChatCompletion(responsesData: any): any {
|
|
1032
|
+
// Extract content from Responses API format
|
|
1033
|
+
let outputText = responsesData.output_text || ''
|
|
1034
|
+
const usage = responsesData.usage || {}
|
|
1035
|
+
|
|
1036
|
+
// 🚀 GPT-5 Reasoning Summary Integration
|
|
1037
|
+
// If reasoning summary is available, prepend it to the output for transparency
|
|
1038
|
+
if (responsesData.output && Array.isArray(responsesData.output)) {
|
|
1039
|
+
const reasoningItems = responsesData.output.filter(item => item.type === 'reasoning' && item.summary)
|
|
1040
|
+
const messageItems = responsesData.output.filter(item => item.type === 'message')
|
|
1041
|
+
|
|
1042
|
+
if (reasoningItems.length > 0 && messageItems.length > 0) {
|
|
1043
|
+
const reasoningSummary = reasoningItems
|
|
1044
|
+
.map(item => item.summary?.map(s => s.text).join('\n'))
|
|
1045
|
+
.filter(Boolean)
|
|
1046
|
+
.join('\n\n')
|
|
1047
|
+
|
|
1048
|
+
const mainContent = messageItems
|
|
1049
|
+
.map(item => item.content?.map(c => c.text).join('\n'))
|
|
1050
|
+
.filter(Boolean)
|
|
1051
|
+
.join('\n\n')
|
|
1052
|
+
|
|
1053
|
+
if (reasoningSummary) {
|
|
1054
|
+
outputText = `**🧠 Reasoning Process:**\n${reasoningSummary}\n\n**📝 Response:**\n${mainContent}`
|
|
1055
|
+
} else {
|
|
1056
|
+
outputText = mainContent
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
return {
|
|
1062
|
+
id: responsesData.id || `chatcmpl-${Date.now()}`,
|
|
1063
|
+
object: 'chat.completion',
|
|
1064
|
+
created: Math.floor(Date.now() / 1000),
|
|
1065
|
+
model: responsesData.model || '',
|
|
1066
|
+
choices: [
|
|
1067
|
+
{
|
|
1068
|
+
index: 0,
|
|
1069
|
+
message: {
|
|
1070
|
+
role: 'assistant',
|
|
1071
|
+
content: outputText,
|
|
1072
|
+
// 🚀 Include reasoning metadata if available
|
|
1073
|
+
...(responsesData.reasoning && {
|
|
1074
|
+
reasoning: {
|
|
1075
|
+
effort: responsesData.reasoning.effort,
|
|
1076
|
+
summary: responsesData.reasoning.summary,
|
|
1077
|
+
},
|
|
1078
|
+
}),
|
|
1079
|
+
},
|
|
1080
|
+
finish_reason: responsesData.status === 'completed' ? 'stop' : 'length',
|
|
1081
|
+
},
|
|
1082
|
+
],
|
|
1083
|
+
usage: {
|
|
1084
|
+
prompt_tokens: usage.input_tokens || 0,
|
|
1085
|
+
completion_tokens: usage.output_tokens || 0,
|
|
1086
|
+
total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0),
|
|
1087
|
+
// 🔧 GPT-5 Enhanced Usage Details
|
|
1088
|
+
prompt_tokens_details: {
|
|
1089
|
+
cached_tokens: usage.input_tokens_details?.cached_tokens || 0,
|
|
1090
|
+
},
|
|
1091
|
+
completion_tokens_details: {
|
|
1092
|
+
reasoning_tokens: usage.output_tokens_details?.reasoning_tokens || 0,
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Enhanced getCompletionWithProfile that supports GPT-5 Responses API
|
|
1100
|
+
* 🔥 Optimized for both official OpenAI and third-party GPT-5 providers
|
|
1101
|
+
*/
|
|
1102
|
+
async function getGPT5CompletionWithProfile(
|
|
1103
|
+
modelProfile: any,
|
|
1104
|
+
opts: OpenAI.ChatCompletionCreateParams,
|
|
1105
|
+
attempt: number = 0,
|
|
1106
|
+
maxAttempts: number = 10,
|
|
1107
|
+
signal?: AbortSignal,
|
|
1108
|
+
): Promise<OpenAI.ChatCompletion | AsyncIterable<OpenAI.ChatCompletionChunk>> {
|
|
1109
|
+
const features = getModelFeatures(opts.model)
|
|
1110
|
+
const isOfficialOpenAI = !modelProfile.baseURL ||
|
|
1111
|
+
modelProfile.baseURL.includes('api.openai.com')
|
|
1112
|
+
|
|
1113
|
+
// 🚀 Try Responses API for official OpenAI non-streaming requests
|
|
1114
|
+
if (features.supportsResponsesAPI && !opts.stream && isOfficialOpenAI) {
|
|
1115
|
+
try {
|
|
1116
|
+
debugLogger.api('ATTEMPTING_GPT5_RESPONSES_API', {
|
|
1117
|
+
model: opts.model,
|
|
1118
|
+
baseURL: modelProfile.baseURL || 'official',
|
|
1119
|
+
provider: modelProfile.provider,
|
|
1120
|
+
stream: opts.stream,
|
|
1121
|
+
requestId: getCurrentRequest()?.id,
|
|
1122
|
+
})
|
|
1123
|
+
|
|
1124
|
+
const result = await callGPT5ResponsesAPI(modelProfile, opts, signal)
|
|
1125
|
+
|
|
1126
|
+
debugLogger.api('GPT5_RESPONSES_API_SUCCESS', {
|
|
1127
|
+
model: opts.model,
|
|
1128
|
+
baseURL: modelProfile.baseURL || 'official',
|
|
1129
|
+
requestId: getCurrentRequest()?.id,
|
|
1130
|
+
})
|
|
1131
|
+
|
|
1132
|
+
return result
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
debugLogger.api('GPT5_RESPONSES_API_FALLBACK', {
|
|
1135
|
+
model: opts.model,
|
|
1136
|
+
error: error.message,
|
|
1137
|
+
baseURL: modelProfile.baseURL || 'official',
|
|
1138
|
+
requestId: getCurrentRequest()?.id,
|
|
1139
|
+
})
|
|
1140
|
+
|
|
1141
|
+
console.warn(
|
|
1142
|
+
`🔄 GPT-5 Responses API failed, falling back to Chat Completions: ${error.message}`
|
|
1143
|
+
)
|
|
1144
|
+
// Fall through to Chat Completions API
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// 🌐 Handle third-party GPT-5 providers with enhanced compatibility
|
|
1149
|
+
else if (!isOfficialOpenAI) {
|
|
1150
|
+
debugLogger.api('GPT5_THIRD_PARTY_PROVIDER', {
|
|
1151
|
+
model: opts.model,
|
|
1152
|
+
baseURL: modelProfile.baseURL,
|
|
1153
|
+
provider: modelProfile.provider,
|
|
1154
|
+
supportsResponsesAPI: features.supportsResponsesAPI,
|
|
1155
|
+
requestId: getCurrentRequest()?.id,
|
|
1156
|
+
})
|
|
1157
|
+
|
|
1158
|
+
// 🔧 Apply enhanced parameter optimization for third-party providers
|
|
1159
|
+
console.log(`🌐 Using GPT-5 via third-party provider: ${modelProfile.provider} (${modelProfile.baseURL})`)
|
|
1160
|
+
|
|
1161
|
+
// Some third-party providers may need additional parameter adjustments
|
|
1162
|
+
if (modelProfile.provider === 'azure') {
|
|
1163
|
+
// Azure OpenAI specific adjustments
|
|
1164
|
+
delete opts.reasoning_effort // Azure may not support this yet
|
|
1165
|
+
} else if (modelProfile.provider === 'custom-openai') {
|
|
1166
|
+
// Generic OpenAI-compatible provider optimizations
|
|
1167
|
+
console.log(`🔧 Applying OpenAI-compatible optimizations for custom provider`)
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// 📡 Handle streaming requests (Responses API doesn't support streaming yet)
|
|
1172
|
+
else if (opts.stream) {
|
|
1173
|
+
debugLogger.api('GPT5_STREAMING_MODE', {
|
|
1174
|
+
model: opts.model,
|
|
1175
|
+
baseURL: modelProfile.baseURL || 'official',
|
|
1176
|
+
reason: 'responses_api_no_streaming',
|
|
1177
|
+
requestId: getCurrentRequest()?.id,
|
|
1178
|
+
})
|
|
1179
|
+
|
|
1180
|
+
console.log(`🔄 Using Chat Completions for streaming (Responses API streaming not available)`)
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// 🔧 Enhanced Chat Completions fallback with GPT-5 optimizations
|
|
1184
|
+
debugLogger.api('USING_CHAT_COMPLETIONS_FOR_GPT5', {
|
|
1185
|
+
model: opts.model,
|
|
1186
|
+
baseURL: modelProfile.baseURL || 'official',
|
|
1187
|
+
provider: modelProfile.provider,
|
|
1188
|
+
reason: isOfficialOpenAI ? 'streaming_or_fallback' : 'third_party_provider',
|
|
1189
|
+
requestId: getCurrentRequest()?.id,
|
|
1190
|
+
})
|
|
1191
|
+
|
|
1192
|
+
return await getCompletionWithProfile(
|
|
1193
|
+
modelProfile,
|
|
1194
|
+
opts,
|
|
1195
|
+
attempt,
|
|
1196
|
+
maxAttempts,
|
|
1197
|
+
signal,
|
|
1198
|
+
)
|
|
1199
|
+
}
|
|
1200
|
+
|
|
692
1201
|
/**
|
|
693
1202
|
* Fetch available models from custom OpenAI-compatible API
|
|
694
1203
|
*/
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPT-5 Responses API state management
|
|
3
|
+
* Manages previous_response_id for conversation continuity and reasoning context reuse
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
interface ConversationState {
|
|
7
|
+
previousResponseId?: string
|
|
8
|
+
lastUpdate: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class ResponseStateManager {
|
|
12
|
+
private conversationStates = new Map<string, ConversationState>()
|
|
13
|
+
|
|
14
|
+
// Cache cleanup after 1 hour of inactivity
|
|
15
|
+
private readonly CLEANUP_INTERVAL = 60 * 60 * 1000
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
// Periodic cleanup of stale conversations
|
|
19
|
+
setInterval(() => {
|
|
20
|
+
this.cleanup()
|
|
21
|
+
}, this.CLEANUP_INTERVAL)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Set the previous response ID for a conversation
|
|
26
|
+
*/
|
|
27
|
+
setPreviousResponseId(conversationId: string, responseId: string): void {
|
|
28
|
+
this.conversationStates.set(conversationId, {
|
|
29
|
+
previousResponseId: responseId,
|
|
30
|
+
lastUpdate: Date.now()
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the previous response ID for a conversation
|
|
36
|
+
*/
|
|
37
|
+
getPreviousResponseId(conversationId: string): string | undefined {
|
|
38
|
+
const state = this.conversationStates.get(conversationId)
|
|
39
|
+
if (state) {
|
|
40
|
+
// Update last access time
|
|
41
|
+
state.lastUpdate = Date.now()
|
|
42
|
+
return state.previousResponseId
|
|
43
|
+
}
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clear state for a conversation
|
|
49
|
+
*/
|
|
50
|
+
clearConversation(conversationId: string): void {
|
|
51
|
+
this.conversationStates.delete(conversationId)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Clear all conversation states
|
|
56
|
+
*/
|
|
57
|
+
clearAll(): void {
|
|
58
|
+
this.conversationStates.clear()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Clean up stale conversations
|
|
63
|
+
*/
|
|
64
|
+
private cleanup(): void {
|
|
65
|
+
const now = Date.now()
|
|
66
|
+
for (const [conversationId, state] of this.conversationStates.entries()) {
|
|
67
|
+
if (now - state.lastUpdate > this.CLEANUP_INTERVAL) {
|
|
68
|
+
this.conversationStates.delete(conversationId)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get current state size (for debugging/monitoring)
|
|
75
|
+
*/
|
|
76
|
+
getStateSize(): number {
|
|
77
|
+
return this.conversationStates.size
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Singleton instance
|
|
82
|
+
export const responseStateManager = new ResponseStateManager()
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Helper to generate conversation ID from context
|
|
86
|
+
*/
|
|
87
|
+
export function getConversationId(agentId?: string, messageId?: string): string {
|
|
88
|
+
// Use agentId as primary identifier, fallback to messageId or timestamp
|
|
89
|
+
return agentId || messageId || `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
90
|
+
}
|