GigaChat: reuse OAuth profiles and keep prompts exact
This commit is contained in:
parent
5d553b19fe
commit
302201526e
@ -502,6 +502,57 @@ describe("createGigachatStreamFn tool calling", () => {
|
||||
expect(event.content).toEqual([{ type: "text", text: "done" }]);
|
||||
});
|
||||
|
||||
it("preserves exact Unicode punctuation in system, user, and assistant history text", async () => {
|
||||
request.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: createSseStream(['data: {"choices":[{"delta":{"content":"done"}}]}', "data: [DONE]"]),
|
||||
});
|
||||
|
||||
const systemPrompt = "Keep “curly quotes”, em dashes —, NBSP\u00A0gaps, and ellipses…";
|
||||
const userText = "User asked to preserve “exact” punctuation — including\u00A0spacing…";
|
||||
const assistantText = "Assistant replied with “quoted” text — unchanged\u00A0too…";
|
||||
|
||||
const streamFn = createGigachatStreamFn({
|
||||
baseUrl: "https://gigachat.devices.sberbank.ru/api/v1",
|
||||
authMode: "oauth",
|
||||
});
|
||||
|
||||
const stream = await streamFn(
|
||||
{ api: "gigachat", provider: "gigachat", id: "GigaChat-2-Max" } as never,
|
||||
{
|
||||
systemPrompt,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: userText,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: assistantText }],
|
||||
},
|
||||
],
|
||||
tools: [],
|
||||
} as never,
|
||||
{ apiKey: "token" } as never,
|
||||
);
|
||||
|
||||
await expect(stream.result()).resolves.toMatchObject({
|
||||
content: [{ type: "text", text: "done" }],
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
messages: [
|
||||
expect.objectContaining({ role: "system", content: systemPrompt }),
|
||||
expect.objectContaining({ role: "user", content: userText }),
|
||||
expect.objectContaining({ role: "assistant", content: assistantText }),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves all historical tool calls from a single assistant turn", async () => {
|
||||
request.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
|
||||
@ -293,11 +293,6 @@ function sanitizeContent(content: string | null | undefined): string {
|
||||
content
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "")
|
||||
.replace(/[\u201C\u201D]/g, '"')
|
||||
.replace(/[\u2018\u2019]/g, "'")
|
||||
.replace(/[\u2013\u2014]/g, "-")
|
||||
.replace(/\u00A0/g, " ")
|
||||
.replace(/\u2026/g, "...")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -43,14 +43,17 @@ describe("applySimpleNonInteractiveApiKeyChoice", () => {
|
||||
applyLitellmConfig.mockImplementation((cfg: OpenClawConfig) => cfg);
|
||||
});
|
||||
|
||||
it("disables profile fallback for GigaChat personal OAuth onboarding", async () => {
|
||||
it("allows stored OAuth profile fallback for GigaChat personal OAuth onboarding", async () => {
|
||||
const agentDir = "/tmp/openclaw-agents/work/agent";
|
||||
const nextConfig = { agents: { defaults: {} } } as OpenClawConfig;
|
||||
const resolveApiKey = vi.fn(async () => ({
|
||||
key: "gigachat-oauth-credentials",
|
||||
source: "env" as const,
|
||||
source: "profile" as const,
|
||||
}));
|
||||
const maybeSetResolvedApiKey = vi.fn(async (resolved, setter) => {
|
||||
if (resolved.source === "profile") {
|
||||
return true;
|
||||
}
|
||||
await setter(resolved.key);
|
||||
return true;
|
||||
});
|
||||
@ -73,20 +76,11 @@ describe("applySimpleNonInteractiveApiKeyChoice", () => {
|
||||
flagName: "--gigachat-api-key",
|
||||
envVar: "GIGACHAT_CREDENTIALS",
|
||||
agentDir,
|
||||
allowProfile: false,
|
||||
allowProfile: true,
|
||||
}),
|
||||
);
|
||||
expect(maybeSetResolvedApiKey).toHaveBeenCalledOnce();
|
||||
expect(setGigachatApiKey).toHaveBeenCalledWith(
|
||||
"gigachat-oauth-credentials",
|
||||
agentDir,
|
||||
undefined,
|
||||
{
|
||||
authMode: "oauth",
|
||||
insecureTls: "false",
|
||||
scope: "GIGACHAT_API_PERS",
|
||||
},
|
||||
);
|
||||
expect(setGigachatApiKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("accepts the generic --token input for GigaChat non-interactive OAuth", async () => {
|
||||
@ -120,7 +114,7 @@ describe("applySimpleNonInteractiveApiKeyChoice", () => {
|
||||
flagName: "--gigachat-api-key",
|
||||
envVar: "GIGACHAT_CREDENTIALS",
|
||||
agentDir,
|
||||
allowProfile: false,
|
||||
allowProfile: true,
|
||||
}),
|
||||
);
|
||||
expect(setGigachatApiKey).toHaveBeenCalledWith(
|
||||
@ -170,6 +164,41 @@ describe("applySimpleNonInteractiveApiKeyChoice", () => {
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("rejects Basic-shaped stored profiles in the OAuth onboarding path", async () => {
|
||||
const agentDir = "/tmp/openclaw-agents/work/agent";
|
||||
const nextConfig = { agents: { defaults: {} } } as OpenClawConfig;
|
||||
const runtime: RuntimeEnv = {
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
log: vi.fn(),
|
||||
};
|
||||
const resolveApiKey = vi.fn(async () => ({
|
||||
key: "basic-user:basic-pass",
|
||||
source: "profile" as const,
|
||||
}));
|
||||
const maybeSetResolvedApiKey = vi.fn();
|
||||
|
||||
const result = await applySimpleNonInteractiveApiKeyChoice({
|
||||
authChoice: "gigachat-api-key",
|
||||
nextConfig,
|
||||
baseConfig: nextConfig,
|
||||
opts: {} as never,
|
||||
runtime,
|
||||
agentDir,
|
||||
apiKeyStorageOptions: undefined,
|
||||
resolveApiKey,
|
||||
maybeSetResolvedApiKey,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(maybeSetResolvedApiKey).not.toHaveBeenCalled();
|
||||
expect(setGigachatApiKey).not.toHaveBeenCalled();
|
||||
expect(runtime.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Basic user:password credentials"),
|
||||
);
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("accepts OAuth credentials keys that contain colons", async () => {
|
||||
const agentDir = "/tmp/openclaw-agents/work/agent";
|
||||
const nextConfig = { agents: { defaults: {} } } as OpenClawConfig;
|
||||
|
||||
@ -59,9 +59,9 @@ async function applyGigachatNonInteractiveApiKeyChoice(params: {
|
||||
envVar: "GIGACHAT_CREDENTIALS",
|
||||
runtime: params.runtime,
|
||||
agentDir: params.agentDir,
|
||||
// Personal OAuth onboarding must not silently reuse an existing Basic
|
||||
// username:password profile and then rewrite the provider to OAuth config.
|
||||
allowProfile: false,
|
||||
// Allow existing OAuth profiles to be reused, but reject Basic-shaped
|
||||
// credentials below before any OAuth metadata/config rewrite happens.
|
||||
allowProfile: true,
|
||||
});
|
||||
if (!resolved) {
|
||||
return null;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user