File size: 5,421 Bytes
88c4c60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// E2E test: verify RTK compression runs for every configured provider/route.
// Each test covers a different source→target translator path.
// Run with: RUN_E2E=1 RTK_E2E_PORT=... RTK_E2E_KEY=... RTK_E2E_LOG=<server stdout file> npm test rtk.multi-provider.e2e.test.js
import { describe, it, expect } from "vitest";
import fs from "node:fs";

const PORT = process.env.RTK_E2E_PORT || "20128";
const BASE = `http://localhost:${PORT}`;
const API_KEY = process.env.RTK_E2E_KEY || "";
const LOG_FILE = process.env.RTK_E2E_LOG || "";

const RUN = process.env.RUN_E2E === "1";
const maybe = RUN ? describe : describe.skip;

function logOffset() {
  if (!LOG_FILE || !fs.existsSync(LOG_FILE)) return 0;
  return fs.statSync(LOG_FILE).size;
}

function readLogSince(offset) {
  if (!LOG_FILE || !fs.existsSync(LOG_FILE)) return "";
  const stat = fs.statSync(LOG_FILE);
  if (stat.size <= offset) return "";
  const fd = fs.openSync(LOG_FILE, "r");
  const buf = Buffer.alloc(stat.size - offset);
  fs.readSync(fd, buf, 0, buf.length, offset);
  fs.closeSync(fd);
  return buf.toString("utf8");
}

function makeBigDiff(fileCount = 2, linesPerFile = 60) {
  const out = [];
  for (let f = 0; f < fileCount; f++) {
    out.push(`diff --git a/src/file${f}.js b/src/file${f}.js`);
    out.push(`index abc${f}..def${f} 100644`);
    out.push(`--- a/src/file${f}.js`);
    out.push(`+++ b/src/file${f}.js`);
    out.push(`@@ -1,${linesPerFile} +1,${linesPerFile} @@`);
    for (let i = 0; i < linesPerFile; i++) {
      out.push(`-const old${f}_${i} = "removed value ${i} padding padding padding";`);
      out.push(`+const new${f}_${i} = "added value ${i} padding padding padding padding";`);
    }
  }
  return out.join("\n");
}

async function sendChat(body) {
  return fetch(`${BASE}/v1/chat/completions`, {
    method: "POST",
    headers: { "content-type": "application/json", "authorization": `Bearer ${API_KEY}` },
    body: JSON.stringify(body)
  });
}

// Wait for server to emit a matching [RTK] log line (race-safe against concurrent traffic).
async function waitForRtkLine({ minBytes, filterName, timeoutMs = 5000 }) {
  const start = Date.now();
  const startOffset = logOffset();
  while (Date.now() - start < timeoutMs) {
    const text = readLogSince(startOffset);
    const matches = [...text.matchAll(/\[RTK\] saved (\d+)B \/ (\d+)B \(([\d.]+)%\) via \[([\w,-]+)\] hits=(\d+)/g)];
    const mine = matches.find(m => Number(m[2]) >= minBytes && m[4].includes(filterName));
    if (mine) {
      return {
        saved: Number(mine[1]),
        total: Number(mine[2]),
        pct: Number(mine[3]),
        filters: mine[4],
        hits: Number(mine[5])
      };
    }
    await new Promise(r => setTimeout(r, 200));
  }
  return null;
}

// Build a chat request with OpenAI-style tool_result carrying large content.
function chatBodyWithDiff(model, diff) {
  return {
    model,
    stream: false,
    max_tokens: 16,
    messages: [
      { role: "user", content: "run git diff" },
      {
        role: "assistant",
        content: null,
        tool_calls: [{ id: "call_1", type: "function", function: { name: "Bash", arguments: JSON.stringify({ command: "git diff" }) } }]
      },
      { role: "tool", tool_call_id: "call_1", content: diff },
      { role: "user", content: "ok" }
    ]
  };
}

// Matrix of routes to cover — one entry per translator target format.
const ROUTES = [
  { name: "claude (cc/* → openai→claude)",        model: "cc/claude-opus-4-7" },
  { name: "codex (cx/* → openai→openai-responses)", model: "cx/gpt-5.4" },
  { name: "antigravity (ag/* → openai→antigravity)", model: "ag/gemini-3-flash" },
  { name: "cursor (cu/* → openai→cursor)",         model: "cu/claude-4.5-sonnet" },
  { name: "kiro (kr/* → openai→kiro)",             model: "kr/claude-sonnet-4.5" },
  { name: "gemini (gemini/* → openai→gemini)",     model: "gemini/gemini-2.5-flash" },
  { name: "deepseek (deepseek/* → openai, passthrough)", model: "deepseek/deepseek-chat" },
  { name: "ollama (ollama/* → openai→ollama)",     model: "ollama/gpt-oss:120b" },
];

maybe("RTK multi-provider E2E", () => {
  it("server reachable and rtkEnabled=true", async () => {
    const health = await fetch(`${BASE}/api/health`);
    expect(health.ok).toBe(true);
    const settings = await fetch(`${BASE}/api/settings`).then(r => r.json());
    expect(settings.rtkEnabled).toBe(true);
  });

  for (const route of ROUTES) {
    it(`compresses git diff for ${route.name}`, async () => {
      const diff = makeBigDiff();
      expect(diff.length).toBeGreaterThan(500);

      const res = await sendChat(chatBodyWithDiff(route.model, diff));
      // Provider may respond with 200/400/401/402/404/429/500 depending on account state.
      // The important thing: proxy must NOT crash (we just need status code).
      expect(res.status).toBeGreaterThanOrEqual(200);
      expect(res.status).toBeLessThan(600);

      if (!LOG_FILE) return;
      const hit = await waitForRtkLine({ minBytes: diff.length, filterName: "git-diff" });
      expect(hit, `[RTK] git-diff log line not found for ${route.name}`).toBeTruthy();
      expect(hit.saved).toBeGreaterThan(500);
      expect(hit.filters).toContain("git-diff");

      // Log actual savings for visibility
      console.log(`  ✓ ${route.name}: saved ${hit.saved}B / ${hit.total}B (${hit.pct}%) filters=${hit.filters}`);
    }, 20000);
  }
});