mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
feat: enhance Codex authentication and API key management
- Introduced a new method to check Codex authentication status, allowing for better handling of API keys and OAuth tokens. - Updated API key management to include OpenAI, enabling users to manage their keys more effectively. - Enhanced the CodexProvider to support session ID tracking and deduplication of text blocks in assistant messages. - Improved error handling and logging in authentication routes, providing clearer feedback to users. These changes improve the overall user experience and security of the Codex integration, ensuring smoother authentication processes and better management of API keys.
This commit is contained in:
@@ -14,8 +14,15 @@ import { useNavigate } from '@tanstack/react-router';
|
||||
|
||||
export function ApiKeysSection() {
|
||||
const { apiKeys, setApiKeys } = useAppStore();
|
||||
const { claudeAuthStatus, setClaudeAuthStatus, setSetupComplete } = useSetupStore();
|
||||
const {
|
||||
claudeAuthStatus,
|
||||
setClaudeAuthStatus,
|
||||
codexAuthStatus,
|
||||
setCodexAuthStatus,
|
||||
setSetupComplete,
|
||||
} = useSetupStore();
|
||||
const [isDeletingAnthropicKey, setIsDeletingAnthropicKey] = useState(false);
|
||||
const [isDeletingOpenaiKey, setIsDeletingOpenaiKey] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { providerConfigParams, handleSave, saved } = useApiKeyManagement();
|
||||
@@ -51,6 +58,34 @@ export function ApiKeysSection() {
|
||||
}
|
||||
}, [apiKeys, setApiKeys, claudeAuthStatus, setClaudeAuthStatus]);
|
||||
|
||||
// Delete OpenAI API key
|
||||
const deleteOpenaiKey = useCallback(async () => {
|
||||
setIsDeletingOpenaiKey(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api.setup?.deleteApiKey) {
|
||||
toast.error('Delete API not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await api.setup.deleteApiKey('openai');
|
||||
if (result.success) {
|
||||
setApiKeys({ ...apiKeys, openai: '' });
|
||||
setCodexAuthStatus({
|
||||
authenticated: false,
|
||||
method: 'none',
|
||||
});
|
||||
toast.success('OpenAI API key deleted');
|
||||
} else {
|
||||
toast.error(result.error || 'Failed to delete API key');
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Failed to delete API key');
|
||||
} finally {
|
||||
setIsDeletingOpenaiKey(false);
|
||||
}
|
||||
}, [apiKeys, setApiKeys, setCodexAuthStatus]);
|
||||
|
||||
// Open setup wizard
|
||||
const openSetupWizard = useCallback(() => {
|
||||
setSetupComplete(false);
|
||||
@@ -137,6 +172,23 @@ export function ApiKeysSection() {
|
||||
Delete Anthropic Key
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{apiKeys.openai && (
|
||||
<Button
|
||||
onClick={deleteOpenaiKey}
|
||||
disabled={isDeletingOpenaiKey}
|
||||
variant="outline"
|
||||
className="h-10 border-red-500/30 text-red-500 hover:bg-red-500/10 hover:border-red-500/50"
|
||||
data-testid="delete-openai-key"
|
||||
>
|
||||
{isDeletingOpenaiKey ? (
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
Delete OpenAI Key
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@ interface TestResult {
|
||||
interface ApiKeyStatus {
|
||||
hasAnthropicKey: boolean;
|
||||
hasGoogleKey: boolean;
|
||||
hasOpenaiKey: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,16 +28,20 @@ export function useApiKeyManagement() {
|
||||
// API key values
|
||||
const [anthropicKey, setAnthropicKey] = useState(apiKeys.anthropic);
|
||||
const [googleKey, setGoogleKey] = useState(apiKeys.google);
|
||||
const [openaiKey, setOpenaiKey] = useState(apiKeys.openai);
|
||||
|
||||
// Visibility toggles
|
||||
const [showAnthropicKey, setShowAnthropicKey] = useState(false);
|
||||
const [showGoogleKey, setShowGoogleKey] = useState(false);
|
||||
const [showOpenaiKey, setShowOpenaiKey] = useState(false);
|
||||
|
||||
// Test connection states
|
||||
const [testingConnection, setTestingConnection] = useState(false);
|
||||
const [testResult, setTestResult] = useState<TestResult | null>(null);
|
||||
const [testingGeminiConnection, setTestingGeminiConnection] = useState(false);
|
||||
const [geminiTestResult, setGeminiTestResult] = useState<TestResult | null>(null);
|
||||
const [testingOpenaiConnection, setTestingOpenaiConnection] = useState(false);
|
||||
const [openaiTestResult, setOpenaiTestResult] = useState<TestResult | null>(null);
|
||||
|
||||
// API key status from environment
|
||||
const [apiKeyStatus, setApiKeyStatus] = useState<ApiKeyStatus | null>(null);
|
||||
@@ -48,6 +53,7 @@ export function useApiKeyManagement() {
|
||||
useEffect(() => {
|
||||
setAnthropicKey(apiKeys.anthropic);
|
||||
setGoogleKey(apiKeys.google);
|
||||
setOpenaiKey(apiKeys.openai);
|
||||
}, [apiKeys]);
|
||||
|
||||
// Check API key status from environment on mount
|
||||
@@ -61,6 +67,7 @@ export function useApiKeyManagement() {
|
||||
setApiKeyStatus({
|
||||
hasAnthropicKey: status.hasAnthropicKey,
|
||||
hasGoogleKey: status.hasGoogleKey,
|
||||
hasOpenaiKey: status.hasOpenaiKey,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -136,11 +143,42 @@ export function useApiKeyManagement() {
|
||||
setTestingGeminiConnection(false);
|
||||
};
|
||||
|
||||
// Test OpenAI/Codex connection
|
||||
const handleTestOpenaiConnection = async () => {
|
||||
setTestingOpenaiConnection(true);
|
||||
setOpenaiTestResult(null);
|
||||
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const data = await api.setup.verifyCodexAuth('api_key', openaiKey);
|
||||
|
||||
if (data.success && data.authenticated) {
|
||||
setOpenaiTestResult({
|
||||
success: true,
|
||||
message: 'Connection successful! Codex responded.',
|
||||
});
|
||||
} else {
|
||||
setOpenaiTestResult({
|
||||
success: false,
|
||||
message: data.error || 'Failed to connect to OpenAI API.',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
setOpenaiTestResult({
|
||||
success: false,
|
||||
message: 'Network error. Please check your connection.',
|
||||
});
|
||||
} finally {
|
||||
setTestingOpenaiConnection(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Save API keys
|
||||
const handleSave = () => {
|
||||
setApiKeys({
|
||||
anthropic: anthropicKey,
|
||||
google: googleKey,
|
||||
openai: openaiKey,
|
||||
});
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
@@ -167,6 +205,15 @@ export function useApiKeyManagement() {
|
||||
onTest: handleTestGeminiConnection,
|
||||
result: geminiTestResult,
|
||||
},
|
||||
openai: {
|
||||
value: openaiKey,
|
||||
setValue: setOpenaiKey,
|
||||
show: showOpenaiKey,
|
||||
setShow: setShowOpenaiKey,
|
||||
testing: testingOpenaiConnection,
|
||||
onTest: handleTestOpenaiConnection,
|
||||
result: openaiTestResult,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Cpu } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CodexModelId } from '@automaker/types';
|
||||
import { CODEX_MODEL_MAP } from '@automaker/types';
|
||||
import { OpenAIIcon } from '@/components/ui/provider-icon';
|
||||
|
||||
interface CodexModelConfigurationProps {
|
||||
enabledCodexModels: CodexModelId[];
|
||||
codexDefaultModel: CodexModelId;
|
||||
isSaving: boolean;
|
||||
onDefaultModelChange: (model: CodexModelId) => void;
|
||||
onModelToggle: (model: CodexModelId, enabled: boolean) => void;
|
||||
}
|
||||
|
||||
interface CodexModelInfo {
|
||||
id: CodexModelId;
|
||||
label: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const CODEX_MODEL_INFO: Record<CodexModelId, CodexModelInfo> = {
|
||||
'gpt-5.2-codex': {
|
||||
id: 'gpt-5.2-codex',
|
||||
label: 'GPT-5.2-Codex',
|
||||
description: 'Most advanced agentic coding model for complex software engineering',
|
||||
},
|
||||
'gpt-5-codex': {
|
||||
id: 'gpt-5-codex',
|
||||
label: 'GPT-5-Codex',
|
||||
description: 'Purpose-built for Codex CLI with versatile tool use',
|
||||
},
|
||||
'gpt-5-codex-mini': {
|
||||
id: 'gpt-5-codex-mini',
|
||||
label: 'GPT-5-Codex-Mini',
|
||||
description: 'Faster workflows optimized for low-latency code Q&A and editing',
|
||||
},
|
||||
'codex-1': {
|
||||
id: 'codex-1',
|
||||
label: 'Codex-1',
|
||||
description: 'Version of o3 optimized for software engineering',
|
||||
},
|
||||
'codex-mini-latest': {
|
||||
id: 'codex-mini-latest',
|
||||
label: 'Codex-Mini-Latest',
|
||||
description: 'Version of o4-mini for Codex, optimized for faster workflows',
|
||||
},
|
||||
'gpt-5': {
|
||||
id: 'gpt-5',
|
||||
label: 'GPT-5',
|
||||
description: 'GPT-5 base flagship model',
|
||||
},
|
||||
};
|
||||
|
||||
export function CodexModelConfiguration({
|
||||
enabledCodexModels,
|
||||
codexDefaultModel,
|
||||
isSaving,
|
||||
onDefaultModelChange,
|
||||
onModelToggle,
|
||||
}: CodexModelConfigurationProps) {
|
||||
const availableModels = Object.values(CODEX_MODEL_INFO);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-2xl overflow-hidden',
|
||||
'border border-border/50',
|
||||
'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
|
||||
'shadow-sm shadow-black/5'
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
||||
<OpenAIIcon className="w-5 h-5 text-brand-500" />
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">
|
||||
Model Configuration
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
Configure which Codex models are available in the feature modal
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label>Default Model</Label>
|
||||
<Select
|
||||
value={codexDefaultModel}
|
||||
onValueChange={(v) => onDefaultModelChange(v as CodexModelId)}
|
||||
disabled={isSaving}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableModels.map((model) => (
|
||||
<SelectItem key={model.id} value={model.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{model.label}</span>
|
||||
{supportsReasoningEffort(model.id) && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Thinking
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Label>Available Models</Label>
|
||||
<div className="grid gap-3">
|
||||
{availableModels.map((model) => {
|
||||
const isEnabled = enabledCodexModels.includes(model.id);
|
||||
const isDefault = model.id === codexDefaultModel;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={model.id}
|
||||
className="flex items-center justify-between p-3 rounded-xl border border-border/50 bg-card/50 hover:bg-accent/30 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
checked={isEnabled}
|
||||
onCheckedChange={(checked) => onModelToggle(model.id, !!checked)}
|
||||
disabled={isSaving || isDefault}
|
||||
/>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">{model.label}</span>
|
||||
{supportsReasoningEffort(model.id) && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Thinking
|
||||
</Badge>
|
||||
)}
|
||||
{isDefault && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Default
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{model.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getModelDisplayName(modelId: string): string {
|
||||
const displayNames: Record<string, string> = {
|
||||
'gpt-5.2-codex': 'GPT-5.2-Codex',
|
||||
'gpt-5-codex': 'GPT-5-Codex',
|
||||
'gpt-5-codex-mini': 'GPT-5-Codex-Mini',
|
||||
'codex-1': 'Codex-1',
|
||||
'codex-mini-latest': 'Codex-Mini-Latest',
|
||||
'gpt-5': 'GPT-5',
|
||||
};
|
||||
return displayNames[modelId] || modelId;
|
||||
}
|
||||
|
||||
function supportsReasoningEffort(modelId: string): boolean {
|
||||
const reasoningModels = ['gpt-5.2-codex', 'gpt-5-codex', 'gpt-5', 'codex-1'];
|
||||
return reasoningModels.includes(modelId);
|
||||
}
|
||||
@@ -1,27 +1,35 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useSetupStore } from '@/store/setup-store';
|
||||
import { CodexCliStatus } from '../cli-status/codex-cli-status';
|
||||
import { CodexSettings } from '../codex/codex-settings';
|
||||
import { CodexUsageSection } from '../codex/codex-usage-section';
|
||||
import { Info } from 'lucide-react';
|
||||
import { CodexModelConfiguration } from './codex-model-configuration';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import type { CliStatus as SharedCliStatus } from '../shared/types';
|
||||
import type { CodexModelId } from '@automaker/types';
|
||||
|
||||
const logger = createLogger('CodexSettings');
|
||||
|
||||
export function CodexSettingsTab() {
|
||||
// TODO: Add these to app-store
|
||||
const [codexAutoLoadAgents, setCodexAutoLoadAgents] = useState(false);
|
||||
const [codexSandboxMode, setCodexSandboxMode] = useState<
|
||||
'read-only' | 'workspace-write' | 'danger-full-access'
|
||||
>('read-only');
|
||||
const [codexApprovalPolicy, setCodexApprovalPolicy] = useState<
|
||||
'untrusted' | 'on-failure' | 'on-request' | 'never'
|
||||
>('untrusted');
|
||||
const [codexEnableWebSearch, setCodexEnableWebSearch] = useState(false);
|
||||
const [codexEnableImages, setCodexEnableImages] = useState(false);
|
||||
const {
|
||||
codexAutoLoadAgents,
|
||||
codexSandboxMode,
|
||||
codexApprovalPolicy,
|
||||
codexEnableWebSearch,
|
||||
codexEnableImages,
|
||||
enabledCodexModels,
|
||||
codexDefaultModel,
|
||||
setCodexAutoLoadAgents,
|
||||
setCodexSandboxMode,
|
||||
setCodexApprovalPolicy,
|
||||
setCodexEnableWebSearch,
|
||||
setCodexEnableImages,
|
||||
setEnabledCodexModels,
|
||||
setCodexDefaultModel,
|
||||
toggleCodexModel,
|
||||
} = useAppStore();
|
||||
|
||||
const {
|
||||
codexAuthStatus,
|
||||
@@ -32,8 +40,8 @@ export function CodexSettingsTab() {
|
||||
|
||||
const [isCheckingCodexCli, setIsCheckingCodexCli] = useState(false);
|
||||
const [displayCliStatus, setDisplayCliStatus] = useState<SharedCliStatus | null>(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
// Convert setup-store CliStatus to shared/types CliStatus for display
|
||||
const codexCliStatus: SharedCliStatus | null =
|
||||
displayCliStatus ||
|
||||
(setupCliStatus
|
||||
@@ -46,28 +54,28 @@ export function CodexSettingsTab() {
|
||||
}
|
||||
: null);
|
||||
|
||||
const handleRefreshCodexCli = useCallback(async () => {
|
||||
setIsCheckingCodexCli(true);
|
||||
try {
|
||||
// Load Codex CLI status on mount
|
||||
useEffect(() => {
|
||||
const checkCodexStatus = async () => {
|
||||
const api = getElectronAPI();
|
||||
if (api?.setup?.getCodexStatus) {
|
||||
const result = await api.setup.getCodexStatus();
|
||||
if (result.success) {
|
||||
// Update setup store
|
||||
try {
|
||||
const result = await api.setup.getCodexStatus();
|
||||
setDisplayCliStatus({
|
||||
success: result.success,
|
||||
status: result.installed ? 'installed' : 'not_installed',
|
||||
method: result.auth?.method,
|
||||
version: result.version,
|
||||
path: result.path,
|
||||
recommendation: result.recommendation,
|
||||
installCommands: result.installCommands,
|
||||
});
|
||||
setCodexCliStatus({
|
||||
installed: result.installed,
|
||||
version: result.version,
|
||||
path: result.path,
|
||||
method: result.auth?.method || 'none',
|
||||
});
|
||||
// Update display status
|
||||
setDisplayCliStatus({
|
||||
success: true,
|
||||
status: result.installed ? 'installed' : 'not_installed',
|
||||
method: result.auth?.method,
|
||||
version: result.version || undefined,
|
||||
path: result.path || undefined,
|
||||
});
|
||||
if (result.auth) {
|
||||
setCodexAuthStatus({
|
||||
authenticated: result.auth.authenticated,
|
||||
@@ -80,6 +88,42 @@ export function CodexSettingsTab() {
|
||||
hasApiKey: result.auth.hasApiKey,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to check Codex CLI status:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
checkCodexStatus();
|
||||
}, [setCodexCliStatus, setCodexAuthStatus]);
|
||||
|
||||
const handleRefreshCodexCli = useCallback(async () => {
|
||||
setIsCheckingCodexCli(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (api?.setup?.getCodexStatus) {
|
||||
const result = await api.setup.getCodexStatus();
|
||||
setDisplayCliStatus({
|
||||
success: result.success,
|
||||
status: result.installed ? 'installed' : 'not_installed',
|
||||
method: result.auth?.method,
|
||||
version: result.version,
|
||||
path: result.path,
|
||||
recommendation: result.recommendation,
|
||||
installCommands: result.installCommands,
|
||||
});
|
||||
setCodexCliStatus({
|
||||
installed: result.installed,
|
||||
version: result.version,
|
||||
path: result.path,
|
||||
method: result.auth?.method || 'none',
|
||||
});
|
||||
if (result.auth) {
|
||||
setCodexAuthStatus({
|
||||
authenticated: result.auth.authenticated,
|
||||
method: result.auth.method as 'cli_authenticated' | 'api_key' | 'api_key_env' | 'none',
|
||||
hasAuthFile: result.auth.method === 'cli_authenticated',
|
||||
hasApiKey: result.auth.hasApiKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -89,27 +133,50 @@ export function CodexSettingsTab() {
|
||||
}
|
||||
}, [setCodexCliStatus, setCodexAuthStatus]);
|
||||
|
||||
// Show usage tracking when CLI is authenticated
|
||||
const handleDefaultModelChange = useCallback(
|
||||
(model: CodexModelId) => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
setCodexDefaultModel(model);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
},
|
||||
[setCodexDefaultModel]
|
||||
);
|
||||
|
||||
const handleModelToggle = useCallback(
|
||||
(model: CodexModelId, enabled: boolean) => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
toggleCodexModel(model, enabled);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
},
|
||||
[toggleCodexModel]
|
||||
);
|
||||
|
||||
const showUsageTracking = codexAuthStatus?.authenticated ?? false;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Usage Info */}
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20">
|
||||
<Info className="w-5 h-5 text-emerald-400 shrink-0 mt-0.5" />
|
||||
<div className="text-sm text-emerald-400/90">
|
||||
<span className="font-medium">OpenAI via Codex CLI</span>
|
||||
<p className="text-xs text-emerald-400/70 mt-1">
|
||||
Access GPT models with tool support for advanced coding workflows.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CodexCliStatus
|
||||
status={codexCliStatus}
|
||||
isChecking={isCheckingCodexCli}
|
||||
onRefresh={handleRefreshCodexCli}
|
||||
/>
|
||||
|
||||
{showUsageTracking && <CodexUsageSection />}
|
||||
|
||||
<CodexModelConfiguration
|
||||
enabledCodexModels={enabledCodexModels}
|
||||
codexDefaultModel={codexDefaultModel}
|
||||
isSaving={isSaving}
|
||||
onDefaultModelChange={handleDefaultModelChange}
|
||||
onModelToggle={handleModelToggle}
|
||||
/>
|
||||
|
||||
<CodexSettings
|
||||
autoLoadCodexAgents={codexAutoLoadAgents}
|
||||
codexSandboxMode={codexSandboxMode}
|
||||
@@ -122,7 +189,6 @@ export function CodexSettingsTab() {
|
||||
onCodexEnableWebSearchChange={setCodexEnableWebSearch}
|
||||
onCodexEnableImagesChange={setCodexEnableImages}
|
||||
/>
|
||||
{showUsageTracking && <CodexUsageSection />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,10 @@ interface CliSetupConfig {
|
||||
buildClearedAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus;
|
||||
statusApi: () => Promise<any>;
|
||||
installApi: () => Promise<any>;
|
||||
verifyAuthApi: (method: 'cli' | 'api_key') => Promise<{
|
||||
verifyAuthApi: (
|
||||
method: 'cli' | 'api_key',
|
||||
apiKey?: string
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
authenticated: boolean;
|
||||
error?: string;
|
||||
@@ -194,7 +197,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
|
||||
setApiKeyVerificationError(null);
|
||||
|
||||
try {
|
||||
const result = await config.verifyAuthApi('api_key');
|
||||
const result = await config.verifyAuthApi('api_key', apiKey);
|
||||
|
||||
const hasLimitOrBillingError =
|
||||
result.error?.toLowerCase().includes('limit reached') ||
|
||||
|
||||
@@ -31,8 +31,8 @@ export function CodexSetupStep({ onNext, onBack, onSkip }: CodexSetupStepProps)
|
||||
);
|
||||
|
||||
const verifyAuthApi = useCallback(
|
||||
(method: 'cli' | 'api_key') =>
|
||||
getElectronAPI().setup?.verifyCodexAuth(method) || Promise.reject(),
|
||||
(method: 'cli' | 'api_key', apiKey?: string) =>
|
||||
getElectronAPI().setup?.verifyCodexAuth(method, apiKey) || Promise.reject(),
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user