




















@@ -1,10 +1,18 @@
11import fs from "node:fs/promises";
22import os from "node:os";
33import path from "node:path";
4-import { afterEach, beforeEach, describe, expect, it } from "vitest";
4+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+6+const sharedClientMocks = vi.hoisted(() => ({
7+getSharedCodexAppServerClient: vi.fn(),
8+}));
9+10+vi.mock("./app-server/shared-client.js", () => sharedClientMocks);
11+512import {
613handleCodexConversationBindingResolved,
714handleCodexConversationInboundClaim,
15+startCodexConversationThread,
816} from "./conversation-binding.js";
9171018let tempDir: string;
@@ -15,9 +23,58 @@ describe("codex conversation binding", () => {
1523});
16241725afterEach(async () => {
26+sharedClientMocks.getSharedCodexAppServerClient.mockReset();
1827await fs.rm(tempDir, { recursive: true, force: true });
1928});
202930+it("preserves Codex auth and omits the public OpenAI provider for native bind threads", async () => {
31+const sessionFile = path.join(tempDir, "session.jsonl");
32+await fs.writeFile(
33+`${sessionFile}.codex-app-server.json`,
34+JSON.stringify({
35+schemaVersion: 1,
36+threadId: "thread-old",
37+cwd: tempDir,
38+authProfileId: "openai-codex:work",
39+modelProvider: "openai",
40+}),
41+);
42+const requests: Array<{ method: string; params: Record<string, unknown> }> = [];
43+sharedClientMocks.getSharedCodexAppServerClient.mockResolvedValue({
44+request: vi.fn(async (method: string, requestParams: Record<string, unknown>) => {
45+requests.push({ method, params: requestParams });
46+return {
47+thread: { id: "thread-new", cwd: tempDir },
48+model: "gpt-5.4-mini",
49+modelProvider: "openai",
50+};
51+}),
52+});
53+54+await startCodexConversationThread({
55+ sessionFile,
56+workspaceDir: tempDir,
57+model: "gpt-5.4-mini",
58+modelProvider: "openai",
59+});
60+61+expect(sharedClientMocks.getSharedCodexAppServerClient).toHaveBeenCalledWith(
62+expect.objectContaining({ authProfileId: "openai-codex:work" }),
63+);
64+expect(requests).toHaveLength(1);
65+expect(requests[0]).toMatchObject({
66+method: "thread/start",
67+params: expect.objectContaining({ model: "gpt-5.4-mini" }),
68+});
69+expect(requests[0]?.params).not.toHaveProperty("modelProvider");
70+await expect(fs.readFile(`${sessionFile}.codex-app-server.json`, "utf8")).resolves.toContain(
71+'"authProfileId": "openai-codex:work"',
72+);
73+await expect(
74+fs.readFile(`${sessionFile}.codex-app-server.json`, "utf8"),
75+).resolves.not.toContain('"modelProvider": "openai"');
76+});
77+2178it("clears the Codex app-server sidecar when a pending bind is denied", async () => {
2279const sessionFile = path.join(tempDir, "session.jsonl");
2380const sidecar = `${sessionFile}.codex-app-server.json`;
@@ -73,4 +130,76 @@ describe("codex conversation binding", () => {
7313074131expect(result).toEqual({ handled: true });
75132});
133+134+it("returns a clean failure reply when app-server turn start rejects", async () => {
135+const sessionFile = path.join(tempDir, "session.jsonl");
136+await fs.writeFile(
137+`${sessionFile}.codex-app-server.json`,
138+JSON.stringify({
139+schemaVersion: 1,
140+threadId: "thread-1",
141+cwd: tempDir,
142+authProfileId: "openai-codex:work",
143+}),
144+);
145+const unhandledRejections: unknown[] = [];
146+const onUnhandledRejection = (reason: unknown) => {
147+unhandledRejections.push(reason);
148+};
149+process.on("unhandledRejection", onUnhandledRejection);
150+sharedClientMocks.getSharedCodexAppServerClient.mockResolvedValue({
151+request: vi.fn(async (method: string) => {
152+if (method === "turn/start") {
153+throw new Error(
154+"unexpected status 401 Unauthorized: Missing bearer or basic authentication in header",
155+);
156+}
157+throw new Error(`unexpected method: ${method}`);
158+}),
159+addNotificationHandler: vi.fn(() => () => undefined),
160+addRequestHandler: vi.fn(() => () => undefined),
161+});
162+163+try {
164+const result = await handleCodexConversationInboundClaim(
165+{
166+content: "hi",
167+bodyForAgent: "hi",
168+channel: "telegram",
169+isGroup: false,
170+commandAuthorized: true,
171+},
172+{
173+channelId: "telegram",
174+pluginBinding: {
175+bindingId: "binding-1",
176+pluginId: "codex",
177+pluginRoot: tempDir,
178+channel: "telegram",
179+accountId: "default",
180+conversationId: "5185575566",
181+boundAt: Date.now(),
182+data: {
183+kind: "codex-app-server-session",
184+version: 1,
185+ sessionFile,
186+workspaceDir: tempDir,
187+},
188+},
189+},
190+{ timeoutMs: 50 },
191+);
192+await new Promise<void>((resolve) => setImmediate(resolve));
193+194+expect(result).toEqual({
195+handled: true,
196+reply: {
197+text: "Codex app-server turn failed: unexpected status 401 Unauthorized: Missing bearer or basic authentication in header",
198+},
199+});
200+expect(unhandledRejections).toEqual([]);
201+} finally {
202+process.off("unhandledRejection", onUnhandledRejection);
203+}
204+});
76205});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。