mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
182 lines
6.6 KiB
TypeScript
182 lines
6.6 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
import type { ExtensionFactory, SessionManager } from "@mariozechner/pi-coding-agent";
|
|
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
|
import { listAgentToolResultMiddlewares } from "../../plugins/agent-tool-result-middleware.js";
|
|
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
|
|
import { resolveContextWindowInfo } from "../context-window-guard.js";
|
|
import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js";
|
|
import { createAgentToolResultMiddlewareRunner } from "../harness/tool-result-middleware.js";
|
|
import { setCompactionSafeguardRuntime } from "../pi-hooks/compaction-safeguard-runtime.js";
|
|
import compactionSafeguardExtension from "../pi-hooks/compaction-safeguard.js";
|
|
import contextPruningExtension from "../pi-hooks/context-pruning.js";
|
|
import { setContextPruningRuntime } from "../pi-hooks/context-pruning/runtime.js";
|
|
import { computeEffectiveSettings } from "../pi-hooks/context-pruning/settings.js";
|
|
import { makeToolPrunablePredicate } from "../pi-hooks/context-pruning/tools.js";
|
|
import { ensurePiCompactionReserveTokens } from "../pi-settings.js";
|
|
import { resolveTranscriptPolicy } from "../transcript-policy.js";
|
|
import { isCacheTtlEligibleProvider, readLastCacheTtlTimestamp } from "./cache-ttl.js";
|
|
|
|
type PiToolResultEvent = {
|
|
threadId?: string;
|
|
turnId?: string;
|
|
toolCallId?: string;
|
|
toolName?: string;
|
|
input?: unknown;
|
|
content?: AgentToolResult<unknown>["content"];
|
|
details?: unknown;
|
|
isError?: boolean;
|
|
};
|
|
|
|
function recordFromUnknown(value: unknown): Record<string, unknown> {
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
? (value as Record<string, unknown>)
|
|
: {};
|
|
}
|
|
|
|
function buildAgentToolResultMiddlewareFactory(): ExtensionFactory {
|
|
const handlers = listAgentToolResultMiddlewares("pi");
|
|
const runner = createAgentToolResultMiddlewareRunner({ runtime: "pi" }, handlers);
|
|
return (pi) => {
|
|
pi.on("tool_result", async (rawEvent: unknown, ctx: { cwd?: string }) => {
|
|
if (handlers.length === 0) {
|
|
return undefined;
|
|
}
|
|
const event = recordFromUnknown(rawEvent) as PiToolResultEvent;
|
|
if (!event.toolName) {
|
|
return undefined;
|
|
}
|
|
const toolCallId =
|
|
typeof event.toolCallId === "string" && event.toolCallId.trim()
|
|
? event.toolCallId
|
|
: `pi-${randomUUID()}`;
|
|
const content = Array.isArray(event.content) ? event.content : [];
|
|
const current = {
|
|
content,
|
|
details: event.details,
|
|
} satisfies AgentToolResult<unknown>;
|
|
const result = await runner.applyToolResultMiddleware({
|
|
threadId: event.threadId,
|
|
turnId: event.turnId,
|
|
toolCallId,
|
|
toolName: event.toolName,
|
|
args: recordFromUnknown(event.input),
|
|
cwd: ctx.cwd,
|
|
isError: event.isError,
|
|
result: current,
|
|
});
|
|
return {
|
|
content: result.content,
|
|
details: result.details,
|
|
};
|
|
});
|
|
};
|
|
}
|
|
|
|
function resolveContextWindowTokens(params: {
|
|
cfg: OpenClawConfig | undefined;
|
|
provider: string;
|
|
modelId: string;
|
|
model: ProviderRuntimeModel | undefined;
|
|
}): number {
|
|
return resolveContextWindowInfo({
|
|
cfg: params.cfg,
|
|
provider: params.provider,
|
|
modelId: params.modelId,
|
|
modelContextTokens: params.model?.contextTokens,
|
|
modelContextWindow: params.model?.contextWindow,
|
|
defaultTokens: DEFAULT_CONTEXT_TOKENS,
|
|
}).tokens;
|
|
}
|
|
|
|
function buildContextPruningFactory(params: {
|
|
cfg: OpenClawConfig | undefined;
|
|
sessionManager: SessionManager;
|
|
provider: string;
|
|
modelId: string;
|
|
model: ProviderRuntimeModel | undefined;
|
|
}): ExtensionFactory | undefined {
|
|
const raw = params.cfg?.agents?.defaults?.contextPruning;
|
|
if (raw?.mode !== "cache-ttl") {
|
|
return undefined;
|
|
}
|
|
if (!isCacheTtlEligibleProvider(params.provider, params.modelId, params.model?.api)) {
|
|
return undefined;
|
|
}
|
|
|
|
const settings = computeEffectiveSettings(raw);
|
|
if (!settings) {
|
|
return undefined;
|
|
}
|
|
const transcriptPolicy = resolveTranscriptPolicy({
|
|
modelApi: params.model?.api,
|
|
provider: params.provider,
|
|
modelId: params.modelId,
|
|
});
|
|
|
|
setContextPruningRuntime(params.sessionManager, {
|
|
settings,
|
|
contextWindowTokens: resolveContextWindowTokens(params),
|
|
isToolPrunable: makeToolPrunablePredicate(settings.tools),
|
|
dropThinkingBlocks: transcriptPolicy.dropThinkingBlocks,
|
|
lastCacheTouchAt: readLastCacheTtlTimestamp(params.sessionManager, {
|
|
provider: params.provider,
|
|
modelId: params.modelId,
|
|
}),
|
|
});
|
|
|
|
return contextPruningExtension;
|
|
}
|
|
|
|
function resolveCompactionMode(cfg?: OpenClawConfig): "default" | "safeguard" {
|
|
const compaction = cfg?.agents?.defaults?.compaction;
|
|
// A registered compaction provider requires the safeguard extension path
|
|
if (compaction?.provider) {
|
|
return "safeguard";
|
|
}
|
|
return compaction?.mode === "safeguard" ? "safeguard" : "default";
|
|
}
|
|
|
|
export function buildEmbeddedExtensionFactories(params: {
|
|
cfg: OpenClawConfig | undefined;
|
|
sessionManager: SessionManager;
|
|
provider: string;
|
|
modelId: string;
|
|
model: ProviderRuntimeModel | undefined;
|
|
}): ExtensionFactory[] {
|
|
const factories: ExtensionFactory[] = [];
|
|
if (resolveCompactionMode(params.cfg) === "safeguard") {
|
|
const compactionCfg = params.cfg?.agents?.defaults?.compaction;
|
|
const qualityGuardCfg = compactionCfg?.qualityGuard;
|
|
const contextWindowInfo = resolveContextWindowInfo({
|
|
cfg: params.cfg,
|
|
provider: params.provider,
|
|
modelId: params.modelId,
|
|
modelContextTokens: params.model?.contextTokens,
|
|
modelContextWindow: params.model?.contextWindow,
|
|
defaultTokens: DEFAULT_CONTEXT_TOKENS,
|
|
});
|
|
setCompactionSafeguardRuntime(params.sessionManager, {
|
|
maxHistoryShare: compactionCfg?.maxHistoryShare,
|
|
contextWindowTokens: contextWindowInfo.tokens,
|
|
identifierPolicy: compactionCfg?.identifierPolicy,
|
|
identifierInstructions: compactionCfg?.identifierInstructions,
|
|
customInstructions: compactionCfg?.customInstructions,
|
|
qualityGuardEnabled: qualityGuardCfg?.enabled ?? true,
|
|
qualityGuardMaxRetries: qualityGuardCfg?.maxRetries,
|
|
model: params.model,
|
|
recentTurnsPreserve: compactionCfg?.recentTurnsPreserve,
|
|
provider: compactionCfg?.provider,
|
|
});
|
|
factories.push(compactionSafeguardExtension);
|
|
}
|
|
const pruningFactory = buildContextPruningFactory(params);
|
|
if (pruningFactory) {
|
|
factories.push(pruningFactory);
|
|
}
|
|
factories.push(buildAgentToolResultMiddlewareFactory());
|
|
return factories;
|
|
}
|
|
|
|
export { ensurePiCompactionReserveTokens };
|