import axios from 'axios'; import React, { useEffect, useState } from 'react'; import { logEvent } from 'src/services/analytics/index.js'; import { Spinner } from '../components/Spinner.js'; import { getOauthConfig } from '../constants/oauth.js'; import { useTimeout } from '../hooks/useTimeout.js'; import { Box, Text } from '../ink.js'; import { getSSLErrorHint } from '../services/api/errorUtils.js'; import { getUserAgent } from './http.js'; import { logError } from './log.js'; export interface PreflightCheckResult { success: boolean; error?: string; sslHint?: string; } async function checkEndpoints(): Promise { try { const oauthConfig = getOauthConfig(); const tokenUrl = new URL(oauthConfig.TOKEN_URL); const endpoints = [`${oauthConfig.BASE_API_URL}/api/hello`, `${tokenUrl.origin}/v1/oauth/hello`]; const checkEndpoint = async (url: string): Promise => { try { const response = await axios.get(url, { headers: { 'User-Agent': getUserAgent() } }); if (response.status !== 200) { const hostname = new URL(url).hostname; return { success: false, error: `Failed to connect to ${hostname}: Status ${response.status}` }; } return { success: true }; } catch (error) { const hostname = new URL(url).hostname; const sslHint = getSSLErrorHint(error); return { success: false, error: `Failed to connect to ${hostname}: ${error instanceof Error ? (error as ErrnoException).code || error.message : String(error)}`, sslHint: sslHint ?? undefined }; } }; const results = await Promise.all(endpoints.map(checkEndpoint)); const failedResult = results.find(result => !result.success); if (failedResult) { // Log failure to Statsig logEvent('tengu_preflight_check_failed', { isConnectivityError: false, hasErrorMessage: !!failedResult.error, isSSLError: !!failedResult.sslHint }); } return failedResult || { success: true }; } catch (error) { logError(error as Error); // Log to Statsig logEvent('tengu_preflight_check_failed', { isConnectivityError: true }); return { success: false, error: `Connectivity check error: ${error instanceof Error ? (error as ErrnoException).code || error.message : String(error)}` }; } } interface PreflightStepProps { onSuccess: () => void; } export function PreflightStep({ onSuccess }: PreflightStepProps) { const [result, setResult] = useState(null); const [isChecking, setIsChecking] = useState(true); const showSpinner = useTimeout(1000) && isChecking; useEffect(() => { checkEndpoints().then((checkResult) => { setResult(checkResult); setIsChecking(false); }); }, []); useEffect(() => { if (result?.success) { onSuccess(); } else if (result && !result.success) { logError(result.error ?? 'Preflight connectivity check failed'); onSuccess(); } }, [result, onSuccess]); let content = null; if (isChecking && showSpinner) { content = ( Checking connectivity... ); } else if (!result?.success && !isChecking) { content = ( Unable to connect to Anthropic services {result?.error} {result?.sslHint ? ( {result.sslHint} See https://code.claude.com/docs/en/network-config ) : ( Please check your internet connection and network settings. Note: Claude Code might not be available in your country. Check supported countries at{" "} https://anthropic.com/supported-countries )} ); } return ( {content} ); }