

































@@ -0,0 +1,162 @@
1+import { spawn } from "node:child_process";
2+import { createServer, type Server } from "node:http";
3+import path from "node:path";
4+import { describe, expect, it } from "vitest";
5+6+const clientPath = path.resolve("scripts/e2e/lib/openai-chat-tools/client.mjs");
7+8+interface ClientResult {
9+error?: Error;
10+signal: NodeJS.Signals | null;
11+status: number | null;
12+stderr: string;
13+stdout: string;
14+}
15+16+async function listen(server: Server): Promise<number> {
17+await new Promise<void>((resolve, reject) => {
18+server.once("error", reject);
19+server.listen(0, "127.0.0.1", () => {
20+server.off("error", reject);
21+resolve();
22+});
23+});
24+const address = server.address();
25+if (!address || typeof address === "string") {
26+throw new Error("test server did not expose a TCP port");
27+}
28+return address.port;
29+}
30+31+function runClient(
32+port: number,
33+env: Record<string, string> = {},
34+timeout = 5_000,
35+): Promise<ClientResult> {
36+return new Promise((resolve) => {
37+const child = spawn(process.execPath, [clientPath], {
38+env: {
39+ ...process.env,
40+MODEL_REF: "openai/gpt-5.4-mini",
41+OPENCLAW_GATEWAY_TOKEN: "test-token",
42+OPENCLAW_OPENAI_CHAT_TOOLS_TIMEOUT_SECONDS: "1",
43+PORT: String(port),
44+ ...env,
45+},
46+stdio: ["ignore", "pipe", "pipe"],
47+});
48+let stdout = "";
49+let stderr = "";
50+let timedOut = false;
51+child.stdout.setEncoding("utf8");
52+child.stderr.setEncoding("utf8");
53+child.stdout.on("data", (chunk) => {
54+stdout += chunk;
55+});
56+child.stderr.on("data", (chunk) => {
57+stderr += chunk;
58+});
59+const timer = setTimeout(() => {
60+timedOut = true;
61+child.kill("SIGKILL");
62+}, timeout);
63+child.on("error", (error) => {
64+clearTimeout(timer);
65+resolve({ error, signal: null, status: null, stderr, stdout });
66+});
67+child.on("exit", (status, signal) => {
68+clearTimeout(timer);
69+resolve({
70+error: timedOut ? new Error(`client timed out after ${timeout}ms`) : undefined,
71+ signal,
72+ status,
73+ stderr,
74+ stdout,
75+});
76+});
77+});
78+}
79+80+function toolCallResponse() {
81+return {
82+choices: [
83+{
84+finish_reason: "tool_calls",
85+message: {
86+tool_calls: [
87+{
88+type: "function",
89+function: {
90+name: "get_weather",
91+arguments: JSON.stringify({ city: "Paris, France" }),
92+},
93+},
94+],
95+},
96+},
97+],
98+};
99+}
100+101+describe("scripts/e2e/lib/openai-chat-tools/client.mjs", () => {
102+it("accepts a matching chat completions tool call response", async () => {
103+const server = createServer((request, response) => {
104+expect(request.method).toBe("POST");
105+expect(request.url).toBe("/v1/chat/completions");
106+expect(request.headers.authorization).toBe("Bearer test-token");
107+expect(request.headers["x-openclaw-model"]).toBe("openai/gpt-5.4-mini");
108+response.writeHead(200, { "content-type": "application/json" });
109+response.end(JSON.stringify(toolCallResponse()));
110+});
111+const port = await listen(server);
112+try {
113+const result = await runClient(port);
114+115+expect(result.status).toBe(0);
116+expect(JSON.parse(result.stdout)).toMatchObject({
117+args: { city: "Paris, France" },
118+finishReason: "tool_calls",
119+ok: true,
120+toolName: "get_weather",
121+});
122+} finally {
123+server.close();
124+}
125+});
126+127+it("keeps the request timeout active while reading the response body", async () => {
128+const server = createServer((_request, response) => {
129+response.writeHead(200, { "content-type": "application/json" });
130+response.write('{"choices":');
131+});
132+const port = await listen(server);
133+const startedAt = Date.now();
134+try {
135+const result = await runClient(port, {}, 4_000);
136+const elapsedMs = Date.now() - startedAt;
137+138+expect(result.error).toBeUndefined();
139+expect(result.status).not.toBe(0);
140+expect(result.stderr).toMatch(/aborted|AbortError/iu);
141+expect(elapsedMs).toBeLessThan(3_500);
142+} finally {
143+server.close();
144+}
145+});
146+147+it("caps chat completion response bodies before JSON parsing", async () => {
148+const server = createServer((_request, response) => {
149+response.writeHead(200, { "content-type": "application/json" });
150+response.end("x".repeat(256));
151+});
152+const port = await listen(server);
153+try {
154+const result = await runClient(port, { OPENCLAW_OPENAI_CHAT_TOOLS_MAX_BODY_BYTES: "64" });
155+156+expect(result.status).not.toBe(0);
157+expect(result.stderr).toContain("chat completions response body exceeded 64 bytes");
158+} finally {
159+server.close();
160+}
161+});
162+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。