From 693c6ca7cacbe9f436ec325d9904ee39ef7767c7 Mon Sep 17 00:00:00 2001 From: adm-humanerd Date: Wed, 15 Jul 2026 16:40:13 +0900 Subject: [PATCH] feat: support env:VAR apiKey + fix auth.json key field - resolveValueRef(): resolves apiKey values prefixed with `env:` from process.env at runtime (normalizeAccount, resolveAccountEnv) - auth.json: write `key` instead of `access` for api auth type (buildAuthJsonEntry, overwriteAuthJsonProvider, _findAccountBySecret) - Remove console.log calls from account-manager.ts (stdout pollution) --- src/account-manager.ts | 17 ++++------------- src/auth-json.ts | 2 +- src/provider-credentials.ts | 31 ++++++++++++++++++++++++------- src/storage.ts | 6 +++--- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/account-manager.ts b/src/account-manager.ts index a72d535..c03381a 100644 --- a/src/account-manager.ts +++ b/src/account-manager.ts @@ -445,16 +445,7 @@ export class AccountManager { } const providerScope = account?.provider - // DEBUG: Log provider scope and pool status - console.log(`[DEBUG handleRateLimit] accountId=${accountId}, providerScope=${providerScope}`) - if (providerScope) { - const pool = this.providerPools[providerScope] ?? [] - console.log(`[DEBUG handleRateLimit] Pool for ${providerScope}: ${pool.length} accounts`) - for (const acc of pool) { - const state = this.state.accountStates[acc.id] - console.log(`[DEBUG handleRateLimit] - ${acc.id}: state=${state?.status}, rateLimited=${state?.rateLimitUntil}`) - } - } + // DEBUG: Log provider scope and pool status (removed console.log — TUI pollution) const next = providerScope ? this.getNextAvailableAccount(accountId, providerScope) : this.getNextAvailableAccount(accountId) @@ -707,7 +698,7 @@ export class AccountManager { const pool = this.providerPools[provider] ?? [] for (const acc of pool) { if (!acc.rawEntry) continue - const logicalToken = (acc.rawEntry as any)?.access || (acc.rawEntry as any)?.apiKey + const logicalToken = (acc.rawEntry as any)?.key || (acc.rawEntry as any)?.access || (acc.rawEntry as any)?.apiKey if (logicalToken === secret) return acc } return null @@ -740,14 +731,14 @@ export class AccountManager { if (!pool || pool.length === 0) continue const physicalEntry = authData[provider] as any - const physicalToken = physicalEntry?.access || physicalEntry?.apiKey + const physicalToken = physicalEntry?.key || physicalEntry?.access || physicalEntry?.apiKey if (!physicalToken) continue const physicalAccount = this._findAccountBySecret(provider, physicalToken) const best = this.getNextAvailableAccount(undefined, provider) if (!best || !best.rawEntry) continue - const logicalToken = (best.rawEntry as any)?.access || (best.rawEntry as any)?.apiKey + const logicalToken = (best.rawEntry as any)?.key || (best.rawEntry as any)?.access || (best.rawEntry as any)?.apiKey const isDifferent = physicalToken !== logicalToken const accState = physicalAccount ? this.state.accountStates[physicalAccount.id] : null diff --git a/src/auth-json.ts b/src/auth-json.ts index 67da6c3..933472d 100644 --- a/src/auth-json.ts +++ b/src/auth-json.ts @@ -54,6 +54,6 @@ export function buildAuthJsonEntry(account: Account): AuthJsonEntry | null { // Default: API key style return { type: "api", - access: primary, + key: primary, } as any } diff --git a/src/provider-credentials.ts b/src/provider-credentials.ts index a30dcb2..9f2367c 100644 --- a/src/provider-credentials.ts +++ b/src/provider-credentials.ts @@ -1,6 +1,14 @@ import type { Account, AccountCredentials, AuthType } from "./types.js" import { PROVIDER_ENV_VARS } from "./types.js" +export function resolveValueRef(value: string): string { + if (typeof value === "string" && value.startsWith("env:")) { + const varName = value.slice(4) + return process.env[varName] ?? value + } + return value +} + function inferDefaultEnvVar(provider: string): string { const known = PROVIDER_ENV_VARS[provider]?.[0] if (known) return known @@ -19,11 +27,12 @@ export function normalizeAccount(account: Account): { account: Account; changed: if (!account.credentials) { const env: Record = {} - if (account.apiKey && account.envVarName) { - env[account.envVarName] = account.apiKey - } else if (account.apiKey) { + const resolvedApiKey = account.apiKey ? resolveValueRef(account.apiKey) : undefined + if (resolvedApiKey && account.envVarName) { + env[account.envVarName] = resolvedApiKey + } else if (resolvedApiKey) { const inferred = inferDefaultEnvVar(account.provider) - env[inferred] = account.apiKey + env[inferred] = resolvedApiKey changed = true } @@ -47,16 +56,24 @@ export function normalizeAccount(account: Account): { account: Account; changed: return { account, changed } } +function resolveEnvVars(env: Record): Record { + const resolved: Record = {} + for (const [k, v] of Object.entries(env)) { + resolved[k] = resolveValueRef(v) + } + return resolved +} + export function resolveAccountEnv(account: Account): Record { - if (account.credentials?.env) return account.credentials.env + if (account.credentials?.env) return resolveEnvVars(account.credentials.env) if (account.apiKey && account.envVarName) { - return { [account.envVarName]: account.apiKey } + return { [account.envVarName]: resolveValueRef(account.apiKey) } } if (account.apiKey) { const inferred = inferDefaultEnvVar(account.provider) - return { [inferred]: account.apiKey } + return { [inferred]: resolveValueRef(account.apiKey) } } return {} diff --git a/src/storage.ts b/src/storage.ts index 5efff61..be4a080 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -148,11 +148,11 @@ export function overwriteAuthJsonProvider(provider: string, rawEntry: unknown): const raw = fs.readFileSync(activePath, "utf8") const parsed = parseJson(raw) if (typeof parsed === "object" && parsed !== null) { - // 🚨 PERFECT FORMAT FIX: Web UI butuh objek { type, access }, bukan string plain. + // opencode expects { type, key }, not { type, access }. if (typeof rawEntry === "string") { parsed[provider] = { - type: "api_key", - access: rawEntry + type: "api", + key: rawEntry } as any } else { parsed[provider] = rawEntry as any