mimo2codex 0.1.3 → 0.1.6
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/AGENTS.md +20 -5
- package/README.md +139 -14
- package/README.zh.md +140 -15
- package/dist/admin/router.js +291 -0
- package/dist/admin/router.js.map +1 -0
- package/dist/cli.js +83 -7
- package/dist/cli.js.map +1 -1
- package/dist/config.js +89 -10
- package/dist/config.js.map +1 -1
- package/dist/db/dataDir.js +14 -0
- package/dist/db/dataDir.js.map +1 -0
- package/dist/db/index.js +110 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/logs.js +94 -0
- package/dist/db/logs.js.map +1 -0
- package/dist/db/models.js +114 -0
- package/dist/db/models.js.map +1 -0
- package/dist/db/schema.js +74 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/settings.js +44 -0
- package/dist/db/settings.js.map +1 -0
- package/dist/providers/deepseek.js +76 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/providers/mimo.js +88 -0
- package/dist/providers/mimo.js.map +1 -0
- package/dist/providers/registry.js +25 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/server.js +281 -67
- package/dist/server.js.map +1 -1
- package/dist/upstream/{mimoClient.js → openaiCompatClient.js} +21 -32
- package/dist/upstream/openaiCompatClient.js.map +1 -0
- package/dist/web/assets/index-DAJbSznk.css +1 -0
- package/dist/web/assets/index-DwvEnXbj.js +67 -0
- package/dist/web/index.html +13 -0
- package/package.json +9 -3
- package/dist/upstream/mimoClient.js.map +0 -1
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import { log, redactKey } from "../util/log.js";
|
|
2
|
-
// Marker that MiMo emits in 400 responses when web_search is forwarded but
|
|
3
|
-
// the user's account doesn't have the Web Search Plugin activated.
|
|
4
|
-
// Full param string: "web search tool found in the request body, but webSearchEnabled is false"
|
|
5
|
-
const WEB_SEARCH_DISABLED_MARKER = "webSearchEnabled is false";
|
|
6
|
-
const WEB_SEARCH_HINT = "MiMo Web Search Plugin is not activated for this account. " +
|
|
7
|
-
"Activate it at https://platform.xiaomimimo.com/#/console/plugin (separately billed) " +
|
|
8
|
-
"and restart mimo2codex. The model has decided to call web_search; if your account " +
|
|
9
|
-
"doesn't include the plugin, this request will keep failing until activated.";
|
|
10
2
|
export class UpstreamError extends Error {
|
|
11
3
|
status;
|
|
12
4
|
bodySnippet;
|
|
@@ -24,8 +16,8 @@ function buildUrl(baseUrl) {
|
|
|
24
16
|
return `${trimmed}/chat/completions`;
|
|
25
17
|
}
|
|
26
18
|
function authHeader(apiKey) {
|
|
27
|
-
// MiMo
|
|
28
|
-
// universally supported by intermediaries
|
|
19
|
+
// Both MiMo and DeepSeek accept the OpenAI-style Bearer scheme, which is
|
|
20
|
+
// also more universally supported by intermediaries than the api-key header.
|
|
29
21
|
return { Authorization: `Bearer ${apiKey}` };
|
|
30
22
|
}
|
|
31
23
|
async function readSnippet(res) {
|
|
@@ -37,7 +29,18 @@ async function readSnippet(res) {
|
|
|
37
29
|
return undefined;
|
|
38
30
|
}
|
|
39
31
|
}
|
|
40
|
-
|
|
32
|
+
function defaultErrorCode(status) {
|
|
33
|
+
if (status === 401)
|
|
34
|
+
return "authentication_error";
|
|
35
|
+
if (status === 403)
|
|
36
|
+
return "permission_denied";
|
|
37
|
+
if (status === 429)
|
|
38
|
+
return "rate_limit_exceeded";
|
|
39
|
+
if (status >= 500)
|
|
40
|
+
return "server_error";
|
|
41
|
+
return "bad_request";
|
|
42
|
+
}
|
|
43
|
+
export async function callOpenAICompat(cfg, body, signal) {
|
|
41
44
|
const url = buildUrl(cfg.baseUrl);
|
|
42
45
|
const headers = {
|
|
43
46
|
"Content-Type": "application/json",
|
|
@@ -52,8 +55,6 @@ export async function callMimo(cfg, body, signal) {
|
|
|
52
55
|
tools: body.tools?.length ?? 0,
|
|
53
56
|
apiKey: redactKey(cfg.apiKey),
|
|
54
57
|
});
|
|
55
|
-
// Full body in --verbose. Useful when MiMo returns an opaque 400 — you can
|
|
56
|
-
// see exactly what the proxy sent. No api key leaks; that's only in headers.
|
|
57
58
|
log.debug("upstream POST body", body);
|
|
58
59
|
const attempt = async () => {
|
|
59
60
|
const res = await fetch(url, {
|
|
@@ -71,7 +72,6 @@ export async function callMimo(cfg, body, signal) {
|
|
|
71
72
|
catch (err) {
|
|
72
73
|
if (err.name === "AbortError")
|
|
73
74
|
throw err;
|
|
74
|
-
// Retry once on transient network errors.
|
|
75
75
|
log.warn("upstream connect failed, retrying once", { error: err.message });
|
|
76
76
|
try {
|
|
77
77
|
res = await attempt();
|
|
@@ -80,28 +80,17 @@ export async function callMimo(cfg, body, signal) {
|
|
|
80
80
|
throw new UpstreamError({
|
|
81
81
|
status: 502,
|
|
82
82
|
code: "upstream_unreachable",
|
|
83
|
-
message: `failed to reach
|
|
83
|
+
message: `failed to reach upstream: ${err2.message}`,
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
if (!res.ok) {
|
|
88
88
|
const snippet = await readSnippet(res);
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
? "rate_limit_exceeded"
|
|
95
|
-
: res.status >= 500
|
|
96
|
-
? "server_error"
|
|
97
|
-
: res.status === 400 && snippet?.includes(WEB_SEARCH_DISABLED_MARKER)
|
|
98
|
-
? "web_search_plugin_not_activated"
|
|
99
|
-
: "bad_request";
|
|
100
|
-
const message = res.status === 400 && snippet?.includes(WEB_SEARCH_DISABLED_MARKER)
|
|
101
|
-
? `${WEB_SEARCH_HINT} (raw: ${snippet})`
|
|
102
|
-
: `MiMo returned ${res.status}: ${snippet ?? "(no body)"}`;
|
|
103
|
-
if (res.status === 400 && snippet?.includes(WEB_SEARCH_DISABLED_MARKER)) {
|
|
104
|
-
log.warn(WEB_SEARCH_HINT);
|
|
89
|
+
const enhanced = cfg.enhanceError?.({ status: res.status, snippet });
|
|
90
|
+
const code = enhanced?.code ?? defaultErrorCode(res.status);
|
|
91
|
+
const message = enhanced?.message ?? `upstream returned ${res.status}: ${snippet ?? "(no body)"}`;
|
|
92
|
+
if (enhanced) {
|
|
93
|
+
log.warn(enhanced.message);
|
|
105
94
|
}
|
|
106
95
|
throw new UpstreamError({
|
|
107
96
|
status: res.status,
|
|
@@ -112,4 +101,4 @@ export async function callMimo(cfg, body, signal) {
|
|
|
112
101
|
}
|
|
113
102
|
return res;
|
|
114
103
|
}
|
|
115
|
-
//# sourceMappingURL=
|
|
104
|
+
//# sourceMappingURL=openaiCompatClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openaiCompatClient.js","sourceRoot":"","sources":["../../src/upstream/openaiCompatClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{color-scheme:light dark;--bg: #0f1419;--panel: #161b22;--panel-2: #1d242d;--border: #2a323d;--fg: #e6edf3;--muted: #8b95a3;--accent: #4f8cf7;--accent-2: #6ea8ff;--ok: #3fb950;--warn: #d29922;--err: #f85149;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,sans-serif}*{box-sizing:border-box}html,body,#root{height:100%;margin:0;background:var(--bg);color:var(--fg)}a{color:var(--accent-2);text-decoration:none}a:hover{text-decoration:underline}.layout{display:grid;grid-template-columns:220px 1fr;min-height:100vh}.sidebar{background:var(--panel);border-right:1px solid var(--border);padding:24px 16px}.sidebar h1{margin:0 0 24px;font-size:18px;font-weight:600;letter-spacing:-.02em}.sidebar nav a{display:block;padding:8px 12px;margin-bottom:4px;border-radius:6px;color:var(--fg);font-size:14px}.sidebar nav a.active{background:var(--panel-2);color:var(--accent-2)}.sidebar nav a:hover{background:var(--panel-2);text-decoration:none}.main{padding:32px 40px;overflow-y:auto}.main h2{margin:0 0 24px;font-size:22px;font-weight:600}.main h3{margin:24px 0 12px;font-size:16px;font-weight:600}.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:16px;margin-bottom:24px}.card{background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:16px}.card .label{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em}.card .value{font-size:28px;font-weight:600;margin-top:6px}.card .sub{font-size:12px;color:var(--muted);margin-top:4px}table{width:100%;border-collapse:collapse;background:var(--panel);border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:13px}th,td{text-align:left;padding:10px 14px;border-bottom:1px solid var(--border)}th{background:var(--panel-2);font-weight:500;color:var(--muted);text-transform:uppercase;font-size:11px;letter-spacing:.05em}tbody tr:last-child td{border-bottom:none}tbody tr:hover{background:var(--panel-2)}code,pre,.mono{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Monaco,Consolas,monospace;font-size:12.5px}.banner{display:flex;align-items:flex-start;gap:12px;border-radius:8px;padding:14px 16px;margin-bottom:16px;border:1px solid}.banner.ok{background:#3fb9500f;border-color:#3fb95066}.banner.warn{background:#d299220f;border-color:#d2992266}.banner.err{background:#f851490f;border-color:#f8514966}.banner.info{background:#4f8cf70f;border-color:#4f8cf766}.banner .ic{font-size:18px;line-height:1;margin-top:2px}.banner .body{flex:1;font-size:13px;line-height:1.5}.banner .body code{background:var(--panel-2);padding:2px 6px;border-radius:4px}.tag{display:inline-block;padding:2px 8px;border-radius:100px;font-size:11px;background:var(--panel-2);color:var(--muted);border:1px solid var(--border)}.tag.ok{color:var(--ok);border-color:#3fb95066}.tag.warn{color:var(--warn);border-color:#d2992266}.tag.err{color:var(--err);border-color:#f8514966}.tag.muted{color:var(--muted)}.field{display:grid;grid-template-columns:200px 1fr;gap:12px;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)}.field:last-child{border-bottom:none}.field label{color:var(--muted);font-size:13px}.field input,.field select{background:var(--panel-2);border:1px solid var(--border);color:var(--fg);padding:8px 10px;border-radius:6px;font-size:13px;width:100%}.field input:disabled{color:var(--muted)}button,.btn{background:var(--accent);color:#fff;border:none;border-radius:6px;padding:6px 14px;font-size:13px;cursor:pointer}button:disabled{background:var(--panel-2);color:var(--muted);cursor:not-allowed}button.secondary{background:var(--panel-2);color:var(--fg);border:1px solid var(--border)}button.danger{background:var(--err)}.row{display:flex;gap:8px;align-items:center;margin:12px 0}.row>*{flex-shrink:0}.row .grow{flex-grow:1;flex-shrink:1}.empty{padding:32px;text-align:center;color:var(--muted)}
|