| import { useCallback, useEffect, useState } from 'react' |
| import { getIsNonInteractiveSession } from '../bootstrap/state.js' |
| import { verifyApiKey } from '../services/api/claude.js' |
| import { getGlobalConfig } from '../utils/config.js' |
| import { |
| getAnthropicApiKeyWithSource, |
| getApiKeyFromApiKeyHelper, |
| isAnthropicAuthEnabled, |
| isClaudeAISubscriber, |
| getConfiguredAuthProvider, |
| getConfiguredAuthProviderFromFile, |
| getOpenAIAuthTokens, |
| getLocalBaseUrl, |
| getLocalModelName, |
| } from '../utils/auth.js' |
| import { useAppState } from '../state/AppState.js' |
|
|
| export type VerificationStatus = |
| | 'loading' |
| | 'valid' |
| | 'invalid' |
| | 'missing' |
| | 'error' |
|
|
| export type ApiKeyVerificationResult = { |
| status: VerificationStatus |
| reverify: () => Promise<void> |
| error: Error | null |
| } |
|
|
| export function useApiKeyVerification(): ApiKeyVerificationResult { |
| const [status, setStatus] = useState<VerificationStatus>(() => { |
| const authProvider = getConfiguredAuthProviderFromFile() |
| |
| |
| if (authProvider === 'local') { |
| const baseUrl = getLocalBaseUrl() |
| const modelName = getLocalModelName() |
| if (baseUrl && modelName) { |
| return 'valid' |
| } |
| return 'missing' |
| } |
| |
| |
| if (authProvider === 'openai') { |
| const config = getGlobalConfig() |
| const hasApiKey = !!config.openAiApiKey |
| const hasAccessToken = !!config.openAiAccessToken |
| if (hasApiKey || hasAccessToken) { |
| return 'valid' |
| } |
| return 'missing' |
| } |
| |
| |
| if (authProvider === 'openrouter') { |
| const config = getGlobalConfig() |
| const hasApiKey = !!config.openRouterApiKey |
| if (hasApiKey) { |
| return 'valid' |
| } |
| return 'missing' |
| } |
|
|
| |
| |
| if (authProvider === 'opencode') { |
| return 'valid' |
| } |
|
|
| |
| if (authProvider === 'nvidia') { |
| const config = getGlobalConfig() |
| const hasApiKey = !!config.nvidiaApiKey |
| if (hasApiKey) { |
| return 'valid' |
| } |
| return 'missing' |
| } |
|
|
| |
| if (!isAnthropicAuthEnabled() || isClaudeAISubscriber()) { |
| return 'valid' |
| } |
| |
| |
| const { key, source } = getAnthropicApiKeyWithSource({ |
| skipRetrievingKeyFromApiKeyHelper: true, |
| }) |
| |
| |
| if (key || source === 'apiKeyHelper') { |
| return 'loading' |
| } |
| return 'missing' |
| }) |
| const [error, setError] = useState<Error | null>(null) |
|
|
| |
| const authVersion = useAppState(s => s.authVersion) |
|
|
| useEffect(() => { |
| |
| const reverify = async (): Promise<void> => { |
| const authProvider = getConfiguredAuthProviderFromFile() |
| console.log('[useApiKeyVerification] authVersion changed, authProvider:', authProvider) |
| |
| |
| if (authProvider === 'local') { |
| |
| const baseUrl = getLocalBaseUrl() |
| const modelName = getLocalModelName() |
| console.log('[useApiKeyVerification] Local - baseUrl:', baseUrl, 'modelName:', modelName) |
| if (baseUrl && modelName) { |
| setStatus('valid') |
| } else { |
| setStatus('missing') |
| } |
| return |
| } |
| |
| |
| if (authProvider === 'openai') { |
| const { getGlobalConfig: getConfig } = await import('../utils/config.js') |
| const freshConfig = getConfig() |
| const hasApiKey = !!freshConfig.openAiApiKey |
| const hasAccessToken = !!freshConfig.openAiAccessToken |
| if (hasApiKey || hasAccessToken) { |
| setStatus('valid') |
| } else { |
| setStatus('missing') |
| } |
| return |
| } |
| |
| |
| if (authProvider === 'openrouter') { |
| const { getGlobalConfig: getConfig } = await import('../utils/config.js') |
| const freshConfig = getConfig() |
| const hasApiKey = !!freshConfig.openRouterApiKey |
| if (hasApiKey) { |
| setStatus('valid') |
| } else { |
| setStatus('missing') |
| } |
| return |
| } |
| |
| |
| if (authProvider === 'opencode') { |
| setStatus('valid') |
| return |
| } |
|
|
| |
| if (authProvider === 'nvidia') { |
| const { getGlobalConfig: getConfig } = await import('../utils/config.js') |
| const freshConfig = getConfig() |
| const hasApiKey = !!freshConfig.nvidiaApiKey |
| if (hasApiKey) { |
| setStatus('valid') |
| } else { |
| setStatus('missing') |
| } |
| return |
| } |
|
|
| |
| if (!isAnthropicAuthEnabled() || isClaudeAISubscriber()) { |
| setStatus('valid') |
| return |
| } |
| |
| |
| await getApiKeyFromApiKeyHelper(getIsNonInteractiveSession()) |
| const { key: apiKey, source } = getAnthropicApiKeyWithSource() |
| if (!apiKey) { |
| if (source === 'apiKeyHelper') { |
| setStatus('error') |
| setError(new Error('API key helper did not return a valid key')) |
| return |
| } |
| const newStatus = 'missing' |
| setStatus(newStatus) |
| return |
| } |
|
|
| try { |
| const isValid = await verifyApiKey(apiKey, false) |
| const newStatus = isValid ? 'valid' : 'invalid' |
| setStatus(newStatus) |
| return |
| } catch (error) { |
| |
| |
| |
| setError(error as Error) |
| const newStatus = 'error' |
| setStatus(newStatus) |
| return |
| } |
| } |
|
|
| void reverify() |
| }, [authVersion]) |
|
|
| const verify = useCallback(async (): Promise<void> => { |
| const authProvider = getConfiguredAuthProviderFromFile() |
| |
| |
| if (authProvider === 'local') { |
| const baseUrl = getLocalBaseUrl() |
| const modelName = getLocalModelName() |
| if (baseUrl && modelName) { |
| setStatus('valid') |
| } else { |
| setStatus('missing') |
| } |
| return |
| } |
| |
| |
| if (authProvider === 'openai') { |
| const config = getGlobalConfig() |
| const hasApiKey = !!config.openAiApiKey |
| const hasAccessToken = !!config.openAiAccessToken |
| if (hasApiKey || hasAccessToken) { |
| setStatus('valid') |
| } else { |
| setStatus('missing') |
| } |
| return |
| } |
| |
| |
| if (authProvider === 'openrouter') { |
| const config = getGlobalConfig() |
| const hasApiKey = !!config.openRouterApiKey |
| if (hasApiKey) { |
| setStatus('valid') |
| } else { |
| setStatus('missing') |
| } |
| return |
| } |
| |
| |
| if (authProvider === 'opencode') { |
| setStatus('valid') |
| return |
| } |
|
|
| |
| if (authProvider === 'nvidia') { |
| const config = getGlobalConfig() |
| const hasApiKey = !!config.nvidiaApiKey |
| if (hasApiKey) { |
| setStatus('valid') |
| } else { |
| setStatus('missing') |
| } |
| return |
| } |
|
|
| |
| if (!isAnthropicAuthEnabled() || isClaudeAISubscriber()) { |
| setStatus('valid') |
| return |
| } |
| |
| |
| await getApiKeyFromApiKeyHelper(getIsNonInteractiveSession()) |
| const { key: apiKey, source } = getAnthropicApiKeyWithSource() |
| if (!apiKey) { |
| if (source === 'apiKeyHelper') { |
| setStatus('error') |
| setError(new Error('API key helper did not return a valid key')) |
| return |
| } |
| const newStatus = 'missing' |
| setStatus(newStatus) |
| return |
| } |
|
|
| try { |
| const isValid = await verifyApiKey(apiKey, false) |
| const newStatus = isValid ? 'valid' : 'invalid' |
| setStatus(newStatus) |
| return |
| } catch (error) { |
| |
| |
| |
| setError(error as Error) |
| const newStatus = 'error' |
| setStatus(newStatus) |
| return |
| } |
| }, []) |
|
|
| return { |
| status, |
| reverify: verify, |
| error, |
| } |
| } |
|
|