u1s1-cli 1.11.8 → 1.13.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/agent-setup.d.ts +19 -3
- package/dist/agent-setup.js +85 -10
- package/dist/api.js +42 -34
- package/dist/bench-report.js +12 -9
- package/dist/bench.js +34 -31
- package/dist/brand.d.ts +2 -1
- package/dist/brand.js +38 -12
- package/dist/config.d.ts +11 -1
- package/dist/config.js +25 -8
- package/dist/context-usage.js +12 -9
- package/dist/deploy.js +49 -45
- package/dist/device-auth.js +7 -5
- package/dist/error-humanize.d.ts +1 -0
- package/dist/error-humanize.js +18 -6
- package/dist/feedback.js +48 -31
- package/dist/i18n.d.ts +21 -0
- package/dist/i18n.js +46 -0
- package/dist/import/index.js +64 -28
- package/dist/import/skills.js +72 -41
- package/dist/import/util.js +3 -2
- package/dist/import/write.js +2 -1
- package/dist/index.js +140 -49
- package/dist/login.js +39 -28
- package/dist/loop.js +18 -12
- package/dist/mcp/client.js +15 -14
- package/dist/mcp/command.d.ts +2 -2
- package/dist/mcp/command.js +109 -69
- package/dist/mcp/config.d.ts +22 -6
- package/dist/mcp/config.js +0 -0
- package/dist/mcp/extension.js +5 -3
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +10 -5
- package/dist/model.js +11 -10
- package/dist/nudges.d.ts +8 -2
- package/dist/nudges.js +22 -3
- package/dist/search-tools.js +7 -6
- package/dist/shell-doctor.js +9 -8
- package/dist/subagent.js +5 -4
- package/dist/tools.js +53 -37
- package/dist/update.js +29 -28
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +96 -43
- package/dist/web.js +2 -1
- package/dist/workflow/runner.js +33 -29
- package/dist/workflow/templates.js +70 -45
- package/dist/workflow/tool.js +15 -12
- package/package.json +4 -4
- package/scripts/patch-pi.js +347 -278
- package/scripts/render-regression.mjs +55 -4
package/dist/agent-setup.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { type CliConfig, type CustomEndpoint, type ModelDef, type ThinkingLevel } from "./config.js";
|
|
2
2
|
import type { ShellDoctorResult } from "./shell-doctor.js";
|
|
3
3
|
import type { Site } from "./site.js";
|
|
4
|
+
/**
|
|
5
|
+
* 系统提示里的品牌人设。中国站面向编程新手、兜底语言中文(保持原文一字不差);
|
|
6
|
+
* 国际站面向开发者、兜底语言英文。按站点而不是界面语言判定,用时求值(配置可能晚于 import 才读)。
|
|
7
|
+
*/
|
|
8
|
+
export declare function brandAppend(site?: Site): string;
|
|
4
9
|
/** pi auto-appends <agentDir>/APPEND_SYSTEM.md to its system prompt — our branding hook. */
|
|
5
10
|
export declare function ensureBrandPrompt(shell: ShellDoctorResult): void;
|
|
6
11
|
/**
|
|
@@ -9,6 +14,16 @@ export declare function ensureBrandPrompt(shell: ShellDoctorResult): void;
|
|
|
9
14
|
* them while a diverged user value becomes user-owned.
|
|
10
15
|
*/
|
|
11
16
|
export declare function applyModelThinkingDefaults(settings: Record<string, unknown>, models: ModelDef[]): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* pi 内置 codemode 默认不开,u1s1 给每个用户开一次(`+codemode` 只加不替换默认工具)。
|
|
19
|
+
* 标记写下后不再碰:用户在 defaultTools 里删掉或写 -codemode 都算用户的选择。
|
|
20
|
+
*/
|
|
21
|
+
export declare function applyCodemodeDefault(settings: Record<string, unknown>): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* pi 1.0 内置 llama.cpp 扩展(本地模型 provider + /llama),u1s1 走网关用不上,默认关一次。
|
|
24
|
+
* 用户在 extensions 里自己写过 builtin:llama.cpp 的任何开关就不动;标记写下后同样不再碰。
|
|
25
|
+
*/
|
|
26
|
+
export declare function applyLlamaDisabledDefault(settings: Record<string, unknown>): boolean;
|
|
12
27
|
/** Defaults that don't overwrite values the user already set. */
|
|
13
28
|
export declare function ensureDefaultSettings(models?: ModelDef[]): void;
|
|
14
29
|
/** ≤0.4.0 wrote u1s1-dark/u1s1-light into the pi themes dir; remove them. */
|
|
@@ -51,9 +66,10 @@ export declare function writeErrorHumanizeExtension(): void;
|
|
|
51
66
|
*/
|
|
52
67
|
export declare function writeAnnouncementsExtension(): void;
|
|
53
68
|
/**
|
|
54
|
-
* 生成「自定义 MCP 服务」扩展到 <agentDir>/extensions/u1s1-mcp.js
|
|
55
|
-
*
|
|
56
|
-
*
|
|
69
|
+
* 生成「自定义 MCP 服务」扩展到 <agentDir>/extensions/u1s1-mcp.js,只给没有内置 MCP 的
|
|
70
|
+
* 旧引擎用(Desktop App 的 pi-web-ui 还是 0.83):读 agent/mcp.json,把工具注册成
|
|
71
|
+
* mcp_<服务>_<工具>,逻辑在 mcp/extension.ts。pi ≥0.99 有 pi.registerMcpServer,说明内置
|
|
72
|
+
* MCP 会读同一份配置,扩展直接让位。投影前先把 ≤1.12 的 ~/.u1s1/mcp.json 迁过去。
|
|
57
73
|
* 子会话(spawn_subagent)里不挂 MCP 工具:避免每个子进程都拉起一份服务。
|
|
58
74
|
*/
|
|
59
75
|
export declare function writeMcpExtension(): void;
|
package/dist/agent-setup.js
CHANGED
|
@@ -2,7 +2,25 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { activeSite, agentDir, CUSTOM_ENDPOINTS, ENDPOINT_ID_RE, effectiveContextWindow, endpointKeyEnvName, MODELS, readCompactionBudgetSetting, PROVIDER_ID, UNKNOWN_MODEL_COST, VERSION, } from "./config.js";
|
|
5
|
-
|
|
5
|
+
import { tr } from "./i18n.js";
|
|
6
|
+
import { migrateLegacyMcpConfig } from "./mcp/config.js";
|
|
7
|
+
/**
|
|
8
|
+
* 系统提示里的品牌人设。中国站面向编程新手、兜底语言中文(保持原文一字不差);
|
|
9
|
+
* 国际站面向开发者、兜底语言英文。按站点而不是界面语言判定,用时求值(配置可能晚于 import 才读)。
|
|
10
|
+
*/
|
|
11
|
+
export function brandAppend(site = activeSite()) {
|
|
12
|
+
if (site.id === "intl") {
|
|
13
|
+
return `## u1s1
|
|
14
|
+
|
|
15
|
+
You are u1s1 — a plain-spoken AI coding assistant:
|
|
16
|
+
- When introducing yourself or asked "who are you", say you are u1s1, never "pi"; only mention the underlying pi engine if the user explicitly asks about internals.
|
|
17
|
+
- Reply in the language of the user's latest request by default. If that is ambiguous, follow the conversation language, then the UI locale when available, and otherwise use English. Never infer language from IP or location. Keep code, commands, and verbatim error messages in English.
|
|
18
|
+
- Explain problems in plain, direct language and skip needless jargon; use a one-sentence analogy when helpful.
|
|
19
|
+
- Before making changes, briefly state what you plan to do; afterwards summarize what changed in one or two sentences.
|
|
20
|
+
- If the user's request is vague, assume the most likely intent and confirm it instead of asking a long list of questions.
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
return `## u1s1
|
|
6
24
|
|
|
7
25
|
You are u1s1 — a plain-spoken AI coding buddy for programming beginners. Most users are unfamiliar with jargon:
|
|
8
26
|
- When introducing yourself or asked "who are you", say you are u1s1, never "pi"; only mention the underlying pi engine if the user explicitly asks about internals.
|
|
@@ -11,6 +29,7 @@ You are u1s1 — a plain-spoken AI coding buddy for programming beginners. Most
|
|
|
11
29
|
- Before making changes, briefly state what you plan to do; afterwards summarize what changed in one or two sentences.
|
|
12
30
|
- If the user's request is vague, assume the most likely intent and confirm it instead of asking a long list of questions.
|
|
13
31
|
`;
|
|
32
|
+
}
|
|
14
33
|
const EXEC_RULES = `
|
|
15
34
|
## Command execution
|
|
16
35
|
|
|
@@ -45,7 +64,7 @@ function envSection(shell) {
|
|
|
45
64
|
/** pi auto-appends <agentDir>/APPEND_SYSTEM.md to its system prompt — our branding hook. */
|
|
46
65
|
export function ensureBrandPrompt(shell) {
|
|
47
66
|
mkdirSync(agentDir, { recursive: true });
|
|
48
|
-
const content =
|
|
67
|
+
const content = brandAppend() + envSection(shell) + EXEC_RULES;
|
|
49
68
|
const p = join(agentDir, "APPEND_SYSTEM.md");
|
|
50
69
|
if (!existsSync(p) || readFileSync(p, "utf8") !== content) {
|
|
51
70
|
writeFileSync(p, content);
|
|
@@ -67,6 +86,8 @@ function bundledPiVersion() {
|
|
|
67
86
|
}
|
|
68
87
|
}
|
|
69
88
|
const THINKING_DEFAULTS_MARKER = "u1s1ModelThinkingDefaults";
|
|
89
|
+
const CODEMODE_DEFAULT_MARKER = "u1s1CodemodeDefault";
|
|
90
|
+
const LLAMA_DISABLED_MARKER = "u1s1LlamaDisabledDefault";
|
|
70
91
|
function recordValue(value) {
|
|
71
92
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
72
93
|
? value
|
|
@@ -110,6 +131,37 @@ export function applyModelThinkingDefaults(settings, models) {
|
|
|
110
131
|
}
|
|
111
132
|
return changed;
|
|
112
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* pi 内置 codemode 默认不开,u1s1 给每个用户开一次(`+codemode` 只加不替换默认工具)。
|
|
136
|
+
* 标记写下后不再碰:用户在 defaultTools 里删掉或写 -codemode 都算用户的选择。
|
|
137
|
+
*/
|
|
138
|
+
export function applyCodemodeDefault(settings) {
|
|
139
|
+
if (settings[CODEMODE_DEFAULT_MARKER] === true)
|
|
140
|
+
return false;
|
|
141
|
+
const tools = Array.isArray(settings["defaultTools"])
|
|
142
|
+
? settings["defaultTools"].filter((t) => typeof t === "string")
|
|
143
|
+
: [];
|
|
144
|
+
if (!tools.some((t) => t.replace(/^[+-]/, "") === "codemode"))
|
|
145
|
+
settings["defaultTools"] = [...tools, "+codemode"];
|
|
146
|
+
settings[CODEMODE_DEFAULT_MARKER] = true;
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* pi 1.0 内置 llama.cpp 扩展(本地模型 provider + /llama),u1s1 走网关用不上,默认关一次。
|
|
151
|
+
* 用户在 extensions 里自己写过 builtin:llama.cpp 的任何开关就不动;标记写下后同样不再碰。
|
|
152
|
+
*/
|
|
153
|
+
export function applyLlamaDisabledDefault(settings) {
|
|
154
|
+
if (settings[LLAMA_DISABLED_MARKER] === true)
|
|
155
|
+
return false;
|
|
156
|
+
const entries = Array.isArray(settings["extensions"])
|
|
157
|
+
? settings["extensions"].filter((e) => typeof e === "string")
|
|
158
|
+
: [];
|
|
159
|
+
if (!entries.some((e) => e.replace(/^[+!-]/, "") === "builtin:llama.cpp")) {
|
|
160
|
+
settings["extensions"] = [...entries, "-builtin:llama.cpp"];
|
|
161
|
+
}
|
|
162
|
+
settings[LLAMA_DISABLED_MARKER] = true;
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
113
165
|
/** Defaults that don't overwrite values the user already set. */
|
|
114
166
|
export function ensureDefaultSettings(models = []) {
|
|
115
167
|
mkdirSync(agentDir, { recursive: true });
|
|
@@ -158,6 +210,15 @@ export function ensureDefaultSettings(models = []) {
|
|
|
158
210
|
settings["autoUpdate"] = true;
|
|
159
211
|
changed = true;
|
|
160
212
|
}
|
|
213
|
+
// pi 1.0 起默认全屏;u1s1 的精简 UI 与滚动习惯按常规模式调的,未选过的用户保持常规
|
|
214
|
+
if (!("tuiMode" in settings)) {
|
|
215
|
+
settings["tuiMode"] = "regular";
|
|
216
|
+
changed = true;
|
|
217
|
+
}
|
|
218
|
+
if (applyCodemodeDefault(settings))
|
|
219
|
+
changed = true;
|
|
220
|
+
if (applyLlamaDisabledDefault(settings))
|
|
221
|
+
changed = true;
|
|
161
222
|
if (applyModelThinkingDefaults(settings, models))
|
|
162
223
|
changed = true;
|
|
163
224
|
// ≤0.4.0 shipped branded themes and forced them as default; the files are
|
|
@@ -229,19 +290,21 @@ export function writeWebToolsExtension(cfg, features) {
|
|
|
229
290
|
// 密钥剥离守卫也从这里装:Desktop App 的 pi 跑在 pi-web-ui 子进程里,没有
|
|
230
291
|
// 启动器代码,只有扩展能替它把 U1S1_API_KEY / U1S1_EP_KEY_* 挡在子进程之外
|
|
231
292
|
const secretEnvUrl = new URL("./secret-env.js", import.meta.url).href;
|
|
293
|
+
// 提示文案在生成时按当前界面语言定好,扩展文件里只是字面量
|
|
294
|
+
const toolsLoadFailed = tr("联网工具本次未能加载(程序文件可能正在更新),重启 u1s1 即可恢复", "Web tools couldn't load this time (program files may be updating). Restart u1s1 to restore them.");
|
|
232
295
|
writeFileSync(join(dir, "u1s1-tools.js"), `// 由 u1s1 每次启动自动生成,请勿手改\n` +
|
|
233
296
|
`export default async function (pi) {\n` +
|
|
234
297
|
` (await import(${JSON.stringify(secretEnvUrl)})).installChildEnvGuard();\n` +
|
|
235
298
|
` if (process.env.U1S1_TOOLS_VIA_EXTENSION !== "1") return;\n` +
|
|
236
299
|
` const baseUrl = process.env.U1S1_SIGNING_PROXY_URL || ${JSON.stringify(cfg.baseUrl)};\n` +
|
|
237
300
|
// 升级半途 / 权限问题导致 CLI dist 不可读时,宁可这一会话没有联网工具,
|
|
238
|
-
// 也不能让扩展加载失败刷英文堆栈(审计 B4);只在 UI
|
|
301
|
+
// 也不能让扩展加载失败刷英文堆栈(审计 B4);只在 UI 里提示一句人话
|
|
239
302
|
` let tools;\n` +
|
|
240
303
|
` try {\n` +
|
|
241
304
|
` tools = await import(${JSON.stringify(toolsUrl)});\n` +
|
|
242
305
|
` } catch {\n` +
|
|
243
306
|
` pi.on("session_start", (_event, ctx) => {\n` +
|
|
244
|
-
` try { if (ctx.hasUI) ctx.ui.notify(
|
|
307
|
+
` try { if (ctx.hasUI) ctx.ui.notify(${JSON.stringify(toolsLoadFailed)}, "warning"); } catch {}\n` +
|
|
245
308
|
` });\n` +
|
|
246
309
|
` return;\n` +
|
|
247
310
|
` }\n` +
|
|
@@ -333,18 +396,27 @@ export function writeAnnouncementsExtension() {
|
|
|
333
396
|
`}\n`);
|
|
334
397
|
}
|
|
335
398
|
/**
|
|
336
|
-
* 生成「自定义 MCP 服务」扩展到 <agentDir>/extensions/u1s1-mcp.js
|
|
337
|
-
*
|
|
338
|
-
*
|
|
399
|
+
* 生成「自定义 MCP 服务」扩展到 <agentDir>/extensions/u1s1-mcp.js,只给没有内置 MCP 的
|
|
400
|
+
* 旧引擎用(Desktop App 的 pi-web-ui 还是 0.83):读 agent/mcp.json,把工具注册成
|
|
401
|
+
* mcp_<服务>_<工具>,逻辑在 mcp/extension.ts。pi ≥0.99 有 pi.registerMcpServer,说明内置
|
|
402
|
+
* MCP 会读同一份配置,扩展直接让位。投影前先把 ≤1.12 的 ~/.u1s1/mcp.json 迁过去。
|
|
339
403
|
* 子会话(spawn_subagent)里不挂 MCP 工具:避免每个子进程都拉起一份服务。
|
|
340
404
|
*/
|
|
341
405
|
export function writeMcpExtension() {
|
|
406
|
+
try {
|
|
407
|
+
migrateLegacyMcpConfig();
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
// 迁移失败不挡启动:旧文件还在,下次再试
|
|
411
|
+
}
|
|
342
412
|
const dir = join(agentDir, "extensions");
|
|
343
413
|
mkdirSync(dir, { recursive: true });
|
|
344
414
|
const url = new URL("./mcp/extension.js", import.meta.url).href;
|
|
415
|
+
const mcpLoadFailed = tr("MCP 服务加载失败:", "Failed to load MCP servers: ");
|
|
345
416
|
writeFileSync(join(dir, "u1s1-mcp.js"), `// 由 u1s1 每次启动自动生成,请勿手改\n` +
|
|
346
417
|
`export default async function (pi) {\n` +
|
|
347
418
|
` if (process.env.U1S1_IN_SUBAGENT === "1") return;\n` +
|
|
419
|
+
` if (typeof pi.registerMcpServer === "function") return;\n` +
|
|
348
420
|
` let registerMcpServers;\n` +
|
|
349
421
|
` try {\n` +
|
|
350
422
|
` registerMcpServers = (await import(${JSON.stringify(url)})).registerMcpServers;\n` +
|
|
@@ -356,7 +428,7 @@ export function writeMcpExtension() {
|
|
|
356
428
|
` } catch (err) {\n` +
|
|
357
429
|
` const msg = err instanceof Error ? err.message : String(err);\n` +
|
|
358
430
|
` pi.on("session_start", (_event, ctx) => {\n` +
|
|
359
|
-
` if (ctx.hasUI) ctx.ui.notify(
|
|
431
|
+
` if (ctx.hasUI) ctx.ui.notify(${JSON.stringify(mcpLoadFailed)} + msg, "warning");\n` +
|
|
360
432
|
` });\n` +
|
|
361
433
|
` }\n` +
|
|
362
434
|
`}\n`);
|
|
@@ -391,6 +463,9 @@ export function writeAttributionExtension(site = activeSite()) {
|
|
|
391
463
|
export function writeCompactUiExtension(outputDir) {
|
|
392
464
|
const dir = outputDir ?? join(agentDir, "extensions");
|
|
393
465
|
mkdirSync(dir, { recursive: true });
|
|
466
|
+
// 汇总行文案按生成时的界面语言写死进扩展;英文版面向开发者,直接用工具原名
|
|
467
|
+
const toolSummaryLabels = tr(`read: "读文件", write: "写文件", edit: "改文件", bash: "跑命令", grep: "搜内容", find: "找文件", ls: "列目录",`, `read: "read", write: "write", edit: "edit", bash: "bash", grep: "grep", find: "find", ls: "ls",`);
|
|
468
|
+
const toolCallsLabel = tr(`d.total + " 次工具调用"`, `d.total + (d.total === 1 ? " tool call" : " tool calls")`);
|
|
394
469
|
writeFileSync(join(dir, "u1s1-compact-ui.js"), `// 由 u1s1 每次启动自动生成,请勿手改
|
|
395
470
|
function oneLine(text, max = 160) {
|
|
396
471
|
const flat = String(text).replace(/\\s*\\n\\s*/g, " ; ").trim();
|
|
@@ -408,7 +483,7 @@ export default async function (pi) {
|
|
|
408
483
|
let errors = 0;
|
|
409
484
|
// 工具调用被折叠后,这一行摘要是新手了解「AI 刚才做了什么」的唯一入口,内置工具名给人话
|
|
410
485
|
const TOOL_SUMMARY_LABELS = {
|
|
411
|
-
|
|
486
|
+
${toolSummaryLabels}
|
|
412
487
|
};
|
|
413
488
|
pi.on("agent_start", async () => {
|
|
414
489
|
counts = {};
|
|
@@ -432,7 +507,7 @@ export default async function (pi) {
|
|
|
432
507
|
pi.registerEntryRenderer("tool-summary", (entry, _opts, theme) => {
|
|
433
508
|
const d = entry.data;
|
|
434
509
|
let text = theme.fg("muted", "⚙ ");
|
|
435
|
-
text += theme.fg("toolTitle", theme.bold(
|
|
510
|
+
text += theme.fg("toolTitle", theme.bold(${toolCallsLabel}));
|
|
436
511
|
text += theme.fg("dim", " · " + d.summary);
|
|
437
512
|
return new Text(text, 0, 0);
|
|
438
513
|
});
|
package/dist/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apiEndpointToCustom, loadEndpointsCache, saveEndpointsCache, setCustomEndpoints, VERSION, } from "./config.js";
|
|
2
2
|
import { authorizedFetch } from "./device-auth.js";
|
|
3
|
+
import { tr } from "./i18n.js";
|
|
3
4
|
const MAX_API_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
4
5
|
const MAX_API_ERROR_BYTES = 64 * 1024;
|
|
5
6
|
const MAX_IMAGE_RESULT_BYTES = 64 * 1024;
|
|
@@ -60,6 +61,12 @@ export class AuthError extends Error {
|
|
|
60
61
|
/** 403:登录着但被服务端拒绝(封禁/停用/设备不受信任),重新登录也没用,进 TUI 每条消息都会失败。 */
|
|
61
62
|
export class AccessDeniedError extends Error {
|
|
62
63
|
}
|
|
64
|
+
function unreachableMessage(baseUrl) {
|
|
65
|
+
return tr(`连不上 ${baseUrl},检查一下网络?`, `Can't reach ${baseUrl}. Check your network?`);
|
|
66
|
+
}
|
|
67
|
+
function sessionExpiredMessage() {
|
|
68
|
+
return tr("登录已失效,请重新运行 u1s1 login", "Your session has expired. Run u1s1 login to sign in again.");
|
|
69
|
+
}
|
|
63
70
|
/** 网关错误壳统一是 { error: { message } };解析不出来时回退到状态码提示。 */
|
|
64
71
|
async function errorMessageFromResponse(resp, fallback) {
|
|
65
72
|
const body = jsonRecord(await readJsonResponseCapped(resp, MAX_API_ERROR_BYTES).catch(() => null));
|
|
@@ -79,17 +86,17 @@ export async function fetchModels(cfg) {
|
|
|
79
86
|
catch (error) {
|
|
80
87
|
if (signal.aborted || (error instanceof Error && error.name === "TimeoutError"))
|
|
81
88
|
throw error;
|
|
82
|
-
throw new Error(
|
|
89
|
+
throw new Error(unreachableMessage(cfg.baseUrl), { cause: error });
|
|
83
90
|
}
|
|
84
91
|
if (resp.status === 401)
|
|
85
|
-
throw new AuthError(
|
|
92
|
+
throw new AuthError(sessionExpiredMessage());
|
|
86
93
|
if (resp.status === 403)
|
|
87
|
-
throw new AccessDeniedError(await errorMessageFromResponse(resp, "账号当前无法使用 u1s1"));
|
|
94
|
+
throw new AccessDeniedError(await errorMessageFromResponse(resp, tr("账号当前无法使用 u1s1", "This account can't use u1s1 right now")));
|
|
88
95
|
if (!resp.ok)
|
|
89
|
-
throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status}
|
|
96
|
+
throw new Error(await errorMessageFromResponse(resp, tr(`服务端返回 ${resp.status},稍后再试`, `Server returned ${resp.status}. Please try again later.`)));
|
|
90
97
|
const body = await readJsonResponseCapped(resp);
|
|
91
98
|
if (!body || !Array.isArray(body.data))
|
|
92
|
-
throw new Error("服务端模型列表格式不正确");
|
|
99
|
+
throw new Error(tr("服务端模型列表格式不正确", "The server returned a malformed model list"));
|
|
93
100
|
const token = body.client_attestation?.token;
|
|
94
101
|
const expiresIn = body.client_attestation?.expires_in;
|
|
95
102
|
return {
|
|
@@ -104,7 +111,7 @@ export async function fetchModels(cfg) {
|
|
|
104
111
|
if (error instanceof AuthError || error instanceof AccessDeniedError)
|
|
105
112
|
throw error;
|
|
106
113
|
if (signal.aborted || (error instanceof Error && error.name === "TimeoutError")) {
|
|
107
|
-
throw new Error("获取模型列表超时(15 秒内未完成);可能与网络响应或本机繁忙有关,请稍后重试。", { cause: error });
|
|
114
|
+
throw new Error(tr("获取模型列表超时(15 秒内未完成);可能与网络响应或本机繁忙有关,请稍后重试。", "Fetching the model list timed out (no response within 15 seconds). The network or this machine may be busy; please try again later."), { cause: error });
|
|
108
115
|
}
|
|
109
116
|
throw error;
|
|
110
117
|
}
|
|
@@ -122,12 +129,12 @@ export async function fetchLatestAnnouncement(cfg) {
|
|
|
122
129
|
});
|
|
123
130
|
}
|
|
124
131
|
catch {
|
|
125
|
-
throw new Error(`连不上 ${cfg.baseUrl}`);
|
|
132
|
+
throw new Error(tr(`连不上 ${cfg.baseUrl}`, `Can't reach ${cfg.baseUrl}`));
|
|
126
133
|
}
|
|
127
134
|
if (resp.status === 404)
|
|
128
135
|
return fetchModels(cfg);
|
|
129
136
|
if (!resp.ok)
|
|
130
|
-
throw new Error(`服务端返回 ${resp.status}`);
|
|
137
|
+
throw new Error(tr(`服务端返回 ${resp.status}`, `Server returned ${resp.status}`));
|
|
131
138
|
const body = jsonRecord(await readJsonResponseCapped(resp));
|
|
132
139
|
const announcement = body?.announcement;
|
|
133
140
|
return {
|
|
@@ -147,10 +154,10 @@ export async function fetchUserEndpoints(cfg) {
|
|
|
147
154
|
});
|
|
148
155
|
}
|
|
149
156
|
catch {
|
|
150
|
-
throw new Error(`连不上 ${cfg.baseUrl}`);
|
|
157
|
+
throw new Error(tr(`连不上 ${cfg.baseUrl}`, `Can't reach ${cfg.baseUrl}`));
|
|
151
158
|
}
|
|
152
159
|
if (!resp.ok)
|
|
153
|
-
throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status}`));
|
|
160
|
+
throw new Error(await errorMessageFromResponse(resp, tr(`服务端返回 ${resp.status}`, `Server returned ${resp.status}`)));
|
|
154
161
|
const body = jsonRecord(await readJsonResponseCapped(resp));
|
|
155
162
|
return Array.isArray(body?.endpoints) ? body.endpoints : [];
|
|
156
163
|
}
|
|
@@ -171,7 +178,7 @@ export async function loadCustomEndpoints(cfg) {
|
|
|
171
178
|
/** 联网搜索走网关代理(上游 key 只在服务端)。maxResults 不传时由服务端决定默认值。 */
|
|
172
179
|
export async function searchWeb(cfg, input) {
|
|
173
180
|
if (!cfg.apiKey)
|
|
174
|
-
throw new Error("没有配置 API Key");
|
|
181
|
+
throw new Error(tr("没有配置 API Key", "No API key configured"));
|
|
175
182
|
let resp;
|
|
176
183
|
try {
|
|
177
184
|
resp = await authorizedFetch(cfg, `${cfg.baseUrl}/search`, {
|
|
@@ -187,15 +194,15 @@ export async function searchWeb(cfg, input) {
|
|
|
187
194
|
catch (error) {
|
|
188
195
|
if (input.signal?.aborted)
|
|
189
196
|
throw error;
|
|
190
|
-
throw new Error(
|
|
197
|
+
throw new Error(unreachableMessage(cfg.baseUrl), { cause: error });
|
|
191
198
|
}
|
|
192
199
|
if (resp.status === 401)
|
|
193
|
-
throw new Error(
|
|
200
|
+
throw new Error(sessionExpiredMessage());
|
|
194
201
|
if (!resp.ok)
|
|
195
|
-
throw new Error(await errorMessageFromResponse(resp, `搜索服务返回 ${resp.status}
|
|
202
|
+
throw new Error(await errorMessageFromResponse(resp, tr(`搜索服务返回 ${resp.status},稍后再试`, `Search service returned ${resp.status}. Please try again later.`)));
|
|
196
203
|
const body = jsonRecord(await readJsonResponseCapped(resp));
|
|
197
204
|
if (!body || !Array.isArray(body.results))
|
|
198
|
-
throw new Error("搜索服务返回格式不正确");
|
|
205
|
+
throw new Error(tr("搜索服务返回格式不正确", "The search service returned a malformed response"));
|
|
199
206
|
const results = [];
|
|
200
207
|
for (const value of body.results.slice(0, 10)) {
|
|
201
208
|
const result = jsonRecord(value);
|
|
@@ -216,7 +223,7 @@ export async function searchWeb(cfg, input) {
|
|
|
216
223
|
/** web_fetch 直连失败时的回退:网关用云端真浏览器渲染后转 markdown(能过挑战页)。 */
|
|
217
224
|
export async function renderPage(cfg, url, signal) {
|
|
218
225
|
if (!cfg.apiKey)
|
|
219
|
-
throw new Error("没有配置 API Key");
|
|
226
|
+
throw new Error(tr("没有配置 API Key", "No API key configured"));
|
|
220
227
|
let resp;
|
|
221
228
|
try {
|
|
222
229
|
resp = await authorizedFetch(cfg, `${cfg.baseUrl}/fetch`, {
|
|
@@ -232,15 +239,15 @@ export async function renderPage(cfg, url, signal) {
|
|
|
232
239
|
catch (error) {
|
|
233
240
|
if (signal?.aborted)
|
|
234
241
|
throw error;
|
|
235
|
-
throw new Error(`连不上 ${cfg.baseUrl}`, { cause: error });
|
|
242
|
+
throw new Error(tr(`连不上 ${cfg.baseUrl}`, `Can't reach ${cfg.baseUrl}`), { cause: error });
|
|
236
243
|
}
|
|
237
244
|
if (resp.status === 401)
|
|
238
|
-
throw new Error(
|
|
245
|
+
throw new Error(sessionExpiredMessage());
|
|
239
246
|
if (!resp.ok)
|
|
240
|
-
throw new Error(await errorMessageFromResponse(resp, `渲染服务返回 ${resp.status}`));
|
|
247
|
+
throw new Error(await errorMessageFromResponse(resp, tr(`渲染服务返回 ${resp.status}`, `Render service returned ${resp.status}`)));
|
|
241
248
|
const body = jsonRecord(await readJsonResponseCapped(resp));
|
|
242
249
|
if (!body || typeof body.url !== "string" || typeof body.markdown !== "string") {
|
|
243
|
-
throw new Error("渲染服务返回格式不正确");
|
|
250
|
+
throw new Error(tr("渲染服务返回格式不正确", "The render service returned a malformed response"));
|
|
244
251
|
}
|
|
245
252
|
return { url: body.url, markdown: body.markdown };
|
|
246
253
|
}
|
|
@@ -284,17 +291,17 @@ export async function readGeneratedImageResponse(resp) {
|
|
|
284
291
|
try {
|
|
285
292
|
const body = jsonRecord(await readJsonResponseCapped(resp, MAX_IMAGE_RESULT_BYTES));
|
|
286
293
|
if (typeof body?.url !== "string") {
|
|
287
|
-
throw new Error("服务端没有返回图片下载地址");
|
|
294
|
+
throw new Error(tr("服务端没有返回图片下载地址", "The server didn't return an image download URL"));
|
|
288
295
|
}
|
|
289
296
|
let parsed;
|
|
290
297
|
try {
|
|
291
298
|
parsed = new URL(body.url);
|
|
292
299
|
}
|
|
293
300
|
catch {
|
|
294
|
-
throw new Error("服务端没有返回图片下载地址");
|
|
301
|
+
throw new Error(tr("服务端没有返回图片下载地址", "The server didn't return an image download URL"));
|
|
295
302
|
}
|
|
296
303
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
297
|
-
throw new Error("服务端没有返回图片下载地址");
|
|
304
|
+
throw new Error(tr("服务端没有返回图片下载地址", "The server didn't return an image download URL"));
|
|
298
305
|
}
|
|
299
306
|
return {
|
|
300
307
|
url: body.url,
|
|
@@ -302,14 +309,15 @@ export async function readGeneratedImageResponse(resp) {
|
|
|
302
309
|
};
|
|
303
310
|
}
|
|
304
311
|
catch (error) {
|
|
305
|
-
throw new Error(`图片可能已经生成,但客户端下载结果响应失败: ${responseErrorDetail(error)}。` +
|
|
306
|
-
"不要重新调用 generate_image,请升级 u1s1 后再试",
|
|
312
|
+
throw new Error(tr(`图片可能已经生成,但客户端下载结果响应失败: ${responseErrorDetail(error)}。` +
|
|
313
|
+
"不要重新调用 generate_image,请升级 u1s1 后再试", `The image may already have been generated, but reading the result response failed: ${responseErrorDetail(error)}. ` +
|
|
314
|
+
"Do not call generate_image again; update u1s1 and try again"), { cause: error });
|
|
307
315
|
}
|
|
308
316
|
}
|
|
309
317
|
/** 生图走网关代理(方舟 key 只在服务端);返回 TOS 图片 URL(24h 有效),调用方应立即下载。 */
|
|
310
318
|
export async function generateImage(cfg, req, signal) {
|
|
311
319
|
if (!cfg.apiKey)
|
|
312
|
-
throw new Error("没有配置 API Key");
|
|
320
|
+
throw new Error(tr("没有配置 API Key", "No API key configured"));
|
|
313
321
|
let resp;
|
|
314
322
|
try {
|
|
315
323
|
resp = await authorizedFetch(cfg, `${cfg.baseUrl}/image`, {
|
|
@@ -325,17 +333,17 @@ export async function generateImage(cfg, req, signal) {
|
|
|
325
333
|
catch (error) {
|
|
326
334
|
if (signal?.aborted)
|
|
327
335
|
throw error;
|
|
328
|
-
throw new Error(
|
|
336
|
+
throw new Error(unreachableMessage(cfg.baseUrl), { cause: error });
|
|
329
337
|
}
|
|
330
338
|
if (resp.status === 401)
|
|
331
|
-
throw new Error(
|
|
339
|
+
throw new Error(sessionExpiredMessage());
|
|
332
340
|
if (!resp.ok)
|
|
333
|
-
throw new Error(await errorMessageFromResponse(resp, `图片生成服务返回 ${resp.status}
|
|
341
|
+
throw new Error(await errorMessageFromResponse(resp, tr(`图片生成服务返回 ${resp.status},稍后再试`, `Image service returned ${resp.status}. Please try again later.`)));
|
|
334
342
|
return readGeneratedImageResponse(resp);
|
|
335
343
|
}
|
|
336
344
|
export async function fetchMe(cfg) {
|
|
337
345
|
if (!cfg.apiKey)
|
|
338
|
-
throw new Error("没有配置 API Key");
|
|
346
|
+
throw new Error(tr("没有配置 API Key", "No API key configured"));
|
|
339
347
|
let resp;
|
|
340
348
|
try {
|
|
341
349
|
resp = await authorizedFetch(cfg, `${cfg.baseUrl}/me`, {
|
|
@@ -344,14 +352,14 @@ export async function fetchMe(cfg) {
|
|
|
344
352
|
});
|
|
345
353
|
}
|
|
346
354
|
catch {
|
|
347
|
-
throw new Error(
|
|
355
|
+
throw new Error(unreachableMessage(cfg.baseUrl));
|
|
348
356
|
}
|
|
349
357
|
if (resp.status === 401)
|
|
350
|
-
throw new Error(
|
|
358
|
+
throw new Error(sessionExpiredMessage());
|
|
351
359
|
if (!resp.ok)
|
|
352
|
-
throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status}
|
|
360
|
+
throw new Error(await errorMessageFromResponse(resp, tr(`服务端返回 ${resp.status},稍后再试`, `Server returned ${resp.status}. Please try again later.`)));
|
|
353
361
|
const body = await readJsonResponseCapped(resp);
|
|
354
362
|
if (!body || typeof body !== "object")
|
|
355
|
-
throw new Error("服务端账户响应格式不正确");
|
|
363
|
+
throw new Error(tr("服务端账户响应格式不正确", "The server returned a malformed account response"));
|
|
356
364
|
return body;
|
|
357
365
|
}
|
package/dist/bench-report.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PROVIDER_ID } from "./config.js";
|
|
2
2
|
import { DEFAULT_MAX_SCORE } from "./bench-types.js";
|
|
3
|
+
import { isEnglish, tr } from "./i18n.js";
|
|
3
4
|
const MAX_ANSWER_CHARS = 2_000;
|
|
4
5
|
export function resultModelLabel(result) {
|
|
5
6
|
return result.providerName === PROVIDER_ID
|
|
@@ -94,7 +95,7 @@ function appendScoreDetails(lines, results, maxScore) {
|
|
|
94
95
|
const scored = results.filter((result) => result.scoreDetails?.length);
|
|
95
96
|
if (scored.length === 0)
|
|
96
97
|
return;
|
|
97
|
-
lines.push("",
|
|
98
|
+
lines.push("", `<details><summary>${tr("评分明细", "Score details")}</summary>`, "");
|
|
98
99
|
for (const result of scored) {
|
|
99
100
|
lines.push(`**${resultModelLabel(result)}:** ${result.score}/${maxScore}`);
|
|
100
101
|
for (const detail of result.scoreDetails) {
|
|
@@ -106,10 +107,10 @@ function appendScoreDetails(lines, results, maxScore) {
|
|
|
106
107
|
}
|
|
107
108
|
function appendAnswers(lines, results) {
|
|
108
109
|
for (const result of results) {
|
|
109
|
-
lines.push(`<details><summary>${resultModelLabel(result)}
|
|
110
|
+
lines.push(`<details><summary>${tr(`${resultModelLabel(result)} 的回答`, `Answer from ${resultModelLabel(result)}`)}</summary>`, "", "```");
|
|
110
111
|
lines.push(result.response.slice(0, MAX_ANSWER_CHARS));
|
|
111
112
|
if (result.response.length > MAX_ANSWER_CHARS)
|
|
112
|
-
lines.push("...(截断)");
|
|
113
|
+
lines.push(tr("...(截断)", "...(truncated)"));
|
|
113
114
|
lines.push("```", "</details>", "");
|
|
114
115
|
}
|
|
115
116
|
}
|
|
@@ -124,7 +125,7 @@ function appendQuestion(lines, questionId, results) {
|
|
|
124
125
|
String(result.tokensOut),
|
|
125
126
|
result.error ? `❌ ${result.error.slice(0, 40)}` : "✅",
|
|
126
127
|
]);
|
|
127
|
-
lines.push("```", renderTable(["模型", `得分/${maxScore}`, "延迟", "输出tok", "状态"], rows), "```");
|
|
128
|
+
lines.push("```", renderTable(isEnglish() ? ["Model", `Score/${maxScore}`, "Latency", "Out tok", "Status"] : ["模型", `得分/${maxScore}`, "延迟", "输出tok", "状态"], rows), "```");
|
|
128
129
|
appendScoreDetails(lines, results, maxScore);
|
|
129
130
|
appendAnswers(lines, results);
|
|
130
131
|
}
|
|
@@ -138,16 +139,18 @@ function appendSummary(lines, results) {
|
|
|
138
139
|
costStr(summary.totalCost),
|
|
139
140
|
summary.errors > 0 ? `${errorRate(summary)}%` : "0%",
|
|
140
141
|
]);
|
|
141
|
-
lines.push("## 汇总", "
|
|
142
|
+
lines.push(tr("## 汇总", "## Summary"), "", "```", renderTable(isEnglish()
|
|
143
|
+
? ["Model", "Score", "Rate", "Avg latency", "Speed (tok/s)", "Total cost", "Error rate"]
|
|
144
|
+
: ["模型", "总分", "得分率", "平均延迟", "速度(tok/s)", "总花费", "错误率"], rows), "```");
|
|
142
145
|
}
|
|
143
146
|
export function generateReport(run) {
|
|
144
147
|
const lines = [
|
|
145
|
-
`# Bench 报告: ${run.suiteName}`,
|
|
146
|
-
`运行时间: ${run.timestamp}`,
|
|
147
|
-
`模型: ${run.models.join(", ")}`,
|
|
148
|
+
tr(`# Bench 报告: ${run.suiteName}`, `# Bench report: ${run.suiteName}`),
|
|
149
|
+
tr(`运行时间: ${run.timestamp}`, `Run at: ${run.timestamp}`),
|
|
150
|
+
tr(`模型: ${run.models.join(", ")}`, `Models: ${run.models.join(", ")}`),
|
|
148
151
|
"",
|
|
149
152
|
];
|
|
150
|
-
for (const [category, categoryResults] of groupResultsBy(run.results, (result) => result.category || "未分类")) {
|
|
153
|
+
for (const [category, categoryResults] of groupResultsBy(run.results, (result) => result.category || tr("未分类", "Uncategorized"))) {
|
|
151
154
|
lines.push(`## ${category}`, "");
|
|
152
155
|
for (const [questionId, questionResults] of groupResultsBy(categoryResults, (result) => result.questionId)) {
|
|
153
156
|
appendQuestion(lines, questionId, questionResults);
|