fix(ai,coding-agent): gate xai-oauth on dedicated credential source
The cross-provider env fallback (stream.ts: "xai-oauth" → XAI_OAUTH_TOKEN || XAI_API_KEY) lets an XAI_API_KEY-only setup silently satisfy the xai-oauth credential branch in resolveXAIHttpCredentials. Once the helper enters that branch it resolves baseURL under xai-oauth instead of xai, bypassing providers.xai.baseUrl overrides for image/TTS traffic. Add AuthStorage.hasNonEnvCredential — hasAuth minus the env-fallback leg — and gate the xai-oauth branch on (dedicated credential source || $env.XAI_OAUTH_TOKEN). The XAI_API_KEY borrow now falls through to the xai branch, preserving back-compat while restoring provider-level baseUrl precedence for users with a dedicated xai-oauth source. Op: correct Restores: ref:feat/xai-grok-oauth@015437534
This commit is contained in:
@@ -1280,6 +1280,25 @@ export class AuthStorage {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True iff a dedicated, non-env credential source is configured for this
|
||||
* provider — i.e. anything in the cascade EXCEPT `getEnvApiKey(provider)`.
|
||||
*
|
||||
* Mirrors `hasAuth` minus the env-fallback leg. Useful for callers that
|
||||
* need to distinguish "the user explicitly configured this provider"
|
||||
* from "an env var happens to alias this provider via the cross-provider
|
||||
* fallback map" (see e.g. `xai-oauth → XAI_OAUTH_TOKEN || XAI_API_KEY` in
|
||||
* `stream.ts`). Without that distinction, an `XAI_API_KEY`-only setup
|
||||
* silently satisfies xai-oauth and routes around `providers.xai.baseUrl`.
|
||||
*/
|
||||
hasNonEnvCredential(provider: string): boolean {
|
||||
if (this.#runtimeOverrides.has(provider)) return true;
|
||||
if (this.#configOverrides.has(provider)) return true;
|
||||
if (this.#getCredentialsForProvider(provider).length > 0) return true;
|
||||
if (this.#fallbackResolver?.(provider)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OAuth credentials are configured for a provider.
|
||||
*/
|
||||
|
||||
@@ -18,10 +18,25 @@ export function ohMyPiXAIUserAgent(): string {
|
||||
/**
|
||||
* Resolve xAI credentials for HTTP tool calls.
|
||||
*
|
||||
* Priority:
|
||||
* 1. xai-oauth (SuperGrok subscription token via AuthStorage; refresh
|
||||
* cascade runs inside ModelRegistry.getApiKeyForProvider).
|
||||
* 2. XAI_API_KEY environment variable (legacy/headless).
|
||||
* Credential priority:
|
||||
* 1. xai-oauth — only when a *dedicated* xai-oauth source exists. Composed
|
||||
* of two checks against the registry layer:
|
||||
* a. `authStorage.hasNonEnvCredential("xai-oauth")` covers stored
|
||||
* credentials (OAuth or api_key), runtime overrides (CLI
|
||||
* `--api-key` for xai-oauth), config overrides (models.yml
|
||||
* `providers.xai-oauth.apiKey`), and fallback resolvers.
|
||||
* b. `$env.XAI_OAUTH_TOKEN` covers the xai-oauth-specific env var.
|
||||
* `XAI_API_KEY` is intentionally NOT a signal here, even though the
|
||||
* env-fallback map (`stream.ts: "xai-oauth"`) lets xai-oauth borrow it
|
||||
* as a back-compat convenience: the borrow lets API-key-only setups
|
||||
* satisfy the xai-oauth branch and then resolve baseUrl under
|
||||
* xai-oauth instead of xai, silently bypassing `providers.xai.baseUrl`
|
||||
* overrides for image/TTS traffic. The gate routes the borrow case to
|
||||
* step 2 while preserving every dedicated xai-oauth path.
|
||||
* 2. xai (plain API key). Delegates to ModelRegistry.getApiKeyForProvider
|
||||
* which runs AuthStorage.getApiKey's full cascade: runtime override →
|
||||
* models.yml config override → stored api_key credential → OAuth
|
||||
* resolution → XAI_API_KEY env var → custom fallback resolver.
|
||||
*
|
||||
* Returns null when neither credential is available. Caller is responsible
|
||||
* for surfacing an actionable error message in that case.
|
||||
@@ -32,12 +47,16 @@ export function ohMyPiXAIUserAgent(): string {
|
||||
export async function resolveXAIHttpCredentials(modelRegistry: ModelRegistry): Promise<XAICredentials | null> {
|
||||
const baseURL = ($env.XAI_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
||||
|
||||
const oauthKey = await modelRegistry.getApiKeyForProvider("xai-oauth");
|
||||
if (oauthKey) {
|
||||
return { provider: "xai-oauth", apiKey: oauthKey, baseURL };
|
||||
const hasDedicatedXaiOAuth =
|
||||
modelRegistry.authStorage.hasNonEnvCredential("xai-oauth") || Boolean($env.XAI_OAUTH_TOKEN);
|
||||
if (hasDedicatedXaiOAuth) {
|
||||
const oauthKey = await modelRegistry.getApiKeyForProvider("xai-oauth");
|
||||
if (oauthKey) {
|
||||
return { provider: "xai-oauth", apiKey: oauthKey, baseURL };
|
||||
}
|
||||
}
|
||||
|
||||
const apiKey = $env.XAI_API_KEY;
|
||||
const apiKey = await modelRegistry.getApiKeyForProvider("xai");
|
||||
if (apiKey) {
|
||||
return { provider: "xai", apiKey, baseURL };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user