我会把每一步操作、配置、代码、注意事项全部写全,零基础也能直接跟着做,实现Claude Code 免费调用 MIMO-V2-Pro

前置说明

  1. 核心目标:通过本地代理程序,免费调用 OpenRouter+ ClaudeCode + MIMO-V2-Pro 编程模型

  2. 工具准备:电脑(Windows/Mac/Linux)、网络、10分钟时间

  3. 完全免费:全程无付费、无充值,使用平台免费额度

1.1 注册 OpenRouter(核心聚合平台)

  1. 打开官网:https://openrouter.ai/

  2. 点击右上角 Sign in,用谷歌/邮箱注册

  3. 注册后进入:https://openrouter.ai/settings/keys

  4. 点击 Create API Key,名称随便填(如proxy

  5. 复制生成的API Key(保存好,后面要用)

  •  免费额度:每日有免费调用额度,足够日常编程

1.2 运行代理程序proxy.js

#运行代理程序
node proxy.js

// OpenRouter Transparent Proxy — 转发至 OpenRouter Xiaomi MiMo 免费端点
//
// Xiaomi MiMo 系列模型通过 OpenClaw 渠道在 OpenRouter 上提供免费推理服务。
// 客户端请求需携带 HTTP-Referer / X-OpenRouter-Title 头以识别 OpenClaw 来源。
// 本代理自动注入上述头部,并将请求透传至 openrouter.ai,对上层应用透明。
//
// Usage:
//   node proxy.js
//   → 将目标 base URL 从 https://openrouter.ai 替换为 http://127.0.0.1:8899
//   → 原有 API Path / 请求体 / 返回格式均保持不变

const http = require("http");
const https = require("https");
const { URL } = require("url");

// ─── 配置 ────────────────────────────────────────────────

const LISTEN_HOST = "127.0.0.1";
const LISTEN_PORT = 8899;
const TARGET_PROTOCOL = "https:";
const TARGET_HOSTNAME = "openrouter.ai";
const TARGET_PORT = null;
const SSL_REJECT_UNAUTHORIZED = true;
const VERBOSE = true;

const HEADERS_ADD = {};
const HEADERS_REMOVE = ["host", "x-forwarded-for"];
const HEADERS_OVERRIDE = {
    "HTTP-Referer": "https://openclaw.ai",
    "X-OpenRouter-Title": "OpenClaw",
};

// ─── 日志 ────────────────────────────────────────────────────

const COLORS = {
    reset: "\x1b[0m",
    dim: "\x1b[2m",
    red: "\x1b[31m",
    green: "\x1b[32m",
    yellow: "\x1b[33m",
    blue: "\x1b[34m",
    magenta: "\x1b[35m",
    cyan: "\x1b[36m",
};

function log(level, msg, extra) {
    const ts = new Date().toISOString().slice(11, 23);
    const prefix = {
        info: `${COLORS.green}●${COLORS.reset}`,
        warn: `${COLORS.yellow}●${COLORS.reset}`,
        error: `${COLORS.red}●${COLORS.reset}`,
        proxy: `${COLORS.cyan}→${COLORS.reset}`,
        sse: `${COLORS.magenta}?${COLORS.reset}`,
        ws: `${COLORS.blue}◎${COLORS.reset}`,
    }[level] || " ";

    const line = `${COLORS.dim}${ts}${COLORS.reset} ${prefix} ${msg}`;
    if (extra) {
        console.log(line, extra);
    } else {
        console.log(line);
    }
}

// ─── Header 处理 ─────────────────────────────────────────────

function buildProxyHeaders(originalHeaders) {
    const headers = { ...originalHeaders };

    for (const key of HEADERS_REMOVE) {
        delete headers[key.toLowerCase()];
    }

    for (const [key, val] of Object.entries(HEADERS_ADD)) {
        const lower = key.toLowerCase();
        if (!(lower in headers)) {
            headers[lower] = val;
        }
    }

    for (const [key, val] of Object.entries(HEADERS_OVERRIDE)) {
        headers[key.toLowerCase()] = val;
    }

    return headers;
}

// ─── SSE 检测 ────────────────────────────────────────────────

function isSSEResponse(headers) {
    return (headers["content-type"] || "").includes("text/event-stream");
}

function isStreamingRequest(headers) {
    return (headers["accept"] || "").includes("text/event-stream");
}

// ─── 核心:代理请求 ─────────────────────────────────────────

function proxyRequest(clientReq, clientRes) {
    const targetUrl = new URL(clientReq.url, `${TARGET_PROTOCOL}//${TARGET_HOSTNAME}`);
    if (TARGET_PORT) targetUrl.port = TARGET_PORT;

    const proxyHeaders = buildProxyHeaders(clientReq.headers);
    proxyHeaders["host"] = TARGET_HOSTNAME + (TARGET_PORT ? `:${TARGET_PORT}` : "");
    proxyHeaders["x-forwarded-for"] = clientReq.socket.remoteAddress || "unknown";
    proxyHeaders["x-forwarded-host"] = clientReq.headers.host || "";
    proxyHeaders["x-forwarded-proto"] = "http";

    for (const h of ["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade"]) {
        delete proxyHeaders[h];
    }

    const isSSE = isStreamingRequest(clientReq.headers);
    const protocol = targetUrl.protocol === "https:" ? https : http;

    const proxyOptions = {
        hostname: targetUrl.hostname,
        port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
        path: targetUrl.pathname + targetUrl.search,
        method: clientReq.method,
        headers: proxyHeaders,
        rejectUnauthorized: SSL_REJECT_UNAUTHORIZED,
    };

    if (VERBOSE) {
        const mode = isSSE ? `${COLORS.magenta}[SSE]${COLORS.reset} ` : "";
        log("proxy", `${mode}${clientReq.method} ${COLORS.cyan}${targetUrl.hostname}${COLORS.reset}${targetUrl.pathname}${targetUrl.search}`);
    }

    const proxyReq = protocol.request(proxyOptions, (proxyRes) => {
        const isSSE = isSSEResponse(proxyRes.headers);
        const resHeaders = { ...proxyRes.headers };

        if (isSSE) {
            delete resHeaders["content-length"];
            delete resHeaders["content-encoding"];
            delete resHeaders["cache-control"];
            resHeaders["cache-control"] = "no-cache, no-transform";
            resHeaders["connection"] = "keep-alive";
            resHeaders["x-accel-buffering"] = "no";

            if (VERBOSE) log("sse", `SSE stream opened — ${proxyRes.statusCode}`);
        }

        clientRes.writeHead(proxyRes.statusCode, proxyRes.statusMessage, resHeaders);
        proxyRes.pipe(clientRes);

        if (isSSE && VERBOSE) {
            let buffer = "";
            proxyRes.on("data", (chunk) => {
                buffer += chunk.toString();
                const lines = buffer.split("\n");
                buffer = lines.pop();
                for (const line of lines) {
                    if (line.startsWith("data:")) {
                        const data = line.slice(5).trim();
                        if (data === "[DONE]") {
                            log("sse", `${COLORS.green}Stream ended [DONE]${COLORS.reset}`);
                        } else if (data.length > 0) {
                            const preview = data.length > 80 ? data.slice(0, 80) + "..." : data;
                            log("sse", `${COLORS.dim}data: ${preview}${COLORS.reset}`);
                        }
                    } else if (line.startsWith("event:")) {
                        log("sse", `${COLORS.yellow}${line}${COLORS.reset}`);
                    }
                }
            });
        }

        proxyRes.on("end", () => {
            if (VERBOSE && !isSSE) {
                log("info", `Response complete — ${proxyRes.statusCode} ${clientReq.method} ${targetUrl.pathname}`);
            }
        });

        proxyRes.on("error", (err) => {
            log("error", `Proxy response error: ${err.message}`);
            if (!clientRes.headersSent) clientRes.writeHead(502, { "Content-Type": "text/plain" });
            clientRes.end("Bad Gateway: upstream response error");
        });
    });

    clientReq.pipe(proxyReq);

    clientReq.on("error", (err) => {
        log("error", `Client request error: ${err.message}`);
        proxyReq.destroy();
    });

    proxyReq.on("error", (err) => {
        log("error", `Proxy request error: ${err.message}`);
        if (!clientRes.headersSent) clientRes.writeHead(502, { "Content-Type": "text/plain" });
        clientRes.end(`Bad Gateway: ${err.message}`);
    });

    proxyReq.setTimeout(isSSE ? 0 : 60000, () => {
        log("warn", "Proxy request timeout");
        proxyReq.destroy();
    });
}

// ─── WebSocket 代理 ──────────────────────────────────────────

function proxyWebSocket(clientReq, clientSocket, clientHead) {
    const targetUrl = new URL(clientReq.url, `ws://${TARGET_HOSTNAME}`);
    if (TARGET_PORT) targetUrl.port = TARGET_PORT;

    const proxyHeaders = buildProxyHeaders(clientReq.headers);
    proxyHeaders["host"] = TARGET_HOSTNAME + (TARGET_PORT ? `:${TARGET_PORT}` : "");

    delete proxyHeaders["sec-websocket-key"];
    delete proxyHeaders["sec-websocket-version"];
    delete proxyHeaders["sec-websocket-extensions"];

    const protocol = targetUrl.protocol === "wss:" ? https : http;

    const proxyReq = protocol.request({
        hostname: targetUrl.hostname,
        port: targetUrl.port || (targetUrl.protocol === "wss:" ? 443 : 80),
        path: targetUrl.pathname + targetUrl.search,
        method: "GET",
        headers: {
            ...proxyHeaders,
            upgrade: "websocket",
            connection: "Upgrade",
            "sec-websocket-key": clientReq.headers["sec-websocket-key"] || "",
            "sec-websocket-version": clientReq.headers["sec-websocket-version"] || "13",
            "sec-websocket-extensions": clientReq.headers["sec-websocket-extensions"] || "",
        },
    });

    proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
        if (VERBOSE) log("ws", `WebSocket connected → ${targetUrl.hostname}${targetUrl.pathname}`);

        clientSocket.write(
            `HTTP/1.1 101 Switching Protocols\r\n` +
            Object.entries(proxyRes.headers).map(([k, v]) => `${k}: ${v}`).join("\r\n") +
            "\r\n\r\n"
        );

        if (clientHead && clientHead.length) proxySocket.write(clientHead);
        proxySocket.pipe(clientSocket);
        clientSocket.pipe(proxySocket);
        proxySocket.on("error", () => clientSocket.destroy());
        clientSocket.on("error", () => proxySocket.destroy());
    });

    proxyReq.on("error", (err) => {
        log("error", `WebSocket proxy error: ${err.message}`);
        clientSocket.end();
    });

    proxyReq.end();
}

// ─── 启动服务器 ──────────────────────────────────────────────

const server = http.createServer((req, res) => proxyRequest(req, res));

server.on("upgrade", (req, socket, head) => {
    if (req.headers.upgrade?.toLowerCase() === "websocket") {
        proxyWebSocket(req, socket, head);
    } else {
        socket.end();
    }
});

server.on("clientError", (err, socket) => {
    if (err.code === "ECONNRESET" || !socket.writable) return;
    socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
});

server.listen(LISTEN_PORT, LISTEN_HOST, () => {
    const target = `${TARGET_PROTOCOL}//${TARGET_HOSTNAME}`;
    console.log(`
${COLORS.cyan}╔══════════════════════════════════════════════════╗
║          Transparent Proxy — 透明代理            ║
╚══════════════════════════════════════════════════╝${COLORS.reset}
  ${COLORS.green}●${COLORS.reset} Listening:  ${COLORS.yellow}http://${LISTEN_HOST}:${LISTEN_PORT}${COLORS.reset}
  ${COLORS.green}●${COLORS.reset} Target:     ${COLORS.cyan}${target}${COLORS.reset}
  ${COLORS.dim}Test:${COLORS.reset}
    curl http://localhost:${LISTEN_PORT}/your/path
`);
});

const shutdown = () => {
    log("info", "Shutting down...");
    server.close(() => process.exit(0));
    setTimeout(() => process.exit(1), 5000);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);

1.3 修改Claude Code for VS Code配置信息
将可以key内容修改为第一步注册创建key

好的,准备工作完成,就开始开始了ai编程了。


 


OpenRouter的日志模块可以清晰地看到调用情况,如下图:



这里要特别感谢雷总,又给我们免费试用一周mimo大模型

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐