fix(ai): do not treat XAI_API_KEY as SuperGrok availability

Paid-key-only setups were marked signed in for xai-oauth, so the shared
grok-4.5 default picker preferred SuperGrok over xai/grok-4.5.
This commit is contained in:
Yang Yang
2026-08-02 22:03:40 -07:00
parent 7a3a558895
commit ca2fa4f5f6
5 changed files with 95 additions and 2 deletions
+3
View File
@@ -148,6 +148,9 @@
### Fixed
- Fixed an issue where Ollama requests without a user-role message would fail to generate output or silently fail with a misleading error.
### 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.
## [17.2.5] - 2026-08-03
+18 -2
View File
@@ -2687,7 +2687,7 @@ export class AuthStorage {
if (this.#runtimeOverrides.has(provider)) return true;
if (this.#configOverrides.has(provider)) return true;
if (this.#getCredentialsForProvider(provider).length > 0) return true;
if (getEnvApiKey(provider)) return true;
if (this.#hasDedicatedEnvAuth(provider)) return true;
if (this.#fallbackResolver?.(provider)) return true;
return false;
}
@@ -2711,6 +2711,22 @@ export class AuthStorage {
return false;
}
/**
* Env auth that belongs to this provider, not a cross-provider alias.
*
* `getEnvApiKey("xai-oauth")` also accepts `XAI_API_KEY` so an explicit
* `xai-oauth/…` stream can still borrow the paid key. Availability and
* origin must not: otherwise an API-key-only setup marks SuperGrok as
* signed in and `pickDefaultAvailableModel` prefers `xai-oauth/grok-4.5`
* over paid `xai/grok-4.5`.
*/
#hasDedicatedEnvAuth(provider: string): boolean {
if (provider === "xai-oauth") {
return Boolean($env.XAI_OAUTH_TOKEN?.trim());
}
return Boolean(getEnvApiKey(provider));
}
/**
* Classify where a provider's auth comes from, following the same precedence
* as {@link AuthStorage.getApiKey}: runtime override → config override →
@@ -2727,7 +2743,7 @@ export class AuthStorage {
if (stored.some(credential => credential.type === "api_key" && credential.source === "login")) {
return { kind: "api_key" };
}
if (getEnvApiKey(provider)) return { kind: "env", envVar: getEnvApiKeyName(provider) };
if (this.#hasDedicatedEnvAuth(provider)) return { kind: "env", envVar: getEnvApiKeyName(provider) };
if (stored.some(credential => credential.type === "api_key")) return { kind: "api_key" };
if (this.#fallbackResolver?.(provider)) return { kind: "fallback" };
return undefined;
+43
View File
@@ -29,6 +29,49 @@ describe("xAI API login wiring", () => {
expect(getEnvApiKey("xai")).toBe("xai-env-key");
});
test("XAI_API_KEY alone does not mark SuperGrok as available", async () => {
const originalOauthToken = Bun.env.XAI_OAUTH_TOKEN;
Bun.env.XAI_API_KEY = "xai-env-key";
delete Bun.env.XAI_OAUTH_TOKEN;
const store = new SqliteAuthCredentialStore(new Database(":memory:"));
const storage = new AuthStorage(store);
await storage.reload();
try {
expect(storage.hasAuth("xai")).toBe(true);
expect(storage.hasAuth("xai-oauth")).toBe(false);
expect(storage.getCredentialOrigin("xai")).toEqual({ kind: "env", envVar: "XAI_API_KEY" });
expect(storage.getCredentialOrigin("xai-oauth")).toBeUndefined();
} finally {
if (originalOauthToken === undefined) {
delete Bun.env.XAI_OAUTH_TOKEN;
} else {
Bun.env.XAI_OAUTH_TOKEN = originalOauthToken;
}
store.close();
}
});
test("XAI_OAUTH_TOKEN marks SuperGrok available without a paid API key", async () => {
const originalOauthToken = Bun.env.XAI_OAUTH_TOKEN;
delete Bun.env.XAI_API_KEY;
Bun.env.XAI_OAUTH_TOKEN = "xai-oauth-env";
const store = new SqliteAuthCredentialStore(new Database(":memory:"));
const storage = new AuthStorage(store);
await storage.reload();
try {
expect(storage.hasAuth("xai")).toBe(false);
expect(storage.hasAuth("xai-oauth")).toBe(true);
expect(storage.getCredentialOrigin("xai-oauth")).toEqual({ kind: "env" });
} finally {
if (originalOauthToken === undefined) {
delete Bun.env.XAI_OAUTH_TOKEN;
} else {
Bun.env.XAI_OAUTH_TOKEN = originalOauthToken;
}
store.close();
}
});
test("AuthStorage.login('xai') validates against /models and stores the pasted key", async () => {
const fetchCalls: Array<{ url: string; init: RequestInit | undefined }> = [];
const fetchMock: FetchImpl = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
+1
View File
@@ -371,6 +371,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`.
## [17.2.5] - 2026-08-03
@@ -437,6 +437,36 @@ describe("pickDefaultAvailableModel", () => {
expect(result?.provider).toBe("zhipu-coding-plan");
expect(result?.id).toBe("glm-5.1");
});
test("prefers SuperGrok over paid xAI when both defaults are present", () => {
const paid = buildModel({
id: "grok-4.5",
name: "Grok 4.5",
api: "openai-responses",
provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: true,
input: ["text", "image"],
cost: { input: 2, output: 6, cacheRead: 0.3, cacheWrite: 0 },
contextWindow: 500000,
maxTokens: 500000,
});
const oauth = buildModel({
id: "grok-4.5",
name: "Grok 4.5",
api: "openai-responses",
provider: "xai-oauth",
baseUrl: "https://api.x.ai/v1",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 500000,
maxTokens: 500000,
});
expect(pickDefaultAvailableModel([paid, oauth])?.provider).toBe("xai-oauth");
expect(pickDefaultAvailableModel([paid])?.provider).toBe("xai");
});
});
describe("parseModelPattern", () => {