Agents: wire GigaChat compaction stream

This commit is contained in:
Alexander Davydov 2026-03-16 17:53:46 +03:00
parent 1ae292ce26
commit 78593ccf84
2 changed files with 102 additions and 1 deletions

View File

@ -6,6 +6,7 @@ const {
ensureRuntimePluginsLoaded,
resolveContextEngineMock,
resolveModelMock,
ensureAuthProfileStoreMock,
sessionCompactImpl,
triggerInternalHook,
sanitizeSessionHistoryMock,
@ -15,6 +16,10 @@ const {
resolveSessionAgentIdMock,
estimateTokensMock,
sessionAbortCompactionMock,
createGigachatStreamFnMock,
gigachatStreamFn,
lastCreatedSession,
lastInitialStreamFn,
} = vi.hoisted(() => {
const contextEngineCompactMock = vi.fn(async () => ({
ok: true as boolean,
@ -67,6 +72,10 @@ const {
resolveSessionAgentIdMock: vi.fn(() => "main"),
estimateTokensMock: vi.fn((_message?: unknown) => 10),
sessionAbortCompactionMock: vi.fn(),
createGigachatStreamFnMock: vi.fn(),
gigachatStreamFn: vi.fn(),
lastCreatedSession: { current: null as null | { agent: { streamFn: unknown } } },
lastInitialStreamFn: { current: null as unknown },
};
});
@ -98,6 +107,7 @@ vi.mock("@mariozechner/pi-coding-agent", () => {
AuthStorage: class AuthStorage {},
ModelRegistry: class ModelRegistry {},
createAgentSession: vi.fn(async () => {
const initialStreamFn = vi.fn();
const session = {
sessionId: "session-1",
messages: [
@ -116,7 +126,7 @@ vi.mock("@mariozechner/pi-coding-agent", () => {
replaceMessages: vi.fn((messages: unknown[]) => {
session.messages = [...(messages as typeof session.messages)];
}),
streamFn: vi.fn(),
streamFn: initialStreamFn,
},
compact: vi.fn(async () => {
// simulate compaction trimming to a single message
@ -126,6 +136,8 @@ vi.mock("@mariozechner/pi-coding-agent", () => {
abortCompaction: sessionAbortCompactionMock,
dispose: vi.fn(),
};
lastCreatedSession.current = session;
lastInitialStreamFn.current = initialStreamFn;
return { session };
}),
SessionManager: {
@ -155,10 +167,15 @@ vi.mock("../models-config.js", () => ({
vi.mock("../model-auth.js", () => ({
applyLocalNoAuthHeaderOverride: vi.fn((model: unknown) => model),
ensureAuthProfileStore: ensureAuthProfileStoreMock,
getApiKeyForModel: vi.fn(async () => ({ apiKey: "test", mode: "env" })),
resolveModelAuthMode: vi.fn(() => "env"),
}));
vi.mock("../gigachat-stream.js", () => ({
createGigachatStreamFn: createGigachatStreamFnMock,
}));
vi.mock("../sandbox.js", () => ({
resolveSandboxContext: vi.fn(async () => null),
}));
@ -425,6 +442,12 @@ describe("compactEmbeddedPiSessionDirect hooks", () => {
estimateTokensMock.mockReset();
estimateTokensMock.mockReturnValue(10);
sessionAbortCompactionMock.mockReset();
ensureAuthProfileStoreMock.mockReset();
ensureAuthProfileStoreMock.mockReturnValue({ profiles: {} });
createGigachatStreamFnMock.mockReset();
createGigachatStreamFnMock.mockReturnValue(gigachatStreamFn);
lastCreatedSession.current = null;
lastInitialStreamFn.current = null;
unregisterApiProviders(getCustomApiRegistrySourceId("ollama"));
});
@ -795,6 +818,62 @@ describe("compactEmbeddedPiSessionDirect hooks", () => {
expect(result.reason).toContain("request timed out");
expect(sessionAbortCompactionMock).toHaveBeenCalledTimes(1);
});
it("installs the GigaChat stream for compaction-created sessions", async () => {
resolveModelMock.mockReturnValue({
model: {
provider: "gigachat",
api: "openai-completions",
id: "GigaChat-2-Max",
input: ["text"],
baseUrl: "https://gigachat.devices.sberbank.ru/api/v1",
},
error: null,
authStorage: { setRuntimeApiKey: vi.fn() },
modelRegistry: {},
} as never);
ensureAuthProfileStoreMock.mockReturnValue({
profiles: {
"gigachat:business": {
type: "api_key",
metadata: {
authMode: "basic",
insecureTls: "true",
scope: "GIGACHAT_API_PERS",
},
},
},
});
sessionCompactImpl.mockImplementation(async () => {
expect(createGigachatStreamFnMock).toHaveBeenCalledWith({
baseUrl: "https://gigachat.devices.sberbank.ru/api/v1",
authMode: "basic",
insecureTls: true,
scope: "GIGACHAT_API_PERS",
});
expect(lastCreatedSession.current?.agent.streamFn).toBe(gigachatStreamFn);
expect(lastCreatedSession.current?.agent.streamFn).not.toBe(lastInitialStreamFn.current);
return {
summary: "summary",
firstKeptEntryId: "entry-1",
tokensBefore: 120,
details: { ok: true },
};
});
const result = await compactEmbeddedPiSessionDirect({
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: "/tmp/session.jsonl",
workspaceDir: "/tmp",
provider: "gigachat",
model: "GigaChat-2-Max",
authProfileId: "gigachat:business",
customInstructions: "focus on decisions",
});
expect(result.ok).toBe(true);
});
});
describe("compactEmbeddedPiSession hooks (ownsCompaction engine)", () => {

View File

@ -43,12 +43,15 @@ import { ensureCustomApiRegistered } from "../custom-api-registry.js";
import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone } from "../date-time.js";
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../defaults.js";
import { resolveOpenClawDocsPath } from "../docs-path.js";
import { createGigachatStreamFn } from "../gigachat-stream.js";
import { resolveMemorySearchConfig } from "../memory-search.js";
import {
applyLocalNoAuthHeaderOverride,
ensureAuthProfileStore,
getApiKeyForModel,
resolveModelAuthMode,
} from "../model-auth.js";
import { normalizeProviderId } from "../model-selection.js";
import { supportsModelTools } from "../model-tool-support.js";
import { ensureOpenClawModelsJson } from "../models-config.js";
import { createConfiguredOllamaStreamFn } from "../ollama-stream.js";
@ -794,6 +797,25 @@ export async function compactEmbeddedPiSessionDirect(
providerBaseUrl,
}),
);
} else if (normalizeProviderId(provider) === "gigachat") {
const providerConfig = params.config?.models?.providers?.[provider];
const baseUrl =
(typeof providerConfig?.baseUrl === "string" ? providerConfig.baseUrl : undefined) ??
(typeof model.baseUrl === "string" ? model.baseUrl : undefined) ??
process.env.GIGACHAT_BASE_URL?.trim() ??
"https://gigachat.devices.sberbank.ru/api/v1";
const gigachatStore = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
const profileId = authProfileId?.trim() || "gigachat:default";
const gigachatCred =
gigachatStore.profiles[profileId] ?? gigachatStore.profiles["gigachat:default"];
const gigachatMeta = gigachatCred?.type === "api_key" ? gigachatCred.metadata : undefined;
session.agent.streamFn = createGigachatStreamFn({
baseUrl,
authMode: (gigachatMeta?.authMode as "oauth" | "basic") ?? "oauth",
insecureTls: gigachatMeta?.insecureTls === "true",
scope: gigachatMeta?.scope,
});
}
try {