u1s1-cli 1.11.8 → 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/dist/agent-setup.d.ts +5 -0
- package/dist/agent-setup.js +31 -7
- 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 +139 -48
- package/dist/login.js +39 -28
- 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 +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 +2 -2
- package/scripts/patch-pi.js +243 -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
|
/**
|
package/dist/agent-setup.js
CHANGED
|
@@ -2,7 +2,24 @@ 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
|
+
/**
|
|
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`);
|
|
@@ -391,6 +412,9 @@ export function writeAttributionExtension(site = activeSite()) {
|
|
|
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);
|
package/dist/bench.js
CHANGED
|
@@ -8,6 +8,7 @@ import { authorizedFetch, hasDeviceCredential } from "./device-auth.js";
|
|
|
8
8
|
import { costStr, errorRate, formatDuration, generateReport, renderTable, scoreBar, scoreRate, sortByScore, summarizeByModel, tokPerSec, } from "./bench-report.js";
|
|
9
9
|
import { scoreResponse } from "./bench-scoring.js";
|
|
10
10
|
import { DEFAULT_MAX_SCORE, } from "./bench-types.js";
|
|
11
|
+
import { isEnglish, tr } from "./i18n.js";
|
|
11
12
|
// ─── 工具 ───
|
|
12
13
|
const BENCH_DIR = join(homedir(), ".u1s1", "bench");
|
|
13
14
|
/** compare 子命令里单条回答的截断长度 */
|
|
@@ -43,7 +44,7 @@ function loadSuite(name) {
|
|
|
43
44
|
if (found)
|
|
44
45
|
return found;
|
|
45
46
|
}
|
|
46
|
-
throw new Error(`找不到 bench suite「${name}
|
|
47
|
+
throw new Error(tr(`找不到 bench suite「${name}」`, `Bench suite "${name}" not found`));
|
|
47
48
|
}
|
|
48
49
|
/** 列出可用的 suite */
|
|
49
50
|
function suiteSummaries(filePath, builtin) {
|
|
@@ -126,7 +127,7 @@ async function callModel(input) {
|
|
|
126
127
|
: await fetch(`${baseUrl}/chat/completions`, request);
|
|
127
128
|
const latencyMs = Math.round(performance.now() - start);
|
|
128
129
|
if (!res.ok) {
|
|
129
|
-
const errBody = await readResponseTextCapped(res, 64 * 1024).catch(() => "未知错误");
|
|
130
|
+
const errBody = await readResponseTextCapped(res, 64 * 1024).catch(() => tr("未知错误", "unknown error"));
|
|
130
131
|
return { response: "", latencyMs, tokensIn: 0, tokensOut: 0, error: `HTTP ${res.status}: ${errBody.slice(0, 200)}` };
|
|
131
132
|
}
|
|
132
133
|
return { ...parseChatCompletion(await readJsonResponseCapped(res)), latencyMs };
|
|
@@ -160,12 +161,12 @@ function saveRun(run) {
|
|
|
160
161
|
mkdirSync(BENCH_DIR, { recursive: true });
|
|
161
162
|
const file = join(BENCH_DIR, `${run.id}.json`);
|
|
162
163
|
writeFileSync(file, JSON.stringify(run, null, 2) + "\n");
|
|
163
|
-
console.log(` 结果已保存:${file}`);
|
|
164
|
+
console.log(tr(` 结果已保存:${file}`, ` Results saved: ${file}`));
|
|
164
165
|
}
|
|
165
166
|
function loadRun(id) {
|
|
166
167
|
const file = join(BENCH_DIR, `${id}.json`);
|
|
167
168
|
if (!existsSync(file))
|
|
168
|
-
throw new Error(`找不到运行记录「${id}
|
|
169
|
+
throw new Error(tr(`找不到运行记录「${id}」`, `Run "${id}" not found`));
|
|
169
170
|
return JSON.parse(readFileSync(file, "utf8"));
|
|
170
171
|
}
|
|
171
172
|
function listRuns() {
|
|
@@ -190,30 +191,30 @@ function failWithError(e) {
|
|
|
190
191
|
}
|
|
191
192
|
function printHelp() {
|
|
192
193
|
console.log("");
|
|
193
|
-
console.log(" u1s1 bench — 模型质量基准测试(带自动评分)");
|
|
194
|
+
console.log(tr(" u1s1 bench — 模型质量基准测试(带自动评分)", " u1s1 bench — model quality benchmark (auto-scored)"));
|
|
194
195
|
console.log("");
|
|
195
|
-
console.log(" 用法:");
|
|
196
|
-
console.log(" u1s1 bench list 列出可用的测试套件");
|
|
197
|
-
console.log(" u1s1 bench run <suite> [models..] 跑基准测试(--delay 秒数:题目间隔,限流渠道用)");
|
|
198
|
-
console.log(" u1s1 bench report <run-id> 查看报告");
|
|
199
|
-
console.log(" u1s1 bench compare <run-id1> <run-id2> 对比两次运行");
|
|
196
|
+
console.log(tr(" 用法:", " Usage:"));
|
|
197
|
+
console.log(tr(" u1s1 bench list 列出可用的测试套件", " u1s1 bench list List available suites"));
|
|
198
|
+
console.log(tr(" u1s1 bench run <suite> [models..] 跑基准测试(--delay 秒数:题目间隔,限流渠道用)", " u1s1 bench run <suite> [models..] Run a benchmark (--delay <seconds> between questions, for rate-limited providers)"));
|
|
199
|
+
console.log(tr(" u1s1 bench report <run-id> 查看报告", " u1s1 bench report <run-id> Show a report"));
|
|
200
|
+
console.log(tr(" u1s1 bench compare <run-id1> <run-id2> 对比两次运行", " u1s1 bench compare <run-id1> <run-id2> Compare two runs"));
|
|
200
201
|
console.log("");
|
|
201
|
-
console.log(" 示例:");
|
|
202
|
-
console.log(" u1s1 bench run quick 用当前默认模型跑快速测试");
|
|
203
|
-
console.log(" u1s1 bench run full deepseek pro 用多个模型跑全面测试");
|
|
204
|
-
console.log(" u1s1 bench report latest 看最近一次报告");
|
|
202
|
+
console.log(tr(" 示例:", " Examples:"));
|
|
203
|
+
console.log(tr(" u1s1 bench run quick 用当前默认模型跑快速测试", " u1s1 bench run quick Quick suite with your default model"));
|
|
204
|
+
console.log(tr(" u1s1 bench run full deepseek pro 用多个模型跑全面测试", " u1s1 bench run full deepseek pro Full suite across several models"));
|
|
205
|
+
console.log(tr(" u1s1 bench report latest 看最近一次报告", " u1s1 bench report latest Show the latest report"));
|
|
205
206
|
console.log("");
|
|
206
207
|
}
|
|
207
208
|
function listSuitesCommand() {
|
|
208
209
|
const suites = listSuites();
|
|
209
210
|
console.log("");
|
|
210
|
-
console.log(" Bench 套件:");
|
|
211
|
+
console.log(tr(" Bench 套件:", " Bench suites:"));
|
|
211
212
|
for (const s of suites) {
|
|
212
|
-
const tag = s.builtin ? "(内置)" : "(自定义)";
|
|
213
|
+
const tag = s.builtin ? tr("(内置)", "(built-in)") : tr("(自定义)", "(custom)");
|
|
213
214
|
console.log(` ${s.name.padEnd(12)} ${s.description} ${tag}`);
|
|
214
215
|
}
|
|
215
216
|
console.log("");
|
|
216
|
-
console.log(" 自定义套件放 ~/.u1s1/bench/<name>.json,格式同内置 suite.json");
|
|
217
|
+
console.log(tr(" 自定义套件放 ~/.u1s1/bench/<name>.json,格式同内置 suite.json", " Put custom suites in ~/.u1s1/bench/<name>.json (same format as the built-in suite.json)"));
|
|
217
218
|
console.log("");
|
|
218
219
|
}
|
|
219
220
|
/** 把模型名解析成走 u1s1 默认端点的调用目标 */
|
|
@@ -329,10 +330,10 @@ async function runBenchQuestion(input) {
|
|
|
329
330
|
async function runBench(args) {
|
|
330
331
|
const { suiteName, modelQueries, delayMs } = parseBenchRunArgs(args);
|
|
331
332
|
if (delayMs > 0)
|
|
332
|
-
console.log(` ⏳ 题目间隔:${(delayMs / 1000).toFixed(0)}s(限流渠道用)`);
|
|
333
|
+
console.log(tr(` ⏳ 题目间隔:${(delayMs / 1000).toFixed(0)}s(限流渠道用)`, ` ⏳ Delay between questions: ${(delayMs / 1000).toFixed(0)}s (for rate-limited providers)`));
|
|
333
334
|
const cfg = loadConfig();
|
|
334
335
|
if (!hasDeviceCredential(cfg)) {
|
|
335
|
-
console.error(" 还没有登录,先 u1s1 login");
|
|
336
|
+
console.error(tr(" 还没有登录,先 u1s1 login", " Not signed in. Run u1s1 login first."));
|
|
336
337
|
process.exit(1);
|
|
337
338
|
}
|
|
338
339
|
await loadCustomEndpoints(cfg);
|
|
@@ -343,8 +344,8 @@ async function runBench(args) {
|
|
|
343
344
|
console.log("");
|
|
344
345
|
console.log(` 📊 Bench: ${suite.name}`);
|
|
345
346
|
console.log(` ${suite.description}`);
|
|
346
|
-
console.log(` 题目数: ${suite.questions.length}`);
|
|
347
|
-
console.log(` 模型: ${targets.map((t) => t.label).join(", ")}`);
|
|
347
|
+
console.log(tr(` 题目数: ${suite.questions.length}`, ` Questions: ${suite.questions.length}`));
|
|
348
|
+
console.log(tr(` 模型: ${targets.map((t) => t.label).join(", ")}`, ` Models: ${targets.map((t) => t.label).join(", ")}`));
|
|
348
349
|
console.log("");
|
|
349
350
|
const results = [];
|
|
350
351
|
const total = suite.questions.length * targets.length;
|
|
@@ -368,14 +369,16 @@ async function runBench(args) {
|
|
|
368
369
|
results,
|
|
369
370
|
});
|
|
370
371
|
printConsoleSummary(results);
|
|
371
|
-
console.log(` 查看完整报告: u1s1 bench report ${runId}`);
|
|
372
|
+
console.log(tr(` 查看完整报告: u1s1 bench report ${runId}`, ` Full report: u1s1 bench report ${runId}`));
|
|
372
373
|
console.log("");
|
|
373
374
|
}
|
|
374
375
|
/** 控制台里的简易评分汇总表 */
|
|
375
376
|
function printConsoleSummary(results) {
|
|
376
377
|
console.log("");
|
|
377
|
-
console.log(" 📋 评分汇总:");
|
|
378
|
-
const header =
|
|
378
|
+
console.log(tr(" 📋 评分汇总:", " 📋 Score summary:"));
|
|
379
|
+
const header = isEnglish()
|
|
380
|
+
? ["Model", "Score", "Rate", "Avg latency", "Speed", "Cost", "Status"]
|
|
381
|
+
: ["模型", "总分", "得分率", "平均延迟", "速度", "花费", "状态"];
|
|
379
382
|
const rows = sortByScore([...summarizeByModel(results, (r) => r.modelId)]).map(([model, s]) => [
|
|
380
383
|
model,
|
|
381
384
|
`${s.totalScore}/${s.totalMax}`,
|
|
@@ -383,7 +386,7 @@ function printConsoleSummary(results) {
|
|
|
383
386
|
formatDuration(s.totalLatency / s.total),
|
|
384
387
|
tokPerSec(s),
|
|
385
388
|
costStr(s.totalCost),
|
|
386
|
-
s.errors > 0 ? `${errorRate(s)}
|
|
389
|
+
s.errors > 0 ? tr(`${errorRate(s)}%失败`, `${errorRate(s)}% failed`) : tr("✅全部通过", "✅ all passed"),
|
|
387
390
|
]);
|
|
388
391
|
console.log(" " + renderTable(header, rows));
|
|
389
392
|
console.log("");
|
|
@@ -395,7 +398,7 @@ function showReport(args) {
|
|
|
395
398
|
try {
|
|
396
399
|
const id = runIdArg === "latest" ? findLatestRun() : runIdArg;
|
|
397
400
|
if (!id)
|
|
398
|
-
throw new Error("还没有运行记录");
|
|
401
|
+
throw new Error(tr("还没有运行记录", "No runs yet"));
|
|
399
402
|
run = loadRun(id);
|
|
400
403
|
}
|
|
401
404
|
catch (e) {
|
|
@@ -405,7 +408,7 @@ function showReport(args) {
|
|
|
405
408
|
const reportFile = join(BENCH_DIR, `${run.id}.md`);
|
|
406
409
|
writeFileSync(reportFile, report);
|
|
407
410
|
console.log(report);
|
|
408
|
-
console.log(` 报告已保存: ${reportFile}`);
|
|
411
|
+
console.log(tr(` 报告已保存: ${reportFile}`, ` Report saved: ${reportFile}`));
|
|
409
412
|
}
|
|
410
413
|
/** 两次运行共同拥有的题目 id */
|
|
411
414
|
function commonQuestionIds(a, b) {
|
|
@@ -428,7 +431,7 @@ function compareRuns(runId1, runId2) {
|
|
|
428
431
|
failWithError(e);
|
|
429
432
|
}
|
|
430
433
|
console.log("");
|
|
431
|
-
console.log(` 对比 ${run1.suiteName}(${run1.timestamp}) vs ${run2.suiteName}(${run2.timestamp})`);
|
|
434
|
+
console.log(tr(` 对比 ${run1.suiteName}(${run1.timestamp}) vs ${run2.suiteName}(${run2.timestamp})`, ` Comparing ${run1.suiteName} (${run1.timestamp}) vs ${run2.suiteName} (${run2.timestamp})`));
|
|
432
435
|
console.log("");
|
|
433
436
|
for (const qId of commonQuestionIds(run1, run2)) {
|
|
434
437
|
console.log(` ── ${qId} ──`);
|
|
@@ -452,13 +455,13 @@ export async function benchCommand(args) {
|
|
|
452
455
|
return showReport(args.slice(1));
|
|
453
456
|
if (sub === "compare") {
|
|
454
457
|
if (!args[1] || !args[2]) {
|
|
455
|
-
console.log(" 用法: u1s1 bench compare <run-id1> <run-id2>");
|
|
458
|
+
console.log(tr(" 用法: u1s1 bench compare <run-id1> <run-id2>", " Usage: u1s1 bench compare <run-id1> <run-id2>"));
|
|
456
459
|
return;
|
|
457
460
|
}
|
|
458
461
|
return compareRuns(args[1], args[2]);
|
|
459
462
|
}
|
|
460
|
-
console.error(` 未知子命令: ${sub}`);
|
|
461
|
-
console.log(" u1s1 bench help 查看用法");
|
|
463
|
+
console.error(tr(` 未知子命令: ${sub}`, ` Unknown subcommand: ${sub}`));
|
|
464
|
+
console.log(tr(" u1s1 bench help 查看用法", " Run u1s1 bench help for usage"));
|
|
462
465
|
process.exit(1);
|
|
463
466
|
}
|
|
464
467
|
function findLatestRun() {
|
package/dist/brand.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
export declare const BRAND_NAME = "u1s1";
|
|
3
3
|
export declare const BRAND_CN = "\u6709\u4E00\u8BF4\u4E00";
|
|
4
|
-
|
|
4
|
+
/** 中文品牌名只在中国站界面出现;国际站只叫 u1s1。 */
|
|
5
|
+
export declare function brandTagline(): string;
|
|
5
6
|
/**
|
|
6
7
|
* 中国站仪表盘。只为 embed 导出保留(Desktop App 现在只有中国站包,P3 出 intl 包时
|
|
7
8
|
* 改用自带的站点配置);CLI 内部一律按当前站点取 siteLinks(activeSite()).dashboard。
|