Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/components/settings/PresetConnectDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ export function PresetConnectDialog({

setSaving(true);
try {
// For CLI OAuth preset, inject auth style marker into env_overrides_json
let finalEnvOverrides = isEdit ? envOverridesJson.trim() || "" : "";
if (preset.key === "cli-oauth") {
finalEnvOverrides = JSON.stringify({ __AUTH_STYLE: "cli_oauth" });
}

await onSave({
name: name.trim() || preset.name,
provider_type: preset.provider_type,
Expand All @@ -250,7 +256,7 @@ export function PresetConnectDialog({
extra_env: finalExtraEnv,
role_models_json: roleModelsJson,
headers_json: isEdit ? headersJson.trim() || "{}" : undefined,
env_overrides_json: isEdit ? envOverridesJson.trim() || "" : undefined,
env_overrides_json: finalEnvOverrides || undefined,
notes: isEdit ? notes.trim() : "",
});
onOpenChange(false);
Expand Down
13 changes: 13 additions & 0 deletions src/components/settings/provider-presets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ export interface QuickPreset {
}

export const QUICK_PRESETS: QuickPreset[] = [
// ── Claude CLI OAuth (Subscription) ──
{
key: "cli-oauth",
name: "Claude CLI (Subscription)",
description: "Use Claude CLI built-in OAuth — for Pro/Max/Team subscription accounts. Requires: claude login",
descriptionZh: "Claude CLI 内置登录 — 适用于 Pro/Max/Team 订阅账号。需先运行: claude login",
icon: <Anthropic size={18} />,
provider_type: "anthropic",
protocol: "anthropic",
base_url: "",
extra_env: "{}",
fields: [],
},
// ── Custom endpoints ──
{
key: "custom-openai",
Expand Down
16 changes: 16 additions & 0 deletions src/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Next.js instrumentation — runs once at server startup.
* Sets up a global fetch dispatcher that respects HTTP_PROXY / HTTPS_PROXY
* environment variables, so Telegram API calls (and other outbound fetch)
* work behind a proxy.
*/
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { EnvHttpProxyAgent, setGlobalDispatcher } = await import('undici');
const proxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || process.env.https_proxy || process.env.http_proxy;
if (proxy) {
setGlobalDispatcher(new EnvHttpProxyAgent());
console.log(`[instrumentation] Global fetch proxy enabled: ${proxy}`);
}
}
}
5 changes: 3 additions & 2 deletions src/lib/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ export function getClaudeCandidatePaths(): string[] {
return candidates;
}
return [
'/usr/local/bin/claude',
'/opt/homebrew/bin/claude',
path.join(home, '.npm-global', 'bin', 'claude'),
path.join(home, '.bun', 'bin', 'claude'),
path.join(home, '.local', 'bin', 'claude'),
path.join(home, '.claude', 'bin', 'claude'),
'/opt/homebrew/bin/claude',
'/usr/local/bin/claude',
];
}

Expand Down
16 changes: 16 additions & 0 deletions src/lib/provider-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type AuthStyle =
| 'api_key' // ANTHROPIC_API_KEY
| 'auth_token' // ANTHROPIC_AUTH_TOKEN
| 'env_only' // No API key; auth via extra env (bedrock/vertex)
| 'cli_oauth' // No API key; use Claude CLI's built-in OAuth (subscription accounts)
| 'custom_header'; // API key in custom header (future)

/**
Expand Down Expand Up @@ -122,6 +123,21 @@ const ANTHROPIC_DEFAULT_MODELS: CatalogModel[] = [
// ── Vendor presets ──────────────────────────────────────────────

export const VENDOR_PRESETS: VendorPreset[] = [
// ── Claude CLI OAuth (Subscription) ──
{
key: 'cli-oauth',
name: 'Claude CLI (Subscription)',
description: 'Use Claude CLI built-in OAuth — for Pro/Max/Team subscription accounts',
descriptionZh: 'Claude CLI 内置登录 — 适用于 Pro/Max/Team 订阅账号',
protocol: 'anthropic',
authStyle: 'cli_oauth',
baseUrl: '',
defaultEnvOverrides: {},
defaultModels: ANTHROPIC_DEFAULT_MODELS,
fields: [],
iconKey: 'anthropic',
},

// ── Official Anthropic ──
{
key: 'anthropic-official',
Expand Down
35 changes: 33 additions & 2 deletions src/lib/provider-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,32 @@ export function toClaudeCodeEnv(
'GEMINI_API_KEY',
]);

if (resolved.provider && resolved.hasCredentials) {
if (resolved.authStyle === 'cli_oauth') {
// CLI OAuth mode — clear all ANTHROPIC_* variables so the Claude CLI
// uses its own built-in OAuth flow (subscription account tokens in ~/.claude/).
// We must NOT inject any API key or auth token here.
for (const key of Object.keys(env)) {
if (key.startsWith('ANTHROPIC_') || MANAGED_ENV_KEYS.has(key)) {
delete env[key];
}
}

// Inject role models as env vars (if configured)
if (resolved.roleModels.default) env.ANTHROPIC_MODEL = resolved.roleModels.default;
if (resolved.roleModels.reasoning) env.ANTHROPIC_REASONING_MODEL = resolved.roleModels.reasoning;
if (resolved.roleModels.small) env.ANTHROPIC_SMALL_FAST_MODEL = resolved.roleModels.small;
if (resolved.roleModels.haiku) env.ANTHROPIC_DEFAULT_HAIKU_MODEL = resolved.roleModels.haiku;
if (resolved.roleModels.sonnet) env.ANTHROPIC_DEFAULT_SONNET_MODEL = resolved.roleModels.sonnet;
if (resolved.roleModels.opus) env.ANTHROPIC_DEFAULT_OPUS_MODEL = resolved.roleModels.opus;

// Inject env overrides (skip internal markers)
for (const [key, value] of Object.entries(resolved.envOverrides)) {
if (key.startsWith('__')) continue; // skip internal markers like __AUTH_STYLE
if (typeof value === 'string') {
if (value === '') { delete env[key]; } else { env[key] = value; }
}
}
} else if (resolved.provider && resolved.hasCredentials) {
// Clear all ANTHROPIC_* variables AND managed env vars to prevent cross-provider leaks
for (const key of Object.keys(env)) {
if (key.startsWith('ANTHROPIC_') || MANAGED_ENV_KEYS.has(key)) {
Expand Down Expand Up @@ -543,7 +568,7 @@ function buildResolution(
}

// Has credentials?
const hasCredentials = !!(provider.api_key) || authStyle === 'env_only';
const hasCredentials = !!(provider.api_key) || authStyle === 'env_only' || authStyle === 'cli_oauth';

// Settings sources — always include 'user' so SDK can load skills from
// ~/.claude/skills/. Env override conflicts are handled by envOverrides.
Expand Down Expand Up @@ -579,6 +604,12 @@ function inferProtocolFromProvider(provider: ApiProvider): Protocol {
}

function inferAuthStyleFromProvider(provider: ApiProvider): AuthStyle {
// Check env_overrides for CLI OAuth marker
try {
const overrides = JSON.parse(provider.env_overrides_json || '{}');
if (overrides.__AUTH_STYLE === 'cli_oauth') return 'cli_oauth';
} catch { /* ignore */ }

// Check preset match first
const preset = findPresetForLegacy(provider.base_url, provider.provider_type);
if (preset) return preset.authStyle;
Expand Down