@shareai-lab/kode 1.0.70 → 1.0.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/README.md +342 -75
  2. package/README.zh-CN.md +292 -0
  3. package/cli.js +62 -0
  4. package/package.json +49 -25
  5. package/scripts/postinstall.js +56 -0
  6. package/src/ProjectOnboarding.tsx +198 -0
  7. package/src/Tool.ts +82 -0
  8. package/src/commands/agents.tsx +3401 -0
  9. package/src/commands/approvedTools.ts +53 -0
  10. package/src/commands/bug.tsx +20 -0
  11. package/src/commands/clear.ts +43 -0
  12. package/src/commands/compact.ts +120 -0
  13. package/src/commands/config.tsx +19 -0
  14. package/src/commands/cost.ts +18 -0
  15. package/src/commands/ctx_viz.ts +209 -0
  16. package/src/commands/doctor.ts +24 -0
  17. package/src/commands/help.tsx +19 -0
  18. package/src/commands/init.ts +37 -0
  19. package/src/commands/listen.ts +42 -0
  20. package/src/commands/login.tsx +51 -0
  21. package/src/commands/logout.tsx +40 -0
  22. package/src/commands/mcp.ts +41 -0
  23. package/src/commands/model.tsx +40 -0
  24. package/src/commands/modelstatus.tsx +20 -0
  25. package/src/commands/onboarding.tsx +34 -0
  26. package/src/commands/pr_comments.ts +59 -0
  27. package/src/commands/refreshCommands.ts +54 -0
  28. package/src/commands/release-notes.ts +34 -0
  29. package/src/commands/resume.tsx +31 -0
  30. package/src/commands/review.ts +49 -0
  31. package/src/commands/terminalSetup.ts +221 -0
  32. package/src/commands.ts +139 -0
  33. package/src/components/ApproveApiKey.tsx +93 -0
  34. package/src/components/AsciiLogo.tsx +13 -0
  35. package/src/components/AutoUpdater.tsx +148 -0
  36. package/src/components/Bug.tsx +367 -0
  37. package/src/components/Config.tsx +293 -0
  38. package/src/components/ConsoleOAuthFlow.tsx +327 -0
  39. package/src/components/Cost.tsx +23 -0
  40. package/src/components/CostThresholdDialog.tsx +46 -0
  41. package/src/components/CustomSelect/option-map.ts +42 -0
  42. package/src/components/CustomSelect/select-option.tsx +78 -0
  43. package/src/components/CustomSelect/select.tsx +152 -0
  44. package/src/components/CustomSelect/theme.ts +45 -0
  45. package/src/components/CustomSelect/use-select-state.ts +414 -0
  46. package/src/components/CustomSelect/use-select.ts +35 -0
  47. package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
  48. package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
  49. package/src/components/Help.tsx +215 -0
  50. package/src/components/HighlightedCode.tsx +33 -0
  51. package/src/components/InvalidConfigDialog.tsx +113 -0
  52. package/src/components/Link.tsx +32 -0
  53. package/src/components/LogSelector.tsx +86 -0
  54. package/src/components/Logo.tsx +145 -0
  55. package/src/components/MCPServerApprovalDialog.tsx +100 -0
  56. package/src/components/MCPServerDialogCopy.tsx +25 -0
  57. package/src/components/MCPServerMultiselectDialog.tsx +109 -0
  58. package/src/components/Message.tsx +221 -0
  59. package/src/components/MessageResponse.tsx +15 -0
  60. package/src/components/MessageSelector.tsx +211 -0
  61. package/src/components/ModeIndicator.tsx +88 -0
  62. package/src/components/ModelConfig.tsx +301 -0
  63. package/src/components/ModelListManager.tsx +227 -0
  64. package/src/components/ModelSelector.tsx +3386 -0
  65. package/src/components/ModelStatusDisplay.tsx +230 -0
  66. package/src/components/Onboarding.tsx +274 -0
  67. package/src/components/PressEnterToContinue.tsx +11 -0
  68. package/src/components/PromptInput.tsx +740 -0
  69. package/src/components/SentryErrorBoundary.ts +33 -0
  70. package/src/components/Spinner.tsx +129 -0
  71. package/src/components/StickerRequestForm.tsx +16 -0
  72. package/src/components/StructuredDiff.tsx +191 -0
  73. package/src/components/TextInput.tsx +259 -0
  74. package/src/components/TodoItem.tsx +11 -0
  75. package/src/components/TokenWarning.tsx +31 -0
  76. package/src/components/ToolUseLoader.tsx +40 -0
  77. package/src/components/TrustDialog.tsx +106 -0
  78. package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
  79. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
  80. package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
  81. package/src/components/binary-feedback/utils.ts +220 -0
  82. package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
  83. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +49 -0
  84. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
  85. package/src/components/messages/AssistantTextMessage.tsx +144 -0
  86. package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
  87. package/src/components/messages/AssistantToolUseMessage.tsx +133 -0
  88. package/src/components/messages/TaskProgressMessage.tsx +32 -0
  89. package/src/components/messages/TaskToolMessage.tsx +58 -0
  90. package/src/components/messages/UserBashInputMessage.tsx +28 -0
  91. package/src/components/messages/UserCommandMessage.tsx +30 -0
  92. package/src/components/messages/UserKodingInputMessage.tsx +28 -0
  93. package/src/components/messages/UserPromptMessage.tsx +35 -0
  94. package/src/components/messages/UserTextMessage.tsx +39 -0
  95. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
  96. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
  97. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
  98. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
  99. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
  100. package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
  101. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
  102. package/src/components/permissions/FallbackPermissionRequest.tsx +153 -0
  103. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
  104. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +77 -0
  105. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
  106. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +83 -0
  107. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +240 -0
  108. package/src/components/permissions/PermissionRequest.tsx +101 -0
  109. package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
  110. package/src/components/permissions/hooks.ts +44 -0
  111. package/src/components/permissions/toolUseOptions.ts +59 -0
  112. package/src/components/permissions/utils.ts +23 -0
  113. package/src/constants/betas.ts +5 -0
  114. package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
  115. package/src/constants/figures.ts +4 -0
  116. package/src/constants/keys.ts +3 -0
  117. package/src/constants/macros.ts +8 -0
  118. package/src/constants/modelCapabilities.ts +179 -0
  119. package/src/constants/models.ts +1025 -0
  120. package/src/constants/oauth.ts +18 -0
  121. package/src/constants/product.ts +17 -0
  122. package/src/constants/prompts.ts +177 -0
  123. package/src/constants/releaseNotes.ts +7 -0
  124. package/src/context/PermissionContext.tsx +149 -0
  125. package/src/context.ts +278 -0
  126. package/src/cost-tracker.ts +84 -0
  127. package/src/entrypoints/cli.tsx +1518 -0
  128. package/src/entrypoints/mcp.ts +176 -0
  129. package/src/history.ts +25 -0
  130. package/src/hooks/useApiKeyVerification.ts +59 -0
  131. package/src/hooks/useArrowKeyHistory.ts +55 -0
  132. package/src/hooks/useCanUseTool.ts +138 -0
  133. package/src/hooks/useCancelRequest.ts +39 -0
  134. package/src/hooks/useDoublePress.ts +42 -0
  135. package/src/hooks/useExitOnCtrlCD.ts +31 -0
  136. package/src/hooks/useInterval.ts +25 -0
  137. package/src/hooks/useLogMessages.ts +16 -0
  138. package/src/hooks/useLogStartupTime.ts +12 -0
  139. package/src/hooks/useNotifyAfterTimeout.ts +65 -0
  140. package/src/hooks/usePermissionRequestLogging.ts +44 -0
  141. package/src/hooks/useTerminalSize.ts +49 -0
  142. package/src/hooks/useTextInput.ts +318 -0
  143. package/src/hooks/useUnifiedCompletion.ts +1404 -0
  144. package/src/messages.ts +38 -0
  145. package/src/permissions.ts +268 -0
  146. package/src/query.ts +707 -0
  147. package/src/screens/ConfigureNpmPrefix.tsx +197 -0
  148. package/src/screens/Doctor.tsx +219 -0
  149. package/src/screens/LogList.tsx +68 -0
  150. package/src/screens/REPL.tsx +798 -0
  151. package/src/screens/ResumeConversation.tsx +68 -0
  152. package/src/services/adapters/base.ts +38 -0
  153. package/src/services/adapters/chatCompletions.ts +90 -0
  154. package/src/services/adapters/responsesAPI.ts +170 -0
  155. package/src/services/browserMocks.ts +66 -0
  156. package/src/services/claude.ts +2083 -0
  157. package/src/services/customCommands.ts +704 -0
  158. package/src/services/fileFreshness.ts +377 -0
  159. package/src/services/gpt5ConnectionTest.ts +340 -0
  160. package/src/services/mcpClient.ts +564 -0
  161. package/src/services/mcpServerApproval.tsx +50 -0
  162. package/src/services/mentionProcessor.ts +273 -0
  163. package/src/services/modelAdapterFactory.ts +69 -0
  164. package/src/services/notifier.ts +40 -0
  165. package/src/services/oauth.ts +357 -0
  166. package/src/services/openai.ts +1305 -0
  167. package/src/services/responseStateManager.ts +90 -0
  168. package/src/services/sentry.ts +3 -0
  169. package/src/services/statsig.ts +171 -0
  170. package/src/services/statsigStorage.ts +86 -0
  171. package/src/services/systemReminder.ts +507 -0
  172. package/src/services/vcr.ts +161 -0
  173. package/src/test/testAdapters.ts +96 -0
  174. package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
  175. package/src/tools/ArchitectTool/prompt.ts +15 -0
  176. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +569 -0
  177. package/src/tools/BashTool/BashTool.tsx +243 -0
  178. package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
  179. package/src/tools/BashTool/OutputLine.tsx +49 -0
  180. package/src/tools/BashTool/prompt.ts +174 -0
  181. package/src/tools/BashTool/utils.ts +56 -0
  182. package/src/tools/FileEditTool/FileEditTool.tsx +315 -0
  183. package/src/tools/FileEditTool/prompt.ts +51 -0
  184. package/src/tools/FileEditTool/utils.ts +58 -0
  185. package/src/tools/FileReadTool/FileReadTool.tsx +404 -0
  186. package/src/tools/FileReadTool/prompt.ts +7 -0
  187. package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
  188. package/src/tools/FileWriteTool/prompt.ts +10 -0
  189. package/src/tools/GlobTool/GlobTool.tsx +119 -0
  190. package/src/tools/GlobTool/prompt.ts +8 -0
  191. package/src/tools/GrepTool/GrepTool.tsx +147 -0
  192. package/src/tools/GrepTool/prompt.ts +11 -0
  193. package/src/tools/MCPTool/MCPTool.tsx +107 -0
  194. package/src/tools/MCPTool/prompt.ts +3 -0
  195. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
  196. package/src/tools/MemoryReadTool/prompt.ts +3 -0
  197. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
  198. package/src/tools/MemoryWriteTool/prompt.ts +3 -0
  199. package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
  200. package/src/tools/MultiEditTool/prompt.ts +45 -0
  201. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
  202. package/src/tools/NotebookEditTool/prompt.ts +3 -0
  203. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +258 -0
  204. package/src/tools/NotebookReadTool/prompt.ts +3 -0
  205. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
  206. package/src/tools/StickerRequestTool/prompt.ts +19 -0
  207. package/src/tools/TaskTool/TaskTool.tsx +466 -0
  208. package/src/tools/TaskTool/constants.ts +1 -0
  209. package/src/tools/TaskTool/prompt.ts +92 -0
  210. package/src/tools/ThinkTool/ThinkTool.tsx +54 -0
  211. package/src/tools/ThinkTool/prompt.ts +12 -0
  212. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +290 -0
  213. package/src/tools/TodoWriteTool/prompt.ts +63 -0
  214. package/src/tools/lsTool/lsTool.tsx +272 -0
  215. package/src/tools/lsTool/prompt.ts +2 -0
  216. package/src/tools.ts +63 -0
  217. package/src/types/PermissionMode.ts +120 -0
  218. package/src/types/RequestContext.ts +72 -0
  219. package/src/types/conversation.ts +51 -0
  220. package/src/types/logs.ts +58 -0
  221. package/src/types/modelCapabilities.ts +64 -0
  222. package/src/types/notebook.ts +87 -0
  223. package/src/utils/Cursor.ts +436 -0
  224. package/src/utils/PersistentShell.ts +373 -0
  225. package/src/utils/advancedFuzzyMatcher.ts +290 -0
  226. package/src/utils/agentLoader.ts +284 -0
  227. package/src/utils/agentStorage.ts +97 -0
  228. package/src/utils/array.ts +3 -0
  229. package/src/utils/ask.tsx +99 -0
  230. package/src/utils/auth.ts +13 -0
  231. package/src/utils/autoCompactCore.ts +223 -0
  232. package/src/utils/autoUpdater.ts +318 -0
  233. package/src/utils/betas.ts +20 -0
  234. package/src/utils/browser.ts +14 -0
  235. package/src/utils/cleanup.ts +72 -0
  236. package/src/utils/commands.ts +261 -0
  237. package/src/utils/commonUnixCommands.ts +161 -0
  238. package/src/utils/config.ts +942 -0
  239. package/src/utils/conversationRecovery.ts +55 -0
  240. package/src/utils/debugLogger.ts +1123 -0
  241. package/src/utils/diff.ts +42 -0
  242. package/src/utils/env.ts +57 -0
  243. package/src/utils/errors.ts +21 -0
  244. package/src/utils/exampleCommands.ts +109 -0
  245. package/src/utils/execFileNoThrow.ts +51 -0
  246. package/src/utils/expertChatStorage.ts +136 -0
  247. package/src/utils/file.ts +402 -0
  248. package/src/utils/fileRecoveryCore.ts +71 -0
  249. package/src/utils/format.tsx +44 -0
  250. package/src/utils/fuzzyMatcher.ts +328 -0
  251. package/src/utils/generators.ts +62 -0
  252. package/src/utils/git.ts +92 -0
  253. package/src/utils/globalLogger.ts +77 -0
  254. package/src/utils/http.ts +10 -0
  255. package/src/utils/imagePaste.ts +38 -0
  256. package/src/utils/json.ts +13 -0
  257. package/src/utils/log.ts +382 -0
  258. package/src/utils/markdown.ts +213 -0
  259. package/src/utils/messageContextManager.ts +289 -0
  260. package/src/utils/messages.tsx +939 -0
  261. package/src/utils/model.ts +836 -0
  262. package/src/utils/permissions/filesystem.ts +118 -0
  263. package/src/utils/responseState.ts +23 -0
  264. package/src/utils/ripgrep.ts +167 -0
  265. package/src/utils/secureFile.ts +559 -0
  266. package/src/utils/sessionState.ts +49 -0
  267. package/src/utils/state.ts +25 -0
  268. package/src/utils/style.ts +29 -0
  269. package/src/utils/terminal.ts +50 -0
  270. package/src/utils/theme.ts +133 -0
  271. package/src/utils/thinking.ts +144 -0
  272. package/src/utils/todoStorage.ts +431 -0
  273. package/src/utils/tokens.ts +43 -0
  274. package/src/utils/toolExecutionController.ts +163 -0
  275. package/src/utils/unaryLogging.ts +26 -0
  276. package/src/utils/user.ts +37 -0
  277. package/src/utils/validate.ts +165 -0
  278. package/cli.mjs +0 -1803
@@ -0,0 +1,1305 @@
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 } 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
+ modelProfileName: 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
+ const errorMessage = errorData?.error?.message || errorData?.message || `HTTP ${response.status}`
612
+
613
+ // Check if this is a parameter error that we can fix
614
+ const isGPT5 = opts.model.startsWith('gpt-5')
615
+ const handlers = isGPT5 ? [...GPT5_ERROR_HANDLERS, ...ERROR_HANDLERS] : ERROR_HANDLERS
616
+
617
+ for (const handler of handlers) {
618
+ if (handler.detect(errorMessage)) {
619
+ console.log(`🔧 Detected ${handler.type} error for ${opts.model}: ${errorMessage}`)
620
+
621
+ // Store this error for future requests
622
+ setModelError(baseURL || '', opts.model, handler.type, errorMessage)
623
+
624
+ // Apply the fix and retry immediately
625
+ await handler.fix(opts)
626
+ console.log(`🔧 Applied fix for ${handler.type}, retrying...`)
627
+
628
+ return getCompletionWithProfile(
629
+ modelProfile,
630
+ opts,
631
+ attempt + 1,
632
+ maxAttempts,
633
+ signal,
634
+ )
635
+ }
636
+ }
637
+
638
+ // If no specific handler found, log the error for debugging
639
+ console.log(`⚠️ Unhandled API error (${response.status}): ${errorMessage}`)
640
+ } catch (parseError) {
641
+ // If we can't parse the error, fall back to generic retry
642
+ console.log(`⚠️ Could not parse error response (${response.status})`)
643
+ }
644
+
645
+ const delayMs = getRetryDelay(attempt)
646
+ console.log(
647
+ ` ⎿ API error (${response.status}), retrying in ${Math.round(delayMs / 1000)}s... (attempt ${attempt + 1}/${maxAttempts})`,
648
+ )
649
+ try {
650
+ await abortableDelay(delayMs, signal)
651
+ } catch (error) {
652
+ // If aborted during delay, throw the error to stop retrying
653
+ if (error.message === 'Request was aborted') {
654
+ throw new Error('Request cancelled by user')
655
+ }
656
+ throw error
657
+ }
658
+ return getCompletionWithProfile(
659
+ modelProfile,
660
+ opts,
661
+ attempt + 1,
662
+ maxAttempts,
663
+ signal, // 🔧 Pass AbortSignal to recursive call
664
+ )
665
+ }
666
+
667
+ const stream = createStreamProcessor(response.body as any)
668
+ return stream
669
+ }
670
+
671
+ // Non-streaming request
672
+ const isOpenAICompatible = [
673
+ 'minimax',
674
+ 'kimi',
675
+ 'deepseek',
676
+ 'siliconflow',
677
+ 'qwen',
678
+ 'glm',
679
+ 'baidu-qianfan',
680
+ 'openai',
681
+ 'mistral',
682
+ 'xai',
683
+ 'groq',
684
+ 'custom-openai',
685
+ ].includes(provider)
686
+
687
+ let response: Response
688
+ let usedEndpoint: string
689
+
690
+ if (isOpenAICompatible && provider !== 'azure') {
691
+ const result = await tryWithEndpointFallback(
692
+ baseURL,
693
+ opts,
694
+ headers,
695
+ provider,
696
+ proxy,
697
+ signal, // 🔧 Pass AbortSignal to endpoint fallback
698
+ )
699
+ response = result.response
700
+ usedEndpoint = result.endpoint
701
+ } else {
702
+ response = await fetch(`${baseURL}${endpoint}`, {
703
+ method: 'POST',
704
+ headers,
705
+ body: JSON.stringify(opts),
706
+ dispatcher: proxy,
707
+ signal: signal, // 🔧 CRITICAL FIX: Connect AbortSignal to non-streaming fetch call
708
+ })
709
+ usedEndpoint = endpoint
710
+ }
711
+
712
+ if (!response.ok) {
713
+ // 🔧 CRITICAL FIX: Check abort signal BEFORE showing retry message
714
+ if (signal?.aborted) {
715
+ throw new Error('Request cancelled by user')
716
+ }
717
+
718
+ // 🔥 NEW: Parse error message to detect and handle specific API errors
719
+ try {
720
+ const errorData = await response.json()
721
+ const errorMessage = errorData?.error?.message || errorData?.message || `HTTP ${response.status}`
722
+
723
+ // Check if this is a parameter error that we can fix
724
+ const isGPT5 = opts.model.startsWith('gpt-5')
725
+ const handlers = isGPT5 ? [...GPT5_ERROR_HANDLERS, ...ERROR_HANDLERS] : ERROR_HANDLERS
726
+
727
+ for (const handler of handlers) {
728
+ if (handler.detect(errorMessage)) {
729
+ console.log(`🔧 Detected ${handler.type} error for ${opts.model}: ${errorMessage}`)
730
+
731
+ // Store this error for future requests
732
+ setModelError(baseURL || '', opts.model, handler.type, errorMessage)
733
+
734
+ // Apply the fix and retry immediately
735
+ await handler.fix(opts)
736
+ console.log(`🔧 Applied fix for ${handler.type}, retrying...`)
737
+
738
+ return getCompletionWithProfile(
739
+ modelProfile,
740
+ opts,
741
+ attempt + 1,
742
+ maxAttempts,
743
+ signal,
744
+ )
745
+ }
746
+ }
747
+
748
+ // If no specific handler found, log the error for debugging
749
+ console.log(`⚠️ Unhandled API error (${response.status}): ${errorMessage}`)
750
+ } catch (parseError) {
751
+ // If we can't parse the error, fall back to generic retry
752
+ console.log(`⚠️ Could not parse error response (${response.status})`)
753
+ }
754
+
755
+ const delayMs = getRetryDelay(attempt)
756
+ console.log(
757
+ ` ⎿ API error (${response.status}), retrying in ${Math.round(delayMs / 1000)}s... (attempt ${attempt + 1}/${maxAttempts})`,
758
+ )
759
+ try {
760
+ await abortableDelay(delayMs, signal)
761
+ } catch (error) {
762
+ // If aborted during delay, throw the error to stop retrying
763
+ if (error.message === 'Request was aborted') {
764
+ throw new Error('Request cancelled by user')
765
+ }
766
+ throw error
767
+ }
768
+ return getCompletionWithProfile(
769
+ modelProfile,
770
+ opts,
771
+ attempt + 1,
772
+ maxAttempts,
773
+ signal, // 🔧 Pass AbortSignal to recursive call
774
+ )
775
+ }
776
+
777
+ const responseData = (await response.json()) as OpenAI.ChatCompletion
778
+ return responseData
779
+ } catch (error) {
780
+ // 🔧 CRITICAL FIX: Check abort signal BEFORE showing retry message
781
+ if (signal?.aborted) {
782
+ throw new Error('Request cancelled by user')
783
+ }
784
+
785
+ if (attempt < maxAttempts) {
786
+ // 🔧 Double-check abort status to avoid showing misleading retry message
787
+ if (signal?.aborted) {
788
+ throw new Error('Request cancelled by user')
789
+ }
790
+
791
+ const delayMs = getRetryDelay(attempt)
792
+ console.log(
793
+ ` ⎿ Network error, retrying in ${Math.round(delayMs / 1000)}s... (attempt ${attempt + 1}/${maxAttempts})`,
794
+ )
795
+ try {
796
+ await abortableDelay(delayMs, signal)
797
+ } catch (error) {
798
+ // If aborted during delay, throw the error to stop retrying
799
+ if (error.message === 'Request was aborted') {
800
+ throw new Error('Request cancelled by user')
801
+ }
802
+ throw error
803
+ }
804
+ return getCompletionWithProfile(
805
+ modelProfile,
806
+ opts,
807
+ attempt + 1,
808
+ maxAttempts,
809
+ signal, // 🔧 Pass AbortSignal to recursive call
810
+ )
811
+ }
812
+ throw error
813
+ }
814
+ }
815
+
816
+ export function createStreamProcessor(
817
+ stream: any,
818
+ ): AsyncGenerator<OpenAI.ChatCompletionChunk, void, unknown> {
819
+ if (!stream) {
820
+ throw new Error('Stream is null or undefined')
821
+ }
822
+
823
+ return (async function* () {
824
+ const reader = stream.getReader()
825
+ const decoder = new TextDecoder('utf-8')
826
+ let buffer = ''
827
+
828
+ try {
829
+ while (true) {
830
+ let readResult
831
+ try {
832
+ readResult = await reader.read()
833
+ } catch (e) {
834
+ console.error('Error reading from stream:', e)
835
+ break
836
+ }
837
+
838
+ const { done, value } = readResult
839
+ if (done) {
840
+ break
841
+ }
842
+
843
+ const chunk = decoder.decode(value, { stream: true })
844
+ buffer += chunk
845
+
846
+ let lineEnd = buffer.indexOf('\n')
847
+ while (lineEnd !== -1) {
848
+ const line = buffer.substring(0, lineEnd).trim()
849
+ buffer = buffer.substring(lineEnd + 1)
850
+
851
+ if (line === 'data: [DONE]') {
852
+ continue
853
+ }
854
+
855
+ if (line.startsWith('data: ')) {
856
+ const data = line.slice(6).trim()
857
+ if (!data) continue
858
+
859
+ try {
860
+ const parsed = JSON.parse(data) as OpenAI.ChatCompletionChunk
861
+ yield parsed
862
+ } catch (e) {
863
+ console.error('Error parsing JSON:', data, e)
864
+ }
865
+ }
866
+
867
+ lineEnd = buffer.indexOf('\n')
868
+ }
869
+ }
870
+
871
+ // Process any remaining data in the buffer
872
+ if (buffer.trim()) {
873
+ const lines = buffer.trim().split('\n')
874
+ for (const line of lines) {
875
+ if (line.startsWith('data: ') && line !== 'data: [DONE]') {
876
+ const data = line.slice(6).trim()
877
+ if (!data) continue
878
+
879
+ try {
880
+ const parsed = JSON.parse(data) as OpenAI.ChatCompletionChunk
881
+ yield parsed
882
+ } catch (e) {
883
+ console.error('Error parsing final JSON:', data, e)
884
+ }
885
+ }
886
+ }
887
+ }
888
+ } catch (e) {
889
+ console.error('Unexpected error in stream processing:', e)
890
+ } finally {
891
+ try {
892
+ reader.releaseLock()
893
+ } catch (e) {
894
+ console.error('Error releasing reader lock:', e)
895
+ }
896
+ }
897
+ })()
898
+ }
899
+
900
+ export function streamCompletion(
901
+ stream: any,
902
+ ): AsyncGenerator<OpenAI.ChatCompletionChunk, void, unknown> {
903
+ return createStreamProcessor(stream)
904
+ }
905
+
906
+ /**
907
+ * Call GPT-5 Responses API with proper parameter handling
908
+ */
909
+ export async function callGPT5ResponsesAPI(
910
+ modelProfile: any,
911
+ opts: any, // Using 'any' for Responses API params which differ from ChatCompletionCreateParams
912
+ signal?: AbortSignal,
913
+ ): Promise<any> {
914
+ const baseURL = modelProfile?.baseURL || 'https://api.openai.com/v1'
915
+ const apiKey = modelProfile?.apiKey
916
+ const proxy = getGlobalConfig().proxy
917
+ ? new ProxyAgent(getGlobalConfig().proxy)
918
+ : undefined
919
+
920
+ const headers: Record<string, string> = {
921
+ 'Content-Type': 'application/json',
922
+ Authorization: `Bearer ${apiKey}`,
923
+ }
924
+
925
+ // 🔥 Enhanced Responses API Parameter Mapping for GPT-5
926
+ const responsesParams: any = {
927
+ model: opts.model,
928
+ input: opts.messages, // Responses API uses 'input' instead of 'messages'
929
+ }
930
+
931
+ // 🔧 GPT-5 Token Configuration
932
+ if (opts.max_completion_tokens) {
933
+ responsesParams.max_completion_tokens = opts.max_completion_tokens
934
+ } else if (opts.max_tokens) {
935
+ // Fallback conversion if max_tokens is still present
936
+ responsesParams.max_completion_tokens = opts.max_tokens
937
+ }
938
+
939
+ // 🔧 GPT-5 Temperature Handling (only 1 or undefined)
940
+ if (opts.temperature === 1) {
941
+ responsesParams.temperature = 1
942
+ }
943
+ // Note: Do not pass temperature if it's not 1, GPT-5 will use default
944
+
945
+ // 🔧 GPT-5 Reasoning Configuration
946
+ const reasoningEffort = opts.reasoning_effort || 'medium'
947
+ responsesParams.reasoning = {
948
+ effort: reasoningEffort,
949
+ // 🚀 Enable reasoning summaries for transparency in coding tasks
950
+ generate_summary: true,
951
+ }
952
+
953
+ // 🔧 GPT-5 Tools Support
954
+ if (opts.tools && opts.tools.length > 0) {
955
+ responsesParams.tools = opts.tools
956
+
957
+ // 🚀 GPT-5 Tool Choice Configuration
958
+ if (opts.tool_choice) {
959
+ responsesParams.tool_choice = opts.tool_choice
960
+ }
961
+ }
962
+
963
+ // 🔧 GPT-5 System Instructions (separate from messages)
964
+ const systemMessages = opts.messages.filter(msg => msg.role === 'system')
965
+ const nonSystemMessages = opts.messages.filter(msg => msg.role !== 'system')
966
+
967
+ if (systemMessages.length > 0) {
968
+ responsesParams.instructions = systemMessages.map(msg => msg.content).join('\n\n')
969
+ responsesParams.input = nonSystemMessages
970
+ }
971
+
972
+ // Handle verbosity (if supported) - optimized for coding tasks
973
+ const features = getModelFeatures(opts.model)
974
+ if (features.supportsVerbosityControl) {
975
+ // High verbosity for coding tasks to get detailed explanations and structured code
976
+ // Based on GPT-5 best practices for agent-like coding environments
977
+ responsesParams.text = {
978
+ verbosity: 'high',
979
+ }
980
+ }
981
+
982
+ // Apply GPT-5 coding optimizations
983
+ if (opts.model.startsWith('gpt-5')) {
984
+ // Set reasoning effort based on task complexity
985
+ if (!responsesParams.reasoning) {
986
+ responsesParams.reasoning = {
987
+ effort: 'medium', // Balanced for most coding tasks
988
+ }
989
+ }
990
+
991
+ // Add instructions parameter for coding-specific guidance
992
+ if (!responsesParams.instructions) {
993
+ responsesParams.instructions = `You are an expert programmer working in a terminal-based coding environment. Follow these guidelines:
994
+ - Provide clear, concise code solutions
995
+ - Use proper error handling and validation
996
+ - Follow coding best practices and patterns
997
+ - Explain complex logic when necessary
998
+ - Focus on maintainable, readable code`
999
+ }
1000
+ }
1001
+
1002
+ try {
1003
+ const response = await fetch(`${baseURL}/responses`, {
1004
+ method: 'POST',
1005
+ headers,
1006
+ body: JSON.stringify(responsesParams),
1007
+ dispatcher: proxy,
1008
+ signal: signal,
1009
+ })
1010
+
1011
+ if (!response.ok) {
1012
+ throw new Error(`GPT-5 Responses API error: ${response.status} ${response.statusText}`)
1013
+ }
1014
+
1015
+ const responseData = await response.json()
1016
+
1017
+ // Convert Responses API response back to Chat Completion format for compatibility
1018
+ return convertResponsesAPIToChatCompletion(responseData)
1019
+ } catch (error) {
1020
+ if (signal?.aborted) {
1021
+ throw new Error('Request cancelled by user')
1022
+ }
1023
+ throw error
1024
+ }
1025
+ }
1026
+
1027
+ /**
1028
+ * Convert Responses API response to Chat Completion format for compatibility
1029
+ * 🔥 Enhanced for GPT-5 with reasoning summary support
1030
+ */
1031
+ function convertResponsesAPIToChatCompletion(responsesData: any): any {
1032
+ // Extract content from Responses API format
1033
+ let outputText = responsesData.output_text || ''
1034
+ const usage = responsesData.usage || {}
1035
+
1036
+ // 🚀 GPT-5 Reasoning Summary Integration
1037
+ // If reasoning summary is available, prepend it to the output for transparency
1038
+ if (responsesData.output && Array.isArray(responsesData.output)) {
1039
+ const reasoningItems = responsesData.output.filter(item => item.type === 'reasoning' && item.summary)
1040
+ const messageItems = responsesData.output.filter(item => item.type === 'message')
1041
+
1042
+ if (reasoningItems.length > 0 && messageItems.length > 0) {
1043
+ const reasoningSummary = reasoningItems
1044
+ .map(item => item.summary?.map(s => s.text).join('\n'))
1045
+ .filter(Boolean)
1046
+ .join('\n\n')
1047
+
1048
+ const mainContent = messageItems
1049
+ .map(item => item.content?.map(c => c.text).join('\n'))
1050
+ .filter(Boolean)
1051
+ .join('\n\n')
1052
+
1053
+ if (reasoningSummary) {
1054
+ outputText = `**🧠 Reasoning Process:**\n${reasoningSummary}\n\n**📝 Response:**\n${mainContent}`
1055
+ } else {
1056
+ outputText = mainContent
1057
+ }
1058
+ }
1059
+ }
1060
+
1061
+ return {
1062
+ id: responsesData.id || `chatcmpl-${Date.now()}`,
1063
+ object: 'chat.completion',
1064
+ created: Math.floor(Date.now() / 1000),
1065
+ model: responsesData.model || '',
1066
+ choices: [
1067
+ {
1068
+ index: 0,
1069
+ message: {
1070
+ role: 'assistant',
1071
+ content: outputText,
1072
+ // 🚀 Include reasoning metadata if available
1073
+ ...(responsesData.reasoning && {
1074
+ reasoning: {
1075
+ effort: responsesData.reasoning.effort,
1076
+ summary: responsesData.reasoning.summary,
1077
+ },
1078
+ }),
1079
+ },
1080
+ finish_reason: responsesData.status === 'completed' ? 'stop' : 'length',
1081
+ },
1082
+ ],
1083
+ usage: {
1084
+ prompt_tokens: usage.input_tokens || 0,
1085
+ completion_tokens: usage.output_tokens || 0,
1086
+ total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0),
1087
+ // 🔧 GPT-5 Enhanced Usage Details
1088
+ prompt_tokens_details: {
1089
+ cached_tokens: usage.input_tokens_details?.cached_tokens || 0,
1090
+ },
1091
+ completion_tokens_details: {
1092
+ reasoning_tokens: usage.output_tokens_details?.reasoning_tokens || 0,
1093
+ },
1094
+ },
1095
+ }
1096
+ }
1097
+
1098
+ /**
1099
+ * Enhanced getCompletionWithProfile that supports GPT-5 Responses API
1100
+ * 🔥 Optimized for both official OpenAI and third-party GPT-5 providers
1101
+ */
1102
+ async function getGPT5CompletionWithProfile(
1103
+ modelProfile: any,
1104
+ opts: OpenAI.ChatCompletionCreateParams,
1105
+ attempt: number = 0,
1106
+ maxAttempts: number = 10,
1107
+ signal?: AbortSignal,
1108
+ ): Promise<OpenAI.ChatCompletion | AsyncIterable<OpenAI.ChatCompletionChunk>> {
1109
+ const features = getModelFeatures(opts.model)
1110
+ const isOfficialOpenAI = !modelProfile.baseURL ||
1111
+ modelProfile.baseURL.includes('api.openai.com')
1112
+
1113
+ // 🚀 Try Responses API for official OpenAI non-streaming requests
1114
+ if (features.supportsResponsesAPI && !opts.stream && isOfficialOpenAI) {
1115
+ try {
1116
+ debugLogger.api('ATTEMPTING_GPT5_RESPONSES_API', {
1117
+ model: opts.model,
1118
+ baseURL: modelProfile.baseURL || 'official',
1119
+ provider: modelProfile.provider,
1120
+ stream: opts.stream,
1121
+ requestId: getCurrentRequest()?.id,
1122
+ })
1123
+
1124
+ const result = await callGPT5ResponsesAPI(modelProfile, opts, signal)
1125
+
1126
+ debugLogger.api('GPT5_RESPONSES_API_SUCCESS', {
1127
+ model: opts.model,
1128
+ baseURL: modelProfile.baseURL || 'official',
1129
+ requestId: getCurrentRequest()?.id,
1130
+ })
1131
+
1132
+ return result
1133
+ } catch (error) {
1134
+ debugLogger.api('GPT5_RESPONSES_API_FALLBACK', {
1135
+ model: opts.model,
1136
+ error: error.message,
1137
+ baseURL: modelProfile.baseURL || 'official',
1138
+ requestId: getCurrentRequest()?.id,
1139
+ })
1140
+
1141
+ console.warn(
1142
+ `🔄 GPT-5 Responses API failed, falling back to Chat Completions: ${error.message}`
1143
+ )
1144
+ // Fall through to Chat Completions API
1145
+ }
1146
+ }
1147
+
1148
+ // 🌐 Handle third-party GPT-5 providers with enhanced compatibility
1149
+ else if (!isOfficialOpenAI) {
1150
+ debugLogger.api('GPT5_THIRD_PARTY_PROVIDER', {
1151
+ model: opts.model,
1152
+ baseURL: modelProfile.baseURL,
1153
+ provider: modelProfile.provider,
1154
+ supportsResponsesAPI: features.supportsResponsesAPI,
1155
+ requestId: getCurrentRequest()?.id,
1156
+ })
1157
+
1158
+ // 🔧 Apply enhanced parameter optimization for third-party providers
1159
+ console.log(`🌐 Using GPT-5 via third-party provider: ${modelProfile.provider} (${modelProfile.baseURL})`)
1160
+
1161
+ // Some third-party providers may need additional parameter adjustments
1162
+ if (modelProfile.provider === 'azure') {
1163
+ // Azure OpenAI specific adjustments
1164
+ delete opts.reasoning_effort // Azure may not support this yet
1165
+ } else if (modelProfile.provider === 'custom-openai') {
1166
+ // Generic OpenAI-compatible provider optimizations
1167
+ console.log(`🔧 Applying OpenAI-compatible optimizations for custom provider`)
1168
+ }
1169
+ }
1170
+
1171
+ // 📡 Handle streaming requests (Responses API doesn't support streaming yet)
1172
+ else if (opts.stream) {
1173
+ debugLogger.api('GPT5_STREAMING_MODE', {
1174
+ model: opts.model,
1175
+ baseURL: modelProfile.baseURL || 'official',
1176
+ reason: 'responses_api_no_streaming',
1177
+ requestId: getCurrentRequest()?.id,
1178
+ })
1179
+
1180
+ console.log(`🔄 Using Chat Completions for streaming (Responses API streaming not available)`)
1181
+ }
1182
+
1183
+ // 🔧 Enhanced Chat Completions fallback with GPT-5 optimizations
1184
+ debugLogger.api('USING_CHAT_COMPLETIONS_FOR_GPT5', {
1185
+ model: opts.model,
1186
+ baseURL: modelProfile.baseURL || 'official',
1187
+ provider: modelProfile.provider,
1188
+ reason: isOfficialOpenAI ? 'streaming_or_fallback' : 'third_party_provider',
1189
+ requestId: getCurrentRequest()?.id,
1190
+ })
1191
+
1192
+ return await getCompletionWithProfile(
1193
+ modelProfile,
1194
+ opts,
1195
+ attempt,
1196
+ maxAttempts,
1197
+ signal,
1198
+ )
1199
+ }
1200
+
1201
+ /**
1202
+ * Fetch available models from custom OpenAI-compatible API
1203
+ */
1204
+ export async function fetchCustomModels(
1205
+ baseURL: string,
1206
+ apiKey: string,
1207
+ ): Promise<any[]> {
1208
+ try {
1209
+ // Check if baseURL already contains version number (e.g., v1, v2, etc.)
1210
+ const hasVersionNumber = /\/v\d+/.test(baseURL)
1211
+ const cleanBaseURL = baseURL.replace(/\/+$/, '')
1212
+ const modelsURL = hasVersionNumber
1213
+ ? `${cleanBaseURL}/models`
1214
+ : `${cleanBaseURL}/v1/models`
1215
+
1216
+ const response = await fetch(modelsURL, {
1217
+ method: 'GET',
1218
+ headers: {
1219
+ Authorization: `Bearer ${apiKey}`,
1220
+ 'Content-Type': 'application/json',
1221
+ },
1222
+ })
1223
+
1224
+ if (!response.ok) {
1225
+ // Provide user-friendly error messages based on status code
1226
+ if (response.status === 401) {
1227
+ throw new Error(
1228
+ 'Invalid API key. Please check your API key and try again.',
1229
+ )
1230
+ } else if (response.status === 403) {
1231
+ throw new Error(
1232
+ 'API key does not have permission to access models. Please check your API key permissions.',
1233
+ )
1234
+ } else if (response.status === 404) {
1235
+ throw new Error(
1236
+ 'API endpoint not found. Please check if the base URL is correct and supports the /models endpoint.',
1237
+ )
1238
+ } else if (response.status === 429) {
1239
+ throw new Error(
1240
+ 'Too many requests. Please wait a moment and try again.',
1241
+ )
1242
+ } else if (response.status >= 500) {
1243
+ throw new Error(
1244
+ 'API service is temporarily unavailable. Please try again later.',
1245
+ )
1246
+ } else {
1247
+ throw new Error(
1248
+ `Unable to connect to API (${response.status}). Please check your base URL, API key, and internet connection.`,
1249
+ )
1250
+ }
1251
+ }
1252
+
1253
+ const data = await response.json()
1254
+
1255
+ // Validate response format and extract models array
1256
+ let models = []
1257
+
1258
+ if (data && data.data && Array.isArray(data.data)) {
1259
+ // Standard OpenAI format: { data: [...] }
1260
+ models = data.data
1261
+ } else if (Array.isArray(data)) {
1262
+ // Direct array format
1263
+ models = data
1264
+ } else if (data && data.models && Array.isArray(data.models)) {
1265
+ // Alternative format: { models: [...] }
1266
+ models = data.models
1267
+ } else {
1268
+ throw new Error(
1269
+ 'API returned unexpected response format. Expected an array of models or an object with a "data" or "models" array.',
1270
+ )
1271
+ }
1272
+
1273
+ // Ensure we have an array and validate it contains model objects
1274
+ if (!Array.isArray(models)) {
1275
+ throw new Error('API response format error: models data is not an array.')
1276
+ }
1277
+
1278
+ return models
1279
+ } catch (error) {
1280
+ // If it's already our custom error, pass it through
1281
+ if (
1282
+ error instanceof Error &&
1283
+ (error.message.includes('API key') ||
1284
+ error.message.includes('API endpoint') ||
1285
+ error.message.includes('API service') ||
1286
+ error.message.includes('response format'))
1287
+ ) {
1288
+ throw error
1289
+ }
1290
+
1291
+ // For network errors or other issues
1292
+ console.error('Failed to fetch custom API models:', error)
1293
+
1294
+ // Check if it's a network error
1295
+ if (error instanceof Error && error.message.includes('fetch')) {
1296
+ throw new Error(
1297
+ 'Unable to connect to the API. Please check the base URL and your internet connection.',
1298
+ )
1299
+ }
1300
+
1301
+ throw new Error(
1302
+ 'Failed to fetch models from custom API. Please check your configuration and try again.',
1303
+ )
1304
+ }
1305
+ }