Spaces:
Runtime error
Runtime error
| // tests/integration/combo-matrix/context-relay-codex.test.ts | |
| // | |
| // Deterministic in-process test for context-relay CODEX-SPECIFIC HANDOFF | |
| // (combo.ts ~2143-2183): the quota-aware handoff that fires after a successful | |
| // codex response when the quota exceeds the threshold. | |
| // | |
| // Code path: | |
| // if (strategy === "context-relay" && relayOptions?.sessionId && relayConfig && | |
| // relayConfig.handoffProviders.includes(provider) && provider === "codex") { | |
| // const connectionId = getSessionConnection(relayOptions.sessionId); | |
| // if (connectionId) { | |
| // const quotaInfo = await fetchCodexQuota(connectionId).catch(() => null); | |
| // if (quotaInfo) { ... maybeGenerateHandoff({ ..., expiresAt: resetCandidates[0] }); } | |
| // } | |
| // } | |
| // | |
| // PRIMARY observable: getHandoff(sessionId, comboName).expiresAt === session-window | |
| // reset time from the codex quota response. This is the CODEX-SPECIFIC proof: | |
| // the universal handoff path does NOT set expiresAt from a codex quota fetch. | |
| // | |
| // SECONDARY observables: | |
| // - The codex usage URL was fetched (proves the quota path ran). | |
| // - The handoff record has a non-empty .summary. | |
| // | |
| // CONTROL: combo targeting openai (not codex) β codex block NEVER runs β | |
| // codex usage URL is never fetched β expiresAt from quota doesn't appear. | |
| // | |
| // Seams used: | |
| // - registerCodexConnection / unregisterCodexConnection (codexQuotaFetcher.ts) | |
| // - clearSessions (sessionManager.ts) β cleanup between tests | |
| // - buildCodexResponsesSse helper (mirrors codex-stream-false.test.ts) | |
| // - Custom fetch mock distinguishes quota URL / summary dispatch / main dispatch | |
| import test from "node:test"; | |
| import assert from "node:assert/strict"; | |
| import { createComboRoutingHarness } from "../_comboRoutingHarness.ts"; | |
| import { | |
| registerCodexConnection, | |
| unregisterCodexConnection, | |
| } from "../../../open-sse/services/codexQuotaFetcher.ts"; | |
| import { clearSessions } from "../../../open-sse/services/sessionManager.ts"; | |
| // ββ Harness setup βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Each harness owns an isolated DATA_DIR so DB handles don't clash. | |
| const h = await createComboRoutingHarness("combo-relay-codex"); | |
| const { | |
| BaseExecutor, | |
| combosDb, | |
| handleChat, | |
| buildRequest, | |
| seedConnection, | |
| resetStorage, | |
| buildOpenAIResponse, | |
| waitFor, | |
| toPlainHeaders, | |
| } = h; | |
| // Import DB helpers AFTER harness creation so they share the same DB instance | |
| // (DATA_DIR is set by the harness before any import triggers DB init). | |
| const { getHandoff } = await import("../../../src/lib/db/contextHandoffs.ts"); | |
| // ββ Constants βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const CODEX_COMBO_NAME = "m-relay-codex-quota"; | |
| const SESSION_HEADER_VALUE = "relay-codex-quota-001"; | |
| const SESSION_ID = `ext:${SESSION_HEADER_VALUE}`; | |
| // Codex endpoint URLs (must match codexQuotaFetcher.ts and the executor config). | |
| const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage"; | |
| const CODEX_RESPONSES_HOST = "chatgpt.com/backend-api/codex/responses"; | |
| // Summary JSON that parseHandoffJSON will successfully parse. | |
| const CODEX_SUMMARY_JSON = JSON.stringify({ | |
| summary: | |
| "User is implementing a TypeScript context-relay codex quota-handoff test using TDD.", | |
| keyDecisions: ["codex provider selected", "quota threshold at 90%"], | |
| taskProgress: "writing deterministic integration test for codex handoff", | |
| activeEntities: ["combo.ts", "codexQuotaFetcher.ts", "contextHandoff.ts"], | |
| }); | |
| // ββ SSE builder (mirrors codex-stream-false.test.ts) βββββββββββββββββββββββββ | |
| function buildCodexResponsesSse(text = "codex assistant reply") { | |
| return new Response( | |
| [ | |
| "event: response.created", | |
| 'data: {"type":"response.created","response":{"id":"resp_codex_1","model":"gpt-5.3-codex","status":"in_progress","output":[]}}', | |
| "", | |
| "event: response.output_text.delta", | |
| `data: ${JSON.stringify({ | |
| type: "response.output_text.delta", | |
| output_index: 0, | |
| delta: text, | |
| })}`, | |
| "", | |
| "event: response.completed", | |
| `data: ${JSON.stringify({ | |
| type: "response.completed", | |
| response: { | |
| id: "resp_codex_1", | |
| object: "response", | |
| model: "gpt-5.3-codex", | |
| status: "completed", | |
| output: [ | |
| { | |
| type: "message", | |
| role: "assistant", | |
| content: [{ type: "output_text", text }], | |
| }, | |
| ], | |
| usage: { input_tokens: 6, output_tokens: 4 }, | |
| }, | |
| })}`, | |
| "", | |
| "data: [DONE]", | |
| "", | |
| ].join("\n"), | |
| { | |
| status: 200, | |
| headers: { "Content-Type": "text/event-stream" }, | |
| } | |
| ); | |
| } | |
| // ββ Quota response builder ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Produces a codex usage API response that parseCodexUsageResponse accepts. | |
| // primary_window β session β percentUsed = 0.90 (90%) β triggers handoff (threshold 0.85) | |
| // secondary_window β weekly β percentUsed = 0.85 (85%) β below threshold for weekly | |
| // Both are below HANDOFF_EXHAUSTION_THRESHOLD (0.95) so maybeGenerateHandoff proceeds. | |
| function buildCodexUsageBody( | |
| sessionResetAtUnix: number, | |
| weeklyResetAtUnix: number | |
| ): Record<string, unknown> { | |
| return { | |
| rate_limit: { | |
| primary_window: { used_percent: 90, reset_at: sessionResetAtUnix }, | |
| secondary_window: { used_percent: 85, reset_at: weeklyResetAtUnix }, | |
| }, | |
| }; | |
| } | |
| // ββ Request builder βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function codexRequest(withSessionId = true) { | |
| return buildRequest({ | |
| headers: withSessionId ? { "x-session-id": SESSION_HEADER_VALUE } : {}, | |
| body: { | |
| model: CODEX_COMBO_NAME, | |
| stream: false, | |
| messages: [{ role: "user", content: "Write a TypeScript hello world." }], | |
| }, | |
| }); | |
| } | |
| // ββ Fetch mock ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Handles three URL classes: | |
| // 1. CODEX_USAGE_URL β codex quota JSON | |
| // 2. CODEX_RESPONSES_HOST (call 1) β main request Responses API SSE | |
| // 3. CODEX_RESPONSES_HOST (call 2) β summary request Responses API SSE (with CODEX_SUMMARY_JSON) | |
| // | |
| // The second codex-responses call is the handoff summary generation, identified by | |
| // call count (the first is always the main request). | |
| function installCodexHandoffFetch( | |
| sessionResetAtUnix: number, | |
| weeklyResetAtUnix: number, | |
| seenUrls: string[] | |
| ) { | |
| let codexResponsesCallCount = 0; | |
| globalThis.fetch = async (url: unknown, init: unknown = {}) => { | |
| const u = String(url); | |
| seenUrls.push(u); | |
| const headers = | |
| init != null && typeof init === "object" | |
| ? toPlainHeaders((init as Record<string, unknown>).headers) | |
| : {}; | |
| void headers; // recorded for debugging; not needed in assertions here | |
| if (u === CODEX_USAGE_URL) { | |
| // Codex quota endpoint β return structured usage JSON. | |
| return new Response( | |
| JSON.stringify(buildCodexUsageBody(sessionResetAtUnix, weeklyResetAtUnix)), | |
| { status: 200, headers: { "Content-Type": "application/json" } } | |
| ); | |
| } | |
| if (u.includes(CODEX_RESPONSES_HOST)) { | |
| codexResponsesCallCount++; | |
| if (codexResponsesCallCount === 1) { | |
| // Main codex request β return a successful Responses API SSE. | |
| return buildCodexResponsesSse("codex assistant reply ok"); | |
| } | |
| // Subsequent call(s) are from the handoff summary generation. | |
| // Return Responses API SSE whose output_text is CODEX_SUMMARY_JSON so that | |
| // generateHandoffAsync β parseHandoffJSON succeeds. | |
| return buildCodexResponsesSse(CODEX_SUMMARY_JSON); | |
| } | |
| // Fallback β should not be reached in these tests. | |
| return new Response(JSON.stringify({ error: { message: "unexpected URL in test" } }), { | |
| status: 400, | |
| headers: { "Content-Type": "application/json" }, | |
| }); | |
| }; | |
| } | |
| // ββ Lifecycle hooks βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| test.beforeEach(async () => { | |
| BaseExecutor.RETRY_CONFIG.delayMs = 0; | |
| await resetStorage(); | |
| }); | |
| test.afterEach(async () => { | |
| BaseExecutor.RETRY_CONFIG.delayMs = h.originalRetryDelayMs; | |
| // Clear in-memory session state so test 2 doesn't see stale connections from test 1. | |
| clearSessions(); | |
| await resetStorage(); | |
| }); | |
| test.after(async () => { | |
| await h.cleanup(); | |
| }); | |
| // ββ Test 1 (PRIMARY): codex quota handoff fires βββββββββββββββββββββββββββββββ | |
| // | |
| // Flow: | |
| // 1. Seed a codex DB connection + register with accessToken. | |
| // 2. Create context-relay combo β codex. | |
| // 3. Send request with x-session-id header. | |
| // 4. During request processing, chat.ts calls touchSession(sessionId, codexConnId). | |
| // 5. After the successful codex response, combo.ts enters the codex block: | |
| // - getSessionConnection β codexConnId (set in step 4). | |
| // - fetchCodexQuota(codexConnId) β hits our mock β returns 90% usage. | |
| // - resetCandidates[0] = session-window resetAt ISO string. | |
| // - maybeGenerateHandoff({ ..., expiresAt: sessionResetISO }) fires via setImmediate. | |
| // 6. generateHandoffAsync dispatches a second codex fetch for the summary. | |
| // 7. upsertHandoff writes to DB with expiresAt = sessionResetISO. | |
| // | |
| // Assertion: handoff.expiresAt === sessionResetISO (codex-specific, not TTL-derived). | |
| test("context-relay codex quota handoff: fires and expiresAt matches session-window reset from quota", async () => { | |
| // 1. Seed codex connection. | |
| const conn = await seedConnection("codex", { apiKey: "sk-codex-handoff-test-1" }); | |
| const codexConnId = conn.id; | |
| // 2. Register connection meta so fetchCodexQuota can find credentials. | |
| // chat.ts also calls registerCodexConnection during request processing for OAuth | |
| // tokens; for API-key connections the accessToken may not be set there, so we | |
| // pre-register with the API key as the token. This is the documented test seam. | |
| registerCodexConnection(codexConnId, { accessToken: "sk-codex-handoff-test-1" }); | |
| // 3. Create a context-relay combo targeting codex. | |
| // Default handoffProviders: ["codex"] and handoffThreshold: 0.85 apply. | |
| await combosDb.createCombo({ | |
| name: CODEX_COMBO_NAME, | |
| strategy: "context-relay", | |
| config: { maxRetries: 0, retryDelayMs: 0, stickyRoundRobinLimit: 1 }, | |
| models: [ | |
| { id: "rc-codex-1", kind: "model", providerId: "codex", model: "gpt-5.3-codex" }, | |
| ], | |
| }); | |
| // 4. Compute quota reset times (future timestamps). | |
| const sessionResetAtUnix = Math.floor((Date.now() + 5 * 60 * 60 * 1000) / 1000); // +5h | |
| const weeklyResetAtUnix = Math.floor((Date.now() + 7 * 24 * 60 * 60 * 1000) / 1000); // +7d | |
| // Expected expiresAt: parseWindowReset converts reset_at (unix seconds) β ISO string. | |
| // resetCandidates[0] = earliest ISO string = session window reset (5h < 7d). | |
| const expectedExpiresAt = new Date(sessionResetAtUnix * 1000).toISOString(); | |
| // 5. Install fetch mock and send request. | |
| const seenUrls: string[] = []; | |
| installCodexHandoffFetch(sessionResetAtUnix, weeklyResetAtUnix, seenUrls); | |
| const r = await handleChat(codexRequest(true)); | |
| assert.equal(r.status, 200, "main codex request must return 200"); | |
| // 6. Wait for setImmediate β generateHandoffAsync β upsertHandoff (up to 3s). | |
| const handoff = await waitFor(() => getHandoff(SESSION_ID, CODEX_COMBO_NAME), 3000); | |
| // ββ Primary assertion: handoff record exists ββββββββββββββββββββββββββββββ | |
| assert.ok( | |
| handoff !== null, | |
| "codex quota handoff record must be written to DB when quota β₯ threshold" | |
| ); | |
| // ββ CODEX-SPECIFIC proof: expiresAt == session-window reset from quota ββββ | |
| // The universal handoff path never calls fetchCodexQuota, so it cannot produce | |
| // this specific expiresAt value. The value below can only come from the codex block. | |
| assert.equal( | |
| handoff!.expiresAt, | |
| expectedExpiresAt, | |
| `handoff.expiresAt must equal the session-window reset from codex quota (${expectedExpiresAt}); ` + | |
| `got ${handoff!.expiresAt}` | |
| ); | |
| // ββ Secondary assertion: codex usage URL was fetched βββββββββββββββββββββ | |
| assert.ok( | |
| seenUrls.includes(CODEX_USAGE_URL), | |
| `codex usage URL must have been fetched to produce the expiresAt; seen URLs: ${JSON.stringify(seenUrls)}` | |
| ); | |
| // ββ Secondary assertion: summary was generated (non-empty) βββββββββββββββ | |
| assert.ok( | |
| typeof handoff!.summary === "string" && handoff!.summary.length > 0, | |
| `handoff.summary must be non-empty; got ${JSON.stringify(handoff!.summary)}` | |
| ); | |
| // ββ Cleanup βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| unregisterCodexConnection(codexConnId); | |
| }); | |
| // ββ Test 2 (CONTROL): non-codex provider never triggers the codex block βββββββ | |
| // | |
| // The codex block gates on `provider === "codex"`. A combo targeting openai | |
| // will never enter it, so the codex usage URL is never fetched and there is no | |
| // quota-derived expiresAt in the DB. | |
| // | |
| // This proves the primary test is not trivially green (i.e., the block is | |
| // actually gated on `provider === "codex"`). | |
| test("context-relay codex quota handoff: does NOT fire when provider is openai (control)", async () => { | |
| // Seed openai (not codex). | |
| await seedConnection("openai", { apiKey: "sk-openai-control-no-codex-block" }); | |
| await combosDb.createCombo({ | |
| name: CODEX_COMBO_NAME, | |
| strategy: "context-relay", | |
| config: { maxRetries: 0, retryDelayMs: 0, stickyRoundRobinLimit: 1 }, | |
| models: [ | |
| { id: "rc-openai-ctrl", kind: "model", providerId: "openai", model: "gpt-4o-mini" }, | |
| ], | |
| }); | |
| const seenUrls: string[] = []; | |
| globalThis.fetch = async (url: unknown, _init: unknown = {}) => { | |
| seenUrls.push(String(url)); | |
| return buildOpenAIResponse("assistant reply ok"); | |
| }; | |
| const r = await handleChat(codexRequest(true)); | |
| assert.equal(r.status, 200, "openai request must return 200"); | |
| // Give setImmediate time to fire if the block were incorrectly entered. | |
| await new Promise((res) => setTimeout(res, 400)); | |
| // The codex block requires provider === "codex", so it never runs for openai. | |
| // The codex usage URL must NOT have been fetched. | |
| assert.ok( | |
| !seenUrls.includes(CODEX_USAGE_URL), | |
| `codex usage URL must NOT be fetched for openai provider; seen: ${JSON.stringify(seenUrls)}` | |
| ); | |
| // No codex quota handoff record in DB. | |
| // (The universal handoff also does not fire because no prior model is seeded, | |
| // so getLastSessionModel returns null β no model switch detected.) | |
| const handoff = getHandoff(SESSION_ID, CODEX_COMBO_NAME); | |
| assert.equal( | |
| handoff, | |
| null, | |
| "no handoff record must exist when provider is openai (codex block never entered)" | |
| ); | |
| }); | |