Spaces:
Runtime error
Runtime error
| import { describe, it, before } from "node:test"; | |
| import assert from "node:assert"; | |
| import { MimocodeExecutor, generateFingerprint } from "../../open-sse/executors/mimocode.ts"; | |
| const PROXY_URL = process.env.MIMOCODE_SOCKS5_PROXY; | |
| function parseProxyUrl(url: string): { type: string; host: string; port: number } | null { | |
| try { | |
| const parsed = new URL(url); | |
| return { type: parsed.protocol.replace(":", ""), host: parsed.hostname, port: parsed.port ? Number(parsed.port) : 1080 }; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| function requireProxy() { | |
| if (!PROXY_URL) { | |
| return false; | |
| } | |
| const parsed = parseProxyUrl(PROXY_URL); | |
| return parsed !== null; | |
| } | |
| const proxyConfig = PROXY_URL ? parseProxyUrl(PROXY_URL) : null; | |
| describe("mimocode per-account proxy — SOCKS5 integration", { timeout: 30_000 }, () => { | |
| before(() => { | |
| if (!PROXY_URL) { | |
| console.log("# MIMOCODE_SOCKS5_PROXY not set, skipping live proxy tests"); | |
| } | |
| }); | |
| it("bootstrap returns JWT through configured proxy", { skip: !requireProxy() ? "MIMOCODE_SOCKS5_PROXY not set" : false }, async () => { | |
| process.env.ENABLE_SOCKS5_PROXY = "true"; | |
| const { Socks5ProxyAgent } = await import("undici"); | |
| const agent = new Socks5ProxyAgent(PROXY_URL!); | |
| const fp = generateFingerprint("integration-bootstrap-" + Date.now()); | |
| const resp = await fetch("https://api.xiaomimimo.com/api/free-ai/bootstrap", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ client: fp }), | |
| // @ts-expect-error — undici dispatcher | |
| dispatcher: agent, | |
| signal: AbortSignal.timeout(15_000), | |
| }); | |
| assert.strictEqual(resp.status, 200, `Bootstrap through proxy: expected 200, got ${resp.status}`); | |
| const data = await resp.json(); | |
| assert.ok(data.jwt, "Response should contain JWT"); | |
| assert.ok(typeof data.jwt === "string" && data.jwt.length > 10, "JWT should be a non-trivial string"); | |
| }); | |
| it("chat request succeeds through configured proxy", { skip: !requireProxy() ? "MIMOCODE_SOCKS5_PROXY not set" : false }, async () => { | |
| process.env.ENABLE_SOCKS5_PROXY = "true"; | |
| const { Socks5ProxyAgent } = await import("undici"); | |
| const agent = new Socks5ProxyAgent(PROXY_URL!); | |
| const fp = generateFingerprint("integration-chat-" + Date.now()); | |
| const bootstrapResp = await fetch("https://api.xiaomimimo.com/api/free-ai/bootstrap", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ client: fp }), | |
| // @ts-expect-error — undici dispatcher | |
| dispatcher: agent, | |
| signal: AbortSignal.timeout(15_000), | |
| }); | |
| assert.strictEqual(bootstrapResp.status, 200); | |
| const { jwt } = await bootstrapResp.json(); | |
| const chatResp = await fetch("https://api.xiaomimimo.com/api/free-ai/openai/chat", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Authorization": `Bearer ${jwt}`, | |
| "X-Mimo-Source": "mimocode-cli-free", | |
| }, | |
| body: JSON.stringify({ | |
| model: "mimo-auto", | |
| messages: [ | |
| { role: "system", content: "You are MiMoCode, an interactive CLI tool that helps users with software engineering tasks." }, | |
| { role: "user", content: "Say exactly: proxy-integration-ok" }, | |
| ], | |
| stream: false, | |
| }), | |
| // @ts-expect-error — undici dispatcher | |
| dispatcher: agent, | |
| signal: AbortSignal.timeout(20_000), | |
| }); | |
| assert.ok(chatResp.status === 200 || chatResp.status === 429, | |
| `Chat through proxy: expected 200/429, got ${chatResp.status}`); | |
| }); | |
| it("accounts carry proxy config after sync", () => { | |
| const exec = new MimocodeExecutor(); | |
| const fp = "integration-fp-1"; | |
| const cfg = proxyConfig || { type: "socks5", host: "127.0.0.1", port: 1080 }; | |
| (exec as any).accounts = [ | |
| { fingerprint: fp, jwt: "", expiresAt: 0, cooldownUntil: 0, consecutiveFails: 0, proxy: null }, | |
| ]; | |
| (exec as any).nextAccountIdx = 0; | |
| (exec as any).syncAccountsFromCredentials({ | |
| providerSpecificData: { | |
| accountProxies: [{ fingerprint: fp, proxy: cfg }], | |
| }, | |
| }); | |
| const acct = (exec as any).accounts.find((a: any) => a.fingerprint === fp); | |
| assert.ok(acct, "Account should exist"); | |
| assert.deepStrictEqual(acct.proxy, cfg); | |
| }); | |
| it("two accounts with different proxies tracked independently", () => { | |
| const exec = new MimocodeExecutor(); | |
| const fp1 = "integration-fp-a"; | |
| const fp2 = "integration-fp-b"; | |
| const proxy1 = { type: "http" as const, host: "proxy-a.example.com", port: 8080 }; | |
| const proxy2 = { type: "socks5" as const, host: "proxy-b.example.com", port: 1080 }; | |
| (exec as any).accounts = [ | |
| { fingerprint: fp1, jwt: "", expiresAt: 0, cooldownUntil: 0, consecutiveFails: 0, proxy: null }, | |
| { fingerprint: fp2, jwt: "", expiresAt: 0, cooldownUntil: 0, consecutiveFails: 0, proxy: null }, | |
| ]; | |
| (exec as any).nextAccountIdx = 0; | |
| (exec as any).syncAccountsFromCredentials({ | |
| providerSpecificData: { | |
| accountProxies: [ | |
| { fingerprint: fp1, proxy: proxy1 }, | |
| { fingerprint: fp2, proxy: proxy2 }, | |
| ], | |
| }, | |
| }); | |
| const a1 = (exec as any).accounts.find((a: any) => a.fingerprint === fp1); | |
| const a2 = (exec as any).accounts.find((a: any) => a.fingerprint === fp2); | |
| assert.deepStrictEqual(a1.proxy, proxy1, "Account 1 should have proxy1"); | |
| assert.deepStrictEqual(a2.proxy, proxy2, "Account 2 should have proxy2"); | |
| assert.notDeepStrictEqual(a1.proxy, a2.proxy, "Proxies should differ"); | |
| }); | |
| it("no accountProxies keeps all proxies null (backward compat)", () => { | |
| const exec = new MimocodeExecutor(); | |
| const accounts = (exec as any).accounts; | |
| assert.ok(accounts.length >= 1); | |
| for (const acct of accounts) { | |
| assert.strictEqual(acct.proxy, null, "Default account proxy should be null"); | |
| } | |
| }); | |
| }); | |