mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-16 17:16:19 +00:00
112 lines
3.1 KiB
TypeScript
112 lines
3.1 KiB
TypeScript
import { readFile } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
import spawn from 'cross-spawn';
|
|
|
|
import type { IProviderAuth } from '@/shared/interfaces.js';
|
|
import type { ProviderAuthStatus } from '@/shared/types.js';
|
|
import { readObjectRecord, readOptionalString } from '@/shared/utils.js';
|
|
|
|
type OpenCodeCredentialsStatus = {
|
|
authenticated: boolean;
|
|
email: string | null;
|
|
method: string | null;
|
|
error?: string;
|
|
};
|
|
|
|
const OPENCODE_ENV_CREDENTIAL_KEYS = [
|
|
'ANTHROPIC_API_KEY',
|
|
'OPENAI_API_KEY',
|
|
'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
'GEMINI_API_KEY',
|
|
'GROQ_API_KEY',
|
|
'OPENROUTER_API_KEY',
|
|
];
|
|
|
|
export class OpenCodeProviderAuth implements IProviderAuth {
|
|
/**
|
|
* Checks whether the OpenCode CLI is available to the server process.
|
|
*/
|
|
private checkInstalled(): boolean {
|
|
try {
|
|
const result = spawn.sync('opencode', ['--version'], { stdio: 'ignore', timeout: 5000 });
|
|
return !result.error && result.status === 0;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns OpenCode CLI installation and credential status.
|
|
*/
|
|
async getStatus(): Promise<ProviderAuthStatus> {
|
|
const installed = this.checkInstalled();
|
|
const credentials = await this.checkCredentials();
|
|
|
|
return {
|
|
installed,
|
|
provider: 'opencode',
|
|
authenticated: credentials.authenticated,
|
|
email: credentials.email,
|
|
method: credentials.method,
|
|
error: credentials.authenticated ? undefined : credentials.error || 'Not authenticated',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reads OpenCode's auth store or falls back to provider API key environment variables.
|
|
*/
|
|
private async checkCredentials(): Promise<OpenCodeCredentialsStatus> {
|
|
try {
|
|
const authPath = path.join(os.homedir(), '.local', 'share', 'opencode', 'auth.json');
|
|
const content = await readFile(authPath, 'utf8');
|
|
const auth = readObjectRecord(JSON.parse(content)) ?? {};
|
|
|
|
for (const [providerId, providerAuth] of Object.entries(auth)) {
|
|
const providerRecord = readObjectRecord(providerAuth);
|
|
if (!providerRecord) {
|
|
continue;
|
|
}
|
|
|
|
const hasCredential = Object.values(providerRecord).some(
|
|
(value) => readOptionalString(value) !== undefined || Boolean(readObjectRecord(value)),
|
|
);
|
|
if (hasCredential) {
|
|
return {
|
|
authenticated: true,
|
|
email: `${providerId} credentials`,
|
|
method: 'credentials_file',
|
|
};
|
|
}
|
|
}
|
|
} catch (error) {
|
|
const code = (error as NodeJS.ErrnoException).code;
|
|
if (code !== 'ENOENT') {
|
|
return {
|
|
authenticated: false,
|
|
email: null,
|
|
method: null,
|
|
error: error instanceof Error ? error.message : 'Failed to read OpenCode auth',
|
|
};
|
|
}
|
|
}
|
|
|
|
const envCredential = OPENCODE_ENV_CREDENTIAL_KEYS.find((key) => process.env[key]?.trim());
|
|
if (envCredential) {
|
|
return {
|
|
authenticated: true,
|
|
email: envCredential,
|
|
method: 'environment',
|
|
};
|
|
}
|
|
|
|
return {
|
|
authenticated: false,
|
|
email: null,
|
|
method: null,
|
|
error: 'OpenCode not configured',
|
|
};
|
|
}
|
|
}
|