u1s1-cli 1.2.3 → 1.2.4

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.
@@ -81,8 +81,8 @@ export function applyModelThinkingDefaults(settings, models) {
81
81
  const defaults = models.filter((model) => model.thinking !== undefined);
82
82
  if (defaults.length === 0)
83
83
  return false;
84
- const levels = { ...(recordValue(settings["modelThinkingLevels"]) ?? {}) };
85
- const managed = { ...(recordValue(settings[THINKING_DEFAULTS_MARKER]) ?? {}) };
84
+ const levels = { ...recordValue(settings["modelThinkingLevels"]) };
85
+ const managed = { ...recordValue(settings[THINKING_DEFAULTS_MARKER]) };
86
86
  let changed = false;
87
87
  for (const model of defaults) {
88
88
  const key = `${PROVIDER_ID}/${model.id}`;
package/dist/api.d.ts CHANGED
@@ -105,7 +105,11 @@ export interface SearchResponse {
105
105
  results: SearchResult[];
106
106
  }
107
107
  /** 联网搜索走网关代理(上游 key 只在服务端)。maxResults 不传时由服务端决定默认值。 */
108
- export declare function searchWeb(cfg: Pick<CliConfig, "baseUrl" | "apiKey">, query: string, maxResults?: number, signal?: AbortSignal): Promise<SearchResponse>;
108
+ export declare function searchWeb(cfg: Pick<CliConfig, "baseUrl" | "apiKey">, input: {
109
+ query: string;
110
+ maxResults?: number;
111
+ signal?: AbortSignal;
112
+ }): Promise<SearchResponse>;
109
113
  /** web_fetch 直连失败时的回退:网关用 Cloudflare Browser Rendering 渲染后转 markdown。 */
110
114
  export declare function renderPage(cfg: Pick<CliConfig, "baseUrl" | "apiKey">, url: string, signal?: AbortSignal): Promise<{
111
115
  url: string;
package/dist/api.js CHANGED
@@ -56,7 +56,7 @@ export async function loadCustomEndpoints(cfg) {
56
56
  }
57
57
  }
58
58
  /** 联网搜索走网关代理(上游 key 只在服务端)。maxResults 不传时由服务端决定默认值。 */
59
- export async function searchWeb(cfg, query, maxResults, signal) {
59
+ export async function searchWeb(cfg, input) {
60
60
  if (!cfg.apiKey)
61
61
  throw new Error("没有配置 API Key");
62
62
  let resp;
@@ -67,8 +67,8 @@ export async function searchWeb(cfg, query, maxResults, signal) {
67
67
  "x-u1s1-version": VERSION,
68
68
  "content-type": "application/json",
69
69
  },
70
- body: JSON.stringify({ query, max_results: maxResults }),
71
- signal: signal ?? AbortSignal.timeout(60_000),
70
+ body: JSON.stringify({ query: input.query, max_results: input.maxResults }),
71
+ signal: input.signal ?? AbortSignal.timeout(60_000),
72
72
  });
73
73
  }
74
74
  catch {
@@ -171,10 +171,10 @@ export async function generateImage(cfg, req, signal) {
171
171
  signal: signal ?? AbortSignal.timeout(180_000),
172
172
  });
173
173
  }
174
- catch (e) {
174
+ catch (error) {
175
175
  if (signal?.aborted)
176
- throw e;
177
- throw new Error(`连不上 ${cfg.baseUrl},检查一下网络?`);
176
+ throw error;
177
+ throw new Error(`连不上 ${cfg.baseUrl},检查一下网络?`, { cause: error });
178
178
  }
179
179
  if (resp.status === 401)
180
180
  throw new Error("登录已失效,请重新运行 u1s1 login");
@@ -0,0 +1,22 @@
1
+ import { type BenchResult, type BenchRunMeta } from "./bench-types.js";
2
+ export interface ModelSummary {
3
+ totalScore: number;
4
+ totalMax: number;
5
+ totalLatency: number;
6
+ totalCost: number;
7
+ errors: number;
8
+ total: number;
9
+ okTokensOut: number;
10
+ okLatency: number;
11
+ }
12
+ export declare function resultModelLabel(result: BenchResult): string;
13
+ export declare function formatDuration(ms: number): string;
14
+ export declare function costStr(usd: number): string;
15
+ export declare function renderTable(header: string[], rows: string[][]): string;
16
+ export declare function scoreBar(score: number, maxScore: number, width?: number): string;
17
+ export declare function summarizeByModel(results: BenchResult[], keyOf: (result: BenchResult) => string): Map<string, ModelSummary>;
18
+ export declare function sortByScore(entries: [string, ModelSummary][]): [string, ModelSummary][];
19
+ export declare function scoreRate(summary: ModelSummary): string;
20
+ export declare function errorRate(summary: ModelSummary): string;
21
+ export declare function tokPerSec(summary: ModelSummary): string;
22
+ export declare function generateReport(run: BenchRunMeta): string;
@@ -0,0 +1,158 @@
1
+ import { PROVIDER_ID } from "./config.js";
2
+ import { DEFAULT_MAX_SCORE } from "./bench-types.js";
3
+ const MAX_ANSWER_CHARS = 2_000;
4
+ export function resultModelLabel(result) {
5
+ return result.providerName === PROVIDER_ID
6
+ ? result.modelId
7
+ : `${result.providerName}:${result.modelId}`;
8
+ }
9
+ export function formatDuration(ms) {
10
+ return ms < 1_000 ? `${ms.toFixed(0)}ms` : `${(ms / 1_000).toFixed(1)}s`;
11
+ }
12
+ export function costStr(usd) {
13
+ if (usd === 0)
14
+ return "—";
15
+ if (usd < 0.001)
16
+ return "<$0.001";
17
+ return `$${usd.toFixed(4)}`;
18
+ }
19
+ export function renderTable(header, rows) {
20
+ const columnWidths = header.map((heading, index) => Math.max(heading.length, ...rows.map((row) => row[index]?.length ?? 0)));
21
+ const separator = "─".repeat(columnWidths.reduce((sum, width) => sum + width + 3, 1));
22
+ const renderRow = (cells) => ` ${cells.map((cell, index) => cell.padEnd(columnWidths[index])).join(" │ ")} `;
23
+ return [renderRow(header), separator, ...rows.map(renderRow)].join("\n");
24
+ }
25
+ export function scoreBar(score, maxScore, width = 8) {
26
+ const ratio = maxScore > 0 ? Math.min(score / maxScore, 1) : 0;
27
+ const filled = Math.round(ratio * width);
28
+ const bar = "█".repeat(filled) + "░".repeat(width - filled);
29
+ if (ratio >= 0.8)
30
+ return `\x1b[32m${bar}\x1b[0m`;
31
+ if (ratio >= 0.5)
32
+ return `\x1b[33m${bar}\x1b[0m`;
33
+ return `\x1b[31m${bar}\x1b[0m`;
34
+ }
35
+ function emptySummary() {
36
+ return {
37
+ totalScore: 0,
38
+ totalMax: 0,
39
+ totalLatency: 0,
40
+ totalCost: 0,
41
+ errors: 0,
42
+ total: 0,
43
+ okTokensOut: 0,
44
+ okLatency: 0,
45
+ };
46
+ }
47
+ export function summarizeByModel(results, keyOf) {
48
+ const summaries = new Map();
49
+ for (const result of results) {
50
+ const key = keyOf(result);
51
+ const summary = summaries.get(key) ?? emptySummary();
52
+ summary.totalScore += result.score ?? 0;
53
+ summary.totalMax += result.maxScore ?? DEFAULT_MAX_SCORE;
54
+ summary.totalLatency += result.latencyMs;
55
+ summary.totalCost += result.costUsd;
56
+ if (result.error)
57
+ summary.errors++;
58
+ else {
59
+ summary.okTokensOut += result.tokensOut;
60
+ summary.okLatency += result.latencyMs;
61
+ }
62
+ summary.total++;
63
+ summaries.set(key, summary);
64
+ }
65
+ return summaries;
66
+ }
67
+ export function sortByScore(entries) {
68
+ return entries.sort((left, right) => right[1].totalScore - left[1].totalScore);
69
+ }
70
+ export function scoreRate(summary) {
71
+ return summary.totalMax > 0
72
+ ? ((summary.totalScore / summary.totalMax) * 100).toFixed(1)
73
+ : "0.0";
74
+ }
75
+ export function errorRate(summary) {
76
+ return ((summary.errors / summary.total) * 100).toFixed(0);
77
+ }
78
+ export function tokPerSec(summary) {
79
+ return summary.okLatency > 0
80
+ ? ((summary.okTokensOut / summary.okLatency) * 1_000).toFixed(1)
81
+ : "—";
82
+ }
83
+ function groupResultsBy(results, keyOf) {
84
+ const groups = new Map();
85
+ for (const result of results) {
86
+ const key = keyOf(result);
87
+ const group = groups.get(key) ?? [];
88
+ group.push(result);
89
+ groups.set(key, group);
90
+ }
91
+ return groups;
92
+ }
93
+ function appendScoreDetails(lines, results, maxScore) {
94
+ const scored = results.filter((result) => result.scoreDetails?.length);
95
+ if (scored.length === 0)
96
+ return;
97
+ lines.push("", "<details><summary>评分明细</summary>", "");
98
+ for (const result of scored) {
99
+ lines.push(`**${resultModelLabel(result)}:** ${result.score}/${maxScore}`);
100
+ for (const detail of result.scoreDetails) {
101
+ lines.push(`- ${detail.passed ? "✅" : "❌"} ${detail.check} (+${detail.earned}/${detail.points})`);
102
+ }
103
+ lines.push("");
104
+ }
105
+ lines.push("</details>", "");
106
+ }
107
+ function appendAnswers(lines, results) {
108
+ for (const result of results) {
109
+ lines.push(`<details><summary>${resultModelLabel(result)} 的回答</summary>`, "", "```");
110
+ lines.push(result.response.slice(0, MAX_ANSWER_CHARS));
111
+ if (result.response.length > MAX_ANSWER_CHARS)
112
+ lines.push("...(截断)");
113
+ lines.push("```", "</details>", "");
114
+ }
115
+ }
116
+ function appendQuestion(lines, questionId, results) {
117
+ const first = results[0];
118
+ const maxScore = first.maxScore ?? DEFAULT_MAX_SCORE;
119
+ lines.push(`### ${questionId}`, `> ${first.prompt}`, "");
120
+ const rows = results.map((result) => [
121
+ resultModelLabel(result),
122
+ result.score === undefined ? "—" : `${result.score}/${maxScore}`,
123
+ formatDuration(result.latencyMs),
124
+ String(result.tokensOut),
125
+ result.error ? `❌ ${result.error.slice(0, 40)}` : "✅",
126
+ ]);
127
+ lines.push("```", renderTable(["模型", `得分/${maxScore}`, "延迟", "输出tok", "状态"], rows), "```");
128
+ appendScoreDetails(lines, results, maxScore);
129
+ appendAnswers(lines, results);
130
+ }
131
+ function appendSummary(lines, results) {
132
+ const rows = sortByScore([...summarizeByModel(results, resultModelLabel)]).map(([model, summary]) => [
133
+ model,
134
+ `${summary.totalScore}/${summary.totalMax}`,
135
+ `${scoreRate(summary)}%`,
136
+ formatDuration(summary.totalLatency / summary.total),
137
+ tokPerSec(summary),
138
+ costStr(summary.totalCost),
139
+ summary.errors > 0 ? `${errorRate(summary)}%` : "0%",
140
+ ]);
141
+ lines.push("## 汇总", "", "```", renderTable(["模型", "总分", "得分率", "平均延迟", "速度(tok/s)", "总花费", "错误率"], rows), "```");
142
+ }
143
+ export function generateReport(run) {
144
+ const lines = [
145
+ `# Bench 报告: ${run.suiteName}`,
146
+ `运行时间: ${run.timestamp}`,
147
+ `模型: ${run.models.join(", ")}`,
148
+ "",
149
+ ];
150
+ for (const [category, categoryResults] of groupResultsBy(run.results, (result) => result.category || "未分类")) {
151
+ lines.push(`## ${category}`, "");
152
+ for (const [questionId, questionResults] of groupResultsBy(categoryResults, (result) => result.questionId)) {
153
+ appendQuestion(lines, questionId, questionResults);
154
+ }
155
+ }
156
+ appendSummary(lines, run.results);
157
+ return lines.join("\n");
158
+ }
@@ -0,0 +1,8 @@
1
+ import type { BenchCheck, ScoreDetail } from "./bench-types.js";
2
+ /** Execute one declarative benchmark check. */
3
+ export declare function runCheck(response: string, check: BenchCheck): boolean;
4
+ /** Score one response against all declared checks. */
5
+ export declare function scoreResponse(response: string, checks: BenchCheck[], maxScore: number): {
6
+ score: number;
7
+ details: ScoreDetail[];
8
+ };
@@ -0,0 +1,101 @@
1
+ /** Remove Markdown fences before parsing a JSON answer. */
2
+ function stripCodeFences(text) {
3
+ return text.replace(/```json\s*\n?/gi, "").replace(/\n?```/g, "").trim();
4
+ }
5
+ function containsExpectedText(response, check) {
6
+ const needle = String(check.value);
7
+ const text = check.ignoreCase ? response.toLowerCase() : response;
8
+ const search = check.ignoreCase ? needle.toLowerCase() : needle;
9
+ const found = text.includes(search);
10
+ return check.type === "contains" ? found : !found;
11
+ }
12
+ function isValidRegex(response, value) {
13
+ try {
14
+ new RegExp(String(value));
15
+ return true;
16
+ }
17
+ catch {
18
+ const literal = response.match(/\/(.+)\/[gimsuy]*/);
19
+ if (literal) {
20
+ new RegExp(literal[1]);
21
+ return true;
22
+ }
23
+ const cleaned = response.replace(/```[\s\S]*?```/g, "").trim();
24
+ const parts = cleaned.startsWith("/") ? cleaned.match(/^\/(.+)\/([gimsuy]*)$/) : null;
25
+ if (!parts)
26
+ return false;
27
+ new RegExp(parts[1]);
28
+ return true;
29
+ }
30
+ }
31
+ function hasJsonKey(response, value) {
32
+ try {
33
+ const parsed = JSON.parse(stripCodeFences(response));
34
+ return parsed[String(value)] !== undefined;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ function usesAllowedWords(response, allowed) {
41
+ const tokens = response.split(/[\s,。!?、;:""''()[【】\]「」]+/).filter(Boolean);
42
+ return tokens.every((token) => /^[,。!?、;:""''()《》\s.。,,、!?;:…\-—]+$/.test(token) || allowed.includes(token));
43
+ }
44
+ function hasExpectedFiveCharacterLines(response, expected) {
45
+ let count = 0;
46
+ for (const line of response.split("\n")) {
47
+ const chars = line.replace(/[,。!?、;:""''()《》\s,.\-,;:!?\dA-Za-z]/g, "").trim();
48
+ if (/^[\u4e00-\u9fff]{5}$/.test(chars))
49
+ count++;
50
+ }
51
+ return count >= expected;
52
+ }
53
+ /** Execute one declarative benchmark check. */
54
+ export function runCheck(response, check) {
55
+ switch (check.type) {
56
+ case "contains":
57
+ case "not_contains":
58
+ return containsExpectedText(response, check);
59
+ case "contains_code_block":
60
+ return /```[\s\S]*?```/.test(response) || /`[^`]+`/.test(response);
61
+ case "parse_json": {
62
+ try {
63
+ JSON.parse(stripCodeFences(response));
64
+ return true;
65
+ }
66
+ catch {
67
+ return false;
68
+ }
69
+ }
70
+ case "valid_regex":
71
+ return isValidRegex(response, check.value);
72
+ case "has_json_key":
73
+ return hasJsonKey(response, check.value);
74
+ case "max_length":
75
+ return response.length <= Number(check.value);
76
+ case "min_length":
77
+ return response.length >= Number(check.value);
78
+ case "allowed_words_only":
79
+ return usesAllowedWords(response, check.value);
80
+ case "has_chinese":
81
+ return /[\u4e00-\u9fff\u3400-\u4dbf]/.test(response);
82
+ case "line_count_5chars":
83
+ return hasExpectedFiveCharacterLines(response, Number(check.value));
84
+ default:
85
+ return false;
86
+ }
87
+ }
88
+ /** Score one response against all declared checks. */
89
+ export function scoreResponse(response, checks, maxScore) {
90
+ const details = checks.map((check) => {
91
+ const passed = runCheck(response, check);
92
+ return {
93
+ check: check.description,
94
+ passed,
95
+ points: check.points,
96
+ earned: passed ? check.points : 0,
97
+ };
98
+ });
99
+ const total = details.reduce((sum, detail) => sum + detail.earned, 0);
100
+ return { score: Math.min(total, maxScore), details };
101
+ }
@@ -0,0 +1,50 @@
1
+ export interface BenchCheck {
2
+ type: string;
3
+ /** Depends on type: string | number | boolean | string[]. */
4
+ value: unknown;
5
+ points: number;
6
+ description: string;
7
+ ignoreCase?: boolean;
8
+ }
9
+ export interface BenchQuestion {
10
+ id: string;
11
+ category: string;
12
+ prompt: string;
13
+ maxScore?: number;
14
+ checks?: BenchCheck[];
15
+ }
16
+ export interface BenchSuite {
17
+ name: string;
18
+ description: string;
19
+ questions: BenchQuestion[];
20
+ }
21
+ export interface ScoreDetail {
22
+ check: string;
23
+ passed: boolean;
24
+ points: number;
25
+ earned: number;
26
+ }
27
+ export interface BenchResult {
28
+ questionId: string;
29
+ category: string;
30
+ prompt: string;
31
+ modelId: string;
32
+ providerName: string;
33
+ response: string;
34
+ latencyMs: number;
35
+ tokensIn: number;
36
+ tokensOut: number;
37
+ costUsd: number;
38
+ error?: string;
39
+ score?: number;
40
+ maxScore?: number;
41
+ scoreDetails?: ScoreDetail[];
42
+ }
43
+ export interface BenchRunMeta {
44
+ id: string;
45
+ timestamp: string;
46
+ suiteName: string;
47
+ models: string[];
48
+ results: BenchResult[];
49
+ }
50
+ export declare const DEFAULT_MAX_SCORE = 10;
@@ -0,0 +1 @@
1
+ export const DEFAULT_MAX_SCORE = 10;