feat: pass modelId to context engine assemble() (#47437)

Merged via squash.

Prepared head SHA: d708ddb222
Co-authored-by: jscianna <9017016+jscianna@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
This commit is contained in:
John Scianna 2026-03-20 23:05:02 +08:00 committed by GitHub
parent dc86b6d72a
commit 5607da90d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 42 additions and 4 deletions

View File

@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai
- Web tools/Tavily: add Tavily as a bundled web-search provider with dedicated `tavily_search` and `tavily_extract` tools, using canonical plugin-owned config under `plugins.entries.tavily.config.webSearch.*`. (#49200) thanks @lakshyaag-tavily.
- Docs/plugins: add the community DingTalk plugin listing to the docs catalog. (#29913) Thanks @sliverp.
- Docs/plugins: add the community QQbot plugin listing to the docs catalog. (#29898) Thanks @sliverp.
- Plugins/context engines: pass the embedded runner `modelId` into context-engine `assemble()` so plugins can adapt context formatting per model. (#47437) thanks @jscianna.
### Fixes

View File

@ -39,6 +39,7 @@ const hoisted = vi.hoisted(() => {
contextFiles: [],
}));
const getGlobalHookRunnerMock = vi.fn<() => unknown>(() => undefined);
const initializeGlobalHookRunnerMock = vi.fn();
const sessionManager = {
getLeafEntry: vi.fn(() => null),
branch: vi.fn(),
@ -55,6 +56,7 @@ const hoisted = vi.hoisted(() => {
acquireSessionWriteLockMock,
resolveBootstrapContextForRunMock,
getGlobalHookRunnerMock,
initializeGlobalHookRunnerMock,
sessionManager,
};
});
@ -94,6 +96,7 @@ vi.mock("../../pi-embedded-subscribe.js", () => ({
vi.mock("../../../plugins/hook-runner-global.js", () => ({
getGlobalHookRunner: hoisted.getGlobalHookRunnerMock,
initializeGlobalHookRunner: hoisted.initializeGlobalHookRunnerMock,
}));
vi.mock("../../../infra/machine-name.js", () => ({
@ -216,6 +219,16 @@ vi.mock("../../cache-trace.js", () => ({
createCacheTrace: () => undefined,
}));
vi.mock("../../pi-tools.js", () => ({
createOpenClawCodingTools: () => [],
resolveToolLoopDetectionConfig: () => undefined,
}));
vi.mock("../../../image-generation/runtime.js", () => ({
generateImage: vi.fn(),
listRuntimeImageGenerationProviders: () => [],
}));
vi.mock("../../model-selection.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../../model-selection.js")>();
@ -346,10 +359,12 @@ function createDefaultEmbeddedSession(params?: {
function createContextEngineBootstrapAndAssemble() {
return {
bootstrap: vi.fn(async (_params: { sessionKey?: string }) => ({ bootstrapped: true })),
assemble: vi.fn(async ({ messages }: { messages: AgentMessage[]; sessionKey?: string }) => ({
messages,
estimatedTokens: 1,
})),
assemble: vi.fn(
async ({ messages }: { messages: AgentMessage[]; sessionKey?: string; model?: string }) => ({
messages,
estimatedTokens: 1,
}),
),
};
}
@ -677,6 +692,7 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
sessionKey?: string;
messages: AgentMessage[];
tokenBudget?: number;
model?: string;
}) => Promise<AssembleResult>;
afterTurn?: (params: {
sessionId: string;
@ -783,6 +799,22 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
expectCalledWithSessionKey(afterTurn, sessionKey);
});
it("forwards modelId to assemble", async () => {
const { bootstrap, assemble } = createContextEngineBootstrapAndAssemble();
const result = await runAttemptWithContextEngine({
bootstrap,
assemble,
});
expect(result.promptError).toBeNull();
expect(assemble).toHaveBeenCalledWith(
expect.objectContaining({
model: "gpt-test",
}),
);
});
it("forwards sessionKey to ingestBatch when afterTurn is absent", async () => {
const { bootstrap, assemble } = createContextEngineBootstrapAndAssemble();
const ingestBatch = vi.fn(

View File

@ -2167,6 +2167,7 @@ export async function runEmbeddedAttempt(
sessionKey: params.sessionKey,
messages: activeSession.messages,
tokenBudget: params.contextTokenBudget,
model: params.modelId,
});
if (assembled.messages !== activeSession.messages) {
activeSession.agent.replaceMessages(assembled.messages);

View File

@ -40,6 +40,7 @@ export class LegacyContextEngine implements ContextEngine {
sessionKey?: string;
messages: AgentMessage[];
tokenBudget?: number;
model?: string;
}): Promise<AssembleResult> {
// Pass-through: the existing sanitize -> validate -> limit -> repair pipeline
// in attempt.ts handles context assembly for the legacy engine.

View File

@ -131,6 +131,9 @@ export interface ContextEngine {
sessionKey?: string;
messages: AgentMessage[];
tokenBudget?: number;
/** Current model identifier (e.g. "claude-opus-4", "gpt-4o", "qwen2.5-7b").
* Allows context engine plugins to adapt formatting per model. */
model?: string;
}): Promise<AssembleResult>;
/**