feat(electrobun): auto-detect Claude models via OAuth token from CLI credentials
This commit is contained in:
parent
a4d180d382
commit
afaa2253de
1 changed files with 28 additions and 5 deletions
|
|
@ -13,18 +13,41 @@ export interface ModelInfo {
|
||||||
|
|
||||||
const TIMEOUT = 8000;
|
const TIMEOUT = 8000;
|
||||||
|
|
||||||
// Known Claude models as fallback when API key is not available
|
// Known Claude models as ultimate fallback
|
||||||
const KNOWN_CLAUDE_MODELS: ModelInfo[] = [
|
const KNOWN_CLAUDE_MODELS: ModelInfo[] = [
|
||||||
{ id: 'claude-opus-4-6', name: 'Claude Opus 4.6', provider: 'claude' },
|
{ id: 'claude-opus-4-6', name: 'Claude Opus 4.6', provider: 'claude' },
|
||||||
{ id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', provider: 'claude' },
|
{ id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', provider: 'claude' },
|
||||||
{ id: 'claude-opus-4-5-20250514', name: 'Claude Opus 4.5', provider: 'claude' },
|
{ id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5', provider: 'claude' },
|
||||||
{ id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', provider: 'claude' },
|
{ id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', provider: 'claude' },
|
||||||
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', provider: 'claude' },
|
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', provider: 'claude' },
|
||||||
{ id: 'claude-sonnet-3-7-20250219', name: 'Claude Sonnet 3.7', provider: 'claude' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to get an API key for Claude:
|
||||||
|
* 1. ANTHROPIC_API_KEY env var (explicit)
|
||||||
|
* 2. Claude CLI OAuth token from ~/.claude/.credentials.json (auto-detected)
|
||||||
|
*/
|
||||||
|
function getClaudeApiKey(): string | null {
|
||||||
|
if (process.env.ANTHROPIC_API_KEY) return process.env.ANTHROPIC_API_KEY;
|
||||||
|
try {
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const home = process.env.HOME || '/home';
|
||||||
|
const credPath = path.join(home, '.claude', '.credentials.json');
|
||||||
|
const data = JSON.parse(fs.readFileSync(credPath, 'utf8'));
|
||||||
|
const token = data?.claudeAiOauth?.accessToken;
|
||||||
|
if (token && typeof token === 'string' && token.startsWith('sk-ant-')) {
|
||||||
|
// Check if token is expired
|
||||||
|
const expiresAt = data.claudeAiOauth.expiresAt;
|
||||||
|
if (expiresAt && Date.now() > expiresAt) return null;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
} catch { /* credentials file not found or unreadable */ }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchClaudeModels(): Promise<ModelInfo[]> {
|
export async function fetchClaudeModels(): Promise<ModelInfo[]> {
|
||||||
const apiKey = process.env.ANTHROPIC_API_KEY;
|
const apiKey = getClaudeApiKey();
|
||||||
if (!apiKey) return KNOWN_CLAUDE_MODELS;
|
if (!apiKey) return KNOWN_CLAUDE_MODELS;
|
||||||
try {
|
try {
|
||||||
const res = await fetch('https://api.anthropic.com/v1/models?limit=100', {
|
const res = await fetch('https://api.anthropic.com/v1/models?limit=100', {
|
||||||
|
|
@ -38,7 +61,7 @@ export async function fetchClaudeModels(): Promise<ModelInfo[]> {
|
||||||
const data = await res.json() as { data?: Array<{ id: string; display_name?: string }> };
|
const data = await res.json() as { data?: Array<{ id: string; display_name?: string }> };
|
||||||
const live = (data.data ?? [])
|
const live = (data.data ?? [])
|
||||||
.map(m => ({ id: m.id, name: m.display_name ?? m.id, provider: 'claude' }))
|
.map(m => ({ id: m.id, name: m.display_name ?? m.id, provider: 'claude' }))
|
||||||
.sort((a, b) => a.id.localeCompare(b.id));
|
.sort((a, b) => b.id.localeCompare(a.id)); // newest first
|
||||||
return live.length > 0 ? live : KNOWN_CLAUDE_MODELS;
|
return live.length > 0 ? live : KNOWN_CLAUDE_MODELS;
|
||||||
} catch {
|
} catch {
|
||||||
return KNOWN_CLAUDE_MODELS;
|
return KNOWN_CLAUDE_MODELS;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue