diff --git a/extensions/mattermost/src/setup.test.ts b/extensions/mattermost/src/setup.test.ts index fdd62f17b49..c69560b0138 100644 --- a/extensions/mattermost/src/setup.test.ts +++ b/extensions/mattermost/src/setup.test.ts @@ -20,9 +20,13 @@ vi.mock("./mattermost/accounts.js", async (importOriginal) => { }; }); -vi.mock("./mattermost/client.js", () => ({ - normalizeMattermostBaseUrl, -})); +vi.mock("./mattermost/client.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + normalizeMattermostBaseUrl, + }; +}); vi.mock("./secret-input.js", async (importOriginal) => { const actual = await importOriginal(); diff --git a/src/agents/compaction.ts b/src/agents/compaction.ts index 20a6d80fa43..af2f522a220 100644 --- a/src/agents/compaction.ts +++ b/src/agents/compaction.ts @@ -69,6 +69,19 @@ export function buildCompactionSummarizationInstructions( return `${identifierPreservation}\n\nAdditional focus:\n${custom}`; } +type GenerateSummaryCompat = ( + currentMessages: AgentMessage[], + model: NonNullable, + reserveTokens: number, + apiKey: string, + headers?: Record, + signal?: AbortSignal, + customInstructions?: string, + previousSummary?: string, +) => Promise; + +const generateSummaryCompat = generateSummary as unknown as GenerateSummaryCompat; + export function estimateMessagesTokens(messages: AgentMessage[]): number { // SECURITY: toolResult.details can contain untrusted/verbose payloads; never include in LLM-facing compaction. const safe = stripToolResultDetails(messages); @@ -252,11 +265,12 @@ async function summarizeChunks(params: { for (const chunk of chunks) { summary = await retryAsync( () => - generateSummary( + generateSummaryCompat( chunk, model, params.reserveTokens, params.apiKey, + params.headers, params.signal, effectiveInstructions, summary, diff --git a/src/agents/pi-extensions/compaction-safeguard.ts b/src/agents/pi-extensions/compaction-safeguard.ts index 9323bece592..3d0d4eccca8 100644 --- a/src/agents/pi-extensions/compaction-safeguard.ts +++ b/src/agents/pi-extensions/compaction-safeguard.ts @@ -1,6 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import type { AgentMessage } from "@mariozechner/pi-agent-core"; +import type { Api, Model } from "@mariozechner/pi-ai"; import type { ExtensionAPI, FileOperations } from "@mariozechner/pi-coding-agent"; import { extractSections } from "../../auto-reply/reply/post-compaction-context.js"; import { openBoundaryFile } from "../../infra/boundary-file-read.js"; @@ -63,6 +64,40 @@ const compactionSafeguardDeps = { summarizeInStages, }; +type ModelApiKeyResolver = { + getApiKeyAndHeaders?: (model: Model) => Promise<{ + ok: boolean; + apiKey?: string; + headers?: Record; + }>; + getApiKey?: (model: Model) => Promise; + getApiKeyForProvider?: (provider: string) => Promise; +}; + +async function resolveModelAuth( + modelRegistry: unknown, + model: Model, +): Promise<{ apiKey?: string; headers?: Record }> { + const registry = modelRegistry as ModelApiKeyResolver; + if (typeof registry.getApiKeyAndHeaders === "function") { + const resolved = await registry.getApiKeyAndHeaders(model); + if (resolved?.ok) { + return { + apiKey: resolved.apiKey, + headers: resolved.headers, + }; + } + return {}; + } + if (typeof registry.getApiKey === "function") { + return { apiKey: await registry.getApiKey(model) }; + } + if (typeof registry.getApiKeyForProvider === "function") { + return { apiKey: await registry.getApiKeyForProvider(model.provider) }; + } + return {}; +} + type ToolFailure = { toolCallId: string; toolName: string; @@ -618,8 +653,9 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void { model.headers && typeof model.headers === "object" && !Array.isArray(model.headers) ? model.headers : undefined; - const apiKey = (await ctx.modelRegistry.getApiKey(model)) ?? ""; - const headers = fallbackHeaders; + const resolvedAuth = await resolveModelAuth(ctx.modelRegistry, model); + const apiKey = resolvedAuth.apiKey ?? ""; + const headers = resolvedAuth.headers ?? fallbackHeaders; if (!apiKey && !headers) { log.warn( "Compaction safeguard: no request auth available; cancelling compaction to preserve history.", diff --git a/src/agents/skills/source.ts b/src/agents/skills/source.ts index 076c10bb8ec..86c9eb7320b 100644 --- a/src/agents/skills/source.ts +++ b/src/agents/skills/source.ts @@ -1,5 +1,6 @@ import type { Skill } from "@mariozechner/pi-coding-agent"; export function resolveSkillSource(skill: Skill): string { - return skill.source; + const source = (skill as Skill & { source?: unknown }).source; + return typeof source === "string" ? source : ""; } diff --git a/src/tui/gateway-chat.test.ts b/src/tui/gateway-chat.test.ts index 1bc5b2955c6..7cc4805b18b 100644 --- a/src/tui/gateway-chat.test.ts +++ b/src/tui/gateway-chat.test.ts @@ -89,8 +89,8 @@ describe("resolveGatewayConnection", () => { "OPENCLAW_GATEWAY_TOKEN", "OPENCLAW_GATEWAY_PASSWORD", ]); - loadConfig.mockClear(); - resolveGatewayPort.mockClear(); + loadConfig.mockReset(); + resolveGatewayPort.mockReset(); resolveGatewayPort.mockReturnValue(18789); delete process.env.OPENCLAW_GATEWAY_URL; delete process.env.OPENCLAW_GATEWAY_TOKEN;