
















@@ -14,7 +14,10 @@ import {
1414setActivePluginRegistry,
1515} from "openclaw/plugin-sdk/plugin-test-runtime";
1616import { afterEach, describe, expect, it, vi } from "vitest";
17-import { createCodexDynamicToolBridge } from "./dynamic-tools.js";
17+import {
18+CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
19+createCodexDynamicToolBridge,
20+} from "./dynamic-tools.js";
1821import type { JsonValue } from "./protocol.js";
19222023function createTool(overrides: Partial<AnyAgentTool>): AnyAgentTool {
@@ -85,6 +88,85 @@ afterEach(() => {
8588});
86898790describe("createCodexDynamicToolBridge", () => {
91+it("defers OpenClaw dynamic tools behind Codex tool search by default", () => {
92+const bridge = createCodexDynamicToolBridge({
93+tools: [
94+createTool({ name: "web_search" }),
95+createTool({ name: "message" }),
96+createTool({ name: HEARTBEAT_RESPONSE_TOOL_NAME }),
97+createTool({ name: "sessions_yield" }),
98+],
99+signal: new AbortController().signal,
100+});
101+102+const webSearch = bridge.specs.find((tool) => tool.name === "web_search");
103+const message = bridge.specs.find((tool) => tool.name === "message");
104+const heartbeat = bridge.specs.find((tool) => tool.name === HEARTBEAT_RESPONSE_TOOL_NAME);
105+const sessionsYield = bridge.specs.find((tool) => tool.name === "sessions_yield");
106+107+expect(webSearch).toEqual(
108+expect.objectContaining({
109+name: "web_search",
110+namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
111+deferLoading: true,
112+}),
113+);
114+expect(message).toEqual(
115+expect.objectContaining({
116+name: "message",
117+namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
118+deferLoading: true,
119+}),
120+);
121+expect(heartbeat).toEqual(
122+expect.objectContaining({
123+name: HEARTBEAT_RESPONSE_TOOL_NAME,
124+namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
125+deferLoading: true,
126+}),
127+);
128+expect(sessionsYield).not.toHaveProperty("namespace");
129+expect(sessionsYield).not.toHaveProperty("deferLoading");
130+});
131+132+it("keeps configured direct tools in the initial Codex tool context", () => {
133+const bridge = createCodexDynamicToolBridge({
134+tools: [createTool({ name: "message" }), createTool({ name: "web_search" })],
135+signal: new AbortController().signal,
136+directToolNames: ["message"],
137+});
138+139+expect(bridge.specs).toEqual([
140+expect.objectContaining({
141+name: "message",
142+}),
143+expect.objectContaining({
144+name: "web_search",
145+namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
146+deferLoading: true,
147+}),
148+]);
149+expect(bridge.specs[0]).not.toHaveProperty("namespace");
150+expect(bridge.specs[0]).not.toHaveProperty("deferLoading");
151+});
152+153+it("can expose all dynamic tools directly for compatibility", () => {
154+const bridge = createCodexDynamicToolBridge({
155+tools: [createTool({ name: "web_search" }), createTool({ name: "message" })],
156+signal: new AbortController().signal,
157+loading: "direct",
158+});
159+160+expect(bridge.specs).toEqual([
161+expect.objectContaining({ name: "web_search" }),
162+expect.objectContaining({ name: "message" }),
163+]);
164+expect(bridge.specs).toEqual([
165+expect.not.objectContaining({ namespace: expect.any(String) }),
166+expect.not.objectContaining({ namespace: expect.any(String) }),
167+]);
168+});
169+88170it.each([
89171{ toolName: "tts", mediaUrl: "/tmp/reply.opus", audioAsVoice: true },
90172{ toolName: "image_generate", mediaUrl: "/tmp/generated.png" },
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。