mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
feat: implement OpenCode authentication and provider setup
- Added OpenCode authentication status check to the OpencodeProvider class. - Introduced OpenCodeAuthStatus interface to manage authentication states. - Updated detectInstallation method to include authentication status in the response. - Created ProvidersSetupStep component to consolidate provider setup UI, including Claude, Cursor, Codex, and OpenCode. - Refactored setup view to streamline navigation and improve user experience. - Enhanced OpenCode CLI integration with updated installation paths and authentication checks. This commit enhances the setup process by allowing users to configure and authenticate multiple AI providers, improving overall functionality and user experience.
This commit is contained in:
@@ -22,7 +22,18 @@ import type {
|
|||||||
ContentBlock,
|
ContentBlock,
|
||||||
} from '@automaker/types';
|
} from '@automaker/types';
|
||||||
import { stripProviderPrefix } from '@automaker/types';
|
import { stripProviderPrefix } from '@automaker/types';
|
||||||
import { type SubprocessOptions } from '@automaker/platform';
|
import { type SubprocessOptions, getOpenCodeAuthIndicators } from '@automaker/platform';
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// OpenCode Auth Types
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export interface OpenCodeAuthStatus {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: 'api_key' | 'oauth' | 'none';
|
||||||
|
hasOAuthToken?: boolean;
|
||||||
|
hasApiKey?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// OpenCode Stream Event Types
|
// OpenCode Stream Event Types
|
||||||
@@ -583,6 +594,48 @@ export class OpencodeProvider extends CliProvider {
|
|||||||
return supportedFeatures.includes(feature);
|
return supportedFeatures.includes(feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Authentication
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check authentication status for OpenCode CLI
|
||||||
|
*
|
||||||
|
* Checks for authentication via:
|
||||||
|
* - OAuth token in auth file
|
||||||
|
* - API key in auth file
|
||||||
|
*/
|
||||||
|
async checkAuth(): Promise<OpenCodeAuthStatus> {
|
||||||
|
const authIndicators = await getOpenCodeAuthIndicators();
|
||||||
|
|
||||||
|
// Check for OAuth token
|
||||||
|
if (authIndicators.hasOAuthToken) {
|
||||||
|
return {
|
||||||
|
authenticated: true,
|
||||||
|
method: 'oauth',
|
||||||
|
hasOAuthToken: true,
|
||||||
|
hasApiKey: authIndicators.hasApiKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for API key
|
||||||
|
if (authIndicators.hasApiKey) {
|
||||||
|
return {
|
||||||
|
authenticated: true,
|
||||||
|
method: 'api_key',
|
||||||
|
hasOAuthToken: false,
|
||||||
|
hasApiKey: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
authenticated: false,
|
||||||
|
method: 'none',
|
||||||
|
hasOAuthToken: false,
|
||||||
|
hasApiKey: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Installation Detection
|
// Installation Detection
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -593,16 +646,21 @@ export class OpencodeProvider extends CliProvider {
|
|||||||
* Checks if the opencode CLI is available either through:
|
* Checks if the opencode CLI is available either through:
|
||||||
* - Direct installation (npm global)
|
* - Direct installation (npm global)
|
||||||
* - NPX (fallback on Windows)
|
* - NPX (fallback on Windows)
|
||||||
|
* Also checks authentication status.
|
||||||
*/
|
*/
|
||||||
async detectInstallation(): Promise<InstallationStatus> {
|
async detectInstallation(): Promise<InstallationStatus> {
|
||||||
this.ensureCliDetected();
|
this.ensureCliDetected();
|
||||||
|
|
||||||
const installed = await this.isInstalled();
|
const installed = await this.isInstalled();
|
||||||
|
const auth = await this.checkAuth();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
installed,
|
installed,
|
||||||
path: this.cliPath || undefined,
|
path: this.cliPath || undefined,
|
||||||
method: this.detectedStrategy === 'npx' ? 'npm' : 'cli',
|
method: this.detectedStrategy === 'npx' ? 'npm' : 'cli',
|
||||||
|
authenticated: auth.authenticated,
|
||||||
|
hasApiKey: auth.hasApiKey,
|
||||||
|
hasOAuthToken: auth.hasOAuthToken,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { getErrorMessage, logError } from '../common.js';
|
|||||||
*/
|
*/
|
||||||
export function createOpencodeStatusHandler() {
|
export function createOpencodeStatusHandler() {
|
||||||
const installCommand = 'curl -fsSL https://opencode.ai/install | bash';
|
const installCommand = 'curl -fsSL https://opencode.ai/install | bash';
|
||||||
const loginCommand = 'opencode auth';
|
const loginCommand = 'opencode auth login';
|
||||||
|
|
||||||
return async (_req: Request, res: Response): Promise<void> => {
|
return async (_req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
@@ -35,11 +35,13 @@ export function createOpencodeStatusHandler() {
|
|||||||
method: authMethod,
|
method: authMethod,
|
||||||
hasApiKey: status.hasApiKey || false,
|
hasApiKey: status.hasApiKey || false,
|
||||||
hasEnvApiKey: !!process.env.ANTHROPIC_API_KEY || !!process.env.OPENAI_API_KEY,
|
hasEnvApiKey: !!process.env.ANTHROPIC_API_KEY || !!process.env.OPENAI_API_KEY,
|
||||||
hasOAuthToken: false, // OpenCode doesn't use OAuth
|
hasOAuthToken: status.hasOAuthToken || false,
|
||||||
},
|
},
|
||||||
recommendation: status.installed
|
recommendation: status.installed
|
||||||
? undefined
|
? undefined
|
||||||
: 'Install OpenCode CLI to use multi-provider AI models.',
|
: 'Install OpenCode CLI to use multi-provider AI models.',
|
||||||
|
installCommand,
|
||||||
|
loginCommand,
|
||||||
installCommands: {
|
installCommands: {
|
||||||
macos: installCommand,
|
macos: installCommand,
|
||||||
linux: installCommand,
|
linux: installCommand,
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ import {
|
|||||||
WelcomeStep,
|
WelcomeStep,
|
||||||
ThemeStep,
|
ThemeStep,
|
||||||
CompleteStep,
|
CompleteStep,
|
||||||
ClaudeSetupStep,
|
ProvidersSetupStep,
|
||||||
CursorSetupStep,
|
|
||||||
CodexSetupStep,
|
|
||||||
OpencodeSetupStep,
|
|
||||||
GitHubSetupStep,
|
GitHubSetupStep,
|
||||||
} from './setup-view/steps';
|
} from './setup-view/steps';
|
||||||
import { useNavigate } from '@tanstack/react-router';
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
@@ -17,30 +14,31 @@ const logger = createLogger('SetupView');
|
|||||||
|
|
||||||
// Main Setup View
|
// Main Setup View
|
||||||
export function SetupView() {
|
export function SetupView() {
|
||||||
const { currentStep, setCurrentStep, completeSetup, setSkipClaudeSetup } = useSetupStore();
|
const { currentStep, setCurrentStep, completeSetup } = useSetupStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const steps = [
|
// Simplified steps: welcome, theme, providers (combined), github, complete
|
||||||
'welcome',
|
const steps = ['welcome', 'theme', 'providers', 'github', 'complete'] as const;
|
||||||
'theme',
|
|
||||||
'claude',
|
|
||||||
'cursor',
|
|
||||||
'codex',
|
|
||||||
'opencode',
|
|
||||||
'github',
|
|
||||||
'complete',
|
|
||||||
] as const;
|
|
||||||
type StepName = (typeof steps)[number];
|
type StepName = (typeof steps)[number];
|
||||||
|
|
||||||
const getStepName = (): StepName => {
|
const getStepName = (): StepName => {
|
||||||
if (currentStep === 'claude_detect' || currentStep === 'claude_auth') return 'claude';
|
// Map old step names to new consolidated steps
|
||||||
if (currentStep === 'welcome') return 'welcome';
|
if (currentStep === 'welcome') return 'welcome';
|
||||||
if (currentStep === 'theme') return 'theme';
|
if (currentStep === 'theme') return 'theme';
|
||||||
if (currentStep === 'cursor') return 'cursor';
|
if (
|
||||||
if (currentStep === 'codex') return 'codex';
|
currentStep === 'claude_detect' ||
|
||||||
if (currentStep === 'opencode') return 'opencode';
|
currentStep === 'claude_auth' ||
|
||||||
|
currentStep === 'cursor' ||
|
||||||
|
currentStep === 'codex' ||
|
||||||
|
currentStep === 'opencode' ||
|
||||||
|
currentStep === 'providers'
|
||||||
|
) {
|
||||||
|
return 'providers';
|
||||||
|
}
|
||||||
if (currentStep === 'github') return 'github';
|
if (currentStep === 'github') return 'github';
|
||||||
return 'complete';
|
return 'complete';
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentIndex = steps.indexOf(getStepName());
|
const currentIndex = steps.indexOf(getStepName());
|
||||||
|
|
||||||
const handleNext = (from: string) => {
|
const handleNext = (from: string) => {
|
||||||
@@ -51,22 +49,10 @@ export function SetupView() {
|
|||||||
setCurrentStep('theme');
|
setCurrentStep('theme');
|
||||||
break;
|
break;
|
||||||
case 'theme':
|
case 'theme':
|
||||||
logger.debug('[Setup Flow] Moving to claude_detect step');
|
logger.debug('[Setup Flow] Moving to providers step');
|
||||||
setCurrentStep('claude_detect');
|
setCurrentStep('providers');
|
||||||
break;
|
break;
|
||||||
case 'claude':
|
case 'providers':
|
||||||
logger.debug('[Setup Flow] Moving to cursor step');
|
|
||||||
setCurrentStep('cursor');
|
|
||||||
break;
|
|
||||||
case 'cursor':
|
|
||||||
logger.debug('[Setup Flow] Moving to codex step');
|
|
||||||
setCurrentStep('codex');
|
|
||||||
break;
|
|
||||||
case 'codex':
|
|
||||||
logger.debug('[Setup Flow] Moving to opencode step');
|
|
||||||
setCurrentStep('opencode');
|
|
||||||
break;
|
|
||||||
case 'opencode':
|
|
||||||
logger.debug('[Setup Flow] Moving to github step');
|
logger.debug('[Setup Flow] Moving to github step');
|
||||||
setCurrentStep('github');
|
setCurrentStep('github');
|
||||||
break;
|
break;
|
||||||
@@ -83,45 +69,15 @@ export function SetupView() {
|
|||||||
case 'theme':
|
case 'theme':
|
||||||
setCurrentStep('welcome');
|
setCurrentStep('welcome');
|
||||||
break;
|
break;
|
||||||
case 'claude':
|
case 'providers':
|
||||||
setCurrentStep('theme');
|
setCurrentStep('theme');
|
||||||
break;
|
break;
|
||||||
case 'cursor':
|
|
||||||
setCurrentStep('claude_detect');
|
|
||||||
break;
|
|
||||||
case 'codex':
|
|
||||||
setCurrentStep('cursor');
|
|
||||||
break;
|
|
||||||
case 'opencode':
|
|
||||||
setCurrentStep('codex');
|
|
||||||
break;
|
|
||||||
case 'github':
|
case 'github':
|
||||||
setCurrentStep('opencode');
|
setCurrentStep('providers');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSkipClaude = () => {
|
|
||||||
logger.debug('[Setup Flow] Skipping Claude setup');
|
|
||||||
setSkipClaudeSetup(true);
|
|
||||||
setCurrentStep('cursor');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSkipCursor = () => {
|
|
||||||
logger.debug('[Setup Flow] Skipping Cursor setup');
|
|
||||||
setCurrentStep('codex');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSkipCodex = () => {
|
|
||||||
logger.debug('[Setup Flow] Skipping Codex setup');
|
|
||||||
setCurrentStep('opencode');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSkipOpencode = () => {
|
|
||||||
logger.debug('[Setup Flow] Skipping OpenCode setup');
|
|
||||||
setCurrentStep('github');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSkipGithub = () => {
|
const handleSkipGithub = () => {
|
||||||
logger.debug('[Setup Flow] Skipping GitHub setup');
|
logger.debug('[Setup Flow] Skipping GitHub setup');
|
||||||
setCurrentStep('complete');
|
setCurrentStep('complete');
|
||||||
@@ -160,35 +116,15 @@ export function SetupView() {
|
|||||||
<ThemeStep onNext={() => handleNext('theme')} onBack={() => handleBack('theme')} />
|
<ThemeStep onNext={() => handleNext('theme')} onBack={() => handleBack('theme')} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(currentStep === 'claude_detect' || currentStep === 'claude_auth') && (
|
{(currentStep === 'providers' ||
|
||||||
<ClaudeSetupStep
|
currentStep === 'claude_detect' ||
|
||||||
onNext={() => handleNext('claude')}
|
currentStep === 'claude_auth' ||
|
||||||
onBack={() => handleBack('claude')}
|
currentStep === 'cursor' ||
|
||||||
onSkip={handleSkipClaude}
|
currentStep === 'codex' ||
|
||||||
/>
|
currentStep === 'opencode') && (
|
||||||
)}
|
<ProvidersSetupStep
|
||||||
|
onNext={() => handleNext('providers')}
|
||||||
{currentStep === 'cursor' && (
|
onBack={() => handleBack('providers')}
|
||||||
<CursorSetupStep
|
|
||||||
onNext={() => handleNext('cursor')}
|
|
||||||
onBack={() => handleBack('cursor')}
|
|
||||||
onSkip={handleSkipCursor}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentStep === 'codex' && (
|
|
||||||
<CodexSetupStep
|
|
||||||
onNext={() => handleNext('codex')}
|
|
||||||
onBack={() => handleBack('codex')}
|
|
||||||
onSkip={handleSkipCodex}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentStep === 'opencode' && (
|
|
||||||
<OpencodeSetupStep
|
|
||||||
onNext={() => handleNext('opencode')}
|
|
||||||
onBack={() => handleBack('opencode')}
|
|
||||||
onSkip={handleSkipOpencode}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ interface ClaudeSetupStepProps {
|
|||||||
onSkip: () => void;
|
onSkip: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ClaudeSetupContentProps {
|
||||||
|
/** Hide header and navigation for embedded use */
|
||||||
|
embedded?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
type VerificationStatus = 'idle' | 'verifying' | 'verified' | 'error';
|
type VerificationStatus = 'idle' | 'verifying' | 'verified' | 'error';
|
||||||
|
|
||||||
// Claude Setup Step
|
// Claude Setup Step
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
export { WelcomeStep } from './welcome-step';
|
export { WelcomeStep } from './welcome-step';
|
||||||
export { ThemeStep } from './theme-step';
|
export { ThemeStep } from './theme-step';
|
||||||
export { CompleteStep } from './complete-step';
|
export { CompleteStep } from './complete-step';
|
||||||
|
export { ProvidersSetupStep } from './providers-setup-step';
|
||||||
|
export { GitHubSetupStep } from './github-setup-step';
|
||||||
|
|
||||||
|
// Legacy individual step exports (kept for backwards compatibility)
|
||||||
export { ClaudeSetupStep } from './claude-setup-step';
|
export { ClaudeSetupStep } from './claude-setup-step';
|
||||||
export { CursorSetupStep } from './cursor-setup-step';
|
export { CursorSetupStep } from './cursor-setup-step';
|
||||||
export { CodexSetupStep } from './codex-setup-step';
|
export { CodexSetupStep } from './codex-setup-step';
|
||||||
export { OpencodeSetupStep } from './opencode-setup-step';
|
export { OpencodeSetupStep } from './opencode-setup-step';
|
||||||
export { GitHubSetupStep } from './github-setup-step';
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Copy login command to clipboard and show instructions
|
// Copy login command to clipboard and show instructions
|
||||||
const loginCommand = opencodeCliStatus?.loginCommand || 'opencode login';
|
const loginCommand = opencodeCliStatus?.loginCommand || 'opencode auth login';
|
||||||
await navigator.clipboard.writeText(loginCommand);
|
await navigator.clipboard.writeText(loginCommand);
|
||||||
toast.info('Login command copied! Paste in terminal to authenticate.');
|
toast.info('Login command copied! Paste in terminal to authenticate.');
|
||||||
|
|
||||||
@@ -297,13 +297,13 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP
|
|||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground">
|
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground">
|
||||||
{opencodeCliStatus?.loginCommand || 'opencode login'}
|
{opencodeCliStatus?.loginCommand || 'opencode auth login'}
|
||||||
</code>
|
</code>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
copyCommand(opencodeCliStatus?.loginCommand || 'opencode login')
|
copyCommand(opencodeCliStatus?.loginCommand || 'opencode auth login')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Copy className="w-4 h-4" />
|
<Copy className="w-4 h-4" />
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -113,6 +113,7 @@ export interface InstallProgress {
|
|||||||
export type SetupStep =
|
export type SetupStep =
|
||||||
| 'welcome'
|
| 'welcome'
|
||||||
| 'theme'
|
| 'theme'
|
||||||
|
| 'providers'
|
||||||
| 'claude_detect'
|
| 'claude_detect'
|
||||||
| 'claude_auth'
|
| 'claude_auth'
|
||||||
| 'cursor'
|
| 'cursor'
|
||||||
|
|||||||
@@ -1019,7 +1019,7 @@ export async function getCodexAuthIndicators(): Promise<CodexAuthIndicators> {
|
|||||||
// OpenCode CLI Detection
|
// OpenCode CLI Detection
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
const OPENCODE_CONFIG_DIR_NAME = '.opencode';
|
const OPENCODE_DATA_DIR = '.local/share/opencode';
|
||||||
const OPENCODE_AUTH_FILENAME = 'auth.json';
|
const OPENCODE_AUTH_FILENAME = 'auth.json';
|
||||||
const OPENCODE_TOKENS_KEY = 'tokens';
|
const OPENCODE_TOKENS_KEY = 'tokens';
|
||||||
|
|
||||||
@@ -1092,10 +1092,12 @@ export function getOpenCodeCliPaths(): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the OpenCode configuration directory path
|
* Get the OpenCode data directory path
|
||||||
|
* macOS/Linux: ~/.local/share/opencode
|
||||||
|
* Windows: %USERPROFILE%\.local\share\opencode
|
||||||
*/
|
*/
|
||||||
export function getOpenCodeConfigDir(): string {
|
export function getOpenCodeConfigDir(): string {
|
||||||
return path.join(os.homedir(), OPENCODE_CONFIG_DIR_NAME);
|
return path.join(os.homedir(), OPENCODE_DATA_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1121,6 +1123,9 @@ export interface OpenCodeAuthIndicators {
|
|||||||
const OPENCODE_OAUTH_KEYS = ['access_token', 'oauth_token'] as const;
|
const OPENCODE_OAUTH_KEYS = ['access_token', 'oauth_token'] as const;
|
||||||
const OPENCODE_API_KEY_KEYS = ['api_key', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY'] as const;
|
const OPENCODE_API_KEY_KEYS = ['api_key', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY'] as const;
|
||||||
|
|
||||||
|
// Provider names that OpenCode uses for provider-specific auth entries
|
||||||
|
const OPENCODE_PROVIDERS = ['anthropic', 'openai', 'google', 'bedrock', 'amazon-bedrock'] as const;
|
||||||
|
|
||||||
function getOpenCodeNestedTokens(record: Record<string, unknown>): Record<string, unknown> | null {
|
function getOpenCodeNestedTokens(record: Record<string, unknown>): Record<string, unknown> | null {
|
||||||
const tokens = record[OPENCODE_TOKENS_KEY];
|
const tokens = record[OPENCODE_TOKENS_KEY];
|
||||||
if (tokens && typeof tokens === 'object' && !Array.isArray(tokens)) {
|
if (tokens && typeof tokens === 'object' && !Array.isArray(tokens)) {
|
||||||
@@ -1129,6 +1134,49 @@ function getOpenCodeNestedTokens(record: Record<string, unknown>): Record<string
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the auth JSON has provider-specific OAuth credentials
|
||||||
|
* OpenCode stores auth in format: { "anthropic": { "type": "oauth", "access": "...", "refresh": "..." } }
|
||||||
|
*/
|
||||||
|
function hasProviderOAuth(authJson: Record<string, unknown>): boolean {
|
||||||
|
for (const provider of OPENCODE_PROVIDERS) {
|
||||||
|
const providerAuth = authJson[provider];
|
||||||
|
if (providerAuth && typeof providerAuth === 'object' && !Array.isArray(providerAuth)) {
|
||||||
|
const auth = providerAuth as Record<string, unknown>;
|
||||||
|
// Check for OAuth type with access token
|
||||||
|
if (auth.type === 'oauth' && typeof auth.access === 'string' && auth.access) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Also check for access_token field directly
|
||||||
|
if (typeof auth.access_token === 'string' && auth.access_token) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the auth JSON has provider-specific API key credentials
|
||||||
|
*/
|
||||||
|
function hasProviderApiKey(authJson: Record<string, unknown>): boolean {
|
||||||
|
for (const provider of OPENCODE_PROVIDERS) {
|
||||||
|
const providerAuth = authJson[provider];
|
||||||
|
if (providerAuth && typeof providerAuth === 'object' && !Array.isArray(providerAuth)) {
|
||||||
|
const auth = providerAuth as Record<string, unknown>;
|
||||||
|
// Check for API key type
|
||||||
|
if (auth.type === 'api_key' && typeof auth.key === 'string' && auth.key) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Also check for api_key field directly
|
||||||
|
if (typeof auth.api_key === 'string' && auth.api_key) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get OpenCode authentication status by checking auth file indicators
|
* Get OpenCode authentication status by checking auth file indicators
|
||||||
*/
|
*/
|
||||||
@@ -1145,8 +1193,12 @@ export async function getOpenCodeAuthIndicators(): Promise<OpenCodeAuthIndicator
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const authJson = JSON.parse(authContent) as Record<string, unknown>;
|
const authJson = JSON.parse(authContent) as Record<string, unknown>;
|
||||||
|
|
||||||
|
// Check for legacy top-level keys
|
||||||
result.hasOAuthToken = hasNonEmptyStringField(authJson, OPENCODE_OAUTH_KEYS);
|
result.hasOAuthToken = hasNonEmptyStringField(authJson, OPENCODE_OAUTH_KEYS);
|
||||||
result.hasApiKey = hasNonEmptyStringField(authJson, OPENCODE_API_KEY_KEYS);
|
result.hasApiKey = hasNonEmptyStringField(authJson, OPENCODE_API_KEY_KEYS);
|
||||||
|
|
||||||
|
// Check for nested tokens object (legacy format)
|
||||||
const nestedTokens = getOpenCodeNestedTokens(authJson);
|
const nestedTokens = getOpenCodeNestedTokens(authJson);
|
||||||
if (nestedTokens) {
|
if (nestedTokens) {
|
||||||
result.hasOAuthToken =
|
result.hasOAuthToken =
|
||||||
@@ -1154,6 +1206,11 @@ export async function getOpenCodeAuthIndicators(): Promise<OpenCodeAuthIndicator
|
|||||||
result.hasApiKey =
|
result.hasApiKey =
|
||||||
result.hasApiKey || hasNonEmptyStringField(nestedTokens, OPENCODE_API_KEY_KEYS);
|
result.hasApiKey || hasNonEmptyStringField(nestedTokens, OPENCODE_API_KEY_KEYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for provider-specific auth entries (current OpenCode format)
|
||||||
|
// Format: { "anthropic": { "type": "oauth", "access": "...", "refresh": "..." } }
|
||||||
|
result.hasOAuthToken = result.hasOAuthToken || hasProviderOAuth(authJson);
|
||||||
|
result.hasApiKey = result.hasApiKey || hasProviderApiKey(authJson);
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore parse errors; file exists but contents are unreadable
|
// Ignore parse errors; file exists but contents are unreadable
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user