@shareai-lab/kode 1.1.14 → 1.1.16-dev.2
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/cli.js +77 -82
- package/dist/entrypoints/cli.js +59 -38
- package/dist/entrypoints/cli.js.map +3 -3
- package/dist/index.js +5 -26
- package/dist/package.json +4 -1
- package/package.json +11 -104
- package/dist/test/testAdapters.js +0 -88
- package/dist/test/testAdapters.js.map +0 -1
- package/src/ProjectOnboarding.tsx +0 -198
- package/src/Tool.ts +0 -83
- package/src/commands/agents.tsx +0 -3416
- package/src/commands/approvedTools.ts +0 -53
- package/src/commands/bug.tsx +0 -20
- package/src/commands/clear.ts +0 -43
- package/src/commands/compact.ts +0 -120
- package/src/commands/config.tsx +0 -19
- package/src/commands/cost.ts +0 -18
- package/src/commands/ctx_viz.ts +0 -209
- package/src/commands/doctor.ts +0 -24
- package/src/commands/help.tsx +0 -19
- package/src/commands/init.ts +0 -37
- package/src/commands/listen.ts +0 -42
- package/src/commands/login.tsx +0 -51
- package/src/commands/logout.tsx +0 -40
- package/src/commands/mcp.ts +0 -41
- package/src/commands/model.tsx +0 -40
- package/src/commands/modelstatus.tsx +0 -20
- package/src/commands/onboarding.tsx +0 -34
- package/src/commands/pr_comments.ts +0 -59
- package/src/commands/refreshCommands.ts +0 -54
- package/src/commands/release-notes.ts +0 -34
- package/src/commands/resume.tsx +0 -31
- package/src/commands/review.ts +0 -49
- package/src/commands/terminalSetup.ts +0 -221
- package/src/commands.ts +0 -139
- package/src/components/ApproveApiKey.tsx +0 -93
- package/src/components/AsciiLogo.tsx +0 -13
- package/src/components/AutoUpdater.tsx +0 -148
- package/src/components/Bug.tsx +0 -367
- package/src/components/Config.tsx +0 -293
- package/src/components/ConsoleOAuthFlow.tsx +0 -327
- package/src/components/Cost.tsx +0 -23
- package/src/components/CostThresholdDialog.tsx +0 -46
- package/src/components/CustomSelect/option-map.ts +0 -42
- package/src/components/CustomSelect/select-option.tsx +0 -78
- package/src/components/CustomSelect/select.tsx +0 -152
- package/src/components/CustomSelect/theme.ts +0 -45
- package/src/components/CustomSelect/use-select-state.ts +0 -414
- package/src/components/CustomSelect/use-select.ts +0 -35
- package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
- package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
- package/src/components/Help.tsx +0 -215
- package/src/components/HighlightedCode.tsx +0 -33
- package/src/components/InvalidConfigDialog.tsx +0 -113
- package/src/components/Link.tsx +0 -32
- package/src/components/LogSelector.tsx +0 -86
- package/src/components/Logo.tsx +0 -170
- package/src/components/MCPServerApprovalDialog.tsx +0 -100
- package/src/components/MCPServerDialogCopy.tsx +0 -25
- package/src/components/MCPServerMultiselectDialog.tsx +0 -109
- package/src/components/Message.tsx +0 -221
- package/src/components/MessageResponse.tsx +0 -15
- package/src/components/MessageSelector.tsx +0 -211
- package/src/components/ModeIndicator.tsx +0 -88
- package/src/components/ModelConfig.tsx +0 -301
- package/src/components/ModelListManager.tsx +0 -227
- package/src/components/ModelSelector.tsx +0 -3387
- package/src/components/ModelStatusDisplay.tsx +0 -230
- package/src/components/Onboarding.tsx +0 -274
- package/src/components/PressEnterToContinue.tsx +0 -11
- package/src/components/PromptInput.tsx +0 -760
- package/src/components/SentryErrorBoundary.ts +0 -39
- package/src/components/Spinner.tsx +0 -129
- package/src/components/StickerRequestForm.tsx +0 -16
- package/src/components/StructuredDiff.tsx +0 -191
- package/src/components/TextInput.tsx +0 -259
- package/src/components/TodoItem.tsx +0 -47
- package/src/components/TokenWarning.tsx +0 -31
- package/src/components/ToolUseLoader.tsx +0 -40
- package/src/components/TrustDialog.tsx +0 -106
- package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
- package/src/components/binary-feedback/utils.ts +0 -220
- package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
- package/src/components/messages/AssistantTextMessage.tsx +0 -144
- package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
- package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
- package/src/components/messages/TaskProgressMessage.tsx +0 -32
- package/src/components/messages/TaskToolMessage.tsx +0 -58
- package/src/components/messages/UserBashInputMessage.tsx +0 -28
- package/src/components/messages/UserCommandMessage.tsx +0 -30
- package/src/components/messages/UserKodingInputMessage.tsx +0 -28
- package/src/components/messages/UserPromptMessage.tsx +0 -35
- package/src/components/messages/UserTextMessage.tsx +0 -39
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
- package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
- package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
- package/src/components/permissions/PermissionRequest.tsx +0 -101
- package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
- package/src/components/permissions/hooks.ts +0 -44
- package/src/components/permissions/toolUseOptions.ts +0 -59
- package/src/components/permissions/utils.ts +0 -23
- package/src/constants/betas.ts +0 -5
- package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
- package/src/constants/figures.ts +0 -4
- package/src/constants/keys.ts +0 -3
- package/src/constants/macros.ts +0 -11
- package/src/constants/modelCapabilities.ts +0 -179
- package/src/constants/models.ts +0 -1025
- package/src/constants/oauth.ts +0 -18
- package/src/constants/product.ts +0 -17
- package/src/constants/prompts.ts +0 -168
- package/src/constants/releaseNotes.ts +0 -7
- package/src/context/PermissionContext.tsx +0 -149
- package/src/context.ts +0 -278
- package/src/cost-tracker.ts +0 -84
- package/src/entrypoints/cli.tsx +0 -1561
- package/src/entrypoints/mcp.ts +0 -175
- package/src/history.ts +0 -25
- package/src/hooks/useApiKeyVerification.ts +0 -59
- package/src/hooks/useArrowKeyHistory.ts +0 -55
- package/src/hooks/useCanUseTool.ts +0 -138
- package/src/hooks/useCancelRequest.ts +0 -39
- package/src/hooks/useDoublePress.ts +0 -41
- package/src/hooks/useExitOnCtrlCD.ts +0 -31
- package/src/hooks/useInterval.ts +0 -25
- package/src/hooks/useLogMessages.ts +0 -16
- package/src/hooks/useLogStartupTime.ts +0 -12
- package/src/hooks/useNotifyAfterTimeout.ts +0 -65
- package/src/hooks/usePermissionRequestLogging.ts +0 -44
- package/src/hooks/useTerminalSize.ts +0 -49
- package/src/hooks/useTextInput.ts +0 -317
- package/src/hooks/useUnifiedCompletion.ts +0 -1405
- package/src/index.ts +0 -34
- package/src/messages.ts +0 -38
- package/src/permissions.ts +0 -268
- package/src/query.ts +0 -720
- package/src/screens/ConfigureNpmPrefix.tsx +0 -197
- package/src/screens/Doctor.tsx +0 -219
- package/src/screens/LogList.tsx +0 -68
- package/src/screens/REPL.tsx +0 -813
- package/src/screens/ResumeConversation.tsx +0 -68
- package/src/services/adapters/base.ts +0 -38
- package/src/services/adapters/chatCompletions.ts +0 -90
- package/src/services/adapters/responsesAPI.ts +0 -170
- package/src/services/browserMocks.ts +0 -66
- package/src/services/claude.ts +0 -2197
- package/src/services/customCommands.ts +0 -704
- package/src/services/fileFreshness.ts +0 -377
- package/src/services/gpt5ConnectionTest.ts +0 -340
- package/src/services/mcpClient.ts +0 -564
- package/src/services/mcpServerApproval.tsx +0 -50
- package/src/services/mentionProcessor.ts +0 -273
- package/src/services/modelAdapterFactory.ts +0 -69
- package/src/services/notifier.ts +0 -40
- package/src/services/oauth.ts +0 -357
- package/src/services/openai.ts +0 -1359
- package/src/services/responseStateManager.ts +0 -90
- package/src/services/sentry.ts +0 -3
- package/src/services/statsig.ts +0 -172
- package/src/services/statsigStorage.ts +0 -86
- package/src/services/systemReminder.ts +0 -507
- package/src/services/vcr.ts +0 -161
- package/src/test/testAdapters.ts +0 -96
- package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
- package/src/tools/ArchitectTool/prompt.ts +0 -15
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
- package/src/tools/BashTool/BashTool.tsx +0 -243
- package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
- package/src/tools/BashTool/OutputLine.tsx +0 -49
- package/src/tools/BashTool/prompt.ts +0 -174
- package/src/tools/BashTool/utils.ts +0 -56
- package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
- package/src/tools/FileEditTool/prompt.ts +0 -51
- package/src/tools/FileEditTool/utils.ts +0 -58
- package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
- package/src/tools/FileReadTool/prompt.ts +0 -7
- package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
- package/src/tools/FileWriteTool/prompt.ts +0 -10
- package/src/tools/GlobTool/GlobTool.tsx +0 -119
- package/src/tools/GlobTool/prompt.ts +0 -8
- package/src/tools/GrepTool/GrepTool.tsx +0 -147
- package/src/tools/GrepTool/prompt.ts +0 -11
- package/src/tools/MCPTool/MCPTool.tsx +0 -107
- package/src/tools/MCPTool/prompt.ts +0 -3
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
- package/src/tools/MemoryReadTool/prompt.ts +0 -3
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
- package/src/tools/MemoryWriteTool/prompt.ts +0 -3
- package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
- package/src/tools/MultiEditTool/prompt.ts +0 -45
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
- package/src/tools/NotebookEditTool/prompt.ts +0 -3
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
- package/src/tools/NotebookReadTool/prompt.ts +0 -3
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
- package/src/tools/StickerRequestTool/prompt.ts +0 -19
- package/src/tools/TaskTool/TaskTool.tsx +0 -438
- package/src/tools/TaskTool/constants.ts +0 -1
- package/src/tools/TaskTool/prompt.ts +0 -92
- package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
- package/src/tools/ThinkTool/prompt.ts +0 -12
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
- package/src/tools/TodoWriteTool/prompt.ts +0 -63
- package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
- package/src/tools/URLFetcherTool/cache.ts +0 -55
- package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
- package/src/tools/URLFetcherTool/prompt.ts +0 -17
- package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
- package/src/tools/WebSearchTool/prompt.ts +0 -13
- package/src/tools/WebSearchTool/searchProviders.ts +0 -66
- package/src/tools/lsTool/lsTool.tsx +0 -272
- package/src/tools/lsTool/prompt.ts +0 -2
- package/src/tools.ts +0 -67
- package/src/types/PermissionMode.ts +0 -120
- package/src/types/RequestContext.ts +0 -72
- package/src/types/common.d.ts +0 -2
- package/src/types/conversation.ts +0 -51
- package/src/types/logs.ts +0 -58
- package/src/types/modelCapabilities.ts +0 -64
- package/src/types/notebook.ts +0 -87
- package/src/utils/Cursor.ts +0 -436
- package/src/utils/PersistentShell.ts +0 -552
- package/src/utils/advancedFuzzyMatcher.ts +0 -290
- package/src/utils/agentLoader.ts +0 -278
- package/src/utils/agentStorage.ts +0 -97
- package/src/utils/array.ts +0 -3
- package/src/utils/ask.tsx +0 -99
- package/src/utils/auth.ts +0 -13
- package/src/utils/autoCompactCore.ts +0 -223
- package/src/utils/autoUpdater.ts +0 -458
- package/src/utils/betas.ts +0 -20
- package/src/utils/browser.ts +0 -14
- package/src/utils/cleanup.ts +0 -72
- package/src/utils/commands.ts +0 -261
- package/src/utils/commonUnixCommands.ts +0 -161
- package/src/utils/config.ts +0 -945
- package/src/utils/conversationRecovery.ts +0 -55
- package/src/utils/debugLogger.ts +0 -1235
- package/src/utils/diff.ts +0 -42
- package/src/utils/env.ts +0 -57
- package/src/utils/errors.ts +0 -21
- package/src/utils/exampleCommands.ts +0 -109
- package/src/utils/execFileNoThrow.ts +0 -51
- package/src/utils/expertChatStorage.ts +0 -136
- package/src/utils/file.ts +0 -405
- package/src/utils/fileRecoveryCore.ts +0 -71
- package/src/utils/format.tsx +0 -44
- package/src/utils/fuzzyMatcher.ts +0 -328
- package/src/utils/generators.ts +0 -62
- package/src/utils/git.ts +0 -92
- package/src/utils/globalLogger.ts +0 -77
- package/src/utils/http.ts +0 -10
- package/src/utils/imagePaste.ts +0 -38
- package/src/utils/json.ts +0 -13
- package/src/utils/log.ts +0 -382
- package/src/utils/markdown.ts +0 -213
- package/src/utils/messageContextManager.ts +0 -294
- package/src/utils/messages.tsx +0 -945
- package/src/utils/model.ts +0 -914
- package/src/utils/permissions/filesystem.ts +0 -127
- package/src/utils/responseState.ts +0 -23
- package/src/utils/ripgrep.ts +0 -167
- package/src/utils/secureFile.ts +0 -564
- package/src/utils/sessionState.ts +0 -49
- package/src/utils/state.ts +0 -25
- package/src/utils/style.ts +0 -29
- package/src/utils/terminal.ts +0 -50
- package/src/utils/theme.ts +0 -127
- package/src/utils/thinking.ts +0 -144
- package/src/utils/todoStorage.ts +0 -431
- package/src/utils/tokens.ts +0 -43
- package/src/utils/toolExecutionController.ts +0 -163
- package/src/utils/unaryLogging.ts +0 -26
- package/src/utils/user.ts +0 -37
- package/src/utils/validate.ts +0 -165
package/src/services/openai.ts
DELETED
|
@@ -1,1359 +0,0 @@
|
|
|
1
|
-
import { OpenAI } from 'openai'
|
|
2
|
-
import { getGlobalConfig, GlobalConfig } from '../utils/config'
|
|
3
|
-
import { ProxyAgent, fetch, Response } from 'undici'
|
|
4
|
-
import { setSessionState, getSessionState } from '../utils/sessionState'
|
|
5
|
-
import { logEvent } from '../services/statsig'
|
|
6
|
-
import { debug as debugLogger, getCurrentRequest, logAPIError } from '../utils/debugLogger'
|
|
7
|
-
|
|
8
|
-
// Helper function to calculate retry delay with exponential backoff
|
|
9
|
-
function getRetryDelay(attempt: number, retryAfter?: string | null): number {
|
|
10
|
-
// If server suggests a retry-after time, use it
|
|
11
|
-
if (retryAfter) {
|
|
12
|
-
const retryAfterMs = parseInt(retryAfter) * 1000
|
|
13
|
-
if (!isNaN(retryAfterMs) && retryAfterMs > 0) {
|
|
14
|
-
return Math.min(retryAfterMs, 60000) // Cap at 60 seconds
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Exponential backoff: base delay of 1 second, doubling each attempt
|
|
19
|
-
const baseDelay = 1000
|
|
20
|
-
const maxDelay = 32000 // Cap at 32 seconds
|
|
21
|
-
const delay = baseDelay * Math.pow(2, attempt - 1)
|
|
22
|
-
|
|
23
|
-
// Add some jitter to avoid thundering herd
|
|
24
|
-
const jitter = Math.random() * 0.1 * delay
|
|
25
|
-
|
|
26
|
-
return Math.min(delay + jitter, maxDelay)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Helper function to create an abortable delay
|
|
30
|
-
function abortableDelay(delayMs: number, signal?: AbortSignal): Promise<void> {
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
// Check if already aborted
|
|
33
|
-
if (signal?.aborted) {
|
|
34
|
-
reject(new Error('Request was aborted'))
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const timeoutId = setTimeout(() => {
|
|
39
|
-
resolve()
|
|
40
|
-
}, delayMs)
|
|
41
|
-
|
|
42
|
-
// If signal is provided, listen for abort event
|
|
43
|
-
if (signal) {
|
|
44
|
-
const abortHandler = () => {
|
|
45
|
-
clearTimeout(timeoutId)
|
|
46
|
-
reject(new Error('Request was aborted'))
|
|
47
|
-
}
|
|
48
|
-
signal.addEventListener('abort', abortHandler, { once: true })
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
enum ModelErrorType {
|
|
54
|
-
MaxLength = '1024',
|
|
55
|
-
MaxCompletionTokens = 'max_completion_tokens',
|
|
56
|
-
TemperatureRestriction = 'temperature_restriction',
|
|
57
|
-
StreamOptions = 'stream_options',
|
|
58
|
-
Citations = 'citations',
|
|
59
|
-
RateLimit = 'rate_limit',
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function getModelErrorKey(
|
|
63
|
-
baseURL: string,
|
|
64
|
-
model: string,
|
|
65
|
-
type: ModelErrorType,
|
|
66
|
-
): string {
|
|
67
|
-
return `${baseURL}:${model}:${type}`
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function hasModelError(
|
|
71
|
-
baseURL: string,
|
|
72
|
-
model: string,
|
|
73
|
-
type: ModelErrorType,
|
|
74
|
-
): boolean {
|
|
75
|
-
return !!getSessionState('modelErrors')[
|
|
76
|
-
getModelErrorKey(baseURL, model, type)
|
|
77
|
-
]
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function setModelError(
|
|
81
|
-
baseURL: string,
|
|
82
|
-
model: string,
|
|
83
|
-
type: ModelErrorType,
|
|
84
|
-
error: string,
|
|
85
|
-
) {
|
|
86
|
-
setSessionState('modelErrors', {
|
|
87
|
-
[getModelErrorKey(baseURL, model, type)]: error,
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// More flexible error detection system
|
|
92
|
-
type ErrorDetector = (errMsg: string) => boolean
|
|
93
|
-
type ErrorFixer = (
|
|
94
|
-
opts: OpenAI.ChatCompletionCreateParams,
|
|
95
|
-
) => Promise<void> | void
|
|
96
|
-
interface ErrorHandler {
|
|
97
|
-
type: ModelErrorType
|
|
98
|
-
detect: ErrorDetector
|
|
99
|
-
fix: ErrorFixer
|
|
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
|
-
|
|
145
|
-
// Standard error handlers
|
|
146
|
-
const ERROR_HANDLERS: ErrorHandler[] = [
|
|
147
|
-
{
|
|
148
|
-
type: ModelErrorType.MaxLength,
|
|
149
|
-
detect: errMsg =>
|
|
150
|
-
errMsg.includes('Expected a string with maximum length 1024'),
|
|
151
|
-
fix: async opts => {
|
|
152
|
-
const toolDescriptions = {}
|
|
153
|
-
for (const tool of opts.tools || []) {
|
|
154
|
-
if (tool.function.description.length <= 1024) continue
|
|
155
|
-
let str = ''
|
|
156
|
-
let remainder = ''
|
|
157
|
-
for (let line of tool.function.description.split('\n')) {
|
|
158
|
-
if (str.length + line.length < 1024) {
|
|
159
|
-
str += line + '\n'
|
|
160
|
-
} else {
|
|
161
|
-
remainder += line + '\n'
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
logEvent('truncated_tool_description', {
|
|
165
|
-
name: tool.function.name,
|
|
166
|
-
original_length: String(tool.function.description.length),
|
|
167
|
-
truncated_length: String(str.length),
|
|
168
|
-
remainder_length: String(remainder.length),
|
|
169
|
-
})
|
|
170
|
-
tool.function.description = str
|
|
171
|
-
toolDescriptions[tool.function.name] = remainder
|
|
172
|
-
}
|
|
173
|
-
if (Object.keys(toolDescriptions).length > 0) {
|
|
174
|
-
let content = '<additional-tool-usage-instructions>\n\n'
|
|
175
|
-
for (const [name, description] of Object.entries(toolDescriptions)) {
|
|
176
|
-
content += `<${name}>\n${description}\n</${name}>\n\n`
|
|
177
|
-
}
|
|
178
|
-
content += '</additional-tool-usage-instructions>'
|
|
179
|
-
|
|
180
|
-
for (let i = opts.messages.length - 1; i >= 0; i--) {
|
|
181
|
-
if (opts.messages[i].role === 'system') {
|
|
182
|
-
opts.messages.splice(i + 1, 0, {
|
|
183
|
-
role: 'system',
|
|
184
|
-
content,
|
|
185
|
-
})
|
|
186
|
-
break
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
type: ModelErrorType.MaxCompletionTokens,
|
|
194
|
-
detect: errMsg => errMsg.includes("Use 'max_completion_tokens'"),
|
|
195
|
-
fix: async opts => {
|
|
196
|
-
opts.max_completion_tokens = opts.max_tokens
|
|
197
|
-
delete opts.max_tokens
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
type: ModelErrorType.StreamOptions,
|
|
202
|
-
detect: errMsg => errMsg.includes('stream_options'),
|
|
203
|
-
fix: async opts => {
|
|
204
|
-
delete opts.stream_options
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
type: ModelErrorType.Citations,
|
|
209
|
-
detect: errMsg =>
|
|
210
|
-
errMsg.includes('Extra inputs are not permitted') &&
|
|
211
|
-
errMsg.includes('citations'),
|
|
212
|
-
fix: async opts => {
|
|
213
|
-
if (!opts.messages) return
|
|
214
|
-
|
|
215
|
-
for (const message of opts.messages) {
|
|
216
|
-
if (!message) continue
|
|
217
|
-
|
|
218
|
-
if (Array.isArray(message.content)) {
|
|
219
|
-
for (const item of message.content) {
|
|
220
|
-
// Convert to unknown first to safely access properties
|
|
221
|
-
if (item && typeof item === 'object') {
|
|
222
|
-
const itemObj = item as unknown as Record<string, unknown>
|
|
223
|
-
if ('citations' in itemObj) {
|
|
224
|
-
delete itemObj.citations
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
} else if (message.content && typeof message.content === 'object') {
|
|
229
|
-
// Convert to unknown first to safely access properties
|
|
230
|
-
const contentObj = message.content as unknown as Record<
|
|
231
|
-
string,
|
|
232
|
-
unknown
|
|
233
|
-
>
|
|
234
|
-
if ('citations' in contentObj) {
|
|
235
|
-
delete contentObj.citations
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
]
|
|
242
|
-
|
|
243
|
-
// Rate limit specific detection
|
|
244
|
-
function isRateLimitError(errMsg: string): boolean {
|
|
245
|
-
if (!errMsg) return false
|
|
246
|
-
const lowerMsg = errMsg.toLowerCase()
|
|
247
|
-
return (
|
|
248
|
-
lowerMsg.includes('rate limit') ||
|
|
249
|
-
lowerMsg.includes('too many requests') ||
|
|
250
|
-
lowerMsg.includes('429')
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Model-specific feature flags - can be extended with more properties as needed
|
|
255
|
-
interface ModelFeatures {
|
|
256
|
-
usesMaxCompletionTokens: boolean
|
|
257
|
-
supportsResponsesAPI?: boolean
|
|
258
|
-
requiresTemperatureOne?: boolean
|
|
259
|
-
supportsVerbosityControl?: boolean
|
|
260
|
-
supportsCustomTools?: boolean
|
|
261
|
-
supportsAllowedTools?: boolean
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Map of model identifiers to their specific features
|
|
265
|
-
const MODEL_FEATURES: Record<string, ModelFeatures> = {
|
|
266
|
-
// OpenAI thinking models
|
|
267
|
-
o1: { usesMaxCompletionTokens: true },
|
|
268
|
-
'o1-preview': { usesMaxCompletionTokens: true },
|
|
269
|
-
'o1-mini': { usesMaxCompletionTokens: true },
|
|
270
|
-
'o1-pro': { usesMaxCompletionTokens: true },
|
|
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
|
-
},
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Helper to get model features based on model ID/name
|
|
306
|
-
function getModelFeatures(modelName: string): ModelFeatures {
|
|
307
|
-
if (!modelName || typeof modelName !== 'string') {
|
|
308
|
-
return { usesMaxCompletionTokens: false }
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Check for exact matches first (highest priority)
|
|
312
|
-
if (MODEL_FEATURES[modelName]) {
|
|
313
|
-
return MODEL_FEATURES[modelName]
|
|
314
|
-
}
|
|
315
|
-
|
|
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)
|
|
329
|
-
for (const [key, features] of Object.entries(MODEL_FEATURES)) {
|
|
330
|
-
if (modelName.includes(key)) {
|
|
331
|
-
return features
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Default features for unknown models
|
|
336
|
-
return { usesMaxCompletionTokens: false }
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Apply model-specific parameter transformations based on model features
|
|
340
|
-
function applyModelSpecificTransformations(
|
|
341
|
-
opts: OpenAI.ChatCompletionCreateParams,
|
|
342
|
-
): void {
|
|
343
|
-
if (!opts.model || typeof opts.model !== 'string') {
|
|
344
|
-
return
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const features = getModelFeatures(opts.model)
|
|
348
|
-
const isGPT5 = opts.model.toLowerCase().includes('gpt-5')
|
|
349
|
-
|
|
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
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Add more transformations here as needed
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
async function applyModelErrorFixes(
|
|
401
|
-
opts: OpenAI.ChatCompletionCreateParams,
|
|
402
|
-
baseURL: string,
|
|
403
|
-
) {
|
|
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) {
|
|
408
|
-
if (hasModelError(baseURL, opts.model, handler.type)) {
|
|
409
|
-
await handler.fix(opts)
|
|
410
|
-
return
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Helper function to try different endpoints for OpenAI-compatible providers
|
|
416
|
-
async function tryWithEndpointFallback(
|
|
417
|
-
baseURL: string,
|
|
418
|
-
opts: OpenAI.ChatCompletionCreateParams,
|
|
419
|
-
headers: Record<string, string>,
|
|
420
|
-
provider: string,
|
|
421
|
-
proxy: any,
|
|
422
|
-
signal?: AbortSignal, // 🔧 Add AbortSignal support
|
|
423
|
-
): Promise<{ response: Response; endpoint: string }> {
|
|
424
|
-
const endpointsToTry = []
|
|
425
|
-
|
|
426
|
-
if (provider === 'minimax') {
|
|
427
|
-
endpointsToTry.push('/text/chatcompletion_v2', '/chat/completions')
|
|
428
|
-
} else {
|
|
429
|
-
endpointsToTry.push('/chat/completions')
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
let lastError = null
|
|
433
|
-
|
|
434
|
-
for (const endpoint of endpointsToTry) {
|
|
435
|
-
try {
|
|
436
|
-
const response = await fetch(`${baseURL}${endpoint}`, {
|
|
437
|
-
method: 'POST',
|
|
438
|
-
headers,
|
|
439
|
-
body: JSON.stringify(opts.stream ? { ...opts, stream: true } : opts),
|
|
440
|
-
dispatcher: proxy,
|
|
441
|
-
signal: signal, // 🔧 Connect AbortSignal to fetch call
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
// If successful, return immediately
|
|
445
|
-
if (response.ok) {
|
|
446
|
-
return { response, endpoint }
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// If it's a 404, try the next endpoint
|
|
450
|
-
if (response.status === 404 && endpointsToTry.length > 1) {
|
|
451
|
-
console.log(
|
|
452
|
-
`Endpoint ${endpoint} returned 404, trying next endpoint...`,
|
|
453
|
-
)
|
|
454
|
-
continue
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// For other error codes, return this response (don't try fallback)
|
|
458
|
-
return { response, endpoint }
|
|
459
|
-
} catch (error) {
|
|
460
|
-
lastError = error
|
|
461
|
-
// Network errors might be temporary, try next endpoint
|
|
462
|
-
if (endpointsToTry.indexOf(endpoint) < endpointsToTry.length - 1) {
|
|
463
|
-
console.log(`Network error on ${endpoint}, trying next endpoint...`)
|
|
464
|
-
continue
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// If we get here, all endpoints failed
|
|
470
|
-
throw lastError || new Error('All endpoints failed')
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Export shared utilities for GPT-5 compatibility
|
|
474
|
-
export { getGPT5CompletionWithProfile, getModelFeatures, applyModelSpecificTransformations }
|
|
475
|
-
|
|
476
|
-
export async function getCompletionWithProfile(
|
|
477
|
-
modelProfile: any,
|
|
478
|
-
opts: OpenAI.ChatCompletionCreateParams,
|
|
479
|
-
attempt: number = 0,
|
|
480
|
-
maxAttempts: number = 10,
|
|
481
|
-
signal?: AbortSignal, // 🔧 CRITICAL FIX: Add AbortSignal support
|
|
482
|
-
): Promise<OpenAI.ChatCompletion | AsyncIterable<OpenAI.ChatCompletionChunk>> {
|
|
483
|
-
if (attempt >= maxAttempts) {
|
|
484
|
-
throw new Error('Max attempts reached')
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const provider = modelProfile?.provider || 'anthropic'
|
|
488
|
-
const baseURL = modelProfile?.baseURL
|
|
489
|
-
const apiKey = modelProfile?.apiKey
|
|
490
|
-
const proxy = getGlobalConfig().proxy
|
|
491
|
-
? new ProxyAgent(getGlobalConfig().proxy)
|
|
492
|
-
: undefined
|
|
493
|
-
|
|
494
|
-
const headers: Record<string, string> = {
|
|
495
|
-
'Content-Type': 'application/json',
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (apiKey) {
|
|
499
|
-
if (provider === 'azure') {
|
|
500
|
-
headers['api-key'] = apiKey
|
|
501
|
-
} else {
|
|
502
|
-
headers['Authorization'] = `Bearer ${apiKey}`
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
applyModelSpecificTransformations(opts)
|
|
507
|
-
await applyModelErrorFixes(opts, baseURL || '')
|
|
508
|
-
|
|
509
|
-
// 🔥 REAL-TIME API CALL DEBUG - 使用全局日志系统
|
|
510
|
-
debugLogger.api('OPENAI_API_CALL_START', {
|
|
511
|
-
endpoint: baseURL || 'DEFAULT_OPENAI',
|
|
512
|
-
model: opts.model,
|
|
513
|
-
provider,
|
|
514
|
-
apiKeyConfigured: !!apiKey,
|
|
515
|
-
apiKeyPrefix: apiKey ? apiKey.substring(0, 8) : null,
|
|
516
|
-
maxTokens: opts.max_tokens,
|
|
517
|
-
temperature: opts.temperature,
|
|
518
|
-
messageCount: opts.messages?.length || 0,
|
|
519
|
-
streamMode: opts.stream,
|
|
520
|
-
timestamp: new Date().toISOString(),
|
|
521
|
-
modelProfileModelName: modelProfile?.modelName,
|
|
522
|
-
modelProfileName: modelProfile?.name,
|
|
523
|
-
})
|
|
524
|
-
|
|
525
|
-
// Make sure all tool messages have string content
|
|
526
|
-
opts.messages = opts.messages.map(msg => {
|
|
527
|
-
if (msg.role === 'tool') {
|
|
528
|
-
if (Array.isArray(msg.content)) {
|
|
529
|
-
return {
|
|
530
|
-
...msg,
|
|
531
|
-
content:
|
|
532
|
-
msg.content
|
|
533
|
-
.map(c => c.text || '')
|
|
534
|
-
.filter(Boolean)
|
|
535
|
-
.join('\n\n') || '(empty content)',
|
|
536
|
-
}
|
|
537
|
-
} else if (typeof msg.content !== 'string') {
|
|
538
|
-
return {
|
|
539
|
-
...msg,
|
|
540
|
-
content:
|
|
541
|
-
typeof msg.content === 'undefined'
|
|
542
|
-
? '(empty content)'
|
|
543
|
-
: JSON.stringify(msg.content),
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
return msg
|
|
548
|
-
})
|
|
549
|
-
|
|
550
|
-
// Define Azure-specific API endpoint with version
|
|
551
|
-
const azureApiVersion = '2024-06-01'
|
|
552
|
-
let endpoint = '/chat/completions'
|
|
553
|
-
|
|
554
|
-
if (provider === 'azure') {
|
|
555
|
-
endpoint = `/chat/completions?api-version=${azureApiVersion}`
|
|
556
|
-
} else if (provider === 'minimax') {
|
|
557
|
-
endpoint = '/text/chatcompletion_v2'
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
try {
|
|
561
|
-
if (opts.stream) {
|
|
562
|
-
const isOpenAICompatible = [
|
|
563
|
-
'minimax',
|
|
564
|
-
'kimi',
|
|
565
|
-
'deepseek',
|
|
566
|
-
'siliconflow',
|
|
567
|
-
'qwen',
|
|
568
|
-
'glm',
|
|
569
|
-
'baidu-qianfan',
|
|
570
|
-
'openai',
|
|
571
|
-
'mistral',
|
|
572
|
-
'xai',
|
|
573
|
-
'groq',
|
|
574
|
-
'custom-openai',
|
|
575
|
-
].includes(provider)
|
|
576
|
-
|
|
577
|
-
let response: Response
|
|
578
|
-
let usedEndpoint: string
|
|
579
|
-
|
|
580
|
-
if (isOpenAICompatible && provider !== 'azure') {
|
|
581
|
-
const result = await tryWithEndpointFallback(
|
|
582
|
-
baseURL,
|
|
583
|
-
opts,
|
|
584
|
-
headers,
|
|
585
|
-
provider,
|
|
586
|
-
proxy,
|
|
587
|
-
signal, // 🔧 Pass AbortSignal to endpoint fallback
|
|
588
|
-
)
|
|
589
|
-
response = result.response
|
|
590
|
-
usedEndpoint = result.endpoint
|
|
591
|
-
} else {
|
|
592
|
-
response = await fetch(`${baseURL}${endpoint}`, {
|
|
593
|
-
method: 'POST',
|
|
594
|
-
headers,
|
|
595
|
-
body: JSON.stringify({ ...opts, stream: true }),
|
|
596
|
-
dispatcher: proxy,
|
|
597
|
-
signal: signal, // 🔧 CRITICAL FIX: Connect AbortSignal to fetch call
|
|
598
|
-
})
|
|
599
|
-
usedEndpoint = endpoint
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
if (!response.ok) {
|
|
603
|
-
// 🔧 CRITICAL FIX: Check abort signal BEFORE showing retry message
|
|
604
|
-
if (signal?.aborted) {
|
|
605
|
-
throw new Error('Request cancelled by user')
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// 🔥 NEW: Parse error message to detect and handle specific API errors
|
|
609
|
-
try {
|
|
610
|
-
const errorData = await response.json()
|
|
611
|
-
// Type guard for error data structure
|
|
612
|
-
const hasError = (data: unknown): data is { error?: { message?: string }; message?: string } => {
|
|
613
|
-
return typeof data === 'object' && data !== null
|
|
614
|
-
}
|
|
615
|
-
const errorMessage = hasError(errorData)
|
|
616
|
-
? (errorData.error?.message || errorData.message || `HTTP ${response.status}`)
|
|
617
|
-
: `HTTP ${response.status}`
|
|
618
|
-
|
|
619
|
-
// Check if this is a parameter error that we can fix
|
|
620
|
-
const isGPT5 = opts.model.startsWith('gpt-5')
|
|
621
|
-
const handlers = isGPT5 ? [...GPT5_ERROR_HANDLERS, ...ERROR_HANDLERS] : ERROR_HANDLERS
|
|
622
|
-
|
|
623
|
-
for (const handler of handlers) {
|
|
624
|
-
if (handler.detect(errorMessage)) {
|
|
625
|
-
console.log(`🔧 Detected ${handler.type} error for ${opts.model}: ${errorMessage}`)
|
|
626
|
-
|
|
627
|
-
// Store this error for future requests
|
|
628
|
-
setModelError(baseURL || '', opts.model, handler.type, errorMessage)
|
|
629
|
-
|
|
630
|
-
// Apply the fix and retry immediately
|
|
631
|
-
await handler.fix(opts)
|
|
632
|
-
console.log(`🔧 Applied fix for ${handler.type}, retrying...`)
|
|
633
|
-
|
|
634
|
-
return getCompletionWithProfile(
|
|
635
|
-
modelProfile,
|
|
636
|
-
opts,
|
|
637
|
-
attempt + 1,
|
|
638
|
-
maxAttempts,
|
|
639
|
-
signal,
|
|
640
|
-
)
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// If no specific handler found, log the error for debugging
|
|
645
|
-
console.log(`⚠️ Unhandled API error (${response.status}): ${errorMessage}`)
|
|
646
|
-
|
|
647
|
-
// Log API error using unified logger
|
|
648
|
-
logAPIError({
|
|
649
|
-
model: opts.model,
|
|
650
|
-
endpoint: `${baseURL}${endpoint}`,
|
|
651
|
-
status: response.status,
|
|
652
|
-
error: errorMessage,
|
|
653
|
-
request: opts,
|
|
654
|
-
response: errorData,
|
|
655
|
-
provider: provider
|
|
656
|
-
})
|
|
657
|
-
} catch (parseError) {
|
|
658
|
-
// If we can't parse the error, fall back to generic retry
|
|
659
|
-
console.log(`⚠️ Could not parse error response (${response.status})`)
|
|
660
|
-
|
|
661
|
-
// Log parse error
|
|
662
|
-
logAPIError({
|
|
663
|
-
model: opts.model,
|
|
664
|
-
endpoint: `${baseURL}${endpoint}`,
|
|
665
|
-
status: response.status,
|
|
666
|
-
error: `Could not parse error response: ${parseError.message}`,
|
|
667
|
-
request: opts,
|
|
668
|
-
response: { parseError: parseError.message },
|
|
669
|
-
provider: provider
|
|
670
|
-
})
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const delayMs = getRetryDelay(attempt)
|
|
674
|
-
console.log(
|
|
675
|
-
` ⎿ API error (${response.status}), retrying in ${Math.round(delayMs / 1000)}s... (attempt ${attempt + 1}/${maxAttempts})`,
|
|
676
|
-
)
|
|
677
|
-
try {
|
|
678
|
-
await abortableDelay(delayMs, signal)
|
|
679
|
-
} catch (error) {
|
|
680
|
-
// If aborted during delay, throw the error to stop retrying
|
|
681
|
-
if (error.message === 'Request was aborted') {
|
|
682
|
-
throw new Error('Request cancelled by user')
|
|
683
|
-
}
|
|
684
|
-
throw error
|
|
685
|
-
}
|
|
686
|
-
return getCompletionWithProfile(
|
|
687
|
-
modelProfile,
|
|
688
|
-
opts,
|
|
689
|
-
attempt + 1,
|
|
690
|
-
maxAttempts,
|
|
691
|
-
signal, // 🔧 Pass AbortSignal to recursive call
|
|
692
|
-
)
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const stream = createStreamProcessor(response.body as any, signal)
|
|
696
|
-
return stream
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Non-streaming request
|
|
700
|
-
const isOpenAICompatible = [
|
|
701
|
-
'minimax',
|
|
702
|
-
'kimi',
|
|
703
|
-
'deepseek',
|
|
704
|
-
'siliconflow',
|
|
705
|
-
'qwen',
|
|
706
|
-
'glm',
|
|
707
|
-
'baidu-qianfan',
|
|
708
|
-
'openai',
|
|
709
|
-
'mistral',
|
|
710
|
-
'xai',
|
|
711
|
-
'groq',
|
|
712
|
-
'custom-openai',
|
|
713
|
-
].includes(provider)
|
|
714
|
-
|
|
715
|
-
let response: Response
|
|
716
|
-
let usedEndpoint: string
|
|
717
|
-
|
|
718
|
-
if (isOpenAICompatible && provider !== 'azure') {
|
|
719
|
-
const result = await tryWithEndpointFallback(
|
|
720
|
-
baseURL,
|
|
721
|
-
opts,
|
|
722
|
-
headers,
|
|
723
|
-
provider,
|
|
724
|
-
proxy,
|
|
725
|
-
signal, // 🔧 Pass AbortSignal to endpoint fallback
|
|
726
|
-
)
|
|
727
|
-
response = result.response
|
|
728
|
-
usedEndpoint = result.endpoint
|
|
729
|
-
} else {
|
|
730
|
-
response = await fetch(`${baseURL}${endpoint}`, {
|
|
731
|
-
method: 'POST',
|
|
732
|
-
headers,
|
|
733
|
-
body: JSON.stringify(opts),
|
|
734
|
-
dispatcher: proxy,
|
|
735
|
-
signal: signal, // 🔧 CRITICAL FIX: Connect AbortSignal to non-streaming fetch call
|
|
736
|
-
})
|
|
737
|
-
usedEndpoint = endpoint
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
if (!response.ok) {
|
|
741
|
-
// 🔧 CRITICAL FIX: Check abort signal BEFORE showing retry message
|
|
742
|
-
if (signal?.aborted) {
|
|
743
|
-
throw new Error('Request cancelled by user')
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// 🔥 NEW: Parse error message to detect and handle specific API errors
|
|
747
|
-
try {
|
|
748
|
-
const errorData = await response.json()
|
|
749
|
-
// Type guard for error data structure
|
|
750
|
-
const hasError = (data: unknown): data is { error?: { message?: string }; message?: string } => {
|
|
751
|
-
return typeof data === 'object' && data !== null
|
|
752
|
-
}
|
|
753
|
-
const errorMessage = hasError(errorData)
|
|
754
|
-
? (errorData.error?.message || errorData.message || `HTTP ${response.status}`)
|
|
755
|
-
: `HTTP ${response.status}`
|
|
756
|
-
|
|
757
|
-
// Check if this is a parameter error that we can fix
|
|
758
|
-
const isGPT5 = opts.model.startsWith('gpt-5')
|
|
759
|
-
const handlers = isGPT5 ? [...GPT5_ERROR_HANDLERS, ...ERROR_HANDLERS] : ERROR_HANDLERS
|
|
760
|
-
|
|
761
|
-
for (const handler of handlers) {
|
|
762
|
-
if (handler.detect(errorMessage)) {
|
|
763
|
-
console.log(`🔧 Detected ${handler.type} error for ${opts.model}: ${errorMessage}`)
|
|
764
|
-
|
|
765
|
-
// Store this error for future requests
|
|
766
|
-
setModelError(baseURL || '', opts.model, handler.type, errorMessage)
|
|
767
|
-
|
|
768
|
-
// Apply the fix and retry immediately
|
|
769
|
-
await handler.fix(opts)
|
|
770
|
-
console.log(`🔧 Applied fix for ${handler.type}, retrying...`)
|
|
771
|
-
|
|
772
|
-
return getCompletionWithProfile(
|
|
773
|
-
modelProfile,
|
|
774
|
-
opts,
|
|
775
|
-
attempt + 1,
|
|
776
|
-
maxAttempts,
|
|
777
|
-
signal,
|
|
778
|
-
)
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// If no specific handler found, log the error for debugging
|
|
783
|
-
console.log(`⚠️ Unhandled API error (${response.status}): ${errorMessage}`)
|
|
784
|
-
} catch (parseError) {
|
|
785
|
-
// If we can't parse the error, fall back to generic retry
|
|
786
|
-
console.log(`⚠️ Could not parse error response (${response.status})`)
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const delayMs = getRetryDelay(attempt)
|
|
790
|
-
console.log(
|
|
791
|
-
` ⎿ API error (${response.status}), retrying in ${Math.round(delayMs / 1000)}s... (attempt ${attempt + 1}/${maxAttempts})`,
|
|
792
|
-
)
|
|
793
|
-
try {
|
|
794
|
-
await abortableDelay(delayMs, signal)
|
|
795
|
-
} catch (error) {
|
|
796
|
-
// If aborted during delay, throw the error to stop retrying
|
|
797
|
-
if (error.message === 'Request was aborted') {
|
|
798
|
-
throw new Error('Request cancelled by user')
|
|
799
|
-
}
|
|
800
|
-
throw error
|
|
801
|
-
}
|
|
802
|
-
return getCompletionWithProfile(
|
|
803
|
-
modelProfile,
|
|
804
|
-
opts,
|
|
805
|
-
attempt + 1,
|
|
806
|
-
maxAttempts,
|
|
807
|
-
signal, // 🔧 Pass AbortSignal to recursive call
|
|
808
|
-
)
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
const responseData = (await response.json()) as OpenAI.ChatCompletion
|
|
812
|
-
return responseData
|
|
813
|
-
} catch (error) {
|
|
814
|
-
// 🔧 CRITICAL FIX: Check abort signal BEFORE showing retry message
|
|
815
|
-
if (signal?.aborted) {
|
|
816
|
-
throw new Error('Request cancelled by user')
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
if (attempt < maxAttempts) {
|
|
820
|
-
// 🔧 Double-check abort status to avoid showing misleading retry message
|
|
821
|
-
if (signal?.aborted) {
|
|
822
|
-
throw new Error('Request cancelled by user')
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
const delayMs = getRetryDelay(attempt)
|
|
826
|
-
console.log(
|
|
827
|
-
` ⎿ Network error, retrying in ${Math.round(delayMs / 1000)}s... (attempt ${attempt + 1}/${maxAttempts})`,
|
|
828
|
-
)
|
|
829
|
-
try {
|
|
830
|
-
await abortableDelay(delayMs, signal)
|
|
831
|
-
} catch (error) {
|
|
832
|
-
// If aborted during delay, throw the error to stop retrying
|
|
833
|
-
if (error.message === 'Request was aborted') {
|
|
834
|
-
throw new Error('Request cancelled by user')
|
|
835
|
-
}
|
|
836
|
-
throw error
|
|
837
|
-
}
|
|
838
|
-
return getCompletionWithProfile(
|
|
839
|
-
modelProfile,
|
|
840
|
-
opts,
|
|
841
|
-
attempt + 1,
|
|
842
|
-
maxAttempts,
|
|
843
|
-
signal, // 🔧 Pass AbortSignal to recursive call
|
|
844
|
-
)
|
|
845
|
-
}
|
|
846
|
-
throw error
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
export function createStreamProcessor(
|
|
851
|
-
stream: any,
|
|
852
|
-
signal?: AbortSignal,
|
|
853
|
-
): AsyncGenerator<OpenAI.ChatCompletionChunk, void, unknown> {
|
|
854
|
-
if (!stream) {
|
|
855
|
-
throw new Error('Stream is null or undefined')
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
return (async function* () {
|
|
859
|
-
const reader = stream.getReader()
|
|
860
|
-
const decoder = new TextDecoder('utf-8')
|
|
861
|
-
let buffer = ''
|
|
862
|
-
|
|
863
|
-
try {
|
|
864
|
-
while (true) {
|
|
865
|
-
// Check for cancellation before attempting to read
|
|
866
|
-
if (signal?.aborted) {
|
|
867
|
-
break
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
let readResult
|
|
871
|
-
try {
|
|
872
|
-
readResult = await reader.read()
|
|
873
|
-
} catch (e) {
|
|
874
|
-
// If signal is aborted, this is user cancellation - exit silently
|
|
875
|
-
if (signal?.aborted) {
|
|
876
|
-
break
|
|
877
|
-
}
|
|
878
|
-
console.error('Error reading from stream:', e)
|
|
879
|
-
break
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
const { done, value } = readResult
|
|
883
|
-
if (done) {
|
|
884
|
-
break
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
const chunk = decoder.decode(value, { stream: true })
|
|
888
|
-
buffer += chunk
|
|
889
|
-
|
|
890
|
-
let lineEnd = buffer.indexOf('\n')
|
|
891
|
-
while (lineEnd !== -1) {
|
|
892
|
-
const line = buffer.substring(0, lineEnd).trim()
|
|
893
|
-
buffer = buffer.substring(lineEnd + 1)
|
|
894
|
-
|
|
895
|
-
if (line === 'data: [DONE]') {
|
|
896
|
-
continue
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
if (line.startsWith('data: ')) {
|
|
900
|
-
const data = line.slice(6).trim()
|
|
901
|
-
if (!data) continue
|
|
902
|
-
|
|
903
|
-
try {
|
|
904
|
-
const parsed = JSON.parse(data) as OpenAI.ChatCompletionChunk
|
|
905
|
-
yield parsed
|
|
906
|
-
} catch (e) {
|
|
907
|
-
console.error('Error parsing JSON:', data, e)
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
lineEnd = buffer.indexOf('\n')
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// Process any remaining data in the buffer
|
|
916
|
-
if (buffer.trim()) {
|
|
917
|
-
const lines = buffer.trim().split('\n')
|
|
918
|
-
for (const line of lines) {
|
|
919
|
-
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
|
|
920
|
-
const data = line.slice(6).trim()
|
|
921
|
-
if (!data) continue
|
|
922
|
-
|
|
923
|
-
try {
|
|
924
|
-
const parsed = JSON.parse(data) as OpenAI.ChatCompletionChunk
|
|
925
|
-
yield parsed
|
|
926
|
-
} catch (e) {
|
|
927
|
-
console.error('Error parsing final JSON:', data, e)
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
} catch (e) {
|
|
933
|
-
console.error('Unexpected error in stream processing:', e)
|
|
934
|
-
} finally {
|
|
935
|
-
try {
|
|
936
|
-
reader.releaseLock()
|
|
937
|
-
} catch (e) {
|
|
938
|
-
console.error('Error releasing reader lock:', e)
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
})()
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
export function streamCompletion(
|
|
945
|
-
stream: any,
|
|
946
|
-
signal?: AbortSignal,
|
|
947
|
-
): AsyncGenerator<OpenAI.ChatCompletionChunk, void, unknown> {
|
|
948
|
-
return createStreamProcessor(stream, signal)
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* Call GPT-5 Responses API with proper parameter handling
|
|
953
|
-
*/
|
|
954
|
-
export async function callGPT5ResponsesAPI(
|
|
955
|
-
modelProfile: any,
|
|
956
|
-
opts: any, // Using 'any' for Responses API params which differ from ChatCompletionCreateParams
|
|
957
|
-
signal?: AbortSignal,
|
|
958
|
-
): Promise<any> {
|
|
959
|
-
const baseURL = modelProfile?.baseURL || 'https://api.openai.com/v1'
|
|
960
|
-
const apiKey = modelProfile?.apiKey
|
|
961
|
-
const proxy = getGlobalConfig().proxy
|
|
962
|
-
? new ProxyAgent(getGlobalConfig().proxy)
|
|
963
|
-
: undefined
|
|
964
|
-
|
|
965
|
-
const headers: Record<string, string> = {
|
|
966
|
-
'Content-Type': 'application/json',
|
|
967
|
-
Authorization: `Bearer ${apiKey}`,
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
// 🔥 Enhanced Responses API Parameter Mapping for GPT-5
|
|
971
|
-
const responsesParams: any = {
|
|
972
|
-
model: opts.model,
|
|
973
|
-
input: opts.messages, // Responses API uses 'input' instead of 'messages'
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// 🔧 GPT-5 Token Configuration
|
|
977
|
-
if (opts.max_completion_tokens) {
|
|
978
|
-
responsesParams.max_completion_tokens = opts.max_completion_tokens
|
|
979
|
-
} else if (opts.max_tokens) {
|
|
980
|
-
// Fallback conversion if max_tokens is still present
|
|
981
|
-
responsesParams.max_completion_tokens = opts.max_tokens
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// 🔧 GPT-5 Temperature Handling (only 1 or undefined)
|
|
985
|
-
if (opts.temperature === 1) {
|
|
986
|
-
responsesParams.temperature = 1
|
|
987
|
-
}
|
|
988
|
-
// Note: Do not pass temperature if it's not 1, GPT-5 will use default
|
|
989
|
-
|
|
990
|
-
// 🔧 GPT-5 Reasoning Configuration
|
|
991
|
-
const reasoningEffort = opts.reasoning_effort || 'medium'
|
|
992
|
-
responsesParams.reasoning = {
|
|
993
|
-
effort: reasoningEffort,
|
|
994
|
-
// 🚀 Enable reasoning summaries for transparency in coding tasks
|
|
995
|
-
generate_summary: true,
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// 🔧 GPT-5 Tools Support
|
|
999
|
-
if (opts.tools && opts.tools.length > 0) {
|
|
1000
|
-
responsesParams.tools = opts.tools
|
|
1001
|
-
|
|
1002
|
-
// 🚀 GPT-5 Tool Choice Configuration
|
|
1003
|
-
if (opts.tool_choice) {
|
|
1004
|
-
responsesParams.tool_choice = opts.tool_choice
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
// 🔧 GPT-5 System Instructions (separate from messages)
|
|
1009
|
-
const systemMessages = opts.messages.filter(msg => msg.role === 'system')
|
|
1010
|
-
const nonSystemMessages = opts.messages.filter(msg => msg.role !== 'system')
|
|
1011
|
-
|
|
1012
|
-
if (systemMessages.length > 0) {
|
|
1013
|
-
responsesParams.instructions = systemMessages.map(msg => msg.content).join('\n\n')
|
|
1014
|
-
responsesParams.input = nonSystemMessages
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// Handle verbosity (if supported) - optimized for coding tasks
|
|
1018
|
-
const features = getModelFeatures(opts.model)
|
|
1019
|
-
if (features.supportsVerbosityControl) {
|
|
1020
|
-
// High verbosity for coding tasks to get detailed explanations and structured code
|
|
1021
|
-
// Based on GPT-5 best practices for agent-like coding environments
|
|
1022
|
-
responsesParams.text = {
|
|
1023
|
-
verbosity: 'high',
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// Apply GPT-5 coding optimizations
|
|
1028
|
-
if (opts.model.startsWith('gpt-5')) {
|
|
1029
|
-
// Set reasoning effort based on task complexity
|
|
1030
|
-
if (!responsesParams.reasoning) {
|
|
1031
|
-
responsesParams.reasoning = {
|
|
1032
|
-
effort: 'medium', // Balanced for most coding tasks
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// Add instructions parameter for coding-specific guidance
|
|
1037
|
-
if (!responsesParams.instructions) {
|
|
1038
|
-
responsesParams.instructions = `You are an expert programmer working in a terminal-based coding environment. Follow these guidelines:
|
|
1039
|
-
- Provide clear, concise code solutions
|
|
1040
|
-
- Use proper error handling and validation
|
|
1041
|
-
- Follow coding best practices and patterns
|
|
1042
|
-
- Explain complex logic when necessary
|
|
1043
|
-
- Focus on maintainable, readable code`
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
try {
|
|
1048
|
-
const response = await fetch(`${baseURL}/responses`, {
|
|
1049
|
-
method: 'POST',
|
|
1050
|
-
headers,
|
|
1051
|
-
body: JSON.stringify(responsesParams),
|
|
1052
|
-
dispatcher: proxy,
|
|
1053
|
-
signal: signal,
|
|
1054
|
-
})
|
|
1055
|
-
|
|
1056
|
-
if (!response.ok) {
|
|
1057
|
-
throw new Error(`GPT-5 Responses API error: ${response.status} ${response.statusText}`)
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
const responseData = await response.json()
|
|
1061
|
-
|
|
1062
|
-
// Convert Responses API response back to Chat Completion format for compatibility
|
|
1063
|
-
return convertResponsesAPIToChatCompletion(responseData)
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
if (signal?.aborted) {
|
|
1066
|
-
throw new Error('Request cancelled by user')
|
|
1067
|
-
}
|
|
1068
|
-
throw error
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
/**
|
|
1073
|
-
* Convert Responses API response to Chat Completion format for compatibility
|
|
1074
|
-
* 🔥 Enhanced for GPT-5 with reasoning summary support
|
|
1075
|
-
*/
|
|
1076
|
-
function convertResponsesAPIToChatCompletion(responsesData: any): any {
|
|
1077
|
-
// Extract content from Responses API format
|
|
1078
|
-
let outputText = responsesData.output_text || ''
|
|
1079
|
-
const usage = responsesData.usage || {}
|
|
1080
|
-
|
|
1081
|
-
// 🚀 GPT-5 Reasoning Summary Integration
|
|
1082
|
-
// If reasoning summary is available, prepend it to the output for transparency
|
|
1083
|
-
if (responsesData.output && Array.isArray(responsesData.output)) {
|
|
1084
|
-
const reasoningItems = responsesData.output.filter(item => item.type === 'reasoning' && item.summary)
|
|
1085
|
-
const messageItems = responsesData.output.filter(item => item.type === 'message')
|
|
1086
|
-
|
|
1087
|
-
if (reasoningItems.length > 0 && messageItems.length > 0) {
|
|
1088
|
-
const reasoningSummary = reasoningItems
|
|
1089
|
-
.map(item => item.summary?.map(s => s.text).join('\n'))
|
|
1090
|
-
.filter(Boolean)
|
|
1091
|
-
.join('\n\n')
|
|
1092
|
-
|
|
1093
|
-
const mainContent = messageItems
|
|
1094
|
-
.map(item => item.content?.map(c => c.text).join('\n'))
|
|
1095
|
-
.filter(Boolean)
|
|
1096
|
-
.join('\n\n')
|
|
1097
|
-
|
|
1098
|
-
if (reasoningSummary) {
|
|
1099
|
-
outputText = `**🧠 Reasoning Process:**\n${reasoningSummary}\n\n**📝 Response:**\n${mainContent}`
|
|
1100
|
-
} else {
|
|
1101
|
-
outputText = mainContent
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
return {
|
|
1107
|
-
id: responsesData.id || `chatcmpl-${Date.now()}`,
|
|
1108
|
-
object: 'chat.completion',
|
|
1109
|
-
created: Math.floor(Date.now() / 1000),
|
|
1110
|
-
model: responsesData.model || '',
|
|
1111
|
-
choices: [
|
|
1112
|
-
{
|
|
1113
|
-
index: 0,
|
|
1114
|
-
message: {
|
|
1115
|
-
role: 'assistant',
|
|
1116
|
-
content: outputText,
|
|
1117
|
-
// 🚀 Include reasoning metadata if available
|
|
1118
|
-
...(responsesData.reasoning && {
|
|
1119
|
-
reasoning: {
|
|
1120
|
-
effort: responsesData.reasoning.effort,
|
|
1121
|
-
summary: responsesData.reasoning.summary,
|
|
1122
|
-
},
|
|
1123
|
-
}),
|
|
1124
|
-
},
|
|
1125
|
-
finish_reason: responsesData.status === 'completed' ? 'stop' : 'length',
|
|
1126
|
-
},
|
|
1127
|
-
],
|
|
1128
|
-
usage: {
|
|
1129
|
-
prompt_tokens: usage.input_tokens || 0,
|
|
1130
|
-
completion_tokens: usage.output_tokens || 0,
|
|
1131
|
-
total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0),
|
|
1132
|
-
// 🔧 GPT-5 Enhanced Usage Details
|
|
1133
|
-
prompt_tokens_details: {
|
|
1134
|
-
cached_tokens: usage.input_tokens_details?.cached_tokens || 0,
|
|
1135
|
-
},
|
|
1136
|
-
completion_tokens_details: {
|
|
1137
|
-
reasoning_tokens: usage.output_tokens_details?.reasoning_tokens || 0,
|
|
1138
|
-
},
|
|
1139
|
-
},
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
/**
|
|
1144
|
-
* Enhanced getCompletionWithProfile that supports GPT-5 Responses API
|
|
1145
|
-
* 🔥 Optimized for both official OpenAI and third-party GPT-5 providers
|
|
1146
|
-
*/
|
|
1147
|
-
async function getGPT5CompletionWithProfile(
|
|
1148
|
-
modelProfile: any,
|
|
1149
|
-
opts: OpenAI.ChatCompletionCreateParams,
|
|
1150
|
-
attempt: number = 0,
|
|
1151
|
-
maxAttempts: number = 10,
|
|
1152
|
-
signal?: AbortSignal,
|
|
1153
|
-
): Promise<OpenAI.ChatCompletion | AsyncIterable<OpenAI.ChatCompletionChunk>> {
|
|
1154
|
-
const features = getModelFeatures(opts.model)
|
|
1155
|
-
const isOfficialOpenAI = !modelProfile.baseURL ||
|
|
1156
|
-
modelProfile.baseURL.includes('api.openai.com')
|
|
1157
|
-
|
|
1158
|
-
// 🚀 Try Responses API for official OpenAI non-streaming requests
|
|
1159
|
-
if (features.supportsResponsesAPI && !opts.stream && isOfficialOpenAI) {
|
|
1160
|
-
try {
|
|
1161
|
-
debugLogger.api('ATTEMPTING_GPT5_RESPONSES_API', {
|
|
1162
|
-
model: opts.model,
|
|
1163
|
-
baseURL: modelProfile.baseURL || 'official',
|
|
1164
|
-
provider: modelProfile.provider,
|
|
1165
|
-
stream: opts.stream,
|
|
1166
|
-
requestId: getCurrentRequest()?.id,
|
|
1167
|
-
})
|
|
1168
|
-
|
|
1169
|
-
const result = await callGPT5ResponsesAPI(modelProfile, opts, signal)
|
|
1170
|
-
|
|
1171
|
-
debugLogger.api('GPT5_RESPONSES_API_SUCCESS', {
|
|
1172
|
-
model: opts.model,
|
|
1173
|
-
baseURL: modelProfile.baseURL || 'official',
|
|
1174
|
-
requestId: getCurrentRequest()?.id,
|
|
1175
|
-
})
|
|
1176
|
-
|
|
1177
|
-
return result
|
|
1178
|
-
} catch (error) {
|
|
1179
|
-
debugLogger.api('GPT5_RESPONSES_API_FALLBACK', {
|
|
1180
|
-
model: opts.model,
|
|
1181
|
-
error: error.message,
|
|
1182
|
-
baseURL: modelProfile.baseURL || 'official',
|
|
1183
|
-
requestId: getCurrentRequest()?.id,
|
|
1184
|
-
})
|
|
1185
|
-
|
|
1186
|
-
console.warn(
|
|
1187
|
-
`🔄 GPT-5 Responses API failed, falling back to Chat Completions: ${error.message}`
|
|
1188
|
-
)
|
|
1189
|
-
// Fall through to Chat Completions API
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// 🌐 Handle third-party GPT-5 providers with enhanced compatibility
|
|
1194
|
-
else if (!isOfficialOpenAI) {
|
|
1195
|
-
debugLogger.api('GPT5_THIRD_PARTY_PROVIDER', {
|
|
1196
|
-
model: opts.model,
|
|
1197
|
-
baseURL: modelProfile.baseURL,
|
|
1198
|
-
provider: modelProfile.provider,
|
|
1199
|
-
supportsResponsesAPI: features.supportsResponsesAPI,
|
|
1200
|
-
requestId: getCurrentRequest()?.id,
|
|
1201
|
-
})
|
|
1202
|
-
|
|
1203
|
-
// 🔧 Apply enhanced parameter optimization for third-party providers
|
|
1204
|
-
console.log(`🌐 Using GPT-5 via third-party provider: ${modelProfile.provider} (${modelProfile.baseURL})`)
|
|
1205
|
-
|
|
1206
|
-
// Some third-party providers may need additional parameter adjustments
|
|
1207
|
-
if (modelProfile.provider === 'azure') {
|
|
1208
|
-
// Azure OpenAI specific adjustments
|
|
1209
|
-
delete opts.reasoning_effort // Azure may not support this yet
|
|
1210
|
-
} else if (modelProfile.provider === 'custom-openai') {
|
|
1211
|
-
// Generic OpenAI-compatible provider optimizations
|
|
1212
|
-
console.log(`🔧 Applying OpenAI-compatible optimizations for custom provider`)
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
// 📡 Handle streaming requests (Responses API doesn't support streaming yet)
|
|
1217
|
-
else if (opts.stream) {
|
|
1218
|
-
debugLogger.api('GPT5_STREAMING_MODE', {
|
|
1219
|
-
model: opts.model,
|
|
1220
|
-
baseURL: modelProfile.baseURL || 'official',
|
|
1221
|
-
reason: 'responses_api_no_streaming',
|
|
1222
|
-
requestId: getCurrentRequest()?.id,
|
|
1223
|
-
})
|
|
1224
|
-
|
|
1225
|
-
console.log(`🔄 Using Chat Completions for streaming (Responses API streaming not available)`)
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
// 🔧 Enhanced Chat Completions fallback with GPT-5 optimizations
|
|
1229
|
-
debugLogger.api('USING_CHAT_COMPLETIONS_FOR_GPT5', {
|
|
1230
|
-
model: opts.model,
|
|
1231
|
-
baseURL: modelProfile.baseURL || 'official',
|
|
1232
|
-
provider: modelProfile.provider,
|
|
1233
|
-
reason: isOfficialOpenAI ? 'streaming_or_fallback' : 'third_party_provider',
|
|
1234
|
-
requestId: getCurrentRequest()?.id,
|
|
1235
|
-
})
|
|
1236
|
-
|
|
1237
|
-
return await getCompletionWithProfile(
|
|
1238
|
-
modelProfile,
|
|
1239
|
-
opts,
|
|
1240
|
-
attempt,
|
|
1241
|
-
maxAttempts,
|
|
1242
|
-
signal,
|
|
1243
|
-
)
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
/**
|
|
1247
|
-
* Fetch available models from custom OpenAI-compatible API
|
|
1248
|
-
*/
|
|
1249
|
-
export async function fetchCustomModels(
|
|
1250
|
-
baseURL: string,
|
|
1251
|
-
apiKey: string,
|
|
1252
|
-
): Promise<any[]> {
|
|
1253
|
-
try {
|
|
1254
|
-
// Check if baseURL already contains version number (e.g., v1, v2, etc.)
|
|
1255
|
-
const hasVersionNumber = /\/v\d+/.test(baseURL)
|
|
1256
|
-
const cleanBaseURL = baseURL.replace(/\/+$/, '')
|
|
1257
|
-
const modelsURL = hasVersionNumber
|
|
1258
|
-
? `${cleanBaseURL}/models`
|
|
1259
|
-
: `${cleanBaseURL}/v1/models`
|
|
1260
|
-
|
|
1261
|
-
const response = await fetch(modelsURL, {
|
|
1262
|
-
method: 'GET',
|
|
1263
|
-
headers: {
|
|
1264
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1265
|
-
'Content-Type': 'application/json',
|
|
1266
|
-
},
|
|
1267
|
-
})
|
|
1268
|
-
|
|
1269
|
-
if (!response.ok) {
|
|
1270
|
-
// Provide user-friendly error messages based on status code
|
|
1271
|
-
if (response.status === 401) {
|
|
1272
|
-
throw new Error(
|
|
1273
|
-
'Invalid API key. Please check your API key and try again.',
|
|
1274
|
-
)
|
|
1275
|
-
} else if (response.status === 403) {
|
|
1276
|
-
throw new Error(
|
|
1277
|
-
'API key does not have permission to access models. Please check your API key permissions.',
|
|
1278
|
-
)
|
|
1279
|
-
} else if (response.status === 404) {
|
|
1280
|
-
throw new Error(
|
|
1281
|
-
'API endpoint not found. Please check if the base URL is correct and supports the /models endpoint.',
|
|
1282
|
-
)
|
|
1283
|
-
} else if (response.status === 429) {
|
|
1284
|
-
throw new Error(
|
|
1285
|
-
'Too many requests. Please wait a moment and try again.',
|
|
1286
|
-
)
|
|
1287
|
-
} else if (response.status >= 500) {
|
|
1288
|
-
throw new Error(
|
|
1289
|
-
'API service is temporarily unavailable. Please try again later.',
|
|
1290
|
-
)
|
|
1291
|
-
} else {
|
|
1292
|
-
throw new Error(
|
|
1293
|
-
`Unable to connect to API (${response.status}). Please check your base URL, API key, and internet connection.`,
|
|
1294
|
-
)
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
const data = await response.json()
|
|
1299
|
-
|
|
1300
|
-
// Type guards for different API response formats
|
|
1301
|
-
const hasDataArray = (obj: unknown): obj is { data: unknown[] } => {
|
|
1302
|
-
return typeof obj === 'object' && obj !== null && 'data' in obj && Array.isArray((obj as any).data)
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
const hasModelsArray = (obj: unknown): obj is { models: unknown[] } => {
|
|
1306
|
-
return typeof obj === 'object' && obj !== null && 'models' in obj && Array.isArray((obj as any).models)
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// Validate response format and extract models array
|
|
1310
|
-
let models = []
|
|
1311
|
-
|
|
1312
|
-
if (hasDataArray(data)) {
|
|
1313
|
-
// Standard OpenAI format: { data: [...] }
|
|
1314
|
-
models = data.data
|
|
1315
|
-
} else if (Array.isArray(data)) {
|
|
1316
|
-
// Direct array format
|
|
1317
|
-
models = data
|
|
1318
|
-
} else if (hasModelsArray(data)) {
|
|
1319
|
-
// Alternative format: { models: [...] }
|
|
1320
|
-
models = data.models
|
|
1321
|
-
} else {
|
|
1322
|
-
throw new Error(
|
|
1323
|
-
'API returned unexpected response format. Expected an array of models or an object with a "data" or "models" array.',
|
|
1324
|
-
)
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
// Ensure we have an array and validate it contains model objects
|
|
1328
|
-
if (!Array.isArray(models)) {
|
|
1329
|
-
throw new Error('API response format error: models data is not an array.')
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
return models
|
|
1333
|
-
} catch (error) {
|
|
1334
|
-
// If it's already our custom error, pass it through
|
|
1335
|
-
if (
|
|
1336
|
-
error instanceof Error &&
|
|
1337
|
-
(error.message.includes('API key') ||
|
|
1338
|
-
error.message.includes('API endpoint') ||
|
|
1339
|
-
error.message.includes('API service') ||
|
|
1340
|
-
error.message.includes('response format'))
|
|
1341
|
-
) {
|
|
1342
|
-
throw error
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
// For network errors or other issues
|
|
1346
|
-
console.error('Failed to fetch custom API models:', error)
|
|
1347
|
-
|
|
1348
|
-
// Check if it's a network error
|
|
1349
|
-
if (error instanceof Error && error.message.includes('fetch')) {
|
|
1350
|
-
throw new Error(
|
|
1351
|
-
'Unable to connect to the API. Please check the base URL and your internet connection.',
|
|
1352
|
-
)
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
throw new Error(
|
|
1356
|
-
'Failed to fetch models from custom API. Please check your configuration and try again.',
|
|
1357
|
-
)
|
|
1358
|
-
}
|
|
1359
|
-
}
|