@shareai-lab/kode 1.0.70 → 1.0.71

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 (253) hide show
  1. package/README.md +202 -76
  2. package/README.zh-CN.md +246 -0
  3. package/cli.js +62 -0
  4. package/package.json +45 -25
  5. package/scripts/postinstall.js +56 -0
  6. package/src/ProjectOnboarding.tsx +180 -0
  7. package/src/Tool.ts +53 -0
  8. package/src/commands/approvedTools.ts +53 -0
  9. package/src/commands/bug.tsx +20 -0
  10. package/src/commands/clear.ts +43 -0
  11. package/src/commands/compact.ts +120 -0
  12. package/src/commands/config.tsx +19 -0
  13. package/src/commands/cost.ts +18 -0
  14. package/src/commands/ctx_viz.ts +209 -0
  15. package/src/commands/doctor.ts +24 -0
  16. package/src/commands/help.tsx +19 -0
  17. package/src/commands/init.ts +37 -0
  18. package/src/commands/listen.ts +42 -0
  19. package/src/commands/login.tsx +51 -0
  20. package/src/commands/logout.tsx +40 -0
  21. package/src/commands/mcp.ts +41 -0
  22. package/src/commands/model.tsx +40 -0
  23. package/src/commands/modelstatus.tsx +20 -0
  24. package/src/commands/onboarding.tsx +34 -0
  25. package/src/commands/pr_comments.ts +59 -0
  26. package/src/commands/refreshCommands.ts +54 -0
  27. package/src/commands/release-notes.ts +34 -0
  28. package/src/commands/resume.tsx +30 -0
  29. package/src/commands/review.ts +49 -0
  30. package/src/commands/terminalSetup.ts +221 -0
  31. package/src/commands.ts +136 -0
  32. package/src/components/ApproveApiKey.tsx +93 -0
  33. package/src/components/AsciiLogo.tsx +13 -0
  34. package/src/components/AutoUpdater.tsx +148 -0
  35. package/src/components/Bug.tsx +367 -0
  36. package/src/components/Config.tsx +289 -0
  37. package/src/components/ConsoleOAuthFlow.tsx +326 -0
  38. package/src/components/Cost.tsx +23 -0
  39. package/src/components/CostThresholdDialog.tsx +46 -0
  40. package/src/components/CustomSelect/option-map.ts +42 -0
  41. package/src/components/CustomSelect/select-option.tsx +52 -0
  42. package/src/components/CustomSelect/select.tsx +143 -0
  43. package/src/components/CustomSelect/use-select-state.ts +414 -0
  44. package/src/components/CustomSelect/use-select.ts +35 -0
  45. package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
  46. package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
  47. package/src/components/Help.tsx +215 -0
  48. package/src/components/HighlightedCode.tsx +33 -0
  49. package/src/components/InvalidConfigDialog.tsx +113 -0
  50. package/src/components/Link.tsx +32 -0
  51. package/src/components/LogSelector.tsx +86 -0
  52. package/src/components/Logo.tsx +145 -0
  53. package/src/components/MCPServerApprovalDialog.tsx +100 -0
  54. package/src/components/MCPServerDialogCopy.tsx +25 -0
  55. package/src/components/MCPServerMultiselectDialog.tsx +109 -0
  56. package/src/components/Message.tsx +219 -0
  57. package/src/components/MessageResponse.tsx +15 -0
  58. package/src/components/MessageSelector.tsx +211 -0
  59. package/src/components/ModeIndicator.tsx +88 -0
  60. package/src/components/ModelConfig.tsx +301 -0
  61. package/src/components/ModelListManager.tsx +223 -0
  62. package/src/components/ModelSelector.tsx +3208 -0
  63. package/src/components/ModelStatusDisplay.tsx +228 -0
  64. package/src/components/Onboarding.tsx +274 -0
  65. package/src/components/PressEnterToContinue.tsx +11 -0
  66. package/src/components/PromptInput.tsx +710 -0
  67. package/src/components/SentryErrorBoundary.ts +33 -0
  68. package/src/components/Spinner.tsx +129 -0
  69. package/src/components/StructuredDiff.tsx +184 -0
  70. package/src/components/TextInput.tsx +246 -0
  71. package/src/components/TokenWarning.tsx +31 -0
  72. package/src/components/ToolUseLoader.tsx +40 -0
  73. package/src/components/TrustDialog.tsx +106 -0
  74. package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
  75. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
  76. package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
  77. package/src/components/binary-feedback/utils.ts +220 -0
  78. package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
  79. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +45 -0
  80. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
  81. package/src/components/messages/AssistantTextMessage.tsx +144 -0
  82. package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
  83. package/src/components/messages/AssistantToolUseMessage.tsx +123 -0
  84. package/src/components/messages/UserBashInputMessage.tsx +28 -0
  85. package/src/components/messages/UserCommandMessage.tsx +30 -0
  86. package/src/components/messages/UserKodingInputMessage.tsx +28 -0
  87. package/src/components/messages/UserPromptMessage.tsx +35 -0
  88. package/src/components/messages/UserTextMessage.tsx +39 -0
  89. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
  90. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
  91. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
  92. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
  93. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
  94. package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
  95. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
  96. package/src/components/permissions/FallbackPermissionRequest.tsx +155 -0
  97. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
  98. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +75 -0
  99. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
  100. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +81 -0
  101. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +242 -0
  102. package/src/components/permissions/PermissionRequest.tsx +103 -0
  103. package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
  104. package/src/components/permissions/hooks.ts +44 -0
  105. package/src/components/permissions/toolUseOptions.ts +59 -0
  106. package/src/components/permissions/utils.ts +23 -0
  107. package/src/constants/betas.ts +5 -0
  108. package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
  109. package/src/constants/figures.ts +4 -0
  110. package/src/constants/keys.ts +3 -0
  111. package/src/constants/macros.ts +6 -0
  112. package/src/constants/models.ts +935 -0
  113. package/src/constants/oauth.ts +18 -0
  114. package/src/constants/product.ts +17 -0
  115. package/src/constants/prompts.ts +177 -0
  116. package/src/constants/releaseNotes.ts +7 -0
  117. package/src/context/PermissionContext.tsx +149 -0
  118. package/src/context.ts +278 -0
  119. package/src/cost-tracker.ts +84 -0
  120. package/src/entrypoints/cli.tsx +1498 -0
  121. package/src/entrypoints/mcp.ts +176 -0
  122. package/src/history.ts +25 -0
  123. package/src/hooks/useApiKeyVerification.ts +59 -0
  124. package/src/hooks/useArrowKeyHistory.ts +55 -0
  125. package/src/hooks/useCanUseTool.ts +138 -0
  126. package/src/hooks/useCancelRequest.ts +39 -0
  127. package/src/hooks/useDoublePress.ts +42 -0
  128. package/src/hooks/useExitOnCtrlCD.ts +31 -0
  129. package/src/hooks/useInterval.ts +25 -0
  130. package/src/hooks/useLogMessages.ts +16 -0
  131. package/src/hooks/useLogStartupTime.ts +12 -0
  132. package/src/hooks/useNotifyAfterTimeout.ts +65 -0
  133. package/src/hooks/usePermissionRequestLogging.ts +44 -0
  134. package/src/hooks/useSlashCommandTypeahead.ts +137 -0
  135. package/src/hooks/useTerminalSize.ts +49 -0
  136. package/src/hooks/useTextInput.ts +315 -0
  137. package/src/messages.ts +37 -0
  138. package/src/permissions.ts +268 -0
  139. package/src/query.ts +704 -0
  140. package/src/screens/ConfigureNpmPrefix.tsx +197 -0
  141. package/src/screens/Doctor.tsx +219 -0
  142. package/src/screens/LogList.tsx +68 -0
  143. package/src/screens/REPL.tsx +792 -0
  144. package/src/screens/ResumeConversation.tsx +68 -0
  145. package/src/services/browserMocks.ts +66 -0
  146. package/src/services/claude.ts +1947 -0
  147. package/src/services/customCommands.ts +683 -0
  148. package/src/services/fileFreshness.ts +377 -0
  149. package/src/services/mcpClient.ts +564 -0
  150. package/src/services/mcpServerApproval.tsx +50 -0
  151. package/src/services/notifier.ts +40 -0
  152. package/src/services/oauth.ts +357 -0
  153. package/src/services/openai.ts +796 -0
  154. package/src/services/sentry.ts +3 -0
  155. package/src/services/statsig.ts +171 -0
  156. package/src/services/statsigStorage.ts +86 -0
  157. package/src/services/systemReminder.ts +406 -0
  158. package/src/services/vcr.ts +161 -0
  159. package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
  160. package/src/tools/ArchitectTool/prompt.ts +15 -0
  161. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +505 -0
  162. package/src/tools/BashTool/BashTool.tsx +270 -0
  163. package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
  164. package/src/tools/BashTool/OutputLine.tsx +48 -0
  165. package/src/tools/BashTool/prompt.ts +174 -0
  166. package/src/tools/BashTool/utils.ts +56 -0
  167. package/src/tools/FileEditTool/FileEditTool.tsx +316 -0
  168. package/src/tools/FileEditTool/prompt.ts +51 -0
  169. package/src/tools/FileEditTool/utils.ts +58 -0
  170. package/src/tools/FileReadTool/FileReadTool.tsx +371 -0
  171. package/src/tools/FileReadTool/prompt.ts +7 -0
  172. package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
  173. package/src/tools/FileWriteTool/prompt.ts +10 -0
  174. package/src/tools/GlobTool/GlobTool.tsx +119 -0
  175. package/src/tools/GlobTool/prompt.ts +8 -0
  176. package/src/tools/GrepTool/GrepTool.tsx +147 -0
  177. package/src/tools/GrepTool/prompt.ts +11 -0
  178. package/src/tools/MCPTool/MCPTool.tsx +106 -0
  179. package/src/tools/MCPTool/prompt.ts +3 -0
  180. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
  181. package/src/tools/MemoryReadTool/prompt.ts +3 -0
  182. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
  183. package/src/tools/MemoryWriteTool/prompt.ts +3 -0
  184. package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
  185. package/src/tools/MultiEditTool/prompt.ts +45 -0
  186. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
  187. package/src/tools/NotebookEditTool/prompt.ts +3 -0
  188. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +266 -0
  189. package/src/tools/NotebookReadTool/prompt.ts +3 -0
  190. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
  191. package/src/tools/StickerRequestTool/prompt.ts +19 -0
  192. package/src/tools/TaskTool/TaskTool.tsx +382 -0
  193. package/src/tools/TaskTool/constants.ts +1 -0
  194. package/src/tools/TaskTool/prompt.ts +56 -0
  195. package/src/tools/ThinkTool/ThinkTool.tsx +56 -0
  196. package/src/tools/ThinkTool/prompt.ts +12 -0
  197. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +289 -0
  198. package/src/tools/TodoWriteTool/prompt.ts +63 -0
  199. package/src/tools/lsTool/lsTool.tsx +269 -0
  200. package/src/tools/lsTool/prompt.ts +2 -0
  201. package/src/tools.ts +63 -0
  202. package/src/types/PermissionMode.ts +120 -0
  203. package/src/types/RequestContext.ts +72 -0
  204. package/src/utils/Cursor.ts +436 -0
  205. package/src/utils/PersistentShell.ts +373 -0
  206. package/src/utils/agentStorage.ts +97 -0
  207. package/src/utils/array.ts +3 -0
  208. package/src/utils/ask.tsx +98 -0
  209. package/src/utils/auth.ts +13 -0
  210. package/src/utils/autoCompactCore.ts +223 -0
  211. package/src/utils/autoUpdater.ts +318 -0
  212. package/src/utils/betas.ts +20 -0
  213. package/src/utils/browser.ts +14 -0
  214. package/src/utils/cleanup.ts +72 -0
  215. package/src/utils/commands.ts +261 -0
  216. package/src/utils/config.ts +771 -0
  217. package/src/utils/conversationRecovery.ts +54 -0
  218. package/src/utils/debugLogger.ts +1123 -0
  219. package/src/utils/diff.ts +42 -0
  220. package/src/utils/env.ts +57 -0
  221. package/src/utils/errors.ts +21 -0
  222. package/src/utils/exampleCommands.ts +108 -0
  223. package/src/utils/execFileNoThrow.ts +51 -0
  224. package/src/utils/expertChatStorage.ts +136 -0
  225. package/src/utils/file.ts +402 -0
  226. package/src/utils/fileRecoveryCore.ts +71 -0
  227. package/src/utils/format.tsx +44 -0
  228. package/src/utils/generators.ts +62 -0
  229. package/src/utils/git.ts +92 -0
  230. package/src/utils/globalLogger.ts +77 -0
  231. package/src/utils/http.ts +10 -0
  232. package/src/utils/imagePaste.ts +38 -0
  233. package/src/utils/json.ts +13 -0
  234. package/src/utils/log.ts +382 -0
  235. package/src/utils/markdown.ts +213 -0
  236. package/src/utils/messageContextManager.ts +289 -0
  237. package/src/utils/messages.tsx +938 -0
  238. package/src/utils/model.ts +836 -0
  239. package/src/utils/permissions/filesystem.ts +118 -0
  240. package/src/utils/ripgrep.ts +167 -0
  241. package/src/utils/sessionState.ts +49 -0
  242. package/src/utils/state.ts +25 -0
  243. package/src/utils/style.ts +29 -0
  244. package/src/utils/terminal.ts +49 -0
  245. package/src/utils/theme.ts +122 -0
  246. package/src/utils/thinking.ts +144 -0
  247. package/src/utils/todoStorage.ts +431 -0
  248. package/src/utils/tokens.ts +43 -0
  249. package/src/utils/toolExecutionController.ts +163 -0
  250. package/src/utils/unaryLogging.ts +26 -0
  251. package/src/utils/user.ts +37 -0
  252. package/src/utils/validate.ts +165 -0
  253. package/cli.mjs +0 -1803
@@ -0,0 +1,357 @@
1
+ import * as crypto from 'crypto'
2
+ import * as http from 'http'
3
+ import { IncomingMessage, ServerResponse } from 'http'
4
+ import * as url from 'url'
5
+
6
+ import { OAUTH_CONFIG } from '../constants/oauth'
7
+ import { openBrowser } from '../utils/browser'
8
+ import { logEvent } from '../services/statsig'
9
+ import { logError } from '../utils/log'
10
+ import { resetAnthropicClient } from './claude'
11
+ import {
12
+ AccountInfo,
13
+ getGlobalConfig,
14
+ saveGlobalConfig,
15
+ normalizeApiKeyForConfig,
16
+ } from '../utils/config.js'
17
+
18
+ // Base64URL encoding function (RFC 4648)
19
+ function base64URLEncode(buffer: Buffer): string {
20
+ return buffer
21
+ .toString('base64')
22
+ .replace(/\+/g, '-')
23
+ .replace(/\//g, '_')
24
+ .replace(/=/g, '')
25
+ }
26
+
27
+ function generateCodeVerifier(): string {
28
+ return base64URLEncode(crypto.randomBytes(32))
29
+ }
30
+
31
+ async function generateCodeChallenge(verifier: string): Promise<string> {
32
+ const encoder = new TextEncoder()
33
+ const data = encoder.encode(verifier)
34
+ const digest = await crypto.subtle.digest('SHA-256', data)
35
+ return base64URLEncode(Buffer.from(digest))
36
+ }
37
+
38
+ type OAuthTokenExchangeResponse = {
39
+ access_token: string
40
+ account?: {
41
+ uuid: string
42
+ email_address: string
43
+ }
44
+ organization?: {
45
+ uuid: string
46
+ name: string
47
+ }
48
+ }
49
+
50
+ export type OAuthResult = {
51
+ accessToken: string
52
+ }
53
+
54
+ export class OAuthService {
55
+ private server: http.Server | null = null
56
+ private codeVerifier: string
57
+ private expectedState: string | null = null
58
+ private pendingCodePromise: {
59
+ resolve: (result: {
60
+ authorizationCode: string
61
+ useManualRedirect: boolean
62
+ }) => void
63
+ reject: (err: Error) => void
64
+ } | null = null
65
+
66
+ constructor() {
67
+ this.codeVerifier = generateCodeVerifier()
68
+ }
69
+
70
+ private generateAuthUrls(
71
+ codeChallenge: string,
72
+ state: string,
73
+ ): { autoUrl: string; manualUrl: string } {
74
+ function makeUrl(isManual: boolean): string {
75
+ const authUrl = new URL(OAUTH_CONFIG.AUTHORIZE_URL)
76
+ authUrl.searchParams.append('client_id', OAUTH_CONFIG.CLIENT_ID)
77
+ authUrl.searchParams.append('response_type', 'code')
78
+ authUrl.searchParams.append(
79
+ 'redirect_uri',
80
+ isManual
81
+ ? OAUTH_CONFIG.MANUAL_REDIRECT_URL
82
+ : `http://localhost:${OAUTH_CONFIG.REDIRECT_PORT}/callback`,
83
+ )
84
+ authUrl.searchParams.append('scope', OAUTH_CONFIG.SCOPES.join(' '))
85
+ authUrl.searchParams.append('code_challenge', codeChallenge)
86
+ authUrl.searchParams.append('code_challenge_method', 'S256')
87
+ authUrl.searchParams.append('state', state)
88
+ return authUrl.toString()
89
+ }
90
+
91
+ return {
92
+ autoUrl: makeUrl(false),
93
+ manualUrl: makeUrl(true),
94
+ }
95
+ }
96
+
97
+ async startOAuthFlow(
98
+ authURLHandler: (url: string) => Promise<void>,
99
+ ): Promise<OAuthResult> {
100
+ const codeChallenge = await generateCodeChallenge(this.codeVerifier)
101
+ const state = base64URLEncode(crypto.randomBytes(32))
102
+ this.expectedState = state
103
+ const { autoUrl, manualUrl } = this.generateAuthUrls(codeChallenge, state)
104
+
105
+ const onReady = async () => {
106
+ await authURLHandler(manualUrl)
107
+ await openBrowser(autoUrl)
108
+ }
109
+
110
+ const { authorizationCode, useManualRedirect } = await new Promise<{
111
+ authorizationCode: string
112
+ useManualRedirect: boolean
113
+ }>((resolve, reject) => {
114
+ this.pendingCodePromise = { resolve, reject }
115
+ this.startLocalServer(state, onReady)
116
+ })
117
+
118
+ // Exchange code for tokens
119
+ const {
120
+ access_token: accessToken,
121
+ account,
122
+ organization,
123
+ } = await this.exchangeCodeForTokens(
124
+ authorizationCode,
125
+ state,
126
+ useManualRedirect,
127
+ )
128
+
129
+ // Store account info
130
+ if (account) {
131
+ const accountInfo: AccountInfo = {
132
+ accountUuid: account.uuid,
133
+ emailAddress: account.email_address,
134
+ organizationUuid: organization?.uuid,
135
+ }
136
+ const config = getGlobalConfig()
137
+ config.oauthAccount = accountInfo
138
+ saveGlobalConfig(config)
139
+ }
140
+
141
+ return { accessToken }
142
+ }
143
+
144
+ private startLocalServer(state: string, onReady?: () => void): void {
145
+ if (this.server) {
146
+ this.closeServer()
147
+ }
148
+ this.server = http.createServer(
149
+ (req: IncomingMessage, res: ServerResponse) => {
150
+ const parsedUrl = url.parse(req.url || '', true)
151
+
152
+ if (parsedUrl.pathname === '/callback') {
153
+ const authorizationCode = parsedUrl.query.code as string
154
+ const returnedState = parsedUrl.query.state as string
155
+
156
+ if (!authorizationCode) {
157
+ res.writeHead(400)
158
+ res.end('Authorization code not found')
159
+ if (this.pendingCodePromise) {
160
+ this.pendingCodePromise.reject(
161
+ new Error('No authorization code received'),
162
+ )
163
+ }
164
+ return
165
+ }
166
+
167
+ if (returnedState !== state) {
168
+ res.writeHead(400)
169
+ res.end('Invalid state parameter')
170
+ if (this.pendingCodePromise) {
171
+ this.pendingCodePromise.reject(
172
+ new Error('Invalid state parameter'), // Possible CSRF attack
173
+ )
174
+ }
175
+ return
176
+ }
177
+
178
+ res.writeHead(302, {
179
+ Location: OAUTH_CONFIG.SUCCESS_URL,
180
+ })
181
+ res.end()
182
+
183
+ // Track which path the user is taking (automatic browser redirect)
184
+ logEvent('tengu_oauth_automatic_redirect', {})
185
+
186
+ this.processCallback({
187
+ authorizationCode,
188
+ state,
189
+ useManualRedirect: false,
190
+ })
191
+ } else {
192
+ res.writeHead(404)
193
+ res.end()
194
+ }
195
+ },
196
+ )
197
+
198
+ this.server.listen(OAUTH_CONFIG.REDIRECT_PORT, async () => {
199
+ onReady?.()
200
+ })
201
+
202
+ this.server.on('error', (err: Error) => {
203
+ const portError = err as NodeJS.ErrnoException
204
+ if (portError.code === 'EADDRINUSE') {
205
+ const error = new Error(
206
+ `Port ${OAUTH_CONFIG.REDIRECT_PORT} is already in use. Please ensure no other applications are using this port.`,
207
+ )
208
+ logError(error)
209
+ this.closeServer()
210
+ if (this.pendingCodePromise) {
211
+ this.pendingCodePromise.reject(error)
212
+ }
213
+ return
214
+ } else {
215
+ logError(err)
216
+ this.closeServer()
217
+ if (this.pendingCodePromise) {
218
+ this.pendingCodePromise.reject(err)
219
+ }
220
+ return
221
+ }
222
+ })
223
+ }
224
+
225
+ private async exchangeCodeForTokens(
226
+ authorizationCode: string,
227
+ state: string,
228
+ useManualRedirect: boolean = false,
229
+ ): Promise<OAuthTokenExchangeResponse> {
230
+ const requestBody = {
231
+ grant_type: 'authorization_code',
232
+ code: authorizationCode,
233
+ redirect_uri: useManualRedirect
234
+ ? OAUTH_CONFIG.MANUAL_REDIRECT_URL
235
+ : `http://localhost:${OAUTH_CONFIG.REDIRECT_PORT}/callback`,
236
+ client_id: OAUTH_CONFIG.CLIENT_ID,
237
+ code_verifier: this.codeVerifier,
238
+ state,
239
+ }
240
+
241
+ const response = await fetch(OAUTH_CONFIG.TOKEN_URL, {
242
+ method: 'POST',
243
+ headers: {
244
+ 'Content-Type': 'application/json',
245
+ },
246
+ body: JSON.stringify(requestBody),
247
+ })
248
+
249
+ if (!response.ok) {
250
+ throw new Error(`Token exchange failed: ${response.statusText}`)
251
+ }
252
+
253
+ const data = await response.json()
254
+ return data
255
+ }
256
+
257
+ processCallback({
258
+ authorizationCode,
259
+ state,
260
+ useManualRedirect,
261
+ }: {
262
+ authorizationCode: string
263
+ state: string
264
+ useManualRedirect: boolean
265
+ }): void {
266
+ this.closeServer()
267
+
268
+ if (state !== this.expectedState) {
269
+ if (this.pendingCodePromise) {
270
+ this.pendingCodePromise.reject(
271
+ new Error('Invalid state parameter'), // Possible CSRF attack
272
+ )
273
+ this.pendingCodePromise = null
274
+ }
275
+ return
276
+ }
277
+
278
+ if (this.pendingCodePromise) {
279
+ this.pendingCodePromise.resolve({ authorizationCode, useManualRedirect })
280
+ this.pendingCodePromise = null
281
+ }
282
+ }
283
+
284
+ private closeServer(): void {
285
+ if (this.server) {
286
+ this.server.close()
287
+ this.server = null
288
+ }
289
+ }
290
+ }
291
+
292
+ export async function createAndStoreApiKey(
293
+ accessToken: string,
294
+ ): Promise<string | null> {
295
+ try {
296
+ // Call create_api_key endpoint
297
+ const createApiKeyResp = await fetch(OAUTH_CONFIG.API_KEY_URL, {
298
+ method: 'POST',
299
+ headers: { Authorization: `Bearer ${accessToken}` },
300
+ })
301
+
302
+ let apiKeyData
303
+ let errorText = ''
304
+
305
+ try {
306
+ apiKeyData = await createApiKeyResp.json()
307
+ } catch (_e) {
308
+ // If response is not valid JSON, get as text for error logging
309
+ errorText = await createApiKeyResp.text()
310
+ }
311
+
312
+ logEvent('tengu_oauth_api_key', {
313
+ status: createApiKeyResp.ok ? 'success' : 'failure',
314
+ statusCode: createApiKeyResp.status.toString(),
315
+ error: createApiKeyResp.ok ? '' : errorText || JSON.stringify(apiKeyData),
316
+ })
317
+
318
+ if (createApiKeyResp.ok && apiKeyData && apiKeyData.raw_key) {
319
+ const apiKey = apiKeyData.raw_key
320
+
321
+ // Store in global config
322
+ const config = getGlobalConfig()
323
+
324
+ // Note: API key is now managed per model profile
325
+
326
+ // Add to approved list
327
+ if (!config.customApiKeyResponses) {
328
+ config.customApiKeyResponses = { approved: [], rejected: [] }
329
+ }
330
+ if (!config.customApiKeyResponses.approved) {
331
+ config.customApiKeyResponses.approved = []
332
+ }
333
+
334
+ const normalizedKey = normalizeApiKeyForConfig(apiKey)
335
+ if (!config.customApiKeyResponses.approved.includes(normalizedKey)) {
336
+ config.customApiKeyResponses.approved.push(normalizedKey)
337
+ }
338
+
339
+ // Save config
340
+ saveGlobalConfig(config)
341
+
342
+ // Reset the Anthropic client to force creation with new API key
343
+ resetAnthropicClient()
344
+
345
+ return apiKey
346
+ }
347
+
348
+ return null
349
+ } catch (error) {
350
+ logEvent('tengu_oauth_api_key', {
351
+ status: 'failure',
352
+ statusCode: 'exception',
353
+ error: error instanceof Error ? error.message : String(error),
354
+ })
355
+ throw error
356
+ }
357
+ }