mimo-cli 0.1.1

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/sdk.mjs ADDED
@@ -0,0 +1,822 @@
1
+ // src/client/index.ts
2
+ import OpenAI2 from "openai";
3
+
4
+ // src/version.ts
5
+ var VERSION = process.env.CLI_VERSION ?? "0.1.0";
6
+
7
+ // src/client/errors.ts
8
+ import OpenAI from "openai";
9
+
10
+ // src/errors/codes.ts
11
+ var ExitCode = {
12
+ SUCCESS: 0,
13
+ GENERAL: 1,
14
+ USAGE: 2,
15
+ AUTH: 3,
16
+ QUOTA: 4,
17
+ TIMEOUT: 5,
18
+ NETWORK: 6,
19
+ CONTENT_FILTER: 10,
20
+ INVALID_INPUT: 11
21
+ };
22
+
23
+ // src/errors/base.ts
24
+ class CLIError extends Error {
25
+ exitCode;
26
+ hint;
27
+ constructor(message, exitCode = ExitCode.GENERAL, hint) {
28
+ super(message);
29
+ this.name = "CLIError";
30
+ this.exitCode = exitCode;
31
+ this.hint = hint;
32
+ }
33
+ toJSON() {
34
+ return {
35
+ error: {
36
+ code: this.exitCode,
37
+ message: this.message,
38
+ ...this.hint ? { hint: this.hint } : {}
39
+ }
40
+ };
41
+ }
42
+ }
43
+
44
+ // src/utils/sanitize.ts
45
+ function maskApiKey(key) {
46
+ if (!key || typeof key !== "string")
47
+ return "****";
48
+ if (key.length <= 8) {
49
+ return key.slice(0, 2) + "****";
50
+ }
51
+ return key.slice(0, 4) + "****" + key.slice(-4);
52
+ }
53
+ function redactApiKeysInText(text) {
54
+ if (!text || typeof text !== "string")
55
+ return text;
56
+ return text.replace(/(?:sk|tp)-[A-Za-z0-9_-]{16,}/g, (match) => {
57
+ return maskApiKey(match);
58
+ });
59
+ }
60
+
61
+ // src/i18n/index.ts
62
+ var currentLocale = "zh";
63
+ var translations = {
64
+ "main.usage": {
65
+ zh: "用法:mimo <资源> <命令> [参数]",
66
+ en: "Usage: mimo <resource> <command> [flags]"
67
+ },
68
+ "main.resources": {
69
+ zh: "可用命令:",
70
+ en: "Resources:"
71
+ },
72
+ "main.globalFlags": {
73
+ zh: "全局参数:",
74
+ en: "Global Flags:"
75
+ },
76
+ "main.gettingHelp": {
77
+ zh: "获取帮助:",
78
+ en: "Getting Help:"
79
+ },
80
+ "main.helpHint1": {
81
+ zh: "在任何命令后添加 --help 查看完整选项、默认值和示例。",
82
+ en: "Add --help after any command to see its full list of options, defaults,"
83
+ },
84
+ "main.helpHint2": {
85
+ zh: "例如:",
86
+ en: "and usage examples. For example:"
87
+ },
88
+ "main.notLoggedIn": {
89
+ zh: " 尚未登录。",
90
+ en: " Not logged in."
91
+ },
92
+ "main.loginHint1": {
93
+ zh: " mimo auth login 使用 API Key 登录",
94
+ en: " mimo auth login Log in with an API key"
95
+ },
96
+ "main.loginHint2": {
97
+ zh: " mimo auth login --api-key 直接保存 API Key",
98
+ en: " mimo auth login --api-key Save an API key directly"
99
+ },
100
+ "main.interrupted": {
101
+ zh: `
102
+ 已中断,退出。`,
103
+ en: `
104
+ Interrupted. Exiting.`
105
+ },
106
+ "main.fatalArgv": {
107
+ zh: `致命错误:无法读取进程参数。
108
+ `,
109
+ en: `Fatal: failed to read process arguments.
110
+ `
111
+ },
112
+ "help.usage": { zh: "用法:", en: "Usage:" },
113
+ "help.options": { zh: "选项:", en: "Options:" },
114
+ "help.examples": { zh: "示例:", en: "Examples:" },
115
+ "help.apiRef": { zh: "API 参考:", en: "API Reference:" },
116
+ "help.commands": { zh: "子命令:", en: "Commands:" },
117
+ "help.globalHint": {
118
+ zh: "全局参数(--api-key、--output、--quiet 等)始终可用。",
119
+ en: "Global flags (--api-key, --output, --quiet, etc.) are always available."
120
+ },
121
+ "help.globalHintRun": {
122
+ zh: "运行 mimo --help 查看完整列表。",
123
+ en: "Run mimo --help for the full list."
124
+ },
125
+ "error.prefix": { zh: "错误:", en: "Error:" },
126
+ "error.exitCode": { zh: "(退出码 {code})", en: "(exit code {code})" },
127
+ "error.timeout": { zh: "请求超时。", en: "Request timed out." },
128
+ "error.timeoutHint": {
129
+ zh: `尝试增大超时时间(如 --timeout 60)。
130
+ 如果使用有效 API Key 仍然频繁超时,请检查网络和区域。
131
+ 运行:mimo auth status — 检查认证状态。`,
132
+ en: `Try increasing --timeout (e.g. --timeout 60).
133
+ If this happens on every request with a valid API key, check your network and region.
134
+ Run: mimo auth status — to check your credentials.`
135
+ },
136
+ "error.network": { zh: "网络请求失败。", en: "Network request failed." },
137
+ "error.networkHint": {
138
+ zh: `请检查网络连接。
139
+ 如需使用代理:设置 HTTPS_PROXY 环境变量,或运行:mimo config set proxy http://HOST:PORT`,
140
+ en: `Check your network connection.
141
+ To use a proxy: set HTTPS_PROXY env var, or run: mimo config set proxy http://HOST:PORT`
142
+ },
143
+ "error.proxyHint": {
144
+ zh: `代理连接失败 — 请检查代理 URL 和认证信息。
145
+ 检查:HTTPS_PROXY / HTTP_PROXY 环境变量,或运行 mimo config show 查看已配置的代理。`,
146
+ en: `Proxy connection failed — verify your proxy URL and authentication.
147
+ Check: HTTPS_PROXY / HTTP_PROXY env vars, or mimo config show for configured proxy.`
148
+ },
149
+ "error.fsEnoent": { zh: "文件或目录不存在。", en: "File or directory not found." },
150
+ "error.fsEacces": { zh: "权限不足 — 请检查文件或目录权限。", en: "Permission denied — check file or directory permissions." },
151
+ "error.fsEnospc": { zh: "磁盘空间不足 — 请释放空间后重试。", en: "Disk full — free up space and try again." },
152
+ "error.fsDefault": { zh: "请检查文件路径和权限。", en: "Check the file path and permissions." },
153
+ "error.fsPrefix": { zh: "文件系统错误:", en: "File system error: " },
154
+ "api.400": { zh: "请求参数错误:", en: "Bad request: " },
155
+ "api.400Hint": { zh: "请检查请求格式和参数。", en: "Check your request format and parameters." },
156
+ "api.401": { zh: "认证失败(HTTP 401)。", en: "Authentication failed (HTTP 401)." },
157
+ "api.401Hint": { zh: `请检查 API Key:mimo auth status
158
+ 重新登录:mimo auth login`, en: `Check your API Key: mimo auth status
159
+ Re-authenticate: mimo auth login` },
160
+ "api.402": { zh: "余额不足:", en: "Payment required: " },
161
+ "api.402Hint": { zh: "账户余额不足,请充值。", en: "Insufficient balance. Please top up your account." },
162
+ "api.403": { zh: "访问被拒绝(HTTP 403)。", en: "Access denied (HTTP 403)." },
163
+ "api.403Hint": { zh: `您的 API Key 可能无权访问此资源,或 Key 受限。
164
+ 请检查 API Key 权限。`, en: `Your API Key may not have access to this resource, or the key is restricted.
165
+ Check your API Key permissions.` },
166
+ "api.404": { zh: "未找到:", en: "Not found: " },
167
+ "api.404Hint": { zh: "请求的模型或功能可能不受支持,请检查模型名称和接口地址。", en: "The requested model or feature may not be supported. Check the model name and endpoint." },
168
+ "api.421": { zh: "内容被过滤:", en: "Content filtered: " },
169
+ "api.421Hint": { zh: "您的输入被内容安全过滤器标记,请修改后重试。", en: "Your input was flagged by the content safety filter. Please modify your request and try again." },
170
+ "api.429": { zh: "请求频率超限:", en: "Rate limit exceeded: " },
171
+ "api.429Hint": { zh: "请求过于频繁,请稍后重试或检查配额。", en: "You are sending requests too quickly. Please wait and retry, or check your quota." },
172
+ "api.500": { zh: "服务器内部错误(HTTP 500)。", en: "Server error (HTTP 500)." },
173
+ "api.500Hint": { zh: "服务器遇到内部错误,请稍后重试。", en: "The server encountered an internal error. Please retry later." },
174
+ "api.503": { zh: "服务暂不可用(HTTP 503)。", en: "Service unavailable (HTTP 503)." },
175
+ "api.503Hint": { zh: "服务器暂时过载或维护中,请稍后重试。", en: "The server is temporarily overloaded or under maintenance. Please retry later." },
176
+ "api.default": { zh: "API 错误:", en: "API error: " },
177
+ "auth.noCreds": { zh: "未找到认证信息。", en: "No credentials found." },
178
+ "auth.noCredsHint": {
179
+ zh: `登录: mimo auth login
180
+ 直接传入: --api-key <key>
181
+ 环境变量: MIMO_API_KEY=<key>`,
182
+ en: `Log in: mimo auth login
183
+ Pass directly: --api-key <key>
184
+ Set env var: MIMO_API_KEY=<key>`
185
+ },
186
+ "auth.noKeyProvided": { zh: "未提供 API Key。", en: "No API key provided." },
187
+ "auth.noKeyHint": { zh: "使用:mimo auth login --api-key <key>", en: "Use: mimo auth login --api-key <key>" },
188
+ "auth.keyEmpty": { zh: "API Key 不能为空。", en: "API key cannot be empty." },
189
+ "auth.keyRequired": { zh: "API Key 为必填项。", en: "API key is required." },
190
+ "auth.keySaved": { zh: "API Key 已保存至", en: "API key saved to" },
191
+ "auth.baseUrlAuto": { zh: "接口地址已自动配置:", en: "Base URL auto-configured: " },
192
+ "auth.promptTitle": { zh: "请输入您的 MiMo API Key:", en: "Please enter your MiMo API key:" },
193
+ "auth.promptPayKey": { zh: '- 按量计费 Key:以 "sk-" 等非 "tp-" 前缀开头', en: '- Pay-as-you-go Key: starts with "sk-" or other non-"tp-" prefix' },
194
+ "auth.promptTpKey": { zh: '- TokenPlan Key:以 "tp-" 前缀开头', en: '- TokenPlan Key: starts with "tp-" prefix' },
195
+ "auth.promptLabel": { zh: "输入 MiMo API Key:", en: "Enter your MiMo API key:" },
196
+ "auth.logoutNoKey": { zh: "配置文件中未找到 API Key,已处于登出状态。", en: "No API key found in config file. Already logged out." },
197
+ "auth.logoutDone": { zh: "API Key 已移除,您已登出。", en: "API key removed. You are now logged out." },
198
+ "cmd.chat.desc": { zh: "发送对话请求", en: "Send a chat completion request" },
199
+ "cmd.repl.desc": { zh: "交互式多轮对话", en: "Interactive multi-turn REPL conversation" },
200
+ "cmd.vision.desc": { zh: "多模态理解(图片、音频、视频)", en: "Multi-modal understanding (image, audio, video)" },
201
+ "cmd.asr.desc": { zh: "语音识别(ASR)", en: "Speech recognition (ASR)" },
202
+ "cmd.ttsSynth.desc": { zh: "预设音色语音合成", en: "Pre-set voice text-to-speech synthesis" },
203
+ "cmd.ttsClone.desc": { zh: "声音克隆语音合成", en: "Voice clone text-to-speech synthesis" },
204
+ "cmd.ttsDesign.desc": { zh: "自定义音色语音合成", en: "Voice design text-to-speech synthesis" },
205
+ "cmd.ttsVoices.desc": { zh: "列出可用的 TTS 音色", en: "List available TTS voices" },
206
+ "cmd.authLogin.desc": { zh: "使用 MiMo API Key 登录(自动识别按量计费/TokenPlan)", en: "Log in with a MiMo API key (auto-detects key type: Pay-as-you-go or TokenPlan)" },
207
+ "cmd.authStatus.desc": { zh: "查看当前认证状态", en: "Show current authentication status" },
208
+ "cmd.authLogout.desc": { zh: "清除已保存的 API Key", en: "Clear saved API key from config file" },
209
+ "cmd.configShow.desc": { zh: "显示当前配置", en: "Display current configuration" },
210
+ "cmd.configSet.desc": { zh: "设置配置项", en: "Set a configuration value" },
211
+ "cmd.update.desc": { zh: "更新 MiMo CLI", en: "Self-update the MiMo CLI" },
212
+ "cmd.help.desc": { zh: "显示命令帮助", en: "Show help for commands" },
213
+ "cmd.language.desc": { zh: "切换界面语言(zh/en)", en: "Switch interface language (zh/en)" },
214
+ "vision.videoTooLarge": {
215
+ zh: "视频文件过大:{size}MB(原始)。Base64 编码后将超过 {limit}MB 的 API 限制。",
216
+ en: "Video file too large: {size}MB (raw). After Base64 encoding it would exceed the {limit}MB API limit."
217
+ },
218
+ "vision.videoTooLargeHint": {
219
+ zh: "请改用 URL 方式传入视频(URL 模式支持最大 300MB),或将视频压缩至 {max}MB 以下。",
220
+ en: "Please use a URL to pass the video instead (URL mode supports up to 300MB), or compress the video to under {max}MB."
221
+ },
222
+ "vision.videoBase64TooLarge": {
223
+ zh: "视频 Base64 大小 {size}MB 超过 {limit}MB 的 API 限制。",
224
+ en: "Video Base64 size {size}MB exceeds the {limit}MB API limit."
225
+ },
226
+ "vision.videoBase64TooLargeHint": {
227
+ zh: "请改用 URL 方式传入视频(URL 模式支持最大 300MB),或压缩视频。",
228
+ en: "Please use a URL to pass the video instead (URL mode supports up to 300MB), or compress the video."
229
+ },
230
+ "vision.noMedia": {
231
+ zh: "至少需要提供 --image、--audio 或 --video 中的一项。",
232
+ en: "At least one of --image, --audio, or --video is required."
233
+ },
234
+ "vision.noPrompt": {
235
+ zh: "缺少必填参数:--prompt",
236
+ en: "Missing required flag: --prompt"
237
+ },
238
+ "vision.requestFailed": {
239
+ zh: "多模态请求失败:",
240
+ en: "Vision request failed: "
241
+ },
242
+ "vision.requestFailedHint": {
243
+ zh: "请检查网络连接和媒体文件格式。",
244
+ en: "Check your network connection and media file format."
245
+ },
246
+ "chat.noMessage": { zh: "缺少必填参数:--message", en: "Missing required flag: --message" },
247
+ "chat.emptyMessage": { zh: "消息不能为空或仅包含空白字符。", en: "Message cannot be empty or whitespace only." },
248
+ "chat.messageTooLong": { zh: "消息过长:{len} 字符(上限 {max})。", en: "Message too long: {len} characters (max {max})." },
249
+ "chat.messageTooLongHint": { zh: "请缩短消息或拆分为多次请求。", en: "Shorten your message or split it into multiple requests." },
250
+ "chat.streamFailed": { zh: "流式请求失败:", en: "Stream request failed: " },
251
+ "chat.streamFailedHint": { zh: "请检查网络连接和 API Key。", en: "Check your network connection and API key." },
252
+ "asr.noFile": { zh: "需要提供音频文件路径。", en: "Audio file path is required." },
253
+ "asr.streamFailed": { zh: "语音识别流式请求失败:", en: "ASR stream request failed: " },
254
+ "asr.requestFailed": { zh: "语音识别请求失败:", en: "ASR request failed: " },
255
+ "asr.checkHint": { zh: "请检查网络连接和音频文件格式。", en: "Check your network connection and audio file format." },
256
+ "tts.noText": { zh: "--text 为必填项。", en: "--text is required." },
257
+ "tts.emptyText": { zh: "--text 不能为空或仅包含空白字符。", en: "--text cannot be empty or whitespace only." },
258
+ "tts.textTooLong": { zh: "文本过长:{len} 字符(上限 {max})。", en: "Text too long: {len} characters (max {max})." },
259
+ "tts.textTooLongHint": { zh: "请缩短文本或拆分为多次合成请求。", en: "Shorten your text or split into multiple synthesis requests." },
260
+ "tts.noSample": { zh: "--sample 为必填项。", en: "--sample is required." },
261
+ "tts.noPrompt": { zh: "--prompt 为必填项。", en: "--prompt is required." },
262
+ "tts.noTextOrOptimize": { zh: "需要提供 --text 或启用 --optimize-text。", en: "Either --text or --optimize-text is required." },
263
+ "tts.noAudioData": { zh: "API 响应中缺少音频数据。", en: "API response missing audio data." },
264
+ "tts.invalidVoice": { zh: "无效音色:", en: "Invalid voice: " },
265
+ "config.keyRequired": { zh: "--key 为必填项。", en: "--key is required." },
266
+ "config.valueRequired": { zh: "--value 为必填项。", en: "--value is required." },
267
+ "config.invalidKey": { zh: "无效的配置键:", en: "Invalid config key: " },
268
+ "config.invalidKeyChars": { zh: "配置键只能包含字母、数字和下划线。", en: "Config key must only contain letters, numbers, and underscores." },
269
+ "config.validKeys": { zh: "有效的键:", en: "Valid keys: " },
270
+ "config.setTimeout": { zh: "timeout 必须为正数。", en: "timeout must be a positive number." },
271
+ "config.setOutput": { zh: 'output 必须为 "text" 或 "json"。', en: 'output must be "text" or "json".' },
272
+ "config.setDone": { zh: "已设置", en: "Set" },
273
+ "config.corrupted": { zh: "警告:配置文件已损坏(", en: "Warning: config file is corrupted (" },
274
+ "config.corruptedHint": { zh: `)。运行 "mimo config set" 重置。
275
+ `, en: `). Run 'mimo config set' to reset.
276
+ ` },
277
+ "update.notImplemented": { zh: `自动更新尚未实现,请从 GitHub 重新安装。
278
+ `, en: `Self-update is not yet implemented. Please reinstall from GitHub.
279
+ ` },
280
+ "repl.intro": { zh: "MiMo 交互对话 — 输入 /exit 退出,/clear 清空对话", en: "MiMo REPL — type /exit to quit, /clear to reset" },
281
+ "repl.you": { zh: "你", en: "You" },
282
+ "repl.placeholder": { zh: "输入你的消息...", en: "Type your message..." },
283
+ "repl.goodbye": { zh: "再见!", en: "Goodbye!" },
284
+ "repl.cleared": { zh: "对话已清空。", en: "Conversation cleared." },
285
+ "spinner.synthesizing": { zh: "正在合成语音...", en: "Synthesizing speech..." },
286
+ "spinner.readingSample": { zh: "正在读取音频样本...", en: "Reading audio sample..." },
287
+ "spinner.cloning": { zh: "正在合成克隆语音...", en: "Synthesizing cloned speech..." },
288
+ "spinner.designing": { zh: "正在设计音色...", en: "Designing voice..." },
289
+ "language.current": { zh: "当前界面语言:", en: "Current interface language: " },
290
+ "language.changed": { zh: "界面语言已切换为中文。", en: "Interface language changed to English." },
291
+ "language.invalid": { zh: "无效的语言代码,请使用 zh(中文)或 en(英文)。", en: "Invalid language code. Use zh (Chinese) or en (English)." },
292
+ "language.hint": { zh: "使用:mimo language zh 或 mimo language en", en: "Usage: mimo language zh or mimo language en" },
293
+ "general.notSet": { zh: "(未设置)", en: "(not set)" },
294
+ "general.or": { zh: "或", en: "or" }
295
+ };
296
+ function t(key, vars) {
297
+ const entry = translations[key];
298
+ if (!entry)
299
+ return key;
300
+ let text = entry[currentLocale] || entry["zh"] || key;
301
+ if (vars) {
302
+ for (const [k, v] of Object.entries(vars)) {
303
+ text = text.replace(new RegExp(`\\{${k}\\}`, "g"), String(v));
304
+ }
305
+ }
306
+ return text;
307
+ }
308
+
309
+ // src/errors/api.ts
310
+ function mapApiError(status, body, url) {
311
+ const apiMsg = redactApiKeysInText(body.error?.message || body.message || body.msg || `HTTP ${status}`);
312
+ switch (status) {
313
+ case 400:
314
+ return new CLIError(t("api.400") + apiMsg, ExitCode.USAGE, t("api.400Hint"));
315
+ case 401:
316
+ return new CLIError(t("api.401"), ExitCode.AUTH, t("api.401Hint"));
317
+ case 402:
318
+ return new CLIError(t("api.402") + apiMsg, ExitCode.QUOTA, t("api.402Hint"));
319
+ case 403:
320
+ return new CLIError(t("api.403"), ExitCode.AUTH, t("api.403Hint"));
321
+ case 404:
322
+ return new CLIError(t("api.404") + apiMsg, ExitCode.USAGE, t("api.404Hint"));
323
+ case 421:
324
+ return new CLIError(t("api.421") + apiMsg, ExitCode.CONTENT_FILTER, t("api.421Hint"));
325
+ case 429:
326
+ return new CLIError(t("api.429") + apiMsg, ExitCode.QUOTA, t("api.429Hint"));
327
+ case 500:
328
+ return new CLIError(t("api.500"), ExitCode.NETWORK, t("api.500Hint"));
329
+ case 503:
330
+ return new CLIError(t("api.503"), ExitCode.NETWORK, t("api.503Hint"));
331
+ default:
332
+ return new CLIError(t("api.default") + apiMsg + " (HTTP " + status + ")", ExitCode.GENERAL);
333
+ }
334
+ }
335
+
336
+ // src/types/api.ts
337
+ function extractDelta(delta) {
338
+ return {
339
+ role: typeof delta.role === "string" ? delta.role : undefined,
340
+ content: typeof delta.content === "string" ? delta.content : undefined,
341
+ reasoning_content: typeof delta.reasoning_content === "string" ? delta.reasoning_content : undefined,
342
+ annotations: Array.isArray(delta.annotations) ? delta.annotations : undefined
343
+ };
344
+ }
345
+ function extractChunkUsage(chunk) {
346
+ const usage = chunk.usage;
347
+ if (typeof usage === "object" && usage !== null) {
348
+ return usage;
349
+ }
350
+ return;
351
+ }
352
+ function extractErrorUrl(err) {
353
+ if ("url" in err) {
354
+ const url = err.url;
355
+ return typeof url === "string" ? url : "";
356
+ }
357
+ return "";
358
+ }
359
+
360
+ // src/auth/resolver.ts
361
+ function inferBaseUrlFromKey(apiKey) {
362
+ if (apiKey.startsWith("tp-")) {
363
+ return "https://token-plan-cn.xiaomimimo.com/v1";
364
+ }
365
+ return;
366
+ }
367
+ function resolveCredential(config) {
368
+ if (config.apiKey) {
369
+ return { token: config.apiKey, method: "api-key", source: "flag" };
370
+ }
371
+ const envKey = process.env.MIMO_API_KEY;
372
+ if (envKey) {
373
+ return { token: envKey, method: "api-key", source: "env" };
374
+ }
375
+ if (config.fileApiKey) {
376
+ return { token: config.fileApiKey, method: "api-key", source: "config.json" };
377
+ }
378
+ throw new CLIError("No credentials found.", ExitCode.AUTH, `Log in: mimo auth login
379
+ Pass directly: --api-key <key>
380
+ Set env var: MIMO_API_KEY=<key>`);
381
+ }
382
+
383
+ // src/client/errors.ts
384
+ function getErrorUrl(err) {
385
+ return extractErrorUrl(err);
386
+ }
387
+ function wrapApiError(err) {
388
+ if (err instanceof CLIError) {
389
+ return err;
390
+ }
391
+ if (err instanceof OpenAI.APIConnectionError) {
392
+ return new CLIError("Network request failed.", ExitCode.NETWORK, `Check your network connection.
393
+ ` + "To use a proxy: set HTTPS_PROXY env var, or run: mimo config set proxy http://HOST:PORT");
394
+ }
395
+ if (err instanceof OpenAI.RateLimitError) {
396
+ const body = err.error ?? {};
397
+ return mapApiError(err.status ?? 429, body, getErrorUrl(err));
398
+ }
399
+ if (err instanceof OpenAI.AuthenticationError) {
400
+ const body = err.error ?? {};
401
+ return mapApiError(err.status ?? 401, body, getErrorUrl(err));
402
+ }
403
+ if (err instanceof OpenAI.BadRequestError) {
404
+ const body = err.error ?? {};
405
+ return mapApiError(err.status ?? 400, body, getErrorUrl(err));
406
+ }
407
+ if (err instanceof OpenAI.APIError) {
408
+ const body = err.error ?? {};
409
+ return mapApiError(err.status ?? 0, body, getErrorUrl(err));
410
+ }
411
+ if (err instanceof Error) {
412
+ if (err.name === "AbortError" || err.name === "TimeoutError" || err.message.includes("timed out")) {
413
+ return new CLIError("Request timed out.", ExitCode.TIMEOUT, "Try increasing --timeout (e.g. --timeout 60).");
414
+ }
415
+ return new CLIError(err.message, ExitCode.GENERAL);
416
+ }
417
+ return new CLIError(String(err), ExitCode.GENERAL);
418
+ }
419
+
420
+ // src/client/index.ts
421
+ var DEFAULT_BASE_URL = "https://api.xiaomimimo.com/v1";
422
+ var DEFAULT_TIMEOUT_SEC = 300;
423
+
424
+ class MiMoClient {
425
+ openai;
426
+ constructor(config) {
427
+ this.openai = new OpenAI2({
428
+ apiKey: config.apiKey,
429
+ baseURL: config.baseURL || DEFAULT_BASE_URL,
430
+ timeout: (config.timeout ?? DEFAULT_TIMEOUT_SEC) * 1000,
431
+ defaultHeaders: {
432
+ "User-Agent": `mimo-cli/${VERSION}`
433
+ }
434
+ });
435
+ }
436
+ buildRequestParams(params, stream) {
437
+ const { thinking, asr_options, audio, tools, ...openaiParams } = params;
438
+ const result = {
439
+ ...openaiParams,
440
+ stream,
441
+ thinking,
442
+ asr_options,
443
+ audio
444
+ };
445
+ if (tools && tools.length > 0) {
446
+ result.tools = tools;
447
+ }
448
+ return result;
449
+ }
450
+ async chatCompletion(params) {
451
+ try {
452
+ const requestParams = this.buildRequestParams(params, false);
453
+ const response = await this.openai.chat.completions.create(requestParams);
454
+ return response;
455
+ } catch (err) {
456
+ throw wrapApiError(err);
457
+ }
458
+ }
459
+ async chatCompletionStream(params) {
460
+ try {
461
+ const requestParams = this.buildRequestParams(params, true);
462
+ return await this.openai.chat.completions.create(requestParams);
463
+ } catch (err) {
464
+ throw wrapApiError(err);
465
+ }
466
+ }
467
+ async chat(params) {
468
+ const isStreaming = params.stream === true;
469
+ if (isStreaming) {
470
+ return this.chatCompletionStream(params);
471
+ }
472
+ return this.chatCompletion(params);
473
+ }
474
+ get raw() {
475
+ return this.openai;
476
+ }
477
+ }
478
+
479
+ // src/sdk/client.ts
480
+ class MiMoSDKClient {
481
+ mimo;
482
+ options;
483
+ constructor(options) {
484
+ this.options = options;
485
+ const resolvedBaseUrl = options.baseUrl || inferBaseUrlFromKey(options.apiKey ?? "") || undefined;
486
+ this.mimo = new MiMoClient({
487
+ apiKey: options.apiKey ?? "",
488
+ baseURL: resolvedBaseUrl,
489
+ timeout: options.timeout
490
+ });
491
+ }
492
+ static async fromConfig(config) {
493
+ const cred = resolveCredential(config);
494
+ return new MiMoSDKClient({
495
+ apiKey: cred.token,
496
+ baseUrl: config.baseUrl,
497
+ timeout: config.timeout
498
+ });
499
+ }
500
+ handleError(error) {
501
+ throw wrapApiError(error);
502
+ }
503
+ }
504
+
505
+ // src/sdk/chat.ts
506
+ class ChatSDK {
507
+ client;
508
+ constructor(client) {
509
+ this.client = client;
510
+ }
511
+ async chat(options) {
512
+ const request = {
513
+ model: options.model ?? "MiMo-7B-RL",
514
+ messages: options.messages,
515
+ max_completion_tokens: options.maxCompletionTokens,
516
+ temperature: options.temperature,
517
+ top_p: options.topP,
518
+ stream: false,
519
+ stop: options.stop,
520
+ frequency_penalty: options.frequencyPenalty,
521
+ presence_penalty: options.presencePenalty,
522
+ thinking: options.thinking ? { type: "enabled" } : undefined,
523
+ response_format: options.responseFormat
524
+ };
525
+ try {
526
+ const response = await this.client.mimo.chatCompletion(request);
527
+ const choice = response.choices[0];
528
+ return {
529
+ id: response.id,
530
+ content: choice?.message?.content ?? "",
531
+ reasoningContent: choice?.message?.reasoning_content,
532
+ model: response.model,
533
+ usage: response.usage,
534
+ finishReason: choice?.finish_reason ?? "stop"
535
+ };
536
+ } catch (error) {
537
+ this.client.handleError(error);
538
+ }
539
+ }
540
+ async* chatStream(options) {
541
+ const request = {
542
+ model: options.model ?? "MiMo-7B-RL",
543
+ messages: options.messages,
544
+ max_completion_tokens: options.maxCompletionTokens,
545
+ temperature: options.temperature,
546
+ top_p: options.topP,
547
+ stream: true,
548
+ stop: options.stop,
549
+ frequency_penalty: options.frequencyPenalty,
550
+ presence_penalty: options.presencePenalty,
551
+ thinking: options.thinking ? { type: "enabled" } : undefined,
552
+ response_format: options.responseFormat
553
+ };
554
+ try {
555
+ const stream = await this.client.mimo.chatCompletionStream(request);
556
+ for await (const chunk of stream) {
557
+ const mapped = {
558
+ id: chunk.id,
559
+ object: "chat.completion.chunk",
560
+ created: chunk.created,
561
+ model: chunk.model,
562
+ choices: chunk.choices.map((c) => ({
563
+ index: c.index,
564
+ delta: extractDelta(c.delta),
565
+ finish_reason: c.finish_reason
566
+ })),
567
+ usage: extractChunkUsage(chunk)
568
+ };
569
+ yield mapped;
570
+ }
571
+ } catch (error) {
572
+ this.client.handleError(error);
573
+ }
574
+ }
575
+ }
576
+
577
+ // src/sdk/vision.ts
578
+ class VisionSDK {
579
+ client;
580
+ constructor(client) {
581
+ this.client = client;
582
+ }
583
+ async describe(options) {
584
+ const contentParts = [];
585
+ if (options.prompt) {
586
+ contentParts.push({ type: "text", text: options.prompt });
587
+ }
588
+ if (options.image) {
589
+ contentParts.push({
590
+ type: "image_url",
591
+ image_url: { url: options.image }
592
+ });
593
+ }
594
+ if (options.audio) {
595
+ contentParts.push({
596
+ type: "input_audio",
597
+ input_audio: { data: options.audio }
598
+ });
599
+ }
600
+ if (options.video) {
601
+ contentParts.push({
602
+ type: "video_url",
603
+ video_url: {
604
+ url: options.video,
605
+ ...options.fps ? { fps: options.fps } : {},
606
+ ...options.mediaResolution ? { media_resolution: options.mediaResolution } : {}
607
+ }
608
+ });
609
+ }
610
+ const messages = [
611
+ {
612
+ role: "user",
613
+ content: contentParts.length > 0 ? contentParts : options.prompt ?? "Describe this."
614
+ }
615
+ ];
616
+ try {
617
+ const request = {
618
+ model: options.model ?? "mimo-v2.5",
619
+ messages,
620
+ stream: false
621
+ };
622
+ const response = await this.client.mimo.chatCompletion(request);
623
+ const choice = response.choices[0];
624
+ return {
625
+ id: response.id,
626
+ content: choice?.message?.content ?? "",
627
+ model: response.model,
628
+ usage: response.usage
629
+ };
630
+ } catch (error) {
631
+ this.client.handleError(error);
632
+ }
633
+ }
634
+ }
635
+
636
+ // src/sdk/asr.ts
637
+ class ASRSDK {
638
+ client;
639
+ constructor(client) {
640
+ this.client = client;
641
+ }
642
+ async transcribe(options) {
643
+ const messages = [
644
+ {
645
+ role: "user",
646
+ content: [
647
+ { type: "input_audio", input_audio: { data: options.file } }
648
+ ]
649
+ }
650
+ ];
651
+ try {
652
+ const request = {
653
+ model: options.model ?? "mimo-v2.5-asr",
654
+ messages,
655
+ stream: false,
656
+ asr_options: { language: options.language ?? "auto" }
657
+ };
658
+ const response = await this.client.mimo.chatCompletion(request);
659
+ const choice = response.choices[0];
660
+ return {
661
+ id: response.id,
662
+ text: choice?.message?.content ?? "",
663
+ model: response.model,
664
+ usage: response.usage
665
+ };
666
+ } catch (error) {
667
+ this.client.handleError(error);
668
+ }
669
+ }
670
+ }
671
+
672
+ // src/sdk/tts.ts
673
+ class TTSSDK {
674
+ client;
675
+ constructor(client) {
676
+ this.client = client;
677
+ }
678
+ async synthesize(options) {
679
+ try {
680
+ const response = await fetch(`${this.client.options.baseUrl}/tts/synthesize`, {
681
+ method: "POST",
682
+ headers: {
683
+ Authorization: `Bearer ${this.client.options.apiKey}`,
684
+ "Content-Type": "application/json"
685
+ },
686
+ body: JSON.stringify({
687
+ model: options.model ?? "mimo-v2.5-tts",
688
+ text: options.text,
689
+ voice: options.voice,
690
+ style: options.style,
691
+ format: options.format ?? "mp3"
692
+ })
693
+ });
694
+ if (!response.ok) {
695
+ const body = await response.json().catch(() => ({}));
696
+ throw wrapApiError({ status: response.status, error: body, url: response.url });
697
+ }
698
+ const buffer = Buffer.from(await response.arrayBuffer());
699
+ return {
700
+ id: response.headers.get("x-request-id") ?? "",
701
+ audio: buffer,
702
+ model: options.model ?? "mimo-v2.5-tts"
703
+ };
704
+ } catch (error) {
705
+ throw wrapApiError(error);
706
+ }
707
+ }
708
+ async clone(options) {
709
+ try {
710
+ const response = await fetch(`${this.client.options.baseUrl}/tts/clone`, {
711
+ method: "POST",
712
+ headers: {
713
+ Authorization: `Bearer ${this.client.options.apiKey}`,
714
+ "Content-Type": "application/json"
715
+ },
716
+ body: JSON.stringify({
717
+ model: options.model ?? "mimo-v2.5-tts",
718
+ sample: options.sample,
719
+ text: options.text,
720
+ format: options.format ?? "mp3"
721
+ })
722
+ });
723
+ if (!response.ok) {
724
+ const body = await response.json().catch(() => ({}));
725
+ throw wrapApiError({ status: response.status, error: body, url: response.url });
726
+ }
727
+ const buffer = Buffer.from(await response.arrayBuffer());
728
+ return {
729
+ id: response.headers.get("x-request-id") ?? "",
730
+ audio: buffer,
731
+ model: options.model ?? "mimo-v2.5-tts"
732
+ };
733
+ } catch (error) {
734
+ throw wrapApiError(error);
735
+ }
736
+ }
737
+ async design(options) {
738
+ try {
739
+ const response = await fetch(`${this.client.options.baseUrl}/tts/design`, {
740
+ method: "POST",
741
+ headers: {
742
+ Authorization: `Bearer ${this.client.options.apiKey}`,
743
+ "Content-Type": "application/json"
744
+ },
745
+ body: JSON.stringify({
746
+ model: options.model ?? "mimo-v2.5-tts",
747
+ prompt: options.prompt,
748
+ text: options.text,
749
+ optimize_text_preview: options.optimizeText ?? false,
750
+ format: options.format ?? "mp3"
751
+ })
752
+ });
753
+ if (!response.ok) {
754
+ const body = await response.json().catch(() => ({}));
755
+ throw wrapApiError({ status: response.status, error: body, url: response.url });
756
+ }
757
+ const buffer = Buffer.from(await response.arrayBuffer());
758
+ return {
759
+ id: response.headers.get("x-request-id") ?? "",
760
+ audio: buffer,
761
+ model: options.model ?? "mimo-v2.5-tts"
762
+ };
763
+ } catch (error) {
764
+ throw wrapApiError(error);
765
+ }
766
+ }
767
+ async voices() {
768
+ try {
769
+ const response = await fetch(`${this.client.options.baseUrl}/tts/voices`, {
770
+ method: "GET",
771
+ headers: {
772
+ Authorization: `Bearer ${this.client.options.apiKey}`
773
+ }
774
+ });
775
+ if (!response.ok) {
776
+ const body = await response.json().catch(() => ({}));
777
+ throw wrapApiError({ status: response.status, error: body, url: response.url });
778
+ }
779
+ const data = await response.json();
780
+ return {
781
+ voices: data.voices ?? data.data ?? []
782
+ };
783
+ } catch (error) {
784
+ throw wrapApiError(error);
785
+ }
786
+ }
787
+ }
788
+
789
+ // src/sdk/index.ts
790
+ class MiMoSDK {
791
+ chat;
792
+ vision;
793
+ asr;
794
+ tts;
795
+ client;
796
+ constructor(options) {
797
+ this.client = new MiMoSDKClient(options);
798
+ this.chat = new ChatSDK(this.client);
799
+ this.vision = new VisionSDK(this.client);
800
+ this.asr = new ASRSDK(this.client);
801
+ this.tts = new TTSSDK(this.client);
802
+ }
803
+ static async fromConfig(config) {
804
+ const client = await MiMoSDKClient.fromConfig(config);
805
+ return new MiMoSDK({
806
+ apiKey: client.options.apiKey,
807
+ baseUrl: client.options.baseUrl,
808
+ timeout: client.options.timeout
809
+ });
810
+ }
811
+ }
812
+ export {
813
+ VisionSDK,
814
+ TTSSDK,
815
+ MiMoSDKClient,
816
+ MiMoSDK,
817
+ ChatSDK,
818
+ ASRSDK
819
+ };
820
+
821
+ //# debugId=12A533A25AFDFD3864756E2164756E21
822
+ //# sourceMappingURL=sdk.mjs.map