Skip to content

Commit a78e1b4

Browse files
author
1bcMax
committed
test: add e2e test for thinking token stripping
1 parent 493ce2e commit a78e1b4

1 file changed

Lines changed: 196 additions & 0 deletions

File tree

test-e2e.mjs

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
#!/usr/bin/env node
2+
/**
3+
* End-to-end test for Kimi thinking token stripping.
4+
*
5+
* 1. Start a mock BlockRun API that returns responses with Kimi thinking tokens
6+
* 2. Start ClawRouter proxy pointing to the mock server
7+
* 3. Send requests through the proxy
8+
* 4. Verify thinking tokens are stripped from responses
9+
*/
10+
11+
import { createServer } from "node:http";
12+
import { startProxy } from "./dist/index.js";
13+
14+
// Test wallet key (for testing only - no real funds)
15+
const TEST_WALLET_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
16+
17+
// Test cases: mock responses with thinking tokens
18+
const TEST_CASES = [
19+
{
20+
name: "Kimi end token in response",
21+
mockResponse: {
22+
id: "chatcmpl-test1",
23+
object: "chat.completion",
24+
created: Date.now(),
25+
model: "moonshot-v1-8k",
26+
choices: [{
27+
index: 0,
28+
message: {
29+
role: "assistant",
30+
content: "sessions_list()<|end▁of▁thinking|>{\"sessions\": [{\"id\": 1}]}"
31+
},
32+
finish_reason: "stop"
33+
}]
34+
},
35+
expectedContent: "sessions_list(){\"sessions\": [{\"id\": 1}]}"
36+
},
37+
{
38+
name: "Full Kimi thinking block",
39+
mockResponse: {
40+
id: "chatcmpl-test2",
41+
object: "chat.completion",
42+
created: Date.now(),
43+
model: "moonshot-v1-8k",
44+
choices: [{
45+
index: 0,
46+
message: {
47+
role: "assistant",
48+
content: "<|begin▁of▁thinking|>Let me think about this...<|end▁of▁thinking|>The answer is 42."
49+
},
50+
finish_reason: "stop"
51+
}]
52+
},
53+
expectedContent: "The answer is 42."
54+
},
55+
{
56+
name: "Standard think tags",
57+
mockResponse: {
58+
id: "chatcmpl-test3",
59+
object: "chat.completion",
60+
created: Date.now(),
61+
model: "gpt-4o",
62+
choices: [{
63+
index: 0,
64+
message: {
65+
role: "assistant",
66+
content: "Hello <think>internal reasoning here</think> world!"
67+
},
68+
finish_reason: "stop"
69+
}]
70+
},
71+
expectedContent: "Hello world!"
72+
},
73+
{
74+
name: "Clean response (no tokens)",
75+
mockResponse: {
76+
id: "chatcmpl-test4",
77+
object: "chat.completion",
78+
created: Date.now(),
79+
model: "gpt-4o",
80+
choices: [{
81+
index: 0,
82+
message: {
83+
role: "assistant",
84+
content: "This is a normal response without any thinking tokens."
85+
},
86+
finish_reason: "stop"
87+
}]
88+
},
89+
expectedContent: "This is a normal response without any thinking tokens."
90+
}
91+
];
92+
93+
let currentTestIndex = 0;
94+
95+
async function runTests() {
96+
console.log("=== ClawRouter E2E Test: Thinking Token Stripping ===\n");
97+
98+
// 1. Start mock BlockRun API server
99+
const mockServer = createServer((req, res) => {
100+
// Skip x402 payment flow - just return 200 directly
101+
const mockResponse = TEST_CASES[currentTestIndex].mockResponse;
102+
res.writeHead(200, { "Content-Type": "application/json" });
103+
res.end(JSON.stringify(mockResponse));
104+
});
105+
106+
await new Promise((resolve) => mockServer.listen(18402, "127.0.0.1", resolve));
107+
console.log("✓ Mock BlockRun API started on port 18402");
108+
109+
// 2. Start ClawRouter proxy pointing to mock server
110+
let proxy;
111+
try {
112+
proxy = await startProxy({
113+
walletKey: TEST_WALLET_KEY,
114+
apiBase: "http://127.0.0.1:18402",
115+
port: 18403,
116+
onReady: (port) => console.log(`✓ ClawRouter proxy started on port ${port}`),
117+
});
118+
} catch (err) {
119+
console.error("Failed to start proxy:", err.message);
120+
mockServer.close();
121+
process.exit(1);
122+
}
123+
124+
console.log("\n--- Running test cases ---\n");
125+
126+
let passed = 0;
127+
let failed = 0;
128+
129+
for (let i = 0; i < TEST_CASES.length; i++) {
130+
currentTestIndex = i;
131+
const tc = TEST_CASES[i];
132+
133+
try {
134+
// Send request through proxy (streaming mode to test SSE conversion)
135+
// Use unique message content to avoid dedup cache
136+
const response = await fetch("http://127.0.0.1:18403/v1/chat/completions", {
137+
method: "POST",
138+
headers: { "Content-Type": "application/json" },
139+
body: JSON.stringify({
140+
model: tc.mockResponse.model,
141+
messages: [{ role: "user", content: `test-${i}-${Date.now()}` }],
142+
stream: true
143+
})
144+
});
145+
146+
if (!response.ok) {
147+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
148+
}
149+
150+
// Read SSE response and extract content
151+
const text = await response.text();
152+
const lines = text.split("\n").filter(l => l.startsWith("data: ") && !l.includes("[DONE]"));
153+
154+
let content = "";
155+
for (const line of lines) {
156+
try {
157+
const data = JSON.parse(line.slice(6));
158+
if (data.choices?.[0]?.delta?.content) {
159+
content += data.choices[0].delta.content;
160+
}
161+
} catch {}
162+
}
163+
164+
// Verify
165+
if (content === tc.expectedContent) {
166+
console.log(`✅ ${tc.name}`);
167+
passed++;
168+
} else {
169+
console.log(`❌ ${tc.name}`);
170+
console.log(` Expected: ${JSON.stringify(tc.expectedContent)}`);
171+
console.log(` Got: ${JSON.stringify(content)}`);
172+
failed++;
173+
}
174+
} catch (err) {
175+
console.log(`❌ ${tc.name}`);
176+
console.log(` Error: ${err.message}`);
177+
failed++;
178+
}
179+
}
180+
181+
console.log("\n=== Results ===");
182+
console.log(`Passed: ${passed}`);
183+
console.log(`Failed: ${failed}`);
184+
console.log(`Total: ${TEST_CASES.length}`);
185+
186+
// Cleanup
187+
await proxy.close();
188+
mockServer.close();
189+
190+
process.exit(failed > 0 ? 1 : 0);
191+
}
192+
193+
runTests().catch(err => {
194+
console.error("Test failed:", err);
195+
process.exit(1);
196+
});

0 commit comments

Comments
 (0)