| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- import { mkdtemp, rm } from 'node:fs/promises';
- import { tmpdir } from 'node:os';
- import { join } from 'node:path';
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
- import { chatBridge } from './chatBridgeService';
- import { CodexRuntime, type CodexProviderSpec } from './codexRuntime';
- /**
- * 真机复现(集成冒烟,不进 npm test):生产同一条链路打真实自部署端点。
- * chatBridge(Responses→Chat 翻译) → 真实 codex app-server → 局域网 llama.cpp
- *
- * 存在的理由:客户端日志里每 ~15 秒一条 `request timed out` 重试、5 次后回落 HTTP,
- * 而桥接/超时/WebSocket/排队四个假设都用受控 mock 否掉了 —— 剩下的只能在这台真端点上验。
- * 端点不可达时整组跳过,不让 smoke 变成网络探测。
- *
- * 断言的是「健康的一轮应当是什么样」:上游只被问一次、Codex 一条重试都不产生、能拿到回复。
- *
- * 跑法:npm run smoke:codex -- electron/service/codex/chatBridge.live.itest.ts
- */
- const UPSTREAM_BASE = process.env.ZSJZ_LIVE_UPSTREAM ?? 'http://10.66.66.66:8080/v1';
- const MODEL = process.env.ZSJZ_LIVE_MODEL ?? '/home/rdd/llama/model/Ornith-1.5-35B-Q5_K_M.gguf';
- const HEALTH_URL = UPSTREAM_BASE.replace(/\/v\d+$/u, '') + '/health';
- let home = '';
- let cwd = '';
- const bridgeLines: string[] = [];
- const codexLines: string[] = [];
- async function probe(url: string, init?: RequestInit): Promise<{ ok: boolean; ms: number; status: number | null; error: string }> {
- const startedAt = Date.now();
- try {
- const res = await fetch(url, { ...init, signal: AbortSignal.timeout(5_000) });
- return { ok: res.ok, ms: Date.now() - startedAt, status: res.status, error: '' };
- } catch (error) {
- return { ok: false, ms: Date.now() - startedAt, status: null, error: error instanceof Error ? error.message : String(error) };
- }
- }
- /**
- * 可达性必须在模块顶层测:describe.skipIf 在收集阶段就求值,
- * 放进 beforeAll 里会永远是初始值、整组被静默跳过。
- */
- const health = await probe(HEALTH_URL);
- const reachable = health.ok;
- beforeAll(async () => {
- home = await mkdtemp(join(tmpdir(), 'zsjz-live-home-'));
- cwd = await mkdtemp(join(tmpdir(), 'zsjz-live-cwd-'));
- chatBridge.setDiagnostic((line) => bridgeLines.push(line));
- });
- afterAll(async () => {
- await chatBridge.stop().catch(() => undefined);
- await rm(home, { recursive: true, force: true }).catch(() => undefined);
- await rm(cwd, { recursive: true, force: true }).catch(() => undefined);
- });
- const retryLines = (): string[] => codexLines.filter((line) => /retrying sampling request/u.test(line));
- const disconnectLines = (): string[] => bridgeLines.filter((line) => /提前断开|转发完成|上游 HTTP|不可达/u.test(line));
- describe.skipIf(!reachable)('真机:自部署 Chat 端点跑一整轮', () => {
- it('上游只被问一次、Codex 不重试、拿到回复', async () => {
- const bridge = await chatBridge.start({ upstreamBaseUrl: UPSTREAM_BASE, headers: null });
- const spec: CodexProviderSpec = { id: 'zsjz', name: '真机冒烟', baseUrl: bridge.url, model: MODEL };
- const runtime = new CodexRuntime({ codexHome: home });
- runtime.on('diagnostic', (line: string) => codexLines.push(line));
- const startedAt = Date.now();
- let text = '';
- let failure = '';
- try {
- await runtime.applyProvider(spec);
- const threadId = await runtime.startThread({ cwd });
- const result = await runtime.runTurn({ threadId, prompt: '用一句话说明什么是资金流向分析', cwd, timeoutMs: 420_000 });
- text = result.text;
- } catch (error) {
- failure = error instanceof Error ? error.message : String(error);
- } finally {
- await runtime.dispose();
- }
- const elapsed = (Date.now() - startedAt) / 1000;
- console.log(`[真机] 耗时=${elapsed.toFixed(1)}s 失败=${failure || '无'}`);
- console.log(`[真机] 回复=${text.slice(0, 80)}`);
- console.log(`[真机] Codex 重试行=${retryLines().length}`);
- console.log('[真机] 桥接:\n ' + bridgeLines.join('\n '));
- console.log('[真机] Codex 关键诊断:\n ' + disconnectLines().join('\n '));
- expect(failure).toBe('');
- expect(text.length).toBeGreaterThan(0);
- expect(retryLines().length).toBe(0);
- // 一次正常的轮次不该让桥接把同一份请求重发给上游
- expect(bridgeLines.filter((line) => /起 round/u.test(line)).length).toBe(1);
- }, 480_000);
- });
|