u1s1-cli 1.2.1 → 1.2.3

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.
@@ -1,9 +1,15 @@
1
- import { type CliConfig, type CustomEndpoint, type ModelDef } from "./config.js";
1
+ import { type CliConfig, type CustomEndpoint, type ModelDef, type ThinkingLevel } from "./config.js";
2
2
  import type { ShellDoctorResult } from "./shell-doctor.js";
3
3
  /** pi auto-appends <agentDir>/APPEND_SYSTEM.md to its system prompt — our branding hook. */
4
4
  export declare function ensureBrandPrompt(shell: ShellDoctorResult): void;
5
+ /**
6
+ * Seed per-model defaults without overwriting a user's Ctrl+S choice. The
7
+ * marker records values managed by u1s1, so a later Gateway default can update
8
+ * them while a diverged user value becomes user-owned.
9
+ */
10
+ export declare function applyModelThinkingDefaults(settings: Record<string, unknown>, models: ModelDef[]): boolean;
5
11
  /** Defaults that don't overwrite values the user already set. */
6
- export declare function ensureDefaultSettings(): void;
12
+ export declare function ensureDefaultSettings(models?: ModelDef[]): void;
7
13
  /** ≤0.4.0 wrote u1s1-dark/u1s1-light into the pi themes dir; remove them. */
8
14
  export declare function cleanupBrandThemes(): void;
9
15
  /**
@@ -54,10 +60,19 @@ export declare function toProviderModels(models: ModelDef[]): {
54
60
  id: string;
55
61
  name: string;
56
62
  reasoning: boolean;
63
+ thinkingLevelMap?: Partial<Record<ThinkingLevel, string | null>>;
57
64
  input: ("text" | "image")[];
58
65
  cost: ModelDef["cost"];
59
66
  contextWindow: number;
60
67
  maxTokens: number;
68
+ compat?: {
69
+ supportsStore?: boolean;
70
+ supportsDeveloperRole?: boolean;
71
+ supportsReasoningEffort: boolean;
72
+ maxTokensField?: "max_tokens";
73
+ requiresReasoningContentOnAssistantMessages?: boolean;
74
+ thinkingFormat: "openai" | "deepseek" | "qwen";
75
+ };
61
76
  }[];
62
77
  /**
63
78
  * 自定义端点 → pi provider 条目。密钥走环境变量引用(启动器已 set,见
@@ -66,8 +66,52 @@ function bundledPiVersion() {
66
66
  return undefined;
67
67
  }
68
68
  }
69
+ const THINKING_DEFAULTS_MARKER = "u1s1ModelThinkingDefaults";
70
+ function recordValue(value) {
71
+ return typeof value === "object" && value !== null && !Array.isArray(value)
72
+ ? value
73
+ : undefined;
74
+ }
75
+ /**
76
+ * Seed per-model defaults without overwriting a user's Ctrl+S choice. The
77
+ * marker records values managed by u1s1, so a later Gateway default can update
78
+ * them while a diverged user value becomes user-owned.
79
+ */
80
+ export function applyModelThinkingDefaults(settings, models) {
81
+ const defaults = models.filter((model) => model.thinking !== undefined);
82
+ if (defaults.length === 0)
83
+ return false;
84
+ const levels = { ...(recordValue(settings["modelThinkingLevels"]) ?? {}) };
85
+ const managed = { ...(recordValue(settings[THINKING_DEFAULTS_MARKER]) ?? {}) };
86
+ let changed = false;
87
+ for (const model of defaults) {
88
+ const key = `${PROVIDER_ID}/${model.id}`;
89
+ const next = model.thinking.defaultLevel;
90
+ const current = levels[key];
91
+ const previousManaged = managed[key];
92
+ if (current === undefined || current === previousManaged) {
93
+ if (current !== next) {
94
+ levels[key] = next;
95
+ changed = true;
96
+ }
97
+ if (previousManaged !== next) {
98
+ managed[key] = next;
99
+ changed = true;
100
+ }
101
+ }
102
+ else if (previousManaged !== undefined) {
103
+ delete managed[key];
104
+ changed = true;
105
+ }
106
+ }
107
+ if (changed) {
108
+ settings["modelThinkingLevels"] = levels;
109
+ settings[THINKING_DEFAULTS_MARKER] = managed;
110
+ }
111
+ return changed;
112
+ }
69
113
  /** Defaults that don't overwrite values the user already set. */
70
- export function ensureDefaultSettings() {
114
+ export function ensureDefaultSettings(models = []) {
71
115
  mkdirSync(agentDir, { recursive: true });
72
116
  const p = join(agentDir, "settings.json");
73
117
  let settings = {};
@@ -114,6 +158,8 @@ export function ensureDefaultSettings() {
114
158
  settings["autoUpdate"] = true;
115
159
  changed = true;
116
160
  }
161
+ if (applyModelThinkingDefaults(settings, models))
162
+ changed = true;
117
163
  // ≤0.4.0 shipped branded themes and forced them as default; the files are
118
164
  // gone now, so a settings.json still pointing at them must fall back to
119
165
  // pi's default theme.
@@ -333,15 +379,40 @@ export function ensureAuthCredential() {
333
379
  }
334
380
  /** pi provider 条目里的模型形状(models.json 与 registerProvider 共用)。 */
335
381
  export function toProviderModels(models) {
336
- return models.map((m) => ({
337
- id: m.id,
338
- name: m.name,
339
- reasoning: m.reasoning,
340
- input: m.vision ? ["text", "image"] : ["text"],
341
- cost: m.cost,
342
- contextWindow: m.contextWindow,
343
- maxTokens: m.maxTokens,
344
- }));
382
+ return models.map((m) => {
383
+ let thinkingLevelMap;
384
+ let compat;
385
+ if (m.thinking) {
386
+ const supported = new Set(m.thinking.levels);
387
+ thinkingLevelMap = {};
388
+ for (const level of ["off", "minimal", "low", "medium", "high", "xhigh", "max"]) {
389
+ thinkingLevelMap[level] = supported.has(level) ? (m.thinking.levelMap[level] ?? level) : null;
390
+ }
391
+ compat = {
392
+ supportsReasoningEffort: true,
393
+ thinkingFormat: m.thinking.requestFormat,
394
+ ...(m.thinking.requestFormat === "deepseek"
395
+ ? {
396
+ supportsStore: false,
397
+ supportsDeveloperRole: false,
398
+ maxTokensField: "max_tokens",
399
+ requiresReasoningContentOnAssistantMessages: true,
400
+ }
401
+ : {}),
402
+ };
403
+ }
404
+ return {
405
+ id: m.id,
406
+ name: m.name,
407
+ reasoning: m.reasoning,
408
+ thinkingLevelMap,
409
+ input: m.vision ? ["text", "image"] : ["text"],
410
+ cost: m.cost,
411
+ contextWindow: m.contextWindow,
412
+ maxTokens: m.maxTokens,
413
+ compat,
414
+ };
415
+ });
345
416
  }
346
417
  /**
347
418
  * 自定义端点 → pi provider 条目。密钥走环境变量引用(启动器已 set,见
package/dist/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type CliConfig } from "./config.js";
1
+ import { type ApiThinkingCapabilities, type CliConfig } from "./config.js";
2
2
  export interface MeResponse {
3
3
  email: string | null;
4
4
  signup_credit_usd?: number;
@@ -35,6 +35,8 @@ export interface ApiModel {
35
35
  id: string;
36
36
  name: string;
37
37
  reasoning: boolean;
38
+ /** Older gateways omit model-specific thinking metadata. */
39
+ thinking?: ApiThinkingCapabilities | null;
38
40
  /** Older gateways omit this field; missing means text-only. */
39
41
  vision?: boolean;
40
42
  context_length: number;
@@ -116,6 +118,14 @@ export interface ImageGenRequest {
116
118
  /** 1K/2K/4K 或 宽x高(如 2048x2048);缺省由服务端定(2K)。 */
117
119
  size?: string;
118
120
  }
121
+ /**
122
+ * 新网关把结果同时放进响应头。这样即使 Cloudflare 到 Node 的 JSON body
123
+ * 在生成完成后被 Undici 报 `terminated`,也能继续下载已经计费的那张图。
124
+ */
125
+ export declare function readGeneratedImageResponse(resp: Response): Promise<{
126
+ url: string;
127
+ size: string | null;
128
+ }>;
119
129
  /** 生图走网关代理(方舟 key 只在服务端);返回 TOS 图片 URL(24h 有效),调用方应立即下载。 */
120
130
  export declare function generateImage(cfg: Pick<CliConfig, "baseUrl" | "apiKey">, req: ImageGenRequest, signal?: AbortSignal): Promise<{
121
131
  url: string;
package/dist/api.js CHANGED
@@ -109,6 +109,52 @@ export async function renderPage(cfg, url, signal) {
109
109
  }
110
110
  return (await resp.json());
111
111
  }
112
+ const IMAGE_URL_HEADER = "x-u1s1-image-url";
113
+ const IMAGE_SIZE_HEADER = "x-u1s1-image-size";
114
+ function imageResultFromHeaders(resp) {
115
+ const encodedUrl = resp.headers.get(IMAGE_URL_HEADER);
116
+ if (!encodedUrl)
117
+ return null;
118
+ try {
119
+ const url = decodeURIComponent(encodedUrl);
120
+ const parsed = new URL(url);
121
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
122
+ return null;
123
+ const encodedSize = resp.headers.get(IMAGE_SIZE_HEADER);
124
+ return { url, size: encodedSize ? decodeURIComponent(encodedSize) : null };
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ }
130
+ function responseErrorDetail(error) {
131
+ const message = error instanceof Error ? error.message : String(error);
132
+ const cause = error instanceof Error ? error.cause : undefined;
133
+ const code = cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string" ? cause.code : "";
134
+ return code && !message.includes(code) ? `${message} (${code})` : message;
135
+ }
136
+ /**
137
+ * 新网关把结果同时放进响应头。这样即使 Cloudflare 到 Node 的 JSON body
138
+ * 在生成完成后被 Undici 报 `terminated`,也能继续下载已经计费的那张图。
139
+ */
140
+ export async function readGeneratedImageResponse(resp) {
141
+ const headerResult = imageResultFromHeaders(resp);
142
+ if (headerResult) {
143
+ await resp.body?.cancel().catch(() => undefined);
144
+ return headerResult;
145
+ }
146
+ try {
147
+ const body = (await resp.json());
148
+ if (typeof body.url !== "string" || !/^https?:\/\//.test(body.url)) {
149
+ throw new Error("服务端没有返回图片下载地址");
150
+ }
151
+ return { url: body.url, size: typeof body.size === "string" ? body.size : null };
152
+ }
153
+ catch (error) {
154
+ throw new Error(`图片可能已经生成,但客户端下载结果响应失败: ${responseErrorDetail(error)}。` +
155
+ "不要重新调用 generate_image,请升级 u1s1 后再试", { cause: error });
156
+ }
157
+ }
112
158
  /** 生图走网关代理(方舟 key 只在服务端);返回 TOS 图片 URL(24h 有效),调用方应立即下载。 */
113
159
  export async function generateImage(cfg, req, signal) {
114
160
  if (!cfg.apiKey)
@@ -136,7 +182,7 @@ export async function generateImage(cfg, req, signal) {
136
182
  const body = (await resp.json().catch(() => null));
137
183
  throw new Error(body?.error?.message ?? `图片生成服务返回 ${resp.status},稍后再试`);
138
184
  }
139
- return (await resp.json());
185
+ return readGeneratedImageResponse(resp);
140
186
  }
141
187
  export async function fetchMe(cfg) {
142
188
  if (!cfg.apiKey)
package/dist/config.d.ts CHANGED
@@ -8,14 +8,28 @@ export declare const VERSION: string;
8
8
  export declare function isPortableInstall(): boolean;
9
9
  export declare const DEFAULT_BASE_URL = "https://api.u1s1.io/v1";
10
10
  export declare const PROVIDER_ID = "u1s1";
11
- /**
12
- * Convert an API model response to our internal ModelDef.
13
- * Aliases are derived from the short id; note is left empty (filled by /model command).
14
- */
15
- export declare function apiModelToDef(m: {
11
+ export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
12
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
13
+ export type ThinkingRequestFormat = "openai" | "deepseek" | "qwen";
14
+ export interface ThinkingCapabilities {
15
+ levels: ThinkingLevel[];
16
+ defaultLevel: ThinkingLevel;
17
+ canDisable: boolean;
18
+ levelMap: Partial<Record<ThinkingLevel, string>>;
19
+ requestFormat: ThinkingRequestFormat;
20
+ }
21
+ export interface ApiThinkingCapabilities {
22
+ levels: string[];
23
+ default_level: string;
24
+ can_disable: boolean;
25
+ level_map: Record<string, string>;
26
+ request_format: string;
27
+ }
28
+ interface ApiModelShape {
16
29
  id: string;
17
30
  name: string;
18
31
  reasoning: boolean;
32
+ thinking?: ApiThinkingCapabilities | null;
19
33
  vision?: boolean;
20
34
  context_length: number;
21
35
  max_tokens: number;
@@ -24,13 +38,20 @@ export declare function apiModelToDef(m: {
24
38
  output: number;
25
39
  cache_read: number | null;
26
40
  };
27
- }): ModelDef;
41
+ }
42
+ /**
43
+ * Convert an API model response to our internal ModelDef.
44
+ * Aliases are derived from the short id; note is left empty (filled by /model command).
45
+ */
46
+ export declare function apiModelToDef(m: ApiModelShape): ModelDef;
28
47
  export interface ModelDef {
29
48
  id: string;
30
49
  name: string;
31
50
  /** short aliases accepted by `u1s1 model <name>` */
32
51
  aliases: string[];
33
52
  reasoning: boolean;
53
+ /** Model-specific thinking levels supplied by the Gateway. Missing means legacy pi behavior. */
54
+ thinking?: ThinkingCapabilities;
34
55
  vision: boolean;
35
56
  contextWindow: number;
36
57
  maxTokens: number;
@@ -137,3 +158,4 @@ export declare function saveConfig(cfg: CliConfig): void;
137
158
  export declare function persistPreferredModel(cfg: CliConfig, provider: string, modelId: string): CliConfig;
138
159
  export declare function saveEndpointsCache(endpoints: CustomEndpoint[]): void;
139
160
  export declare function loadEndpointsCache(): CustomEndpoint[];
161
+ export {};
package/dist/config.js CHANGED
@@ -28,16 +28,44 @@ function defaultAliases(id) {
28
28
  const aliases = [short, ...parts.filter((p) => p.length > 1)];
29
29
  return [...new Set(aliases)];
30
30
  }
31
+ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
32
+ const THINKING_LEVEL_SET = new Set(THINKING_LEVELS);
33
+ function parseThinkingCapabilities(value) {
34
+ if (!value || !Array.isArray(value.levels))
35
+ return undefined;
36
+ const levels = [...new Set(value.levels)].filter((level) => THINKING_LEVEL_SET.has(level));
37
+ if (levels.length === 0 || !THINKING_LEVEL_SET.has(value.default_level) || !levels.includes(value.default_level)) {
38
+ return undefined;
39
+ }
40
+ const requestFormat = value.request_format;
41
+ if (requestFormat !== "openai" && requestFormat !== "deepseek" && requestFormat !== "qwen")
42
+ return undefined;
43
+ const levelMap = {};
44
+ for (const level of levels) {
45
+ const mapped = value.level_map?.[level];
46
+ if (typeof mapped === "string" && mapped)
47
+ levelMap[level] = mapped;
48
+ }
49
+ return {
50
+ levels,
51
+ defaultLevel: value.default_level,
52
+ canDisable: value.can_disable === true && levels.includes("off"),
53
+ levelMap,
54
+ requestFormat,
55
+ };
56
+ }
31
57
  /**
32
58
  * Convert an API model response to our internal ModelDef.
33
59
  * Aliases are derived from the short id; note is left empty (filled by /model command).
34
60
  */
35
61
  export function apiModelToDef(m) {
62
+ const thinking = parseThinkingCapabilities(m.thinking);
36
63
  return {
37
64
  id: m.id,
38
65
  name: m.name,
39
66
  aliases: defaultAliases(m.id),
40
- reasoning: m.reasoning,
67
+ reasoning: m.reasoning || thinking !== undefined,
68
+ thinking,
41
69
  vision: m.vision === true,
42
70
  contextWindow: m.context_length,
43
71
  maxTokens: m.max_tokens,
@@ -55,7 +83,14 @@ export const MODELS = [
55
83
  id: "deepseek-v4-flash",
56
84
  name: "DeepSeek V4 Flash (u1s1)",
57
85
  aliases: ["deepseek", "flash", "v4-flash"],
58
- reasoning: false,
86
+ reasoning: true,
87
+ thinking: {
88
+ levels: ["off", "low", "high", "max"],
89
+ defaultLevel: "high",
90
+ canDisable: true,
91
+ levelMap: { off: "none", low: "low", high: "high", max: "max" },
92
+ requestFormat: "deepseek",
93
+ },
59
94
  vision: false,
60
95
  contextWindow: 1_048_576,
61
96
  maxTokens: 384_000,
package/dist/index.js CHANGED
@@ -189,6 +189,9 @@ async function runAgent(cfg, args) {
189
189
  setAnnouncement(announcement);
190
190
  }
191
191
  await endpointsReady;
192
+ // Gateway metadata owns each official model's default thinking level. Seed
193
+ // it after the live model list arrives; explicit user choices remain intact.
194
+ ensureDefaultSettings(MODELS);
192
195
  // pi provider 只支持静态 header;指向本机 signing proxy,由它逐请求附 DPoP proof。
193
196
  const signing = await ensureSigningProxy(cfg);
194
197
  const officialCfg = { ...cfg, baseUrl: signing.baseUrl, apiKey: signing.localKey };
package/dist/login.js CHANGED
@@ -152,8 +152,8 @@ export async function login(keyArg) {
152
152
  // 新网关且免费包可领/可续,登录成功时顺手提醒
153
153
  quotaNote =
154
154
  me.free_claim === "first"
155
- ? "免费用量包还没领,去 https://u1s1.io/dashboard 点「领取」(首月每天 1 亿 Token)"
156
- : "免费用量包到期了,去 https://u1s1.io/dashboard 续领(每天 3000 万 Token)";
155
+ ? "有免费用量包可领,去 https://u1s1.io/dashboard 查看"
156
+ : "免费用量包状态有更新,去 https://u1s1.io/dashboard 查看";
157
157
  }
158
158
  else if (tpu > 0) {
159
159
  const tok = (usd) => {
package/dist/tools.js CHANGED
@@ -352,6 +352,7 @@ export function createImageTool(cfg) {
352
352
  promptGuidelines: [
353
353
  "Use generate_image when the user wants a picture created or an existing image modified (logo, illustration, poster, placeholder art, style change).",
354
354
  "Each call generates one image and costs the user credits; refine the prompt first instead of regenerating repeatedly.",
355
+ "If an error says the image may/already has been generated or says not to call generate_image again, stop immediately; never retry with a new generation.",
355
356
  "To edit an existing image, pass its path in `images` and describe only the change in `prompt`.",
356
357
  ],
357
358
  parameters: Type.Object({
package/dist/usage.js CHANGED
@@ -42,6 +42,8 @@ export async function usage() {
42
42
  free_first: "首月免费包",
43
43
  free_yearly: "年度免费包",
44
44
  invite: "邀请赠送",
45
+ new_user: "新用户赠送",
46
+ payment_delay_gift: "临时加量包",
45
47
  topup_daily: "每日加量包",
46
48
  admin_grant: "官方赠送",
47
49
  };
@@ -71,9 +73,11 @@ export async function usage() {
71
73
  const label = ((PKG_LABEL[p.kind] ?? p.kind) + (p.count > 1 ? ` ×${p.count}` : "")).padEnd(5, " ");
72
74
  const per = daily ? "/天" : "";
73
75
  console.log(` ${label} 还剩 ${fmtTokensCn(p.remaining)} / ${fmtTokensCn(total)}${per} ${bar(ratio)}`);
74
- const scopeNote = p.kind === "invite"
76
+ const scopeNote = p.kind === "login_checkin"
75
77
  ? "仅限 u1s1 客户端使用 · 全模型可用"
76
- : p.scope === "free" ? "仅默认模型和搜索 · 0 点恢复" : "全模型可用";
78
+ : p.scope === "free"
79
+ ? "免费包适用模型 · 0 点恢复"
80
+ : "仅限 u1s1 客户端使用 · 免费包适用模型";
77
81
  console.log(` ${scopeNote} · ${p.expires_at ? `${p.expires_at.slice(0, 10)} 到期` : "永不过期"}`);
78
82
  }
79
83
  if (me.bonus_balance_usd > 0) {
@@ -82,15 +86,8 @@ export async function usage() {
82
86
  }
83
87
  console.log(` 本月已用 ${tpu > 0 ? `约 ${fmtTokensCn(me.mtd_usd * tpu)} Token($${me.mtd_usd.toFixed(2)})` : `$${me.mtd_usd.toFixed(2)}`}`);
84
88
  console.log("");
85
- if (me.free_claim === "first") {
86
- console.log(" 你有免费用量包没领:首月每天 1 亿 Token,去 https://u1s1.io/dashboard 点「领取」");
87
- }
88
- else if (me.free_claim === "renew") {
89
- console.log(" → 免费用量包到期了,去 https://u1s1.io/dashboard 续领:每天 3000 万 Token,一年有效");
90
- }
91
- else {
92
- console.log(" 免费包每天 0 点恢复;邀请礼包需受邀人手动领取,仅限 u1s1 客户端 → https://u1s1.io/dashboard");
93
- }
89
+ console.log(" 免费用量包覆盖 DeepSeek V4 Flash / Vision、GLM-5.3 Flash、Qwen3.8 Flash 和联网搜索。");
90
+ console.log(" DeepSeek V4 Pro 仅可使用余额 https://u1s1.io/dashboard");
94
91
  console.log(" Token 数按默认模型单价折算,为约数;用更贵的模型时消耗更快。");
95
92
  console.log("");
96
93
  return;
package/dist/web.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { mkdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { cleanupBrandThemes, endpointKeyEnv, ensureAuthCredential, ensureBrandPrompt, ensureWorkflowPromptTemplate, ensureDefaultSettings, ensureProviderModels, scrubForeignProviderEnv, writeAttributionExtension, writeWebToolsExtension, } from "./agent-setup.js";
4
- import { agentDir, apiModelToDef, resolvePreferredModel, setModelsFromApi, u1s1Dir, writeAgentDefaultModel, } from "./config.js";
4
+ import { agentDir, apiModelToDef, MODELS, resolvePreferredModel, setModelsFromApi, u1s1Dir, writeAgentDefaultModel, } from "./config.js";
5
5
  import { ensureSearchTools } from "./search-tools.js";
6
6
  import { ensureUsableShell } from "./shell-doctor.js";
7
7
  import { fetchModels, loadCustomEndpoints } from "./api.js";
@@ -38,6 +38,7 @@ export async function prepareWebEnv(cfg) {
38
38
  console.error(" 获取模型列表失败,使用内置列表:", e.message);
39
39
  }
40
40
  await endpointsReady;
41
+ ensureDefaultSettings(MODELS);
41
42
  const signing = await ensureSigningProxy(cfg, "desktop");
42
43
  const officialCfg = { ...cfg, baseUrl: signing.baseUrl, apiKey: signing.localKey };
43
44
  ensureBrandPrompt(await shellReady);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {