From 78593ccf84d18978907cfb8dc7864bf133324972 Mon Sep 17 00:00:00 2001 From: Alexander Davydov Date: Mon, 16 Mar 2026 17:53:46 +0300 Subject: [PATCH] Agents: wire GigaChat compaction stream --- .../pi-embedded-runner/compact.hooks.test.ts | 81 ++++++++++++++++++- src/agents/pi-embedded-runner/compact.ts | 22 +++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/agents/pi-embedded-runner/compact.hooks.test.ts b/src/agents/pi-embedded-runner/compact.hooks.test.ts index 0a864236b81..76426c87cf0 100644 --- a/src/agents/pi-embedded-runner/compact.hooks.test.ts +++ b/src/agents/pi-embedded-runner/compact.hooks.test.ts @@ -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)", () => { diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index 67c5b8184b2..86a302f5595 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -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 {