fix: keep codex tool result context config-free

This commit is contained in:
Eva
2026-05-02 01:53:28 +07:00
committed by Josh Lehman
parent 2d5357d7bc
commit a65011b2f4
2 changed files with 121 additions and 19 deletions

View File

@@ -414,6 +414,92 @@ describe("createCodexDynamicToolBridge", () => {
expect(result).toEqual(expectInputText("legacy compacted"));
});
it("keeps config out of Codex tool-result contexts", async () => {
const config = { session: { store: "/tmp/openclaw-session-store.json" } };
const registry = createEmptyPluginRegistry();
const middlewareContexts: Record<string, unknown>[] = [];
const legacyContexts: Record<string, unknown>[] = [];
const middleware = vi.fn(async (_event: unknown, ctx: Record<string, unknown>) => {
middlewareContexts.push(ctx);
return undefined;
});
const factory = async (codex: {
on: (
event: "tool_result",
handler: (
event: unknown,
ctx: Record<string, unknown>,
) => Promise<{ result: AgentToolResult<unknown> } | void>,
) => void;
}) => {
codex.on("tool_result", async (_event, ctx) => {
legacyContexts.push(ctx);
});
};
registry.agentToolResultMiddlewares.push({
pluginId: "tokenjuice",
pluginName: "Tokenjuice",
rawHandler: middleware,
handler: middleware,
runtimes: ["codex"],
source: "test",
});
registry.codexAppServerExtensionFactories.push({
pluginId: "legacy",
pluginName: "Legacy",
rawFactory: factory,
factory,
source: "test",
});
setActivePluginRegistry(registry);
const execute = vi.fn(async () => textToolResult("done"));
const bridge = createCodexDynamicToolBridge({
tools: [createTool({ name: "exec", execute })],
signal: new AbortController().signal,
hookContext: {
agentId: "agent-1",
config: config as never,
sessionId: "session-1",
sessionKey: "agent:agent-1:session-1",
runId: "run-1",
},
});
await bridge.handleToolCall({
threadId: "thread-1",
turnId: "turn-1",
callId: "call-1",
namespace: null,
tool: "exec",
arguments: { command: "pwd" },
});
expect(execute).toHaveBeenCalledWith(
"call-1",
{ command: "pwd" },
expect.any(AbortSignal),
undefined,
);
expect(middlewareContexts).toHaveLength(1);
expect(middlewareContexts[0]).toMatchObject({
runtime: "codex",
agentId: "agent-1",
sessionId: "session-1",
sessionKey: "agent:agent-1:session-1",
runId: "run-1",
});
expect(middlewareContexts[0]).not.toHaveProperty("config");
expect(legacyContexts).toHaveLength(1);
expect(legacyContexts[0]).toMatchObject({
agentId: "agent-1",
sessionId: "session-1",
sessionKey: "agent:agent-1:session-1",
runId: "run-1",
});
expect(legacyContexts[0]).not.toHaveProperty("config");
});
it("fires after_tool_call for successful codex tool executions", async () => {
const afterToolCall = vi.fn();
initializeGlobalHookRunner(

View File

@@ -25,6 +25,16 @@ import {
type JsonValue,
} from "./protocol.js";
type CodexDynamicToolHookContext = {
agentId?: string;
config?: EmbeddedRunAttemptParams["config"];
sessionId?: string;
sessionKey?: string;
runId?: string;
};
type CodexToolResultHookContext = Omit<CodexDynamicToolHookContext, "config">;
export type CodexDynamicToolBridge = {
specs: CodexDynamicToolSpec[];
handleToolCall: (
@@ -46,14 +56,9 @@ export type CodexDynamicToolBridge = {
export function createCodexDynamicToolBridge(params: {
tools: AnyAgentTool[];
signal: AbortSignal;
hookContext?: {
agentId?: string;
config?: EmbeddedRunAttemptParams["config"];
sessionId?: string;
sessionKey?: string;
runId?: string;
};
hookContext?: CodexDynamicToolHookContext;
}): CodexDynamicToolBridge {
const toolResultHookContext = toToolResultHookContext(params.hookContext);
const tools = params.tools.map((tool) =>
isToolWrappedWithBeforeToolCallHook(tool)
? tool
@@ -70,11 +75,10 @@ export function createCodexDynamicToolBridge(params: {
};
const middlewareRunner = createAgentToolResultMiddlewareRunner({
runtime: "codex",
...params.hookContext,
...toolResultHookContext,
});
const legacyExtensionRunner = createCodexAppServerToolResultExtensionRunner(
params.hookContext ?? {},
);
const legacyExtensionRunner =
createCodexAppServerToolResultExtensionRunner(toolResultHookContext);
return {
specs: tools.map((tool) => ({
@@ -126,10 +130,10 @@ export function createCodexDynamicToolBridge(params: {
void runAgentHarnessAfterToolCallHook({
toolName: tool.name,
toolCallId: call.callId,
runId: params.hookContext?.runId,
agentId: params.hookContext?.agentId,
sessionId: params.hookContext?.sessionId,
sessionKey: params.hookContext?.sessionKey,
runId: toolResultHookContext.runId,
agentId: toolResultHookContext.agentId,
sessionId: toolResultHookContext.sessionId,
sessionKey: toolResultHookContext.sessionKey,
startArgs: args,
result,
startedAt,
@@ -149,10 +153,10 @@ export function createCodexDynamicToolBridge(params: {
void runAgentHarnessAfterToolCallHook({
toolName: tool.name,
toolCallId: call.callId,
runId: params.hookContext?.runId,
agentId: params.hookContext?.agentId,
sessionId: params.hookContext?.sessionId,
sessionKey: params.hookContext?.sessionKey,
runId: toolResultHookContext.runId,
agentId: toolResultHookContext.agentId,
sessionId: toolResultHookContext.sessionId,
sessionKey: toolResultHookContext.sessionKey,
startArgs: args,
error: error instanceof Error ? error.message : String(error),
startedAt,
@@ -171,6 +175,18 @@ export function createCodexDynamicToolBridge(params: {
};
}
function toToolResultHookContext(
ctx: CodexDynamicToolHookContext | undefined,
): CodexToolResultHookContext {
const { agentId, sessionId, sessionKey, runId } = ctx ?? {};
return {
...(agentId && { agentId }),
...(sessionId && { sessionId }),
...(sessionKey && { sessionKey }),
...(runId && { runId }),
};
}
function composeAbortSignals(...signals: Array<AbortSignal | undefined>): AbortSignal {
const activeSignals = signals.filter((signal): signal is AbortSignal => Boolean(signal));
if (activeSignals.length === 0) {