mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 05:10:29 +00:00
feat: expose context-engine compaction delegate helper (#49061)
* ContextEngine: add runtime compaction delegate helper * plugin-sdk: expose compaction delegate through compat * docs: clarify delegated plugin compaction * docs: use scoped compaction delegate import
This commit is contained in:
@@ -5,6 +5,7 @@ import { compactEmbeddedPiSessionDirect } from "../agents/pi-embedded-runner/com
|
||||
// We dynamically import the registry so we can get a fresh module per test
|
||||
// group when needed. For most groups we use the shared singleton directly.
|
||||
// ---------------------------------------------------------------------------
|
||||
import { delegateCompactionToRuntime } from "./delegate.js";
|
||||
import { LegacyContextEngine, registerLegacyContextEngine } from "./legacy.js";
|
||||
import {
|
||||
registerContextEngine,
|
||||
@@ -255,6 +256,40 @@ describe("Engine contract tests", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("delegateCompactionToRuntime reuses the legacy runtime bridge", async () => {
|
||||
const result = await delegateCompactionToRuntime({
|
||||
sessionId: "s2",
|
||||
sessionFile: "/tmp/session.json",
|
||||
tokenBudget: 4096,
|
||||
runtimeContext: {
|
||||
workspaceDir: "/tmp/workspace",
|
||||
currentTokenCount: 12345,
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockedCompactEmbeddedPiSessionDirect).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionId: "s2",
|
||||
sessionFile: "/tmp/session.json",
|
||||
tokenBudget: 4096,
|
||||
currentTokenCount: 12345,
|
||||
workspaceDir: "/tmp/workspace",
|
||||
}),
|
||||
);
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
compacted: false,
|
||||
reason: "mock compaction",
|
||||
result: {
|
||||
summary: "",
|
||||
firstKeptEntryId: "",
|
||||
tokensBefore: 0,
|
||||
tokensAfter: 0,
|
||||
details: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
61
src/context-engine/delegate.ts
Normal file
61
src/context-engine/delegate.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { ContextEngine, CompactResult, ContextEngineRuntimeContext } from "./types.js";
|
||||
|
||||
/**
|
||||
* Delegate a context-engine compaction request to OpenClaw's built-in runtime compaction path.
|
||||
*
|
||||
* This is the same bridge used by the legacy context engine. Third-party
|
||||
* engines can call it from their own `compact()` implementations when they do
|
||||
* not own the compaction algorithm but still need `/compact` and overflow
|
||||
* recovery to use the stock runtime behavior.
|
||||
*
|
||||
* Note: `compactionTarget` is part of the public `compact()` contract, but the
|
||||
* built-in runtime compaction path does not expose that knob. This helper
|
||||
* ignores it to preserve legacy behavior; engines that need target-specific
|
||||
* compaction should implement their own `compact()` algorithm.
|
||||
*/
|
||||
export async function delegateCompactionToRuntime(
|
||||
params: Parameters<ContextEngine["compact"]>[0],
|
||||
): Promise<CompactResult> {
|
||||
// Import through a dedicated runtime boundary so the lazy edge remains effective.
|
||||
const { compactEmbeddedPiSessionDirect } =
|
||||
await import("../agents/pi-embedded-runner/compact.runtime.js");
|
||||
|
||||
// runtimeContext carries the full CompactEmbeddedPiSessionParams fields set
|
||||
// by runtime callers. We spread them and override the fields that come from
|
||||
// the public ContextEngine compact() signature directly.
|
||||
const runtimeContext: ContextEngineRuntimeContext = params.runtimeContext ?? {};
|
||||
const currentTokenCount =
|
||||
params.currentTokenCount ??
|
||||
(typeof runtimeContext.currentTokenCount === "number" &&
|
||||
Number.isFinite(runtimeContext.currentTokenCount) &&
|
||||
runtimeContext.currentTokenCount > 0
|
||||
? Math.floor(runtimeContext.currentTokenCount)
|
||||
: undefined);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- bridge runtimeContext matches CompactEmbeddedPiSessionParams
|
||||
const result = await compactEmbeddedPiSessionDirect({
|
||||
...runtimeContext,
|
||||
sessionId: params.sessionId,
|
||||
sessionFile: params.sessionFile,
|
||||
tokenBudget: params.tokenBudget,
|
||||
...(currentTokenCount !== undefined ? { currentTokenCount } : {}),
|
||||
force: params.force,
|
||||
customInstructions: params.customInstructions,
|
||||
workspaceDir: (runtimeContext.workspaceDir as string) ?? process.cwd(),
|
||||
} as Parameters<typeof compactEmbeddedPiSessionDirect>[0]);
|
||||
|
||||
return {
|
||||
ok: result.ok,
|
||||
compacted: result.compacted,
|
||||
reason: result.reason,
|
||||
result: result.result
|
||||
? {
|
||||
summary: result.result.summary,
|
||||
firstKeptEntryId: result.result.firstKeptEntryId,
|
||||
tokensBefore: result.result.tokensBefore,
|
||||
tokensAfter: result.result.tokensAfter,
|
||||
details: result.result.details,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
@@ -15,5 +15,6 @@ export {
|
||||
export type { ContextEngineFactory } from "./registry.js";
|
||||
|
||||
export { LegacyContextEngine, registerLegacyContextEngine } from "./legacy.js";
|
||||
export { delegateCompactionToRuntime } from "./delegate.js";
|
||||
|
||||
export { ensureContextEnginesInitialized } from "./init.js";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import { delegateCompactionToRuntime } from "./delegate.js";
|
||||
import { registerContextEngineForOwner } from "./registry.js";
|
||||
import type {
|
||||
ContextEngine,
|
||||
@@ -74,48 +75,7 @@ export class LegacyContextEngine implements ContextEngine {
|
||||
customInstructions?: string;
|
||||
runtimeContext?: ContextEngineRuntimeContext;
|
||||
}): Promise<CompactResult> {
|
||||
// Import through a dedicated runtime boundary so the lazy edge remains effective.
|
||||
const { compactEmbeddedPiSessionDirect } =
|
||||
await import("../agents/pi-embedded-runner/compact.runtime.js");
|
||||
|
||||
// runtimeContext carries the full CompactEmbeddedPiSessionParams fields
|
||||
// set by the caller in run.ts. We spread them and override the fields
|
||||
// that come from the ContextEngine compact() signature directly.
|
||||
const runtimeContext = params.runtimeContext ?? {};
|
||||
const currentTokenCount =
|
||||
params.currentTokenCount ??
|
||||
(typeof runtimeContext.currentTokenCount === "number" &&
|
||||
Number.isFinite(runtimeContext.currentTokenCount) &&
|
||||
runtimeContext.currentTokenCount > 0
|
||||
? Math.floor(runtimeContext.currentTokenCount)
|
||||
: undefined);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- bridge runtimeContext matches CompactEmbeddedPiSessionParams
|
||||
const result = await compactEmbeddedPiSessionDirect({
|
||||
...runtimeContext,
|
||||
sessionId: params.sessionId,
|
||||
sessionFile: params.sessionFile,
|
||||
tokenBudget: params.tokenBudget,
|
||||
...(currentTokenCount !== undefined ? { currentTokenCount } : {}),
|
||||
force: params.force,
|
||||
customInstructions: params.customInstructions,
|
||||
workspaceDir: (runtimeContext.workspaceDir as string) ?? process.cwd(),
|
||||
} as Parameters<typeof compactEmbeddedPiSessionDirect>[0]);
|
||||
|
||||
return {
|
||||
ok: result.ok,
|
||||
compacted: result.compacted,
|
||||
reason: result.reason,
|
||||
result: result.result
|
||||
? {
|
||||
summary: result.result.summary,
|
||||
firstKeptEntryId: result.result.firstKeptEntryId,
|
||||
tokensBefore: result.result.tokensBefore,
|
||||
tokensAfter: result.result.tokensAfter,
|
||||
details: result.result.details,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
return await delegateCompactionToRuntime(params);
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
|
||||
@@ -19,6 +19,7 @@ if (shouldWarnCompatImport) {
|
||||
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export { resolveControlCommandGate } from "../channels/command-gating.js";
|
||||
export { delegateCompactionToRuntime } from "../context-engine/delegate.js";
|
||||
|
||||
export { createAccountStatusSink } from "./channel-lifecycle.js";
|
||||
export { createPluginRuntimeStore } from "./runtime-store.js";
|
||||
|
||||
@@ -70,6 +70,7 @@ export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||
export type { PluginRuntime } from "../plugins/runtime/types.js";
|
||||
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export { delegateCompactionToRuntime } from "../context-engine/delegate.js";
|
||||
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
|
||||
export {
|
||||
|
||||
@@ -64,6 +64,7 @@ describe("plugin-sdk exports", () => {
|
||||
|
||||
it("keeps the root runtime surface intentionally small", () => {
|
||||
expect(typeof sdk.emptyPluginConfigSchema).toBe("function");
|
||||
expect(typeof sdk.delegateCompactionToRuntime).toBe("function");
|
||||
expect(Object.prototype.hasOwnProperty.call(sdk, "resolveControlCommandGate")).toBe(false);
|
||||
expect(Object.prototype.hasOwnProperty.call(sdk, "buildAgentSessionKey")).toBe(false);
|
||||
expect(Object.prototype.hasOwnProperty.call(sdk, "isDangerousNameMatchingEnabled")).toBe(false);
|
||||
|
||||
@@ -67,3 +67,4 @@ export type { ContextEngineFactory } from "../context-engine/registry.js";
|
||||
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export { registerContextEngine } from "../context-engine/registry.js";
|
||||
export { delegateCompactionToRuntime } from "../context-engine/delegate.js";
|
||||
|
||||
@@ -127,6 +127,20 @@ describe("plugin-sdk root alias", () => {
|
||||
expect(Object.getOwnPropertyDescriptor(lazyRootSdk, "slowHelper")).toBeDefined();
|
||||
});
|
||||
|
||||
it("forwards delegateCompactionToRuntime through the compat-backed root alias", () => {
|
||||
const delegateCompactionToRuntime = () => "delegated";
|
||||
const lazyModule = loadRootAliasWithStubs({
|
||||
monolithicExports: {
|
||||
delegateCompactionToRuntime,
|
||||
},
|
||||
});
|
||||
const lazyRootSdk = lazyModule.moduleExports;
|
||||
|
||||
expect(typeof lazyRootSdk.delegateCompactionToRuntime).toBe("function");
|
||||
expect(lazyRootSdk.delegateCompactionToRuntime).toBe(delegateCompactionToRuntime);
|
||||
expect("delegateCompactionToRuntime" in lazyRootSdk).toBe(true);
|
||||
});
|
||||
|
||||
it("loads legacy root exports through the merged root wrapper", { timeout: 240_000 }, () => {
|
||||
expect(typeof rootSdk.resolveControlCommandGate).toBe("function");
|
||||
expect(typeof rootSdk.default).toBe("object");
|
||||
|
||||
Reference in New Issue
Block a user