u1s1-cli 1.11.7 → 1.12.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/README.md +5 -5
- package/dist/agent-setup.d.ts +7 -1
- package/dist/agent-setup.js +34 -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 +7 -2
- package/dist/brand.js +44 -13
- package/dist/config.d.ts +28 -2
- package/dist/config.js +52 -14
- package/dist/context-usage.js +12 -9
- package/dist/deploy.js +53 -47
- package/dist/device-auth.js +7 -5
- package/dist/embed.d.ts +2 -1
- package/dist/embed.js +2 -1
- package/dist/error-humanize.d.ts +3 -1
- package/dist/error-humanize.js +24 -9
- package/dist/feedback.d.ts +4 -2
- package/dist/feedback.js +60 -39
- 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 +153 -54
- package/dist/login.d.ts +13 -2
- package/dist/login.js +84 -27
- package/dist/loop.js +18 -12
- package/dist/mcp/client.js +15 -14
- package/dist/mcp/command.js +76 -48
- package/dist/mcp/extension.js +5 -3
- package/dist/mcp/tools.js +6 -5
- package/dist/model.js +12 -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/site.d.ts +71 -0
- package/dist/site.js +123 -0
- package/dist/subagent.js +5 -4
- package/dist/tools.js +53 -37
- package/dist/update.js +47 -39
- package/dist/usage.d.ts +6 -4
- package/dist/usage.js +111 -53
- 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 +3 -3
- package/scripts/patch-pi.js +243 -278
- package/scripts/render-regression.mjs +55 -4
package/README.md
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
**Linux / macOS**
|
|
11
11
|
```bash
|
|
12
|
-
curl -fsSL https://
|
|
12
|
+
curl -fsSL https://u1s1app.com/releases/install.sh | bash
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
**Windows**
|
|
16
16
|
```powershell
|
|
17
|
-
powershell -c "irm https://
|
|
17
|
+
powershell -c "irm https://u1s1app.com/releases/install.ps1 | iex"
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
脚本会自动帮你安装 Node.js 和 u1s1,一行搞定。
|
|
@@ -26,7 +26,7 @@ npm i -g u1s1-cli
|
|
|
26
26
|
|
|
27
27
|
### 安装好后
|
|
28
28
|
```bash
|
|
29
|
-
# 第一次运行会引导你去
|
|
29
|
+
# 第一次运行会引导你去 u1s1app.com 注册领 Key(30 秒,不用绑卡)
|
|
30
30
|
u1s1
|
|
31
31
|
|
|
32
32
|
# 进到你的项目文件夹,像发微信一样说需求
|
|
@@ -66,7 +66,7 @@ u1s1
|
|
|
66
66
|
|
|
67
67
|
- **背后模型**:默认 DeepSeek Flash(1M 上下文,每天免费 $2);难题可随时 `u1s1 model grok` 切 Grok 4.6。对话里 `/model` 也会记住,下次启动还是这个。
|
|
68
68
|
- **怎么收费**:DeepSeek Flash 每天有 $2 免费用量,北京时间 0 点恢复且不累计。免费用完或切换其他模型后,按 API 实际成本从永久余额扣,不加价;新用户另送一次性 $10 加量。
|
|
69
|
-
- **额度不够**:邀请朋友,你俩各得 $1 永久加量 → [
|
|
69
|
+
- **额度不够**:邀请朋友,你俩各得 $1 永久加量 → [u1s1app.com/dashboard](https://u1s1app.com/dashboard)
|
|
70
70
|
- **内核**:基于开源的 [pi](https://pi.dev) coding agent,会话管理、斜杠命令、皮肤等能力全都有。
|
|
71
71
|
|
|
72
|
-
官网:[
|
|
72
|
+
官网:[u1s1app.com](https://u1s1app.com)
|
package/dist/agent-setup.d.ts
CHANGED
|
@@ -1,5 +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
|
+
import type { Site } from "./site.js";
|
|
4
|
+
/**
|
|
5
|
+
* 系统提示里的品牌人设。中国站面向编程新手、兜底语言中文(保持原文一字不差);
|
|
6
|
+
* 国际站面向开发者、兜底语言英文。按站点而不是界面语言判定,用时求值(配置可能晚于 import 才读)。
|
|
7
|
+
*/
|
|
8
|
+
export declare function brandAppend(site?: Site): string;
|
|
3
9
|
/** pi auto-appends <agentDir>/APPEND_SYSTEM.md to its system prompt — our branding hook. */
|
|
4
10
|
export declare function ensureBrandPrompt(shell: ShellDoctorResult): void;
|
|
5
11
|
/**
|
|
@@ -63,7 +69,7 @@ export declare function writeMcpExtension(): void;
|
|
|
63
69
|
* u1s1 的 OpenRouter 公开排行与数据分析(openrouter.ai/docs/app-attribution)。
|
|
64
70
|
* 只在请求目标确认为 OpenRouter 时才改头,其它供应商不加多余头。
|
|
65
71
|
*/
|
|
66
|
-
export declare function writeAttributionExtension(): void;
|
|
72
|
+
export declare function writeAttributionExtension(site?: Site): void;
|
|
67
73
|
/**
|
|
68
74
|
* 生成「精简 UI」扩展到 <agentDir>/extensions/u1s1-compact-ui.js:
|
|
69
75
|
* - 隐藏内置工具调用(read/bash/edit/write/grep/find/ls),出错才显示一行;ctrl+o 可展开
|
package/dist/agent-setup.js
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { agentDir, CUSTOM_ENDPOINTS, ENDPOINT_ID_RE, effectiveContextWindow, endpointKeyEnvName, MODELS, readCompactionBudgetSetting, PROVIDER_ID, UNKNOWN_MODEL_COST, VERSION, } from "./config.js";
|
|
5
|
-
|
|
4
|
+
import { activeSite, agentDir, CUSTOM_ENDPOINTS, ENDPOINT_ID_RE, effectiveContextWindow, endpointKeyEnvName, MODELS, readCompactionBudgetSetting, PROVIDER_ID, UNKNOWN_MODEL_COST, VERSION, } from "./config.js";
|
|
5
|
+
import { tr } from "./i18n.js";
|
|
6
|
+
/**
|
|
7
|
+
* 系统提示里的品牌人设。中国站面向编程新手、兜底语言中文(保持原文一字不差);
|
|
8
|
+
* 国际站面向开发者、兜底语言英文。按站点而不是界面语言判定,用时求值(配置可能晚于 import 才读)。
|
|
9
|
+
*/
|
|
10
|
+
export function brandAppend(site = activeSite()) {
|
|
11
|
+
if (site.id === "intl") {
|
|
12
|
+
return `## u1s1
|
|
13
|
+
|
|
14
|
+
You are u1s1 — a plain-spoken AI coding assistant:
|
|
15
|
+
- 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.
|
|
16
|
+
- 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.
|
|
17
|
+
- Explain problems in plain, direct language and skip needless jargon; use a one-sentence analogy when helpful.
|
|
18
|
+
- Before making changes, briefly state what you plan to do; afterwards summarize what changed in one or two sentences.
|
|
19
|
+
- If the user's request is vague, assume the most likely intent and confirm it instead of asking a long list of questions.
|
|
20
|
+
`;
|
|
21
|
+
}
|
|
22
|
+
return `## u1s1
|
|
6
23
|
|
|
7
24
|
You are u1s1 — a plain-spoken AI coding buddy for programming beginners. Most users are unfamiliar with jargon:
|
|
8
25
|
- 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 +28,7 @@ You are u1s1 — a plain-spoken AI coding buddy for programming beginners. Most
|
|
|
11
28
|
- Before making changes, briefly state what you plan to do; afterwards summarize what changed in one or two sentences.
|
|
12
29
|
- If the user's request is vague, assume the most likely intent and confirm it instead of asking a long list of questions.
|
|
13
30
|
`;
|
|
31
|
+
}
|
|
14
32
|
const EXEC_RULES = `
|
|
15
33
|
## Command execution
|
|
16
34
|
|
|
@@ -45,7 +63,7 @@ function envSection(shell) {
|
|
|
45
63
|
/** pi auto-appends <agentDir>/APPEND_SYSTEM.md to its system prompt — our branding hook. */
|
|
46
64
|
export function ensureBrandPrompt(shell) {
|
|
47
65
|
mkdirSync(agentDir, { recursive: true });
|
|
48
|
-
const content =
|
|
66
|
+
const content = brandAppend() + envSection(shell) + EXEC_RULES;
|
|
49
67
|
const p = join(agentDir, "APPEND_SYSTEM.md");
|
|
50
68
|
if (!existsSync(p) || readFileSync(p, "utf8") !== content) {
|
|
51
69
|
writeFileSync(p, content);
|
|
@@ -229,19 +247,21 @@ export function writeWebToolsExtension(cfg, features) {
|
|
|
229
247
|
// 密钥剥离守卫也从这里装:Desktop App 的 pi 跑在 pi-web-ui 子进程里,没有
|
|
230
248
|
// 启动器代码,只有扩展能替它把 U1S1_API_KEY / U1S1_EP_KEY_* 挡在子进程之外
|
|
231
249
|
const secretEnvUrl = new URL("./secret-env.js", import.meta.url).href;
|
|
250
|
+
// 提示文案在生成时按当前界面语言定好,扩展文件里只是字面量
|
|
251
|
+
const toolsLoadFailed = tr("联网工具本次未能加载(程序文件可能正在更新),重启 u1s1 即可恢复", "Web tools couldn't load this time (program files may be updating). Restart u1s1 to restore them.");
|
|
232
252
|
writeFileSync(join(dir, "u1s1-tools.js"), `// 由 u1s1 每次启动自动生成,请勿手改\n` +
|
|
233
253
|
`export default async function (pi) {\n` +
|
|
234
254
|
` (await import(${JSON.stringify(secretEnvUrl)})).installChildEnvGuard();\n` +
|
|
235
255
|
` if (process.env.U1S1_TOOLS_VIA_EXTENSION !== "1") return;\n` +
|
|
236
256
|
` const baseUrl = process.env.U1S1_SIGNING_PROXY_URL || ${JSON.stringify(cfg.baseUrl)};\n` +
|
|
237
257
|
// 升级半途 / 权限问题导致 CLI dist 不可读时,宁可这一会话没有联网工具,
|
|
238
|
-
// 也不能让扩展加载失败刷英文堆栈(审计 B4);只在 UI
|
|
258
|
+
// 也不能让扩展加载失败刷英文堆栈(审计 B4);只在 UI 里提示一句人话
|
|
239
259
|
` let tools;\n` +
|
|
240
260
|
` try {\n` +
|
|
241
261
|
` tools = await import(${JSON.stringify(toolsUrl)});\n` +
|
|
242
262
|
` } catch {\n` +
|
|
243
263
|
` pi.on("session_start", (_event, ctx) => {\n` +
|
|
244
|
-
` try { if (ctx.hasUI) ctx.ui.notify(
|
|
264
|
+
` try { if (ctx.hasUI) ctx.ui.notify(${JSON.stringify(toolsLoadFailed)}, "warning"); } catch {}\n` +
|
|
245
265
|
` });\n` +
|
|
246
266
|
` return;\n` +
|
|
247
267
|
` }\n` +
|
|
@@ -342,6 +362,7 @@ export function writeMcpExtension() {
|
|
|
342
362
|
const dir = join(agentDir, "extensions");
|
|
343
363
|
mkdirSync(dir, { recursive: true });
|
|
344
364
|
const url = new URL("./mcp/extension.js", import.meta.url).href;
|
|
365
|
+
const mcpLoadFailed = tr("MCP 服务加载失败:", "Failed to load MCP servers: ");
|
|
345
366
|
writeFileSync(join(dir, "u1s1-mcp.js"), `// 由 u1s1 每次启动自动生成,请勿手改\n` +
|
|
346
367
|
`export default async function (pi) {\n` +
|
|
347
368
|
` if (process.env.U1S1_IN_SUBAGENT === "1") return;\n` +
|
|
@@ -356,7 +377,7 @@ export function writeMcpExtension() {
|
|
|
356
377
|
` } catch (err) {\n` +
|
|
357
378
|
` const msg = err instanceof Error ? err.message : String(err);\n` +
|
|
358
379
|
` pi.on("session_start", (_event, ctx) => {\n` +
|
|
359
|
-
` if (ctx.hasUI) ctx.ui.notify(
|
|
380
|
+
` if (ctx.hasUI) ctx.ui.notify(${JSON.stringify(mcpLoadFailed)} + msg, "warning");\n` +
|
|
360
381
|
` });\n` +
|
|
361
382
|
` }\n` +
|
|
362
383
|
`}\n`);
|
|
@@ -368,7 +389,7 @@ export function writeMcpExtension() {
|
|
|
368
389
|
* u1s1 的 OpenRouter 公开排行与数据分析(openrouter.ai/docs/app-attribution)。
|
|
369
390
|
* 只在请求目标确认为 OpenRouter 时才改头,其它供应商不加多余头。
|
|
370
391
|
*/
|
|
371
|
-
export function writeAttributionExtension() {
|
|
392
|
+
export function writeAttributionExtension(site = activeSite()) {
|
|
372
393
|
const dir = join(agentDir, "extensions");
|
|
373
394
|
mkdirSync(dir, { recursive: true });
|
|
374
395
|
writeFileSync(join(dir, "u1s1-attribution.js"), `// 由 u1s1 每次启动自动生成,请勿手改\n` +
|
|
@@ -376,7 +397,7 @@ export function writeAttributionExtension() {
|
|
|
376
397
|
` pi.on("before_provider_headers", (event, ctx) => {\n` +
|
|
377
398
|
` const m = ctx.model;\n` +
|
|
378
399
|
` if (!m || !(m.provider === "openrouter" || String(m.baseUrl ?? "").includes("openrouter.ai"))) return;\n` +
|
|
379
|
-
` event.headers["HTTP-Referer"] =
|
|
400
|
+
` event.headers["HTTP-Referer"] = ${JSON.stringify(site.origin)};\n` +
|
|
380
401
|
` event.headers["X-OpenRouter-Title"] = "u1s1";\n` +
|
|
381
402
|
` event.headers["X-OpenRouter-Categories"] = "cli-agent";\n` +
|
|
382
403
|
` });\n` +
|
|
@@ -391,6 +412,9 @@ export function writeAttributionExtension() {
|
|
|
391
412
|
export function writeCompactUiExtension(outputDir) {
|
|
392
413
|
const dir = outputDir ?? join(agentDir, "extensions");
|
|
393
414
|
mkdirSync(dir, { recursive: true });
|
|
415
|
+
// 汇总行文案按生成时的界面语言写死进扩展;英文版面向开发者,直接用工具原名
|
|
416
|
+
const toolSummaryLabels = tr(`read: "读文件", write: "写文件", edit: "改文件", bash: "跑命令", grep: "搜内容", find: "找文件", ls: "列目录",`, `read: "read", write: "write", edit: "edit", bash: "bash", grep: "grep", find: "find", ls: "ls",`);
|
|
417
|
+
const toolCallsLabel = tr(`d.total + " 次工具调用"`, `d.total + (d.total === 1 ? " tool call" : " tool calls")`);
|
|
394
418
|
writeFileSync(join(dir, "u1s1-compact-ui.js"), `// 由 u1s1 每次启动自动生成,请勿手改
|
|
395
419
|
function oneLine(text, max = 160) {
|
|
396
420
|
const flat = String(text).replace(/\\s*\\n\\s*/g, " ; ").trim();
|
|
@@ -408,7 +432,7 @@ export default async function (pi) {
|
|
|
408
432
|
let errors = 0;
|
|
409
433
|
// 工具调用被折叠后,这一行摘要是新手了解「AI 刚才做了什么」的唯一入口,内置工具名给人话
|
|
410
434
|
const TOOL_SUMMARY_LABELS = {
|
|
411
|
-
|
|
435
|
+
${toolSummaryLabels}
|
|
412
436
|
};
|
|
413
437
|
pi.on("agent_start", async () => {
|
|
414
438
|
counts = {};
|
|
@@ -432,7 +456,7 @@ export default async function (pi) {
|
|
|
432
456
|
pi.registerEntryRenderer("tool-summary", (entry, _opts, theme) => {
|
|
433
457
|
const d = entry.data;
|
|
434
458
|
let text = theme.fg("muted", "⚙ ");
|
|
435
|
-
text += theme.fg("toolTitle", theme.bold(
|
|
459
|
+
text += theme.fg("toolTitle", theme.bold(${toolCallsLabel}));
|
|
436
460
|
text += theme.fg("dim", " · " + d.summary);
|
|
437
461
|
return new Text(text, 0, 0);
|
|
438
462
|
});
|
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);
|