


























@@ -2,11 +2,13 @@ import OpenAI from "openai";
22import type { ChatCompletionCreateParamsNonStreaming } from "openai/resources/chat/completions.js";
33import type { ResponseCreateParamsNonStreaming } from "openai/resources/responses/responses.js";
44import { describe, expect, it } from "vitest";
5+import { createCodexNativeWebSearchWrapper } from "../llm/providers/stream-wrappers/openai.js";
56import type { Context, Model } from "../llm/types.js";
67import { isLiveTestEnabled } from "./live-test-helpers.js";
78import {
89buildOpenAICompletionsParams,
910buildOpenAIResponsesParams,
11+createOpenAIResponsesTransportStreamFn,
1012} from "./openai-transport-stream.js";
11131214const OPENAI_KEY = process.env.OPENAI_API_KEY ?? "";
@@ -131,4 +133,95 @@ describeLive("OpenAI tool projection live", () => {
131133value: "OPENAI_PROJECTION_OK",
132134});
133135}, 45_000);
136+137+it("keeps code-mode tools after a payload hook adds an unreadable sibling", async () => {
138+const model = {
139+id: modelId,
140+name: modelId,
141+api: "openai-responses",
142+provider: "openai",
143+baseUrl: "https://api.openai.com/v1",
144+reasoning: true,
145+input: ["text"],
146+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
147+contextWindow: 200000,
148+maxTokens: 256,
149+} satisfies Model<"openai-responses">;
150+const codeModeContext = {
151+systemPrompt: "Call the requested function exactly once.",
152+messages: [
153+{
154+role: "user",
155+content: "Call exec with value exactly OPENAI_POST_HOOK_OK.",
156+timestamp: 1,
157+},
158+],
159+tools: [
160+{
161+name: "exec",
162+description: "Return the requested probe value.",
163+parameters: {
164+type: "object",
165+properties: { value: { type: "string" } },
166+required: ["value"],
167+additionalProperties: false,
168+},
169+},
170+{
171+name: "wait",
172+description: "Wait without doing work.",
173+parameters: {
174+type: "object",
175+properties: {},
176+additionalProperties: false,
177+},
178+},
179+],
180+} satisfies Context;
181+const streamFn = createCodexNativeWebSearchWrapper(createOpenAIResponsesTransportStreamFn(), {
182+codeModeToolSurfaceEnabled: true,
183+});
184+const streamOptions = {
185+apiKey: OPENAI_KEY,
186+maxTokens: 128,
187+reasoning: "low",
188+toolChoice: { type: "function", name: "exec" },
189+openclawCodeModeToolSurface: true,
190+onPayload(payload: unknown) {
191+const record = payload as Record<string, unknown>;
192+const tools = record.tools;
193+if (!Array.isArray(tools) || tools.length !== 2) {
194+throw new Error("Expected projected exec and wait tools");
195+}
196+record.tools = [
197+tools[0],
198+{
199+type: "function",
200+get function(): { name: string } {
201+throw new Error("live unreadable post-hook function getter");
202+},
203+},
204+tools[1],
205+];
206+return record;
207+},
208+} satisfies Parameters<typeof streamFn>[2] & {
209+reasoning: "low";
210+toolChoice: { type: "function"; name: string };
211+openclawCodeModeToolSurface: true;
212+};
213+const stream = await Promise.resolve(streamFn(model, codeModeContext, streamOptions));
214+215+const result = await stream.result();
216+const toolCall = result.content.find(
217+(block) => block.type === "toolCall" && block.name === "exec",
218+);
219+220+expect(result.stopReason).toBe("toolUse");
221+expect(toolCall).toMatchObject({
222+type: "toolCall",
223+name: "exec",
224+arguments: { value: "OPENAI_POST_HOOK_OK" },
225+});
226+}, 45_000);
134227});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。