




















@@ -0,0 +1,247 @@
1+import http from "node:http";
2+import type { AddressInfo } from "node:net";
3+4+const DIRECT_IMAGE_BYTES = Buffer.from("docker-direct-image");
5+const CODEX_IMAGE_BYTES = Buffer.from("docker-codex-image");
6+const DIRECT_TOKEN = "sk-openclaw-image-auth-e2e";
7+const CODEX_TOKEN = "docker-codex-oauth-token";
8+9+type RequestRecord = {
10+method?: string;
11+url?: string;
12+authorization?: string;
13+accept?: string;
14+contentType?: string;
15+body: string;
16+};
17+18+function assert(condition: unknown, message: string): asserts condition {
19+if (!condition) {
20+throw new Error(message);
21+}
22+}
23+24+function readBody(req: http.IncomingMessage): Promise<string> {
25+return new Promise((resolve, reject) => {
26+let body = "";
27+req.setEncoding("utf8");
28+req.on("data", (chunk) => {
29+body += chunk;
30+});
31+req.on("end", () => resolve(body));
32+req.on("error", reject);
33+});
34+}
35+36+function writeJson(res: http.ServerResponse, status: number, body: unknown): void {
37+res.writeHead(status, { "content-type": "application/json" });
38+res.end(JSON.stringify(body));
39+}
40+41+function writeCodexSse(res: http.ServerResponse): void {
42+const events = [
43+{
44+type: "response.output_item.done",
45+item: {
46+type: "image_generation_call",
47+result: CODEX_IMAGE_BYTES.toString("base64"),
48+revised_prompt: "docker codex revised prompt",
49+},
50+},
51+{
52+type: "response.completed",
53+response: {
54+usage: { input_tokens: 1, output_tokens: 2, total_tokens: 3 },
55+tool_usage: { image_gen: { total_tokens: 3 } },
56+},
57+},
58+];
59+res.writeHead(200, { "content-type": "text/event-stream" });
60+for (const event of events) {
61+res.write(`data: ${JSON.stringify(event)}\n\n`);
62+}
63+res.end("data: [DONE]\n\n");
64+}
65+66+async function startMockServer(records: RequestRecord[]): Promise<{
67+baseUrl: string;
68+close: () => Promise<void>;
69+}> {
70+const server = http.createServer(async (req, res) => {
71+try {
72+const body = await readBody(req);
73+records.push({
74+method: req.method,
75+url: req.url,
76+authorization: req.headers.authorization,
77+accept: req.headers.accept,
78+contentType: req.headers["content-type"],
79+ body,
80+});
81+82+if (req.method === "POST" && req.url === "/v1/images/generations") {
83+assert(
84+req.headers.authorization === `Bearer ${DIRECT_TOKEN}`,
85+`direct image route used wrong auth: ${req.headers.authorization}`,
86+);
87+const parsed = JSON.parse(body) as { model?: string; prompt?: string; size?: string };
88+assert(parsed.model === "gpt-image-2", `direct route model mismatch: ${body}`);
89+assert(
90+parsed.prompt === "docker direct image auth",
91+`direct route prompt mismatch: ${body}`,
92+);
93+assert(parsed.size === "1024x1024", `direct route size mismatch: ${body}`);
94+writeJson(res, 200, {
95+data: [
96+{
97+b64_json: DIRECT_IMAGE_BYTES.toString("base64"),
98+revised_prompt: "docker direct revised prompt",
99+},
100+],
101+});
102+return;
103+}
104+105+if (req.method === "POST" && req.url === "/backend-api/codex/responses") {
106+assert(
107+req.headers.authorization === `Bearer ${CODEX_TOKEN}`,
108+`codex image route used wrong auth: ${req.headers.authorization}`,
109+);
110+const parsed = JSON.parse(body) as {
111+tools?: Array<{ type?: string; model?: string; size?: string }>;
112+input?: Array<{ content?: Array<{ type?: string; text?: string }> }>;
113+};
114+assert(
115+parsed.tools?.[0]?.type === "image_generation" &&
116+parsed.tools[0].model === "gpt-image-2" &&
117+parsed.tools[0].size === "1024x1024",
118+`codex image tool mismatch: ${body}`,
119+);
120+assert(
121+parsed.input?.[0]?.content?.some(
122+(entry) =>
123+entry.type === "input_text" && entry.text === "docker codex oauth image auth",
124+),
125+`codex prompt missing: ${body}`,
126+);
127+writeCodexSse(res);
128+return;
129+}
130+131+writeJson(res, 404, { error: `unexpected ${req.method} ${req.url}` });
132+} catch (error) {
133+writeJson(res, 500, { error: String(error instanceof Error ? error.message : error) });
134+}
135+});
136+137+await new Promise<void>((resolve) => {
138+server.listen(0, "127.0.0.1", resolve);
139+});
140+const address = server.address() as AddressInfo;
141+return {
142+baseUrl: `http://127.0.0.1:${address.port}`,
143+close: () =>
144+new Promise<void>((resolve, reject) => {
145+server.close((error) => (error ? reject(error) : resolve()));
146+}),
147+};
148+}
149+150+function createCodexOAuthStore() {
151+return {
152+version: 1,
153+profiles: {
154+"openai-codex:default": {
155+type: "oauth",
156+provider: "openai-codex",
157+access: CODEX_TOKEN,
158+refresh: "docker-codex-refresh-token",
159+expires: Date.now() + 60 * 60 * 1000,
160+},
161+},
162+} as const;
163+}
164+165+async function main() {
166+assert(
167+process.env.OPENAI_API_KEY === DIRECT_TOKEN,
168+"Docker lane must expose the direct OpenAI API key",
169+);
170+const records: RequestRecord[] = [];
171+const mock = await startMockServer(records);
172+try {
173+const { buildOpenAIImageGenerationProvider } =
174+await import("../../dist/extensions/openai/image-generation-provider.js");
175+const provider = buildOpenAIImageGenerationProvider();
176+177+const directResult = await provider.generateImage({
178+provider: "openai",
179+model: "gpt-image-2",
180+prompt: "docker direct image auth",
181+cfg: {
182+models: {
183+providers: {
184+openai: {
185+baseUrl: `${mock.baseUrl}/v1`,
186+request: { allowPrivateNetwork: true },
187+models: [],
188+},
189+},
190+},
191+},
192+});
193+assert(
194+directResult.images?.[0]?.buffer?.equals(DIRECT_IMAGE_BYTES),
195+"direct image route did not return expected bytes",
196+);
197+assert(
198+records.some((entry) => entry.url === "/v1/images/generations"),
199+"direct image route was not called",
200+);
201+202+records.length = 0;
203+const codexResult = await provider.generateImage({
204+provider: "openai",
205+model: "gpt-image-2",
206+prompt: "docker codex oauth image auth",
207+cfg: {
208+models: {
209+providers: {
210+"openai-codex": {
211+baseUrl: `${mock.baseUrl}/backend-api/codex`,
212+api: "openai-codex-responses",
213+request: { allowPrivateNetwork: true },
214+models: [],
215+},
216+},
217+},
218+},
219+authStore: createCodexOAuthStore(),
220+});
221+assert(
222+codexResult.images?.[0]?.buffer?.equals(CODEX_IMAGE_BYTES),
223+"Codex OAuth image route did not return expected bytes",
224+);
225+assert(
226+records.some((entry) => entry.url === "/backend-api/codex/responses"),
227+"Codex OAuth image route was not called",
228+);
229+assert(
230+!records.some((entry) => entry.url === "/v1/images/generations"),
231+"Codex OAuth image route fell back to the direct OpenAI API key",
232+);
233+234+process.stdout.write(
235+JSON.stringify({
236+ok: true,
237+routes: records.map((entry) => entry.url),
238+directBytes: directResult.images[0]?.buffer.length,
239+codexBytes: codexResult.images[0]?.buffer.length,
240+}) + "\n",
241+);
242+} finally {
243+await mock.close();
244+}
245+}
246+247+await main();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。