mimo2codex 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+ import { buildConfig, parseArgv } from "./config.js";
3
+ import { startServer } from "./server.js";
4
+ import { setVerbose, log, redactKey } from "./util/log.js";
5
+ const VERSION = "0.1.0";
6
+ const HELP = `mimo2codex v${VERSION} — local proxy: Codex Responses API → Xiaomi MiMo Chat Completions
7
+
8
+ USAGE
9
+ mimo2codex [options]
10
+ mimo2codex print-config
11
+ mimo2codex print-cc-switch
12
+
13
+ OPTIONS
14
+ -p, --port <n> listen port (default: 8788, env: MIMO2CODEX_PORT)
15
+ --host <h> bind host (default: 127.0.0.1, env: MIMO2CODEX_HOST)
16
+ --base-url <url> MiMo base url (default: https://api.xiaomimimo.com/v1, env: MIMO_BASE_URL)
17
+ --api-key <key> MiMo api key (env: MIMO_API_KEY) — required
18
+ --no-reasoning hide MiMo reasoning_content from Codex (still re-injected for multi-turn quality)
19
+ --reasoning force reasoning passthrough (default)
20
+ -v, --verbose log every request (env: MIMO2CODEX_VERBOSE=1)
21
+ -V, --version print version
22
+ -h, --help show this help
23
+
24
+ SUBCOMMANDS
25
+ print-config print ~/.codex/auth.json + config.toml snippets (default;
26
+ works for Codex CLI and desktop app)
27
+ print-config --env-key print env-var-based variant (Codex CLI only — desktop app
28
+ will NOT see shell env vars set via export/setx)
29
+ print-cc-switch print auth.json + config.toml snippets for the cc-switch
30
+ desktop app (https://github.com/farion1231/cc-switch)
31
+
32
+ EXAMPLES
33
+ MIMO_API_KEY=sk-... mimo2codex
34
+ mimo2codex --port 9000 --base-url https://token-plan-cn.xiaomimimo.com/v1
35
+ mimo2codex print-config > codex-mimo.toml
36
+ mimo2codex print-config --env-key # legacy env-var variant
37
+ mimo2codex print-cc-switch
38
+ `;
39
+ // Default snippet — uses ~/.codex/auth.json + requires_openai_auth = true.
40
+ // This avoids the common "Missing environment variable: MIMO2CODEX_KEY" error
41
+ // on the Codex desktop app, which doesn't inherit shell env vars set via
42
+ // `export` or `setx`. Works for both CLI and desktop with no env setup.
43
+ function configSnippet(cfg) {
44
+ return `# Step 1 — write ~/.codex/auth.json (Windows: %USERPROFILE%\\.codex\\auth.json)
45
+ # Any non-empty value works; mimo2codex does not validate inbound credentials.
46
+ {
47
+ "OPENAI_API_KEY": "mimo2codex-local"
48
+ }
49
+
50
+ # Step 2 — append to ~/.codex/config.toml (Windows: %USERPROFILE%\\.codex\\config.toml)
51
+ model = "mimo-v2.5-pro"
52
+ model_provider = "mimo"
53
+
54
+ [model_providers.mimo]
55
+ name = "MiMo (via mimo2codex)"
56
+ base_url = "http://${cfg.host}:${cfg.port}/v1"
57
+ wire_api = "responses"
58
+ requires_openai_auth = true
59
+ request_max_retries = 1
60
+
61
+ # Step 3 — completely quit and restart Codex (the desktop app must be relaunched
62
+ # for the new auth.json to be picked up). Then run \`codex\` and pick this provider.
63
+
64
+ # ⚠️ If you also use Codex with your real OpenAI account, this auth.json overwrites
65
+ # your OpenAI login. Use cc-switch (\`mimo2codex print-cc-switch\`) instead to switch
66
+ # between providers cleanly, or use \`mimo2codex print-config --env-key\` for the
67
+ # env-var-based variant (works for Codex CLI but not the desktop app).
68
+ `;
69
+ }
70
+ // Legacy env_key variant — keeps ~/.codex/auth.json untouched (preserving any
71
+ // existing OpenAI login). Requires MIMO2CODEX_KEY to be set in the environment
72
+ // of the process running \`codex\`. Codex DESKTOP APP does not inherit shell env
73
+ // vars on macOS/Windows, so this variant only works reliably for the CLI.
74
+ function configSnippetEnvKey(cfg) {
75
+ return `# ~/.codex/config.toml — env-var variant (Codex CLI only; desktop app won't see shell env vars)
76
+ model = "mimo-v2.5-pro"
77
+ model_provider = "mimo"
78
+
79
+ [model_providers.mimo]
80
+ name = "MiMo (via mimo2codex)"
81
+ base_url = "http://${cfg.host}:${cfg.port}/v1"
82
+ wire_api = "responses"
83
+ env_key = "MIMO2CODEX_KEY"
84
+ request_max_retries = 1
85
+
86
+ # Then in your shell (the same shell you launch \`codex\` from):
87
+ # export MIMO2CODEX_KEY=anything # macOS/Linux/Git Bash
88
+ # $env:MIMO2CODEX_KEY="anything" # Windows PowerShell
89
+ # set MIMO2CODEX_KEY=anything # Windows CMD (current session only)
90
+ #
91
+ # For Codex DESKTOP APP, this variant does NOT work — desktop apps launched from
92
+ # Finder/Start Menu don't inherit shell env vars. Use the default print-config
93
+ # (auth.json variant) or \`mimo2codex print-cc-switch\` instead.
94
+ `;
95
+ }
96
+ // cc-switch (https://github.com/farion1231/cc-switch) is a desktop app that
97
+ // manages multiple Codex providers via a "+" → "Custom" panel. It writes
98
+ // ~/.codex/auth.json + ~/.codex/config.toml when you switch providers.
99
+ // This subcommand prints both snippets in a copy-pasteable form so users can
100
+ // add mimo2codex as a custom Codex provider in cc-switch.
101
+ function ccSwitchSnippet(cfg) {
102
+ const authJson = JSON.stringify({ OPENAI_API_KEY: "mimo2codex-local" }, null, 2);
103
+ const configToml = `model_provider = "mimo2codex"
104
+ model = "mimo-v2.5-pro"
105
+
106
+ [model_providers.mimo2codex]
107
+ name = "MiMo (via mimo2codex)"
108
+ base_url = "http://${cfg.host}:${cfg.port}/v1"
109
+ wire_api = "responses"
110
+ requires_openai_auth = true
111
+ request_max_retries = 1
112
+ `;
113
+ return `# cc-switch — Add Provider → Codex tab → Custom
114
+
115
+ # ───────── auth.json (paste into the auth.json textarea) ─────────
116
+ ${authJson}
117
+
118
+ # ───────── config.toml (paste into the config.toml textarea) ─────────
119
+ ${configToml}
120
+ # Note: OPENAI_API_KEY can be any non-empty string — mimo2codex does not
121
+ # validate inbound credentials. Your real MiMo key stays in MIMO_API_KEY
122
+ # on the machine running mimo2codex.
123
+ `;
124
+ }
125
+ function printStartupBanner(cfg) {
126
+ // eslint-disable-next-line no-console
127
+ console.log(`mimo2codex v${VERSION} listening on http://${cfg.host}:${cfg.port}`);
128
+ // eslint-disable-next-line no-console
129
+ console.log(`upstream: ${cfg.baseUrl}`);
130
+ // eslint-disable-next-line no-console
131
+ console.log(`api key: ${redactKey(cfg.apiKey)}`);
132
+ // eslint-disable-next-line no-console
133
+ console.log(`reasoning: ${cfg.exposeReasoning ? "passthrough" : "hidden"}`);
134
+ // eslint-disable-next-line no-console
135
+ console.log("");
136
+ // eslint-disable-next-line no-console
137
+ console.log(configSnippet({ host: cfg.host, port: cfg.port }));
138
+ }
139
+ function main() {
140
+ let parsed;
141
+ try {
142
+ parsed = parseArgv(process.argv.slice(2));
143
+ }
144
+ catch (err) {
145
+ // eslint-disable-next-line no-console
146
+ console.error(`error: ${err.message}`);
147
+ process.exit(2);
148
+ }
149
+ if (parsed.showHelp) {
150
+ // eslint-disable-next-line no-console
151
+ console.log(HELP);
152
+ return;
153
+ }
154
+ if (parsed.showVersion) {
155
+ // eslint-disable-next-line no-console
156
+ console.log(VERSION);
157
+ return;
158
+ }
159
+ if (parsed.positional[0] === "print-config") {
160
+ const host = parsed.host ?? "127.0.0.1";
161
+ const port = parsed.port ?? 8788;
162
+ const useEnvKey = parsed.envKey === true;
163
+ // eslint-disable-next-line no-console
164
+ console.log(useEnvKey ? configSnippetEnvKey({ host, port }) : configSnippet({ host, port }));
165
+ return;
166
+ }
167
+ if (parsed.positional[0] === "print-cc-switch") {
168
+ const host = parsed.host ?? "127.0.0.1";
169
+ const port = parsed.port ?? 8788;
170
+ // eslint-disable-next-line no-console
171
+ console.log(ccSwitchSnippet({ host, port }));
172
+ return;
173
+ }
174
+ let cfg;
175
+ try {
176
+ cfg = buildConfig(parsed, process.env, VERSION);
177
+ }
178
+ catch (err) {
179
+ // eslint-disable-next-line no-console
180
+ console.error(`error: ${err.message}`);
181
+ process.exit(2);
182
+ }
183
+ setVerbose(cfg.verbose);
184
+ printStartupBanner(cfg);
185
+ const server = startServer(cfg);
186
+ server.on("listening", () => {
187
+ log.debug("server listening");
188
+ });
189
+ server.on("error", (err) => {
190
+ log.error("server error", { error: err.message });
191
+ process.exit(1);
192
+ });
193
+ const shutdown = (sig) => {
194
+ log.info(`received ${sig}, shutting down`);
195
+ server.close(() => process.exit(0));
196
+ setTimeout(() => process.exit(1), 5000).unref();
197
+ };
198
+ process.on("SIGINT", () => shutdown("SIGINT"));
199
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
200
+ }
201
+ main();
202
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/config.js ADDED
@@ -0,0 +1,86 @@
1
+ const DEFAULTS = {
2
+ host: "127.0.0.1",
3
+ port: 8788,
4
+ baseUrl: "https://api.xiaomimimo.com/v1",
5
+ };
6
+ export function parseArgv(argv) {
7
+ const out = { positional: [], showHelp: false, showVersion: false };
8
+ for (let i = 0; i < argv.length; i++) {
9
+ const a = argv[i];
10
+ const next = () => {
11
+ const v = argv[i + 1];
12
+ if (v === undefined)
13
+ throw new Error(`flag ${a} requires a value`);
14
+ i++;
15
+ return v;
16
+ };
17
+ switch (a) {
18
+ case "--port":
19
+ case "-p":
20
+ out.port = Number(next());
21
+ if (Number.isNaN(out.port))
22
+ throw new Error("--port must be a number");
23
+ break;
24
+ case "--host":
25
+ out.host = next();
26
+ break;
27
+ case "--base-url":
28
+ case "--baseurl":
29
+ out.baseUrl = next();
30
+ break;
31
+ case "--api-key":
32
+ out.apiKey = next();
33
+ break;
34
+ case "--no-reasoning":
35
+ out.exposeReasoning = false;
36
+ break;
37
+ case "--reasoning":
38
+ out.exposeReasoning = true;
39
+ break;
40
+ case "--verbose":
41
+ case "-v":
42
+ out.verbose = true;
43
+ break;
44
+ case "--env-key":
45
+ out.envKey = true;
46
+ break;
47
+ case "--help":
48
+ case "-h":
49
+ out.showHelp = true;
50
+ break;
51
+ case "--version":
52
+ case "-V":
53
+ out.showVersion = true;
54
+ break;
55
+ default:
56
+ if (a.startsWith("--")) {
57
+ throw new Error(`unknown flag: ${a}`);
58
+ }
59
+ out.positional.push(a);
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+ export function buildConfig(parsed, env, version) {
65
+ const exposeReasoningEnv = env.MIMO2CODEX_NO_REASONING ? false : true;
66
+ const verboseEnv = !!env.MIMO2CODEX_VERBOSE;
67
+ const apiKey = parsed.apiKey ?? env.MIMO_API_KEY ?? "";
68
+ if (!apiKey) {
69
+ throw new Error("missing MiMo API key — set MIMO_API_KEY env var or pass --api-key. " +
70
+ "Get one at https://platform.xiaomimimo.com/#/console/api-keys");
71
+ }
72
+ const portFromEnv = env.MIMO2CODEX_PORT ? Number(env.MIMO2CODEX_PORT) : undefined;
73
+ if (portFromEnv !== undefined && Number.isNaN(portFromEnv)) {
74
+ throw new Error("MIMO2CODEX_PORT must be a number");
75
+ }
76
+ return {
77
+ host: parsed.host ?? env.MIMO2CODEX_HOST ?? DEFAULTS.host,
78
+ port: parsed.port ?? portFromEnv ?? DEFAULTS.port,
79
+ baseUrl: parsed.baseUrl ?? env.MIMO_BASE_URL ?? DEFAULTS.baseUrl,
80
+ apiKey,
81
+ exposeReasoning: parsed.exposeReasoning ?? exposeReasoningEnv,
82
+ verbose: parsed.verbose ?? verboseEnv,
83
+ userAgent: `mimo2codex/${version}`,
84
+ };
85
+ }
86
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/server.js ADDED
@@ -0,0 +1,161 @@
1
+ import { createServer } from "node:http";
2
+ import { reqToChat } from "./translate/reqToChat.js";
3
+ import { respToResponses } from "./translate/respToResponses.js";
4
+ import { pipeChatStreamToResponses } from "./translate/streamToSse.js";
5
+ import { iterChatStreamChunks } from "./upstream/chatStream.js";
6
+ import { callMimo, UpstreamError } from "./upstream/mimoClient.js";
7
+ import { makeServerResponseSink } from "./util/sse.js";
8
+ import { log } from "./util/log.js";
9
+ const KEEPALIVE_INTERVAL_MS = 15_000;
10
+ async function readJsonBody(req, maxBytes = 16 * 1024 * 1024) {
11
+ return await new Promise((resolve, reject) => {
12
+ const chunks = [];
13
+ let total = 0;
14
+ req.on("data", (chunk) => {
15
+ total += chunk.length;
16
+ if (total > maxBytes) {
17
+ reject(new Error("request body too large"));
18
+ req.destroy();
19
+ return;
20
+ }
21
+ chunks.push(chunk);
22
+ });
23
+ req.on("end", () => {
24
+ try {
25
+ const text = Buffer.concat(chunks).toString("utf-8");
26
+ if (!text)
27
+ return resolve({});
28
+ resolve(JSON.parse(text));
29
+ }
30
+ catch (err) {
31
+ reject(err);
32
+ }
33
+ });
34
+ req.on("error", reject);
35
+ });
36
+ }
37
+ function sendJson(res, status, body) {
38
+ res.statusCode = status;
39
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
40
+ res.end(JSON.stringify(body));
41
+ }
42
+ function errorEnvelope(status, code, message) {
43
+ return {
44
+ error: {
45
+ type: status === 401
46
+ ? "authentication_error"
47
+ : status === 429
48
+ ? "rate_limit_exceeded"
49
+ : status >= 500
50
+ ? "server_error"
51
+ : "invalid_request_error",
52
+ code,
53
+ message,
54
+ status,
55
+ },
56
+ };
57
+ }
58
+ async function handleResponses(cfg, req, res) {
59
+ let payload;
60
+ try {
61
+ payload = await readJsonBody(req);
62
+ }
63
+ catch (err) {
64
+ return sendJson(res, 400, errorEnvelope(400, "invalid_json", `failed to parse request body: ${err.message}`));
65
+ }
66
+ if (!payload.model) {
67
+ return sendJson(res, 400, errorEnvelope(400, "missing_model", "request body must include 'model'"));
68
+ }
69
+ const chat = reqToChat(payload);
70
+ const stream = !!payload.stream;
71
+ chat.stream = stream;
72
+ const ac = new AbortController();
73
+ req.on("close", () => ac.abort());
74
+ if (!stream) {
75
+ try {
76
+ const upstreamRes = await callMimo({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey, userAgent: cfg.userAgent }, chat, ac.signal);
77
+ const chatJson = (await upstreamRes.json());
78
+ const responses = respToResponses(chatJson, payload, {
79
+ exposeReasoning: cfg.exposeReasoning,
80
+ });
81
+ return sendJson(res, 200, responses);
82
+ }
83
+ catch (err) {
84
+ if (err instanceof UpstreamError) {
85
+ return sendJson(res, err.status, errorEnvelope(err.status, err.code, err.message));
86
+ }
87
+ log.error("non-stream request failed", { error: err.message });
88
+ return sendJson(res, 500, errorEnvelope(500, "internal_error", err.message));
89
+ }
90
+ }
91
+ // Streaming path. Strategy: don't open the SSE stream to the client until we
92
+ // know the upstream is OK. This way upstream errors map to clean HTTP errors
93
+ // instead of half-opened SSE streams that confuse the Codex client.
94
+ let upstreamRes;
95
+ try {
96
+ upstreamRes = await callMimo({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey, userAgent: cfg.userAgent }, chat, ac.signal);
97
+ }
98
+ catch (err) {
99
+ if (err instanceof UpstreamError) {
100
+ return sendJson(res, err.status, errorEnvelope(err.status, err.code, err.message));
101
+ }
102
+ log.error("stream request failed (pre-stream)", { error: err.message });
103
+ return sendJson(res, 500, errorEnvelope(500, "internal_error", err.message));
104
+ }
105
+ // Upstream returned 200 — now we can safely open the SSE stream.
106
+ const sink = makeServerResponseSink(res);
107
+ const keepalive = setInterval(() => sink.comment("keepalive"), KEEPALIVE_INTERVAL_MS);
108
+ res.on("close", () => clearInterval(keepalive));
109
+ try {
110
+ const chunks = iterChatStreamChunks(upstreamRes);
111
+ await pipeChatStreamToResponses(sink, { chunks }, payload, { exposeReasoning: cfg.exposeReasoning });
112
+ }
113
+ catch (err) {
114
+ log.error("stream request failed (mid-stream)", { error: err.message });
115
+ // pipeChatStreamToResponses handles its own errors with response.failed,
116
+ // so reaching here means something unexpected in our own code.
117
+ if (!sink.closed()) {
118
+ sink.write("error", {
119
+ type: "error",
120
+ code: "server_error",
121
+ message: err.message,
122
+ sequence_number: 9999,
123
+ });
124
+ sink.end();
125
+ }
126
+ }
127
+ finally {
128
+ clearInterval(keepalive);
129
+ }
130
+ }
131
+ function handleModels(res) {
132
+ sendJson(res, 200, {
133
+ object: "list",
134
+ data: [
135
+ { id: "mimo-v2.5-pro", object: "model", owned_by: "xiaomi" },
136
+ { id: "mimo-v2.5-pro[1m]", object: "model", owned_by: "xiaomi" },
137
+ { id: "mimo-v2-flash", object: "model", owned_by: "xiaomi" },
138
+ ],
139
+ });
140
+ }
141
+ export function startServer(cfg) {
142
+ const server = createServer((req, res) => {
143
+ const url = req.url ?? "/";
144
+ if (req.method === "GET" && (url === "/healthz" || url === "/")) {
145
+ sendJson(res, 200, { ok: true, name: "mimo2codex", baseUrl: cfg.baseUrl });
146
+ return;
147
+ }
148
+ if (req.method === "GET" && url.startsWith("/v1/models")) {
149
+ handleModels(res);
150
+ return;
151
+ }
152
+ if (req.method === "POST" && url.startsWith("/v1/responses")) {
153
+ void handleResponses(cfg, req, res);
154
+ return;
155
+ }
156
+ sendJson(res, 404, errorEnvelope(404, "not_found", `no route for ${req.method} ${url}`));
157
+ });
158
+ server.listen(cfg.port, cfg.host);
159
+ return server;
160
+ }
161
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}