@shareai-lab/kode 1.1.13 → 1.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/dist/entrypoints/cli.js +2 -1
  2. package/dist/entrypoints/cli.js.map +2 -2
  3. package/dist/index.js +5 -26
  4. package/dist/package.json +4 -1
  5. package/package.json +9 -104
  6. package/dist/test/testAdapters.js +0 -88
  7. package/dist/test/testAdapters.js.map +0 -1
  8. package/src/ProjectOnboarding.tsx +0 -198
  9. package/src/Tool.ts +0 -83
  10. package/src/commands/agents.tsx +0 -3416
  11. package/src/commands/approvedTools.ts +0 -53
  12. package/src/commands/bug.tsx +0 -20
  13. package/src/commands/clear.ts +0 -43
  14. package/src/commands/compact.ts +0 -120
  15. package/src/commands/config.tsx +0 -19
  16. package/src/commands/cost.ts +0 -18
  17. package/src/commands/ctx_viz.ts +0 -209
  18. package/src/commands/doctor.ts +0 -24
  19. package/src/commands/help.tsx +0 -19
  20. package/src/commands/init.ts +0 -37
  21. package/src/commands/listen.ts +0 -42
  22. package/src/commands/login.tsx +0 -51
  23. package/src/commands/logout.tsx +0 -40
  24. package/src/commands/mcp.ts +0 -41
  25. package/src/commands/model.tsx +0 -40
  26. package/src/commands/modelstatus.tsx +0 -20
  27. package/src/commands/onboarding.tsx +0 -34
  28. package/src/commands/pr_comments.ts +0 -59
  29. package/src/commands/refreshCommands.ts +0 -54
  30. package/src/commands/release-notes.ts +0 -34
  31. package/src/commands/resume.tsx +0 -31
  32. package/src/commands/review.ts +0 -49
  33. package/src/commands/terminalSetup.ts +0 -221
  34. package/src/commands.ts +0 -139
  35. package/src/components/ApproveApiKey.tsx +0 -93
  36. package/src/components/AsciiLogo.tsx +0 -13
  37. package/src/components/AutoUpdater.tsx +0 -148
  38. package/src/components/Bug.tsx +0 -367
  39. package/src/components/Config.tsx +0 -293
  40. package/src/components/ConsoleOAuthFlow.tsx +0 -327
  41. package/src/components/Cost.tsx +0 -23
  42. package/src/components/CostThresholdDialog.tsx +0 -46
  43. package/src/components/CustomSelect/option-map.ts +0 -42
  44. package/src/components/CustomSelect/select-option.tsx +0 -78
  45. package/src/components/CustomSelect/select.tsx +0 -152
  46. package/src/components/CustomSelect/theme.ts +0 -45
  47. package/src/components/CustomSelect/use-select-state.ts +0 -414
  48. package/src/components/CustomSelect/use-select.ts +0 -35
  49. package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
  50. package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
  51. package/src/components/Help.tsx +0 -215
  52. package/src/components/HighlightedCode.tsx +0 -33
  53. package/src/components/InvalidConfigDialog.tsx +0 -113
  54. package/src/components/Link.tsx +0 -32
  55. package/src/components/LogSelector.tsx +0 -86
  56. package/src/components/Logo.tsx +0 -170
  57. package/src/components/MCPServerApprovalDialog.tsx +0 -100
  58. package/src/components/MCPServerDialogCopy.tsx +0 -25
  59. package/src/components/MCPServerMultiselectDialog.tsx +0 -109
  60. package/src/components/Message.tsx +0 -221
  61. package/src/components/MessageResponse.tsx +0 -15
  62. package/src/components/MessageSelector.tsx +0 -211
  63. package/src/components/ModeIndicator.tsx +0 -88
  64. package/src/components/ModelConfig.tsx +0 -301
  65. package/src/components/ModelListManager.tsx +0 -227
  66. package/src/components/ModelSelector.tsx +0 -3387
  67. package/src/components/ModelStatusDisplay.tsx +0 -230
  68. package/src/components/Onboarding.tsx +0 -274
  69. package/src/components/PressEnterToContinue.tsx +0 -11
  70. package/src/components/PromptInput.tsx +0 -760
  71. package/src/components/SentryErrorBoundary.ts +0 -39
  72. package/src/components/Spinner.tsx +0 -129
  73. package/src/components/StickerRequestForm.tsx +0 -16
  74. package/src/components/StructuredDiff.tsx +0 -191
  75. package/src/components/TextInput.tsx +0 -259
  76. package/src/components/TodoItem.tsx +0 -47
  77. package/src/components/TokenWarning.tsx +0 -31
  78. package/src/components/ToolUseLoader.tsx +0 -40
  79. package/src/components/TrustDialog.tsx +0 -106
  80. package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
  81. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
  82. package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
  83. package/src/components/binary-feedback/utils.ts +0 -220
  84. package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
  85. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
  86. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
  87. package/src/components/messages/AssistantTextMessage.tsx +0 -144
  88. package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
  89. package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
  90. package/src/components/messages/TaskProgressMessage.tsx +0 -32
  91. package/src/components/messages/TaskToolMessage.tsx +0 -58
  92. package/src/components/messages/UserBashInputMessage.tsx +0 -28
  93. package/src/components/messages/UserCommandMessage.tsx +0 -30
  94. package/src/components/messages/UserKodingInputMessage.tsx +0 -28
  95. package/src/components/messages/UserPromptMessage.tsx +0 -35
  96. package/src/components/messages/UserTextMessage.tsx +0 -39
  97. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
  98. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
  99. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
  100. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
  101. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
  102. package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
  103. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
  104. package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
  105. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
  106. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
  107. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
  108. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
  109. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
  110. package/src/components/permissions/PermissionRequest.tsx +0 -101
  111. package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
  112. package/src/components/permissions/hooks.ts +0 -44
  113. package/src/components/permissions/toolUseOptions.ts +0 -59
  114. package/src/components/permissions/utils.ts +0 -23
  115. package/src/constants/betas.ts +0 -5
  116. package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
  117. package/src/constants/figures.ts +0 -4
  118. package/src/constants/keys.ts +0 -3
  119. package/src/constants/macros.ts +0 -11
  120. package/src/constants/modelCapabilities.ts +0 -179
  121. package/src/constants/models.ts +0 -1025
  122. package/src/constants/oauth.ts +0 -18
  123. package/src/constants/product.ts +0 -17
  124. package/src/constants/prompts.ts +0 -168
  125. package/src/constants/releaseNotes.ts +0 -7
  126. package/src/context/PermissionContext.tsx +0 -149
  127. package/src/context.ts +0 -278
  128. package/src/cost-tracker.ts +0 -84
  129. package/src/entrypoints/cli.tsx +0 -1561
  130. package/src/entrypoints/mcp.ts +0 -175
  131. package/src/history.ts +0 -25
  132. package/src/hooks/useApiKeyVerification.ts +0 -59
  133. package/src/hooks/useArrowKeyHistory.ts +0 -55
  134. package/src/hooks/useCanUseTool.ts +0 -138
  135. package/src/hooks/useCancelRequest.ts +0 -39
  136. package/src/hooks/useDoublePress.ts +0 -41
  137. package/src/hooks/useExitOnCtrlCD.ts +0 -31
  138. package/src/hooks/useInterval.ts +0 -25
  139. package/src/hooks/useLogMessages.ts +0 -16
  140. package/src/hooks/useLogStartupTime.ts +0 -12
  141. package/src/hooks/useNotifyAfterTimeout.ts +0 -65
  142. package/src/hooks/usePermissionRequestLogging.ts +0 -44
  143. package/src/hooks/useTerminalSize.ts +0 -49
  144. package/src/hooks/useTextInput.ts +0 -317
  145. package/src/hooks/useUnifiedCompletion.ts +0 -1405
  146. package/src/index.ts +0 -34
  147. package/src/messages.ts +0 -38
  148. package/src/permissions.ts +0 -268
  149. package/src/query.ts +0 -720
  150. package/src/screens/ConfigureNpmPrefix.tsx +0 -197
  151. package/src/screens/Doctor.tsx +0 -219
  152. package/src/screens/LogList.tsx +0 -68
  153. package/src/screens/REPL.tsx +0 -813
  154. package/src/screens/ResumeConversation.tsx +0 -68
  155. package/src/services/adapters/base.ts +0 -38
  156. package/src/services/adapters/chatCompletions.ts +0 -90
  157. package/src/services/adapters/responsesAPI.ts +0 -170
  158. package/src/services/browserMocks.ts +0 -66
  159. package/src/services/claude.ts +0 -2197
  160. package/src/services/customCommands.ts +0 -704
  161. package/src/services/fileFreshness.ts +0 -377
  162. package/src/services/gpt5ConnectionTest.ts +0 -340
  163. package/src/services/mcpClient.ts +0 -564
  164. package/src/services/mcpServerApproval.tsx +0 -50
  165. package/src/services/mentionProcessor.ts +0 -273
  166. package/src/services/modelAdapterFactory.ts +0 -69
  167. package/src/services/notifier.ts +0 -40
  168. package/src/services/oauth.ts +0 -357
  169. package/src/services/openai.ts +0 -1359
  170. package/src/services/responseStateManager.ts +0 -90
  171. package/src/services/sentry.ts +0 -3
  172. package/src/services/statsig.ts +0 -172
  173. package/src/services/statsigStorage.ts +0 -86
  174. package/src/services/systemReminder.ts +0 -507
  175. package/src/services/vcr.ts +0 -161
  176. package/src/test/testAdapters.ts +0 -96
  177. package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
  178. package/src/tools/ArchitectTool/prompt.ts +0 -15
  179. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
  180. package/src/tools/BashTool/BashTool.tsx +0 -243
  181. package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
  182. package/src/tools/BashTool/OutputLine.tsx +0 -49
  183. package/src/tools/BashTool/prompt.ts +0 -174
  184. package/src/tools/BashTool/utils.ts +0 -56
  185. package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
  186. package/src/tools/FileEditTool/prompt.ts +0 -51
  187. package/src/tools/FileEditTool/utils.ts +0 -58
  188. package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
  189. package/src/tools/FileReadTool/prompt.ts +0 -7
  190. package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
  191. package/src/tools/FileWriteTool/prompt.ts +0 -10
  192. package/src/tools/GlobTool/GlobTool.tsx +0 -119
  193. package/src/tools/GlobTool/prompt.ts +0 -8
  194. package/src/tools/GrepTool/GrepTool.tsx +0 -147
  195. package/src/tools/GrepTool/prompt.ts +0 -11
  196. package/src/tools/MCPTool/MCPTool.tsx +0 -107
  197. package/src/tools/MCPTool/prompt.ts +0 -3
  198. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
  199. package/src/tools/MemoryReadTool/prompt.ts +0 -3
  200. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
  201. package/src/tools/MemoryWriteTool/prompt.ts +0 -3
  202. package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
  203. package/src/tools/MultiEditTool/prompt.ts +0 -45
  204. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
  205. package/src/tools/NotebookEditTool/prompt.ts +0 -3
  206. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
  207. package/src/tools/NotebookReadTool/prompt.ts +0 -3
  208. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
  209. package/src/tools/StickerRequestTool/prompt.ts +0 -19
  210. package/src/tools/TaskTool/TaskTool.tsx +0 -438
  211. package/src/tools/TaskTool/constants.ts +0 -1
  212. package/src/tools/TaskTool/prompt.ts +0 -92
  213. package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
  214. package/src/tools/ThinkTool/prompt.ts +0 -12
  215. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
  216. package/src/tools/TodoWriteTool/prompt.ts +0 -63
  217. package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
  218. package/src/tools/URLFetcherTool/cache.ts +0 -55
  219. package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
  220. package/src/tools/URLFetcherTool/prompt.ts +0 -17
  221. package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
  222. package/src/tools/WebSearchTool/prompt.ts +0 -13
  223. package/src/tools/WebSearchTool/searchProviders.ts +0 -66
  224. package/src/tools/lsTool/lsTool.tsx +0 -272
  225. package/src/tools/lsTool/prompt.ts +0 -2
  226. package/src/tools.ts +0 -67
  227. package/src/types/PermissionMode.ts +0 -120
  228. package/src/types/RequestContext.ts +0 -72
  229. package/src/types/common.d.ts +0 -2
  230. package/src/types/conversation.ts +0 -51
  231. package/src/types/logs.ts +0 -58
  232. package/src/types/modelCapabilities.ts +0 -64
  233. package/src/types/notebook.ts +0 -87
  234. package/src/utils/Cursor.ts +0 -436
  235. package/src/utils/PersistentShell.ts +0 -552
  236. package/src/utils/advancedFuzzyMatcher.ts +0 -290
  237. package/src/utils/agentLoader.ts +0 -278
  238. package/src/utils/agentStorage.ts +0 -97
  239. package/src/utils/array.ts +0 -3
  240. package/src/utils/ask.tsx +0 -99
  241. package/src/utils/auth.ts +0 -13
  242. package/src/utils/autoCompactCore.ts +0 -223
  243. package/src/utils/autoUpdater.ts +0 -458
  244. package/src/utils/betas.ts +0 -20
  245. package/src/utils/browser.ts +0 -14
  246. package/src/utils/cleanup.ts +0 -72
  247. package/src/utils/commands.ts +0 -261
  248. package/src/utils/commonUnixCommands.ts +0 -161
  249. package/src/utils/config.ts +0 -945
  250. package/src/utils/conversationRecovery.ts +0 -55
  251. package/src/utils/debugLogger.ts +0 -1235
  252. package/src/utils/diff.ts +0 -42
  253. package/src/utils/env.ts +0 -57
  254. package/src/utils/errors.ts +0 -21
  255. package/src/utils/exampleCommands.ts +0 -109
  256. package/src/utils/execFileNoThrow.ts +0 -51
  257. package/src/utils/expertChatStorage.ts +0 -136
  258. package/src/utils/file.ts +0 -405
  259. package/src/utils/fileRecoveryCore.ts +0 -71
  260. package/src/utils/format.tsx +0 -44
  261. package/src/utils/fuzzyMatcher.ts +0 -328
  262. package/src/utils/generators.ts +0 -62
  263. package/src/utils/git.ts +0 -92
  264. package/src/utils/globalLogger.ts +0 -77
  265. package/src/utils/http.ts +0 -10
  266. package/src/utils/imagePaste.ts +0 -38
  267. package/src/utils/json.ts +0 -13
  268. package/src/utils/log.ts +0 -382
  269. package/src/utils/markdown.ts +0 -213
  270. package/src/utils/messageContextManager.ts +0 -294
  271. package/src/utils/messages.tsx +0 -945
  272. package/src/utils/model.ts +0 -914
  273. package/src/utils/permissions/filesystem.ts +0 -127
  274. package/src/utils/responseState.ts +0 -23
  275. package/src/utils/ripgrep.ts +0 -167
  276. package/src/utils/secureFile.ts +0 -564
  277. package/src/utils/sessionState.ts +0 -49
  278. package/src/utils/state.ts +0 -25
  279. package/src/utils/style.ts +0 -29
  280. package/src/utils/terminal.ts +0 -50
  281. package/src/utils/theme.ts +0 -127
  282. package/src/utils/thinking.ts +0 -144
  283. package/src/utils/todoStorage.ts +0 -431
  284. package/src/utils/tokens.ts +0 -43
  285. package/src/utils/toolExecutionController.ts +0 -163
  286. package/src/utils/unaryLogging.ts +0 -26
  287. package/src/utils/user.ts +0 -37
  288. package/src/utils/validate.ts +0 -165
@@ -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
- }