|
| 1 | +/** |
| 2 | + * Manual verification (not a unit test): drive the REAL `codex app-server` |
| 3 | + * binary end to end through makit's `CodexAppServerAdapter`. Confirms the |
| 4 | + * JSON-RPC handshake (initialize → initialized → thread/start), turn streaming, |
| 5 | + * and event mapping all line up against the installed codex CLI. |
| 6 | + * |
| 7 | + * Requires codex to be on PATH (or MAKIT_CODEX_BIN) and authenticated. Costs a |
| 8 | + * real (tiny) model call. Run: pnpm exec tsx test/verify-codex.ts |
| 9 | + */ |
| 10 | +import { CodexAppServerAdapter } from "../src/adapters/codex.js"; |
| 11 | +import type { AdapterEvent } from "../src/adapters/adapter.js"; |
| 12 | + |
| 13 | +const PROMPT = "Reply with exactly this and nothing else: makit e2e ok"; |
| 14 | +const EXPECT = "makit e2e ok"; |
| 15 | +const TIMEOUT_MS = 60_000; |
| 16 | + |
| 17 | +async function main(): Promise<void> { |
| 18 | + const command = process.env.MAKIT_CODEX_BIN || "codex"; |
| 19 | + const model = process.env.MAKIT_CODEX_MODEL; // undefined → codex default |
| 20 | + const adapter = new CodexAppServerAdapter({ command, ...(model ? { model } : {}) }); |
| 21 | + |
| 22 | + let streamed = ""; |
| 23 | + let finalText = ""; |
| 24 | + let sawError = ""; |
| 25 | + |
| 26 | + adapter.on("event", (e: AdapterEvent) => { |
| 27 | + switch (e.kind) { |
| 28 | + case "agent.message.delta": |
| 29 | + streamed += String((e.payload as { chunk?: string }).chunk ?? ""); |
| 30 | + break; |
| 31 | + case "agent.message": |
| 32 | + finalText = String((e.payload as { text?: string }).text ?? ""); |
| 33 | + break; |
| 34 | + case "session.error": |
| 35 | + sawError = String((e.payload as { message?: string }).message ?? "error"); |
| 36 | + break; |
| 37 | + default: |
| 38 | + break; |
| 39 | + } |
| 40 | + if (process.env.VERIFY_DUMP) console.log("EV:", e.kind, JSON.stringify(e.payload).slice(0, 200)); |
| 41 | + }); |
| 42 | + |
| 43 | + const done = new Promise<void>((resolve) => { |
| 44 | + adapter.on("status", (s) => { |
| 45 | + // The turn is over when the adapter returns to idle after running. |
| 46 | + if (s === "idle" && (streamed || finalText || sawError)) resolve(); |
| 47 | + }); |
| 48 | + }); |
| 49 | + |
| 50 | + const deadline = new Promise<never>((_, reject) => |
| 51 | + setTimeout(() => reject(new Error(`TIMEOUT — no completed turn within ${TIMEOUT_MS / 1000}s`)), TIMEOUT_MS), |
| 52 | + ); |
| 53 | + |
| 54 | + console.log(`[verify] launching ${command} app-server${model ? ` (model ${model})` : ""}`); |
| 55 | + await adapter.start({ cwd: process.cwd(), sessionId: "verify-codex" }); |
| 56 | + console.log("[verify] thread started; sending prompt"); |
| 57 | + await adapter.send({ text: PROMPT }); |
| 58 | + |
| 59 | + try { |
| 60 | + await Promise.race([done, deadline]); |
| 61 | + } finally { |
| 62 | + await adapter.kill().catch(() => {}); |
| 63 | + } |
| 64 | + |
| 65 | + const got = (finalText || streamed).toLowerCase(); |
| 66 | + console.log("[verify] streamed:", JSON.stringify(streamed)); |
| 67 | + console.log("[verify] final: ", JSON.stringify(finalText)); |
| 68 | + if (sawError) { |
| 69 | + console.error("[verify] FAIL ✗ session.error:", sawError); |
| 70 | + process.exit(1); |
| 71 | + } |
| 72 | + const ok = got.includes(EXPECT); |
| 73 | + console.log(ok ? "[verify] PASS ✓" : `[verify] FAIL ✗ (expected to contain '${EXPECT}')`); |
| 74 | + process.exit(ok ? 0 : 1); |
| 75 | +} |
| 76 | + |
| 77 | +main().catch((err) => { |
| 78 | + console.error("[verify] FAIL ✗", err?.message ?? err); |
| 79 | + process.exit(1); |
| 80 | +}); |
0 commit comments