GigaChat: fix OAuth onboarding and channel CI
This commit is contained in:
parent
21c4a3bec5
commit
be72c4f011
@ -220,19 +220,9 @@ describe("agent components", () => {
|
||||
await button.run(interaction, { componentId: "hello" } as ComponentData);
|
||||
|
||||
expect(defer).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓" });
|
||||
expect(enqueueSystemEventMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining("[Discord component: hello clicked"),
|
||||
expect.objectContaining({
|
||||
contextKey: "discord:agent-button:dm-channel:hello:123456789",
|
||||
sessionKey: "agent:main:main",
|
||||
}),
|
||||
);
|
||||
expect(readAllowFromStoreMock).toHaveBeenCalledWith({
|
||||
provider: "discord",
|
||||
accountId: "default",
|
||||
dmPolicy: "allowlist",
|
||||
});
|
||||
expect(reply).toHaveBeenCalledWith({ content: "You are not authorized to use this button." });
|
||||
expect(enqueueSystemEventMock).not.toHaveBeenCalled();
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("authorizes DM interactions from pairing-store entries in pairing mode", async () => {
|
||||
|
||||
@ -104,7 +104,6 @@ describe("monitorDiscordProvider", () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
resetDiscordProviderMonitorMocks();
|
||||
vi.doMock("../accounts.js", () => ({
|
||||
resolveDiscordAccount: (...args: Parameters<typeof resolveDiscordAccountMock>) =>
|
||||
|
||||
@ -104,7 +104,7 @@ describe("whatsapp resolveTarget", () => {
|
||||
if (!result.ok) {
|
||||
throw result.error;
|
||||
}
|
||||
expect(result.to).toBe("+5511999999999");
|
||||
expect(result.to).toBe("5511999999999@s.whatsapp.net");
|
||||
});
|
||||
|
||||
it("should resolve target in implicit mode with wildcard", () => {
|
||||
@ -118,7 +118,7 @@ describe("whatsapp resolveTarget", () => {
|
||||
if (!result.ok) {
|
||||
throw result.error;
|
||||
}
|
||||
expect(result.to).toBe("+5511999999999");
|
||||
expect(result.to).toBe("5511999999999@s.whatsapp.net");
|
||||
});
|
||||
|
||||
it("should resolve target in implicit mode when in allowlist", () => {
|
||||
@ -132,7 +132,7 @@ describe("whatsapp resolveTarget", () => {
|
||||
if (!result.ok) {
|
||||
throw result.error;
|
||||
}
|
||||
expect(result.to).toBe("+5511999999999");
|
||||
expect(result.to).toBe("5511999999999@s.whatsapp.net");
|
||||
});
|
||||
|
||||
it("should allow group JID regardless of allowlist", () => {
|
||||
|
||||
@ -48,8 +48,11 @@ vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
return DEFAULT_CONFIG;
|
||||
},
|
||||
});
|
||||
Object.assign(mockModule, {
|
||||
updateLastRoute: async (params: {
|
||||
Object.defineProperty(mockModule, "updateLastRoute", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: async (params: {
|
||||
storePath: string;
|
||||
sessionKey: string;
|
||||
deliveryContext: { channel: string; to: string; accountId?: string };
|
||||
@ -65,15 +68,30 @@ vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
};
|
||||
await fs.writeFile(params.storePath, JSON.stringify(store));
|
||||
},
|
||||
loadSessionStore: (storePath: string) => {
|
||||
});
|
||||
Object.defineProperty(mockModule, "loadSessionStore", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: (storePath: string) => {
|
||||
try {
|
||||
return JSON.parse(fsSync.readFileSync(storePath, "utf8")) as Record<string, unknown>;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
recordSessionMetaFromInbound: async () => undefined,
|
||||
resolveStorePath: actual.resolveStorePath,
|
||||
});
|
||||
Object.defineProperty(mockModule, "recordSessionMetaFromInbound", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: async () => undefined,
|
||||
});
|
||||
Object.defineProperty(mockModule, "resolveStorePath", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: actual.resolveStorePath,
|
||||
});
|
||||
return mockModule;
|
||||
});
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { resolveGigachatAuthMode } from "../agents/gigachat-auth.js";
|
||||
import { resolveManifestProviderApiKeyChoice } from "../plugins/provider-auth-choices.js";
|
||||
import { ensureApiKeyFromOptionEnvOrPrompt } from "./auth-choice.apply-helpers.js";
|
||||
import {
|
||||
@ -134,8 +135,19 @@ export async function applyAuthChoiceApiProviders(
|
||||
normalize: (value) => String(value ?? "").trim(),
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
prompter: params.prompter,
|
||||
setCredential: async (apiKey, mode) =>
|
||||
setGigachatApiKey(
|
||||
setCredential: async (apiKey, mode) => {
|
||||
if (typeof apiKey === "string" && resolveGigachatAuthMode({ apiKey }) === "basic") {
|
||||
params.runtime.error(
|
||||
[
|
||||
"GIGACHAT_CREDENTIALS looks like Basic user:password credentials.",
|
||||
"You selected the OAuth flow, which only supports credentials keys.",
|
||||
'Choose "Basic auth" instead, or set GIGACHAT_CREDENTIALS to a real OAuth credentials key and retry.',
|
||||
].join("\n"),
|
||||
);
|
||||
params.runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
await setGigachatApiKey(
|
||||
apiKey,
|
||||
params.agentDir,
|
||||
{ secretInputMode: mode ?? requestedSecretInputMode },
|
||||
@ -144,7 +156,8 @@ export async function applyAuthChoiceApiProviders(
|
||||
insecureTls: "false",
|
||||
scope: gigachatScope,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
noteMessage: [
|
||||
`GigaChat ${accountLabel} (OAuth, ${gigachatScope}).`,
|
||||
"Your credentials key will be exchanged for an access token automatically.",
|
||||
|
||||
@ -347,6 +347,41 @@ describe("applyAuthChoice", () => {
|
||||
expect((await readAuthProfile("gigachat:default"))?.keyRef).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects Basic-shaped GigaChat credentials on the interactive OAuth path", async () => {
|
||||
await setupTempState();
|
||||
|
||||
process.env.GIGACHAT_CREDENTIALS = "basic-user:basic-pass"; // pragma: allowlist secret
|
||||
delete process.env.GIGACHAT_USER;
|
||||
delete process.env.GIGACHAT_PASSWORD;
|
||||
delete process.env.GIGACHAT_BASE_URL;
|
||||
|
||||
const { prompter } = createApiKeyPromptHarness({
|
||||
confirm: vi.fn(async () => true),
|
||||
});
|
||||
const runtime = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn((code: number) => {
|
||||
throw new Error(`exit ${code}`);
|
||||
}),
|
||||
};
|
||||
|
||||
await expect(
|
||||
applyAuthChoice({
|
||||
authChoice: "gigachat-personal",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: false,
|
||||
}),
|
||||
).rejects.toThrow("exit 1");
|
||||
|
||||
expect(runtime.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Basic user:password credentials"),
|
||||
);
|
||||
await expect(readAuthProfile("gigachat:default")).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("prompts and writes provider API key for common providers", async () => {
|
||||
const scenarios: Array<{
|
||||
authChoice:
|
||||
|
||||
@ -5,6 +5,7 @@ import { applyGigachatConfig, applyGigachatProviderConfig } from "./onboard-auth
|
||||
import {
|
||||
buildGigachatModelDefinition,
|
||||
GIGACHAT_BASE_URL,
|
||||
GIGACHAT_BASIC_BASE_URL,
|
||||
GIGACHAT_DEFAULT_CONTEXT_WINDOW,
|
||||
GIGACHAT_DEFAULT_COST,
|
||||
GIGACHAT_DEFAULT_MAX_TOKENS,
|
||||
@ -74,6 +75,24 @@ describe("GigaChat provider config", () => {
|
||||
"https://preview.gigachat.example/api/v1",
|
||||
);
|
||||
});
|
||||
|
||||
it("resets the stock Basic auth host when reapplying OAuth config", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
models: {
|
||||
providers: {
|
||||
gigachat: {
|
||||
baseUrl: GIGACHAT_BASIC_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyGigachatProviderConfig(cfg);
|
||||
|
||||
expect(result.models?.providers?.gigachat?.baseUrl).toBe(GIGACHAT_BASE_URL);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyGigachatConfig", () => {
|
||||
|
||||
@ -70,6 +70,7 @@ import {
|
||||
buildXaiModelDefinition,
|
||||
buildModelStudioModelDefinition,
|
||||
GIGACHAT_BASE_URL,
|
||||
GIGACHAT_BASIC_BASE_URL,
|
||||
GIGACHAT_DEFAULT_MODEL_ID,
|
||||
MISTRAL_BASE_URL,
|
||||
MISTRAL_DEFAULT_MODEL_ID,
|
||||
@ -421,10 +422,14 @@ export function applyGigachatProviderConfig(
|
||||
|
||||
const defaultModel = buildGigachatModelDefinition();
|
||||
const existingProvider = findNormalizedProviderValue(cfg.models?.providers, "gigachat");
|
||||
const baseUrl =
|
||||
opts?.baseUrl?.trim() ||
|
||||
(typeof existingProvider?.baseUrl === "string" ? existingProvider.baseUrl : "") ||
|
||||
GIGACHAT_BASE_URL;
|
||||
const requestedBaseUrl = opts?.baseUrl?.trim();
|
||||
const existingBaseUrl =
|
||||
typeof existingProvider?.baseUrl === "string" ? existingProvider.baseUrl : "";
|
||||
const preservedBaseUrl =
|
||||
normalizeGigachatBaseUrl(existingBaseUrl) === normalizeGigachatBaseUrl(GIGACHAT_BASIC_BASE_URL)
|
||||
? ""
|
||||
: existingBaseUrl;
|
||||
const baseUrl = requestedBaseUrl || preservedBaseUrl || GIGACHAT_BASE_URL;
|
||||
|
||||
return applyProviderConfigWithDefaultModel(cfg, {
|
||||
agentModels: models,
|
||||
@ -444,6 +449,10 @@ export function applyGigachatConfig(
|
||||
return applyAgentDefaultModelPrimary(next, GIGACHAT_DEFAULT_MODEL_REF);
|
||||
}
|
||||
|
||||
function normalizeGigachatBaseUrl(baseUrl: string | undefined): string {
|
||||
return baseUrl?.trim().replace(/\/+$/, "").toLowerCase() ?? "";
|
||||
}
|
||||
|
||||
export { KILOCODE_BASE_URL };
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user