fix(ai): allow explicit xai-oauth selectors with XAI_API_KEY

Keep hasAuth() dedicated so SuperGrok is not auto-selected from a paid
key. Explicit preflight uses hasResolvableAuth() so xai-oauth/grok-4.5
can still borrow XAI_API_KEY.
This commit is contained in:
Yang Yang
2026-08-09 23:12:07 -07:00
parent 72168a69ae
commit d14c028aee
5 changed files with 28 additions and 6 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
### Fixed
- Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path.
- Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. Explicit `xai-oauth/…` selectors still accept the paid key via the existing env fallback.
- Omitted unsupported `reasoning.summary` on paid xAI Responses requests (`xai/grok-4.5`), matching SuperGrok, so a thinking level no longer serializes `summary: "auto"`.
- Omitted presence/frequency penalties on all first-party xAI Responses models, including non-reasoning ids such as `xai/grok-2`.
+16 -2
View File
@@ -2680,8 +2680,10 @@ export class AuthStorage {
}
/**
* Check if any form of auth is configured for a provider.
* Unlike getApiKey(), this doesn't refresh OAuth tokens.
* Dedicated auth for default-model availability (picker / `getAvailable`).
* Unlike {@link getApiKey}, this does not refresh OAuth tokens, and unlike
* {@link hasResolvableAuth} it ignores cross-provider env aliases so
* `XAI_API_KEY` does not auto-select SuperGrok (`xai-oauth`).
*/
hasAuth(provider: string): boolean {
if (this.#runtimeOverrides.has(provider)) return true;
@@ -2692,6 +2694,18 @@ export class AuthStorage {
return false;
}
/**
* Whether a request could resolve a key for this provider, including
* cross-provider env aliases (`xai-oauth` borrowing `XAI_API_KEY`).
* Use this for explicit model preflight (`xai-oauth/grok-4.5`); use
* {@link hasAuth} for auto-availability so the default picker stays on
* paid `xai` when only `XAI_API_KEY` is set.
*/
hasResolvableAuth(provider: string): boolean {
if (this.hasAuth(provider)) return true;
return Boolean(getEnvApiKey(provider));
}
/**
* True iff a dedicated, non-env credential source is configured for this
* provider — i.e. anything in the cascade EXCEPT `getEnvApiKey(provider)`.
+3
View File
@@ -39,6 +39,9 @@ describe("xAI API login wiring", () => {
try {
expect(storage.hasAuth("xai")).toBe(true);
expect(storage.hasAuth("xai-oauth")).toBe(false);
expect(storage.hasResolvableAuth("xai")).toBe(true);
expect(storage.hasResolvableAuth("xai-oauth")).toBe(true);
expect(getEnvApiKey("xai-oauth")).toBe("xai-env-key");
expect(storage.getCredentialOrigin("xai")).toEqual({ kind: "env", envVar: "XAI_API_KEY" });
expect(storage.getCredentialOrigin("xai-oauth")).toBeUndefined();
} finally {
+1 -1
View File
@@ -9,7 +9,7 @@
- Changed the default model for SuperGrok OAuth (`xai-oauth`) from `grok-4.3` to `grok-4.5`.
- Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models.
- Replayed encrypted xAI reasoning on follow-up Responses turns for `xai` and `xai-oauth`.
- Kept automatic model selection on paid `xai/grok-4.5` when only `XAI_API_KEY` is set, instead of preferring SuperGrok `xai-oauth/grok-4.5`.
- Kept automatic model selection on paid `xai/grok-4.5` when only `XAI_API_KEY` is set, instead of preferring SuperGrok `xai-oauth/grok-4.5`. Explicit `xai-oauth/grok-4.5` still works with that paid key.
- Stopped sending presence/frequency penalties and stop sequences to xAI reasoning models such as `grok-4.5`, which reject them.
## [17.3.4] - 2026-08-14
@@ -1667,17 +1667,22 @@ export class ModelRegistry {
*
* Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as
* configured by its presence alone — the program is NOT executed — and OAuth
* tokens are NOT refreshed (`authStorage.hasAuth`). This is what keeps the
* tokens are NOT refreshed (`authStorage.hasResolvableAuth`). This is what keeps the
* model-switch pre-flight off the event loop's hot path; the real key
* (command execution + OAuth refresh) is resolved lazily per request via
* {@link ModelRegistry.resolver}.
*
* Cross-provider env aliases count here (`xai-oauth` can borrow `XAI_API_KEY`)
* so an explicit `xai-oauth/…` selector does not fail with "No API key".
* Default-model availability still uses {@link AuthStorage.hasAuth}, which
* ignores that alias so SuperGrok is not auto-selected from a paid key.
*/
hasConfiguredAuth(model: Model<Api>): boolean {
const keyConfig = this.#customProviderApiKeys.get(model.provider);
return (
isCommandConfigValue(keyConfig) ||
this.#keylessProviders.has(model.provider) ||
this.authStorage.hasAuth(model.provider)
this.authStorage.hasResolvableAuth(model.provider)
);
}