GigaChat: reset Basic base URLs on OAuth reauth

This commit is contained in:
Alexander Davydov 2026-03-19 23:51:21 +03:00
parent be72c4f011
commit c46ec2ebf4
4 changed files with 147 additions and 2 deletions

View File

@ -1,3 +1,4 @@
import { loadAuthProfileStoreForSecretsRuntime } from "../agents/auth-profiles.js";
import { resolveGigachatAuthMode } from "../agents/gigachat-auth.js";
import { resolveManifestProviderApiKeyChoice } from "../plugins/provider-auth-choices.js";
import { ensureApiKeyFromOptionEnvOrPrompt } from "./auth-choice.apply-helpers.js";
@ -15,6 +16,7 @@ import {
GIGACHAT_DEFAULT_MODEL_REF,
setGigachatApiKey,
} from "./onboard-auth.js";
import { GIGACHAT_BASE_URL } from "./onboard-auth.models.js";
import type { AuthChoice } from "./onboard-types.js";
const CORE_API_KEY_TOKEN_PROVIDER_AUTH_CHOICES: Partial<Record<string, AuthChoice>> = {
@ -22,6 +24,15 @@ const CORE_API_KEY_TOKEN_PROVIDER_AUTH_CHOICES: Partial<Record<string, AuthChoic
litellm: "litellm-api-key",
};
function hadStoredGigachatBasicProfile(agentDir?: string): boolean {
const profile = loadAuthProfileStoreForSecretsRuntime(agentDir).profiles["gigachat:default"];
return (
profile?.type === "api_key" &&
profile.provider === "gigachat" &&
profile.metadata?.authMode === "basic"
);
}
export function normalizeApiKeyTokenProviderAuthChoice(params: {
authChoice: AuthChoice;
tokenProvider?: string;
@ -123,6 +134,7 @@ export async function applyAuthChoiceApiProviders(
authChoice = "gigachat-basic";
gigachatBasicScope = gigachatScope;
} else {
const resetGigachatBaseUrl = hadStoredGigachatBasicProfile(params.agentDir);
await ensureApiKeyFromOptionEnvOrPrompt({
token: params.opts?.gigachatApiKey ?? params.opts?.token,
provider: "gigachat",
@ -173,8 +185,16 @@ export async function applyAuthChoiceApiProviders(
});
await applyProviderDefaultModel({
defaultModel: GIGACHAT_DEFAULT_MODEL_REF,
applyDefaultConfig: applyGigachatConfig,
applyProviderConfig: applyGigachatProviderConfig,
applyDefaultConfig: (config) =>
applyGigachatConfig(
config,
resetGigachatBaseUrl ? { baseUrl: GIGACHAT_BASE_URL } : undefined,
),
applyProviderConfig: (config) =>
applyGigachatProviderConfig(
config,
resetGigachatBaseUrl ? { baseUrl: GIGACHAT_BASE_URL } : undefined,
),
noteDefault: GIGACHAT_DEFAULT_MODEL_REF,
});
return { config: nextConfig, agentModelOverride };

View File

@ -37,6 +37,7 @@ import { registerProviderPlugins } from "../test-utils/plugin-registration.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import { applyAuthChoice, resolvePreferredProviderForAuthChoice } from "./auth-choice.js";
import { GOOGLE_GEMINI_DEFAULT_MODEL } from "./google-gemini-model-default.js";
import { GIGACHAT_BASE_URL } from "./onboard-auth.models.js";
import type { AuthChoice } from "./onboard-types.js";
import {
authProfilePathForAgent,
@ -382,6 +383,56 @@ describe("applyAuthChoice", () => {
await expect(readAuthProfile("gigachat:default")).rejects.toThrow();
});
it("resets a custom Basic GigaChat base URL when switching to OAuth", async () => {
await setupTempState();
delete process.env.GIGACHAT_CREDENTIALS;
delete process.env.GIGACHAT_USER;
delete process.env.GIGACHAT_PASSWORD;
delete process.env.GIGACHAT_BASE_URL;
const basicText = vi
.fn()
.mockResolvedValueOnce("https://preview-basic.gigachat.example/api/v1")
.mockResolvedValueOnce("basic-user")
.mockResolvedValueOnce("basic-pass");
const basicHarness = createApiKeyPromptHarness({ text: basicText });
const basicResult = await applyAuthChoice({
authChoice: "gigachat-basic",
config: {},
prompter: basicHarness.prompter,
runtime: basicHarness.runtime,
setDefaultModel: true,
});
expect(basicResult.config.models?.providers?.gigachat?.baseUrl).toBe(
"https://preview-basic.gigachat.example/api/v1",
);
process.env.GIGACHAT_CREDENTIALS = "gigachat-oauth-credentials=="; // pragma: allowlist secret
const oauthHarness = createApiKeyPromptHarness();
const oauthResult = await applyAuthChoice({
authChoice: "gigachat-personal",
config: basicResult.config,
prompter: oauthHarness.prompter,
runtime: oauthHarness.runtime,
setDefaultModel: true,
});
expect(oauthResult.config.models?.providers?.gigachat?.baseUrl).toBe(GIGACHAT_BASE_URL);
expect(await readAuthProfile("gigachat:default")).toMatchObject({
type: "api_key",
provider: "gigachat",
metadata: {
authMode: "oauth",
scope: "GIGACHAT_API_PERS",
insecureTls: "false",
},
});
});
it("prompts and writes provider API key for common providers", async () => {
const scenarios: Array<{
authChoice:

View File

@ -1,8 +1,17 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ApiKeyCredential, AuthProfileStore } from "../../../agents/auth-profiles.js";
import type { OpenClawConfig } from "../../../config/config.js";
import type { RuntimeEnv } from "../../../runtime.js";
import { GIGACHAT_BASE_URL } from "../../onboard-auth.models.js";
import { applySimpleNonInteractiveApiKeyChoice } from "./auth-choice.api-key-providers.js";
const loadAuthProfileStoreForSecretsRuntime = vi.hoisted(() =>
vi.fn<() => AuthProfileStore>(() => ({ version: 1, profiles: {} })),
);
vi.mock("../../../agents/auth-profiles.js", () => ({
loadAuthProfileStoreForSecretsRuntime,
}));
const applyAuthProfileConfig = vi.hoisted(() => vi.fn((cfg: OpenClawConfig) => cfg));
vi.mock("../../../plugins/provider-auth-helpers.js", () => ({
applyAuthProfileConfig,
@ -28,6 +37,7 @@ vi.mock("../../onboard-auth.config-litellm.js", () => ({
describe("applySimpleNonInteractiveApiKeyChoice", () => {
beforeEach(() => {
vi.clearAllMocks();
loadAuthProfileStoreForSecretsRuntime.mockReturnValue({ version: 1, profiles: {} });
applyAuthProfileConfig.mockImplementation((cfg: OpenClawConfig) => cfg);
applyGigachatConfig.mockImplementation((cfg: OpenClawConfig) => cfg);
applyLitellmConfig.mockImplementation((cfg: OpenClawConfig) => cfg);
@ -151,4 +161,55 @@ describe("applySimpleNonInteractiveApiKeyChoice", () => {
);
expect(runtime.exit).toHaveBeenCalledWith(1);
});
it("resets the GigaChat provider base URL when replacing a Basic profile with OAuth", async () => {
const nextConfig = { agents: { defaults: {} } } as OpenClawConfig;
const basicProfile: ApiKeyCredential = {
type: "api_key",
provider: "gigachat",
key: "basic-user:basic-pass",
metadata: {
authMode: "basic",
},
};
loadAuthProfileStoreForSecretsRuntime.mockReturnValue({
version: 1,
profiles: {
"gigachat:default": basicProfile,
},
});
const resolveApiKey = vi.fn(async () => ({
key: "gigachat-oauth-credentials",
source: "flag" as const,
}));
const maybeSetResolvedApiKey = vi.fn(async (resolved, setter) => {
await setter(resolved.key);
return true;
});
await applySimpleNonInteractiveApiKeyChoice({
authChoice: "gigachat-oauth",
nextConfig,
baseConfig: {
models: {
providers: {
gigachat: {
baseUrl: "https://preview-basic.gigachat.example/api/v1",
api: "openai-completions",
models: [],
},
},
},
} as OpenClawConfig,
opts: { token: "gigachat-oauth-credentials" } as never,
runtime: { error: vi.fn(), exit: vi.fn(), log: vi.fn() } as never,
apiKeyStorageOptions: undefined,
resolveApiKey,
maybeSetResolvedApiKey,
});
expect(applyGigachatConfig).toHaveBeenCalledWith(expect.any(Object), {
baseUrl: GIGACHAT_BASE_URL,
});
});
});

View File

@ -1,3 +1,4 @@
import { loadAuthProfileStoreForSecretsRuntime } from "../../../agents/auth-profiles.js";
import { resolveGigachatAuthMode } from "../../../agents/gigachat-auth.js";
import type { OpenClawConfig } from "../../../config/config.js";
import type { SecretInput } from "../../../config/types.secrets.js";
@ -6,6 +7,7 @@ import { setGigachatApiKey, setLitellmApiKey } from "../../../plugins/provider-a
import type { RuntimeEnv } from "../../../runtime.js";
import { applyGigachatConfig } from "../../onboard-auth.config-core.js";
import { applyLitellmConfig } from "../../onboard-auth.config-litellm.js";
import { GIGACHAT_BASE_URL } from "../../onboard-auth.models.js";
import type { AuthChoice, OnboardOptions } from "../../onboard-types.js";
type ApiKeyStorageOptions = {
@ -17,6 +19,15 @@ type ResolvedNonInteractiveApiKey = {
source: "profile" | "env" | "flag";
};
function hadStoredGigachatBasicProfile(): boolean {
const profile = loadAuthProfileStoreForSecretsRuntime().profiles["gigachat:default"];
return (
profile?.type === "api_key" &&
profile.provider === "gigachat" &&
profile.metadata?.authMode === "basic"
);
}
async function applyGigachatNonInteractiveApiKeyChoice(params: {
nextConfig: OpenClawConfig;
baseConfig: OpenClawConfig;
@ -37,6 +48,7 @@ async function applyGigachatNonInteractiveApiKeyChoice(params: {
setter: (value: SecretInput) => Promise<void> | void,
) => Promise<boolean>;
}): Promise<OpenClawConfig | null> {
const resetGigachatBaseUrl = hadStoredGigachatBasicProfile();
const resolved = await params.resolveApiKey({
provider: "gigachat",
cfg: params.baseConfig,
@ -79,6 +91,7 @@ async function applyGigachatNonInteractiveApiKeyChoice(params: {
provider: "gigachat",
mode: "api_key",
}),
resetGigachatBaseUrl ? { baseUrl: GIGACHAT_BASE_URL } : undefined,
);
}