feat: codex subscription support (#6600)
Signed-off-by: Adrian Cole <adrian@tetrate.io> Co-authored-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2261,6 +2261,32 @@ export type GetProviderModelsResponses = {
|
||||
|
||||
export type GetProviderModelsResponse = GetProviderModelsResponses[keyof GetProviderModelsResponses];
|
||||
|
||||
export type ConfigureProviderOauthData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Provider name
|
||||
*/
|
||||
name: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/providers/{name}/oauth';
|
||||
};
|
||||
|
||||
export type ConfigureProviderOauthErrors = {
|
||||
/**
|
||||
* OAuth configuration failed
|
||||
*/
|
||||
400: unknown;
|
||||
};
|
||||
|
||||
export type ConfigureProviderOauthResponses = {
|
||||
/**
|
||||
* OAuth configuration completed
|
||||
*/
|
||||
200: unknown;
|
||||
};
|
||||
|
||||
export type ReadConfigData = {
|
||||
body: ConfigKeyQuery;
|
||||
path?: never;
|
||||
|
||||
@@ -73,20 +73,19 @@ export default function ApiKeyTester({ onSuccess, onStartTesting }: ApiKeyTester
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="w-full p-4 sm:p-6 bg-background-muted border border-background-hover rounded-xl">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<Key className="w-4 h-4 mb-3 text-text-standard" />
|
||||
<div className="w-full p-3 sm:p-4 bg-background-muted border border-background-hover rounded-xl">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Key className="w-4 h-4 text-text-standard flex-shrink-0" />
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
||||
<h3 className="font-medium text-text-standard text-sm sm:text-base">
|
||||
Quick Setup with API Key
|
||||
</h3>
|
||||
<span className="text-text-muted text-xs sm:text-sm">
|
||||
Auto-detect your provider
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-text-muted text-sm sm:text-base mb-4">
|
||||
Enter your API key and we'll automatically detect which provider it works with.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2 items-stretch">
|
||||
<input
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useConfig } from './ConfigContext';
|
||||
import { SetupModal } from './SetupModal';
|
||||
import { startOpenRouterSetup } from '../utils/openRouterSetup';
|
||||
import { startTetrateSetup } from '../utils/tetrateSetup';
|
||||
import { startChatGptCodexSetup } from '../utils/chatgptCodexSetup';
|
||||
import WelcomeGooseLogo from './WelcomeGooseLogo';
|
||||
import { toastService } from '../toasts';
|
||||
import { OllamaSetup } from './OllamaSetup';
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
trackOnboardingSetupFailed,
|
||||
} from '../utils/analytics';
|
||||
|
||||
import { Goose, OpenRouter, Tetrate } from './icons';
|
||||
import { Goose, OpenRouter, Tetrate, ChatGPT } from './icons';
|
||||
|
||||
interface ProviderGuardProps {
|
||||
didSelectProvider: boolean;
|
||||
@@ -69,6 +70,14 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
||||
autoClose?: number;
|
||||
} | null>(null);
|
||||
|
||||
const [chatgptCodexSetupState, setChatgptCodexSetupState] = useState<{
|
||||
show: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
showRetry: boolean;
|
||||
autoClose?: number;
|
||||
} | null>(null);
|
||||
|
||||
const handleTetrateSetup = async () => {
|
||||
trackOnboardingProviderSelected('tetrate');
|
||||
try {
|
||||
@@ -97,6 +106,34 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
||||
}
|
||||
};
|
||||
|
||||
const handleChatGptCodexSetup = async () => {
|
||||
trackOnboardingProviderSelected('chatgpt_codex');
|
||||
try {
|
||||
const result = await startChatGptCodexSetup();
|
||||
if (result.success) {
|
||||
setSwitchModelProvider('chatgpt_codex');
|
||||
setShowSwitchModelModal(true);
|
||||
} else {
|
||||
trackOnboardingSetupFailed('chatgpt_codex', result.message);
|
||||
setChatgptCodexSetupState({
|
||||
show: true,
|
||||
title: 'Setup Failed',
|
||||
message: result.message,
|
||||
showRetry: true,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('ChatGPT Codex setup error:', error);
|
||||
trackOnboardingSetupFailed('chatgpt_codex', 'unexpected_error');
|
||||
setChatgptCodexSetupState({
|
||||
show: true,
|
||||
title: 'Setup Error',
|
||||
message: 'An unexpected error occurred during setup.',
|
||||
showRetry: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleApiKeySuccess = async (provider: string, _model: string, apiKey: string) => {
|
||||
trackOnboardingProviderSelected('api_key');
|
||||
const keyName = `${provider.toUpperCase()}_API_KEY`;
|
||||
@@ -163,21 +200,26 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
||||
setShowOllamaSetup(false);
|
||||
};
|
||||
|
||||
const handleRetrySetup = (setupType: 'openrouter' | 'tetrate') => {
|
||||
const handleRetrySetup = (setupType: 'openrouter' | 'tetrate' | 'chatgpt_codex') => {
|
||||
if (setupType === 'openrouter') {
|
||||
setOpenRouterSetupState(null);
|
||||
handleOpenRouterSetup();
|
||||
} else {
|
||||
} else if (setupType === 'tetrate') {
|
||||
setTetrateSetupState(null);
|
||||
handleTetrateSetup();
|
||||
} else {
|
||||
setChatgptCodexSetupState(null);
|
||||
handleChatGptCodexSetup();
|
||||
}
|
||||
};
|
||||
|
||||
const closeSetupModal = (setupType: 'openrouter' | 'tetrate') => {
|
||||
const closeSetupModal = (setupType: 'openrouter' | 'tetrate' | 'chatgpt_codex') => {
|
||||
if (setupType === 'openrouter') {
|
||||
setOpenRouterSetupState(null);
|
||||
} else {
|
||||
} else if (setupType === 'tetrate') {
|
||||
setTetrateSetupState(null);
|
||||
} else {
|
||||
setChatgptCodexSetupState(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -274,9 +316,49 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ChatGPT Subscription Card - Full Width */}
|
||||
<div className="relative w-full mb-4">
|
||||
<div className="absolute -top-2 -right-2 sm:-top-3 sm:-right-3 z-20">
|
||||
<span className="inline-block px-2 py-1 text-xs font-medium bg-blue-600 text-white rounded-full">
|
||||
Recommended if you have ChatGPT Plus/Pro
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={handleChatGptCodexSetup}
|
||||
className="w-full p-4 sm:p-6 bg-transparent border border-background-hover rounded-xl hover:border-text-muted transition-all duration-200 cursor-pointer group"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<ChatGPT className="w-5 h-5 text-text-standard" />
|
||||
<span className="font-medium text-text-standard text-sm sm:text-base">
|
||||
ChatGPT Subscription
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-text-muted group-hover:text-text-standard transition-colors">
|
||||
<svg
|
||||
className="w-4 h-4 sm:w-5 sm:h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-text-muted text-sm sm:text-base">
|
||||
Use your ChatGPT Plus/Pro subscription for GPT-5 Codex models.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tetrate Card - Full Width */}
|
||||
<div className="relative w-full mb-4">
|
||||
{/* Recommended pill */}
|
||||
<div className="absolute -top-2 -right-2 sm:-top-3 sm:-right-3 z-20">
|
||||
<span className="inline-block px-2 py-1 text-xs font-medium bg-blue-600 text-white rounded-full">
|
||||
Recommended for new users
|
||||
@@ -415,6 +497,17 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
||||
/>
|
||||
)}
|
||||
|
||||
{chatgptCodexSetupState?.show && (
|
||||
<SetupModal
|
||||
title={chatgptCodexSetupState.title}
|
||||
message={chatgptCodexSetupState.message}
|
||||
showRetry={chatgptCodexSetupState.showRetry}
|
||||
onRetry={() => handleRetrySetup('chatgpt_codex')}
|
||||
onClose={() => closeSetupModal('chatgpt_codex')}
|
||||
autoClose={chatgptCodexSetupState.autoClose}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showSwitchModelModal && (
|
||||
<SwitchModelModal
|
||||
sessionId={null}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export default function ChatGPT({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import ArrowDown from './ArrowDown';
|
||||
import ArrowUp from './ArrowUp';
|
||||
import Attach from './Attach';
|
||||
import Back from './Back';
|
||||
import ChatGPT from './ChatGPT';
|
||||
import { Bird1 } from './Bird1';
|
||||
import { Bird2 } from './Bird2';
|
||||
import { Bird3 } from './Bird3';
|
||||
@@ -59,6 +60,7 @@ export {
|
||||
ArrowUp,
|
||||
Attach,
|
||||
Back,
|
||||
ChatGPT,
|
||||
Bird1,
|
||||
Bird2,
|
||||
Bird3,
|
||||
|
||||
@@ -187,7 +187,8 @@ export const SwitchModelModal = ({
|
||||
// Load providers for manual model selection
|
||||
(async () => {
|
||||
try {
|
||||
const providersResponse = await getProviders(false);
|
||||
// Force refresh if initialProvider is set (OAuth flow needs fresh data)
|
||||
const providersResponse = await getProviders(!!initialProvider);
|
||||
const activeProviders = providersResponse.filter((provider) => provider.is_configured);
|
||||
// Create provider options and add "Use other provider" option
|
||||
setProviderOptions([
|
||||
@@ -260,7 +261,7 @@ export const SwitchModelModal = ({
|
||||
setLoadingModels(false);
|
||||
}
|
||||
})();
|
||||
}, [getProviders, getProviderModels, usePredefinedModels, read]);
|
||||
}, [getProviders, getProviderModels, usePredefinedModels, read, initialProvider]);
|
||||
|
||||
const filteredModelOptions = provider
|
||||
? modelOptions.filter((group) => group.options[0]?.provider === provider)
|
||||
|
||||
@@ -16,10 +16,24 @@ import { SecureStorageNotice } from './subcomponents/SecureStorageNotice';
|
||||
import { providerConfigSubmitHandler } from './subcomponents/handlers/DefaultSubmitHandler';
|
||||
import { useConfig } from '../../../ConfigContext';
|
||||
import { useModelAndProvider } from '../../../ModelAndProviderContext';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import { ProviderDetails, removeCustomProvider } from '../../../../api';
|
||||
import { AlertTriangle, LogIn } from 'lucide-react';
|
||||
import { ProviderDetails, removeCustomProvider, configureProviderOauth } from '../../../../api';
|
||||
import { Button } from '../../../../components/ui/button';
|
||||
|
||||
const formatErrorMessage = (error: unknown): string => {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
if (typeof error === 'string') {
|
||||
return error;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(error);
|
||||
} catch {
|
||||
return String(error);
|
||||
}
|
||||
};
|
||||
|
||||
interface ProviderConfigurationModalProps {
|
||||
provider: ProviderDetails;
|
||||
onClose: () => void;
|
||||
@@ -38,11 +52,15 @@ export default function ProviderConfigurationModal({
|
||||
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
|
||||
const [isActiveProvider, setIsActiveProvider] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isOAuthLoading, setIsOAuthLoading] = useState(false);
|
||||
|
||||
const requiredParameters = provider.metadata.config_keys.filter(
|
||||
(param) => param.required === true
|
||||
);
|
||||
|
||||
// Check if this provider uses OAuth for configuration
|
||||
const isOAuthProvider = provider.metadata.config_keys.some((key) => key.oauth_flow);
|
||||
|
||||
const isConfigured = provider.is_configured;
|
||||
const headerText = showDeleteConfirmation
|
||||
? `Delete configuration for ${provider.metadata.display_name}`
|
||||
@@ -52,7 +70,28 @@ export default function ProviderConfigurationModal({
|
||||
? isActiveProvider
|
||||
? `You cannot delete this provider while it's currently in use. Please switch to a different model first.`
|
||||
: 'This will permanently delete the current provider configuration.'
|
||||
: `Add your API key(s) for this provider to integrate into Goose`;
|
||||
: isOAuthProvider
|
||||
? `Sign in with your ${provider.metadata.display_name} account to use this provider`
|
||||
: `Add your API key(s) for this provider to integrate into goose`;
|
||||
|
||||
const handleOAuthLogin = async () => {
|
||||
setIsOAuthLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
await configureProviderOauth({
|
||||
path: { name: provider.name },
|
||||
});
|
||||
if (onConfigured) {
|
||||
onConfigured(provider);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
} catch (err) {
|
||||
setError(`OAuth login failed: ${formatErrorMessage(err)}`);
|
||||
} finally {
|
||||
setIsOAuthLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmitForm = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -91,7 +130,7 @@ export default function ProviderConfigurationModal({
|
||||
onClose();
|
||||
}
|
||||
} catch (error) {
|
||||
setError(`${error}`);
|
||||
setError(formatErrorMessage(error));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -179,38 +218,70 @@ export default function ProviderConfigurationModal({
|
||||
{/* Contains information used to set up each provider */}
|
||||
{/* Only show the form when NOT in delete confirmation mode */}
|
||||
{!showDeleteConfirmation ? (
|
||||
<>
|
||||
{/* Contains information used to set up each provider */}
|
||||
<DefaultProviderSetupForm
|
||||
configValues={configValues}
|
||||
setConfigValues={setConfigValues}
|
||||
provider={provider}
|
||||
validationErrors={validationErrors}
|
||||
/>
|
||||
isOAuthProvider ? (
|
||||
<div className="flex flex-col items-center gap-4 py-6">
|
||||
<Button
|
||||
onClick={handleOAuthLogin}
|
||||
disabled={isOAuthLoading}
|
||||
className="flex items-center gap-2 px-6 py-3"
|
||||
size="lg"
|
||||
>
|
||||
<LogIn size={20} />
|
||||
{isOAuthLoading
|
||||
? 'Signing in...'
|
||||
: `Sign in with ${provider.metadata.display_name}`}
|
||||
</Button>
|
||||
<p className="text-sm text-textSubtle text-center">
|
||||
A browser window will open for you to complete the login.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Contains information used to set up each provider */}
|
||||
<DefaultProviderSetupForm
|
||||
configValues={configValues}
|
||||
setConfigValues={setConfigValues}
|
||||
provider={provider}
|
||||
validationErrors={validationErrors}
|
||||
/>
|
||||
|
||||
{requiredParameters.length > 0 &&
|
||||
provider.metadata.config_keys &&
|
||||
provider.metadata.config_keys.length > 0 && <SecureStorageNotice />}
|
||||
</>
|
||||
{requiredParameters.length > 0 &&
|
||||
provider.metadata.config_keys &&
|
||||
provider.metadata.config_keys.length > 0 && <SecureStorageNotice />}
|
||||
</>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<ProviderSetupActions
|
||||
requiredParameters={requiredParameters}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmitForm}
|
||||
onDelete={handleDelete}
|
||||
showDeleteConfirmation={showDeleteConfirmation}
|
||||
onConfirmDelete={handleConfirmDelete}
|
||||
onCancelDelete={() => {
|
||||
setIsActiveProvider(false);
|
||||
setShowDeleteConfirmation(false);
|
||||
}}
|
||||
canDelete={isConfigured && !isActiveProvider}
|
||||
providerName={provider.metadata.display_name}
|
||||
isActiveProvider={isActiveProvider}
|
||||
/>
|
||||
{isOAuthProvider && !showDeleteConfirmation ? (
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
{isConfigured && (
|
||||
<Button variant="destructive" onClick={handleDelete}>
|
||||
Remove Configuration
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<ProviderSetupActions
|
||||
requiredParameters={requiredParameters}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmitForm}
|
||||
onDelete={handleDelete}
|
||||
showDeleteConfirmation={showDeleteConfirmation}
|
||||
onConfirmDelete={handleConfirmDelete}
|
||||
onCancelDelete={() => {
|
||||
setIsActiveProvider(false);
|
||||
setShowDeleteConfirmation(false);
|
||||
}}
|
||||
canDelete={isConfigured && !isActiveProvider}
|
||||
providerName={provider.metadata.display_name}
|
||||
isActiveProvider={isActiveProvider}
|
||||
/>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -69,7 +69,7 @@ export type AnalyticsEvent =
|
||||
| { name: 'onboarding_started'; properties: Record<string, never> }
|
||||
| {
|
||||
name: 'onboarding_provider_selected';
|
||||
properties: { method: 'api_key' | 'openrouter' | 'tetrate' | 'ollama' | 'other' };
|
||||
properties: { method: 'api_key' | 'openrouter' | 'tetrate' | 'chatgpt_codex' | 'ollama' | 'other' };
|
||||
}
|
||||
| {
|
||||
name: 'onboarding_completed';
|
||||
@@ -78,7 +78,7 @@ export type AnalyticsEvent =
|
||||
| { name: 'onboarding_abandoned'; properties: { step: string; duration_seconds?: number } }
|
||||
| {
|
||||
name: 'onboarding_setup_failed';
|
||||
properties: { provider: 'openrouter' | 'tetrate'; error_message?: string };
|
||||
properties: { provider: 'openrouter' | 'tetrate' | 'chatgpt_codex'; error_message?: string };
|
||||
}
|
||||
| {
|
||||
name: 'error_occurred';
|
||||
@@ -282,7 +282,7 @@ export function trackOnboardingStarted(): void {
|
||||
}
|
||||
|
||||
export function trackOnboardingProviderSelected(
|
||||
method: 'api_key' | 'openrouter' | 'tetrate' | 'ollama' | 'other'
|
||||
method: 'api_key' | 'openrouter' | 'tetrate' | 'chatgpt_codex' | 'ollama' | 'other'
|
||||
): void {
|
||||
trackEvent({
|
||||
name: 'onboarding_provider_selected',
|
||||
@@ -315,7 +315,7 @@ export function trackOnboardingAbandoned(step: string): void {
|
||||
}
|
||||
|
||||
export function trackOnboardingSetupFailed(
|
||||
provider: 'openrouter' | 'tetrate',
|
||||
provider: 'openrouter' | 'tetrate' | 'chatgpt_codex',
|
||||
errorMessage?: string
|
||||
): void {
|
||||
trackEvent({
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { configureProviderOauth } from '../api';
|
||||
|
||||
export async function startChatGptCodexSetup(): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await configureProviderOauth({
|
||||
path: { name: 'chatgpt_codex' },
|
||||
throwOnError: true,
|
||||
});
|
||||
return { success: true, message: 'ChatGPT Codex setup completed' };
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to start ChatGPT Codex setup: ${e}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user