Context: extract extension host engine runtime

This commit is contained in:
Gustavo Madeira Santana
2026-03-15 19:03:28 +00:00
parent 67ca7cc1d8
commit fd981bf95c
5 changed files with 119 additions and 15 deletions

View File

@@ -1,5 +1,10 @@
import type { OpenClawConfig } from "../config/config.js";
import { defaultSlotIdForKey } from "../plugins/slots.js";
import {
getExtensionHostContextEngineFactory,
listExtensionHostContextEngineIds,
registerExtensionHostContextEngine,
resolveExtensionHostContextEngine,
type ExtensionHostContextEngineFactory,
} from "../extension-host/context-engine-runtime.js";
import type { ContextEngine } from "./types.js";
/**
@@ -97,18 +102,12 @@ export function registerContextEngine(
return registerContextEngineForOwner(id, factory, PUBLIC_CONTEXT_ENGINE_OWNER);
}
/**
* Return the factory for a registered engine, or undefined.
*/
export function getContextEngineFactory(id: string): ContextEngineFactory | undefined {
return getContextEngineRegistryState().engines.get(id)?.factory;
}
/**
* List all registered engine ids.
*/
export function listContextEngineIds(): string[] {
return [...getContextEngineRegistryState().engines.keys()];
return listExtensionHostContextEngineIds();
}
// ---------------------------------------------------------------------------

View File

@@ -0,0 +1,42 @@
import { describe, expect, it } from "vitest";
import type { ContextEngine } from "../context-engine/types.js";
import {
getExtensionHostContextEngineFactory,
listExtensionHostContextEngineIds,
registerExtensionHostContextEngine,
} from "./context-engine-runtime.js";
class TestContextEngine implements ContextEngine {
readonly info = {
id: "host-test",
name: "Host Test",
version: "1.0.0",
};
async ingest() {
return { ingested: false };
}
async assemble(params: { messages: [] }) {
return { messages: params.messages, estimatedTokens: 0 };
}
async afterTurn() {}
async compact() {
return { ok: true, compacted: false, reason: "noop" };
}
}
describe("extension host context engine runtime", () => {
it("stores registered context-engine factories in the host-owned runtime", async () => {
const factory = () => new TestContextEngine();
registerExtensionHostContextEngine("host-test", factory);
expect(getExtensionHostContextEngineFactory("host-test")).toBe(factory);
expect(listExtensionHostContextEngineIds()).toContain("host-test");
expect(await getExtensionHostContextEngineFactory("host-test")?.()).toBeInstanceOf(
TestContextEngine,
);
});
});

View File

@@ -0,0 +1,60 @@
import type { OpenClawConfig } from "../config/config.js";
import type { ContextEngine } from "../context-engine/types.js";
import { defaultSlotIdForKey } from "../plugins/slots.js";
export type ExtensionHostContextEngineFactory = () => ContextEngine | Promise<ContextEngine>;
const CONTEXT_ENGINE_RUNTIME_STATE = Symbol.for("openclaw.contextEngineRegistryState");
type ExtensionHostContextEngineRuntimeState = {
engines: Map<string, ExtensionHostContextEngineFactory>;
};
function getExtensionHostContextEngineRuntimeState(): ExtensionHostContextEngineRuntimeState {
const globalState = globalThis as typeof globalThis & {
[CONTEXT_ENGINE_RUNTIME_STATE]?: ExtensionHostContextEngineRuntimeState;
};
if (!globalState[CONTEXT_ENGINE_RUNTIME_STATE]) {
globalState[CONTEXT_ENGINE_RUNTIME_STATE] = {
engines: new Map<string, ExtensionHostContextEngineFactory>(),
};
}
return globalState[CONTEXT_ENGINE_RUNTIME_STATE];
}
export function registerExtensionHostContextEngine(
id: string,
factory: ExtensionHostContextEngineFactory,
): void {
getExtensionHostContextEngineRuntimeState().engines.set(id, factory);
}
export function getExtensionHostContextEngineFactory(
id: string,
): ExtensionHostContextEngineFactory | undefined {
return getExtensionHostContextEngineRuntimeState().engines.get(id);
}
export function listExtensionHostContextEngineIds(): string[] {
return [...getExtensionHostContextEngineRuntimeState().engines.keys()];
}
export async function resolveExtensionHostContextEngine(
config?: OpenClawConfig,
): Promise<ContextEngine> {
const slotValue = config?.plugins?.slots?.contextEngine;
const engineId =
typeof slotValue === "string" && slotValue.trim()
? slotValue.trim()
: defaultSlotIdForKey("contextEngine");
const factory = getExtensionHostContextEngineRuntimeState().engines.get(engineId);
if (!factory) {
throw new Error(
`Context engine "${engineId}" is not registered. ` +
`Available engines: ${listExtensionHostContextEngineIds().join(", ") || "(none)"}`,
);
}
return factory();
}

View File

@@ -1,6 +1,5 @@
import type { AnyAgentTool } from "../agents/tools/common.js";
import type { ChannelPlugin } from "../channels/plugins/types.js";
import { registerContextEngine as registerLegacyContextEngine } from "../context-engine/registry.js";
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
import { registerInternalHook } from "../hooks/internal-hooks.js";
import type { PluginRecord, PluginRegistry } from "../plugins/registry.js";
@@ -16,6 +15,7 @@ import type {
OpenClawPluginToolFactory,
PluginHookRegistration as TypedPluginHookRegistration,
} from "../plugins/types.js";
import { registerExtensionHostContextEngine } from "./context-engine-runtime.js";
import {
applyExtensionHostTypedHookPolicy,
bridgeExtensionHostLegacyHooks,
@@ -307,7 +307,7 @@ export function createExtensionHostPluginRegistrationActions(params: {
const registerContextEngine = (
record: PluginRecord,
engineId: string,
factory: Parameters<typeof registerLegacyContextEngine>[1],
factory: Parameters<typeof registerExtensionHostContextEngine>[1],
) => {
const result = resolveExtensionContextEngineRegistration({
engineId,
@@ -325,7 +325,7 @@ export function createExtensionHostPluginRegistrationActions(params: {
}
addExtensionContextEngineRegistration({
entry: result.entry,
registerEngine: registerLegacyContextEngine,
registerEngine: registerExtensionHostContextEngine,
});
};

View File

@@ -1,4 +1,3 @@
import { registerContextEngine, type ContextEngineFactory } from "../context-engine/registry.js";
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
import type {
PluginChannelRegistration,
@@ -13,6 +12,10 @@ import type {
PluginToolRegistration,
} from "../plugins/registry.js";
import type { PluginHookRegistration as TypedPluginHookRegistration } from "../plugins/types.js";
import {
registerExtensionHostContextEngine,
type ExtensionHostContextEngineFactory,
} from "./context-engine-runtime.js";
import type {
ExtensionHostChannelRegistration,
ExtensionHostCliRegistration,
@@ -162,8 +165,8 @@ export function addExtensionCommandRegistration(params: {
export function addExtensionContextEngineRegistration(params: {
entry: ExtensionHostContextEngineRegistration;
registerEngine?: (engineId: string, factory: ContextEngineFactory) => void;
registerEngine?: (engineId: string, factory: ExtensionHostContextEngineFactory) => void;
}): void {
const registerEngine = params.registerEngine ?? registerContextEngine;
const registerEngine = params.registerEngine ?? registerExtensionHostContextEngine;
registerEngine(params.entry.engineId, params.entry.factory);
}