u1s1-cli 1.11.1 → 1.11.5

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/api.d.ts CHANGED
@@ -48,7 +48,7 @@ export interface ApiAnnouncement {
48
48
  /** 服务端能力开关;字段缺失(老网关)时按开启处理,保持现状。 */
49
49
  export interface ApiFeatures {
50
50
  web_search?: boolean;
51
- /** 网关配了 Browser Rendering 才为 true;老网关没有 /v1/fetch,缺失按关闭处理。 */
51
+ /** 网关配了云端浏览器渲染才为 true;老网关没有 /v1/fetch,缺失按关闭处理。 */
52
52
  web_fetch_render?: boolean;
53
53
  /** 网关配了方舟 ARK_API_KEY 才为 true;老网关没有 /v1/image,缺失按关闭处理。 */
54
54
  image_gen?: boolean;
@@ -117,7 +117,7 @@ export declare function searchWeb(cfg: Pick<CliConfig, "baseUrl" | "apiKey">, in
117
117
  maxResults?: number;
118
118
  signal?: AbortSignal;
119
119
  }): Promise<SearchResponse>;
120
- /** web_fetch 直连失败时的回退:网关用 Cloudflare Browser Rendering 渲染后转 markdown。 */
120
+ /** web_fetch 直连失败时的回退:网关用云端真浏览器渲染后转 markdown(能过挑战页)。 */
121
121
  export declare function renderPage(cfg: Pick<CliConfig, "baseUrl" | "apiKey">, url: string, signal?: AbortSignal): Promise<{
122
122
  url: string;
123
123
  markdown: string;
package/dist/api.js CHANGED
@@ -67,34 +67,47 @@ async function errorMessageFromResponse(resp, fallback) {
67
67
  return typeof error?.message === "string" ? error.message.slice(0, 2_000) : fallback;
68
68
  }
69
69
  export async function fetchModels(cfg) {
70
- let resp;
70
+ const signal = AbortSignal.timeout(15_000);
71
71
  try {
72
- resp = await authorizedFetch(cfg, `${cfg.baseUrl}/models`, {
73
- headers: { "x-u1s1-version": VERSION },
74
- signal: AbortSignal.timeout(15_000),
75
- });
72
+ let resp;
73
+ try {
74
+ resp = await authorizedFetch(cfg, `${cfg.baseUrl}/models`, {
75
+ headers: { "x-u1s1-version": VERSION },
76
+ signal,
77
+ });
78
+ }
79
+ catch (error) {
80
+ if (signal.aborted || (error instanceof Error && error.name === "TimeoutError"))
81
+ throw error;
82
+ throw new Error(`连不上 ${cfg.baseUrl},检查一下网络?`, { cause: error });
83
+ }
84
+ if (resp.status === 401)
85
+ throw new AuthError("登录已失效,请重新运行 u1s1 login");
86
+ if (resp.status === 403)
87
+ throw new AccessDeniedError(await errorMessageFromResponse(resp, "账号当前无法使用 u1s1"));
88
+ if (!resp.ok)
89
+ throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status},稍后再试`));
90
+ const body = await readJsonResponseCapped(resp);
91
+ if (!body || !Array.isArray(body.data))
92
+ throw new Error("服务端模型列表格式不正确");
93
+ const token = body.client_attestation?.token;
94
+ const expiresIn = body.client_attestation?.expires_in;
95
+ return {
96
+ models: body.data,
97
+ features: body.features ?? {},
98
+ announcement: body.announcement,
99
+ clientAttestation: typeof token === "string" && token.length <= 1024 ? token : undefined,
100
+ clientAttestationExpiresInSeconds: typeof expiresIn === "number" && Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : undefined,
101
+ };
76
102
  }
77
- catch {
78
- throw new Error(`连不上 ${cfg.baseUrl},检查一下网络?`);
103
+ catch (error) {
104
+ if (error instanceof AuthError || error instanceof AccessDeniedError)
105
+ throw error;
106
+ if (signal.aborted || (error instanceof Error && error.name === "TimeoutError")) {
107
+ throw new Error("获取模型列表超时(15 秒内未完成);可能与网络响应或本机繁忙有关,请稍后重试。", { cause: error });
108
+ }
109
+ throw error;
79
110
  }
80
- if (resp.status === 401)
81
- throw new AuthError("登录已失效,请重新运行 u1s1 login");
82
- if (resp.status === 403)
83
- throw new AccessDeniedError(await errorMessageFromResponse(resp, "账号当前无法使用 u1s1"));
84
- if (!resp.ok)
85
- throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status},稍后再试`));
86
- const body = await readJsonResponseCapped(resp);
87
- if (!body || !Array.isArray(body.data))
88
- throw new Error("服务端模型列表格式不正确");
89
- const token = body.client_attestation?.token;
90
- const expiresIn = body.client_attestation?.expires_in;
91
- return {
92
- models: body.data,
93
- features: body.features ?? {},
94
- announcement: body.announcement,
95
- clientAttestation: typeof token === "string" && token.length <= 1024 ? token : undefined,
96
- clientAttestationExpiresInSeconds: typeof expiresIn === "number" && Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : undefined,
97
- };
98
111
  }
99
112
  /**
100
113
  * 会话内公告轮询用的轻量接口:免鉴权、服务端有缓存,回的就是 /v1/models 里的 announcement
@@ -200,7 +213,7 @@ export async function searchWeb(cfg, input) {
200
213
  results,
201
214
  };
202
215
  }
203
- /** web_fetch 直连失败时的回退:网关用 Cloudflare Browser Rendering 渲染后转 markdown。 */
216
+ /** web_fetch 直连失败时的回退:网关用云端真浏览器渲染后转 markdown(能过挑战页)。 */
204
217
  export async function renderPage(cfg, url, signal) {
205
218
  if (!cfg.apiKey)
206
219
  throw new Error("没有配置 API Key");
package/dist/config.d.ts CHANGED
@@ -172,6 +172,8 @@ export declare const agentSettingsFile: string;
172
172
  export interface AgentSettings {
173
173
  showStartupBanner?: boolean;
174
174
  autoUpdate?: boolean;
175
+ /** HTTP proxy applied to all startup and kernel network requests. */
176
+ httpProxy?: string;
175
177
  /** false 时关闭客户端事件上报(见 telemetry.ts;环境变量 U1S1_TELEMETRY=0 同效) */
176
178
  telemetry?: boolean;
177
179
  /** pi 自己的 compaction 设置块;u1s1 额外认 maxContextTokens(自动压缩预算,0 = 关) */
package/dist/index.js CHANGED
@@ -17,6 +17,19 @@ import { createGatewayTelemetry } from "./telemetry.js";
17
17
  const PACKAGE_NAME = "u1s1-cli";
18
18
  /** 启动时检测到的可自动安装的新版;TUI 退出后才装(见 installPendingUpdate)。 */
19
19
  let pendingUpdate;
20
+ /**
21
+ * Configure the same undici dispatcher used by the embedded kernel before any
22
+ * CLI startup request runs. The kernel keeps this helper in an internal file,
23
+ * so resolve it relative to its public entry instead of relying on an
24
+ * unsupported package subpath export.
25
+ */
26
+ async function configureStartupHttp() {
27
+ const piEntry = import.meta.resolve("@earendil-works/pi-coding-agent");
28
+ const dispatcherUrl = new URL("./core/http-dispatcher.js", piEntry).href;
29
+ const dispatcher = await import(dispatcherUrl);
30
+ dispatcher.applyHttpProxySettings(readSettings().httpProxy);
31
+ dispatcher.configureHttpDispatcher();
32
+ }
20
33
  /**
21
34
  * 启动时自动检查 npm 最新版:autoUpdate 开着就记下退出后安装,关着也在启动横幅的
22
35
  * 版本号后面提示一句(u1s1 vX.Y.Z 后跟升级状态,见 setUpdateNotice)。
@@ -149,14 +162,11 @@ function ensureTmuxKeyboardProtocol() {
149
162
  runTmux(["bind-key", "-n", "S-Enter", "send-keys", "-l", "\x1b[13;2u"]);
150
163
  runTmux(["bind-key", "-n", "C-Enter", "send-keys", "-l", "\x1b[13;5u"]);
151
164
  }
152
- /** 取模型列表的请求发出后、发起 pi import 前的让路时间(见 runAgent 内注释)。 */
153
- const AGENT_IMPORT_DEFER_MS = 200;
154
165
  async function runAgent(cfg, args) {
155
166
  cleanupBrandThemes();
156
167
  ensureDefaultSettings();
157
- // pi 在 import/初始化阶段可能读到的常量环境变量先设好(import 在下面网络请求
158
- // 发出之后才发起);依赖网络结果的变量(签名代理地址/密钥、端点密钥)仍在进 pi
159
- // 之前才设置——pi 只在调用时读它们。
168
+ // Set constant environment values before startup I/O; values derived from
169
+ // responses are configured below, before loading the kernel.
160
170
  process.env["PI_CODING_AGENT_DIR"] = agentDir;
161
171
  process.env["U1S1_TOOLS_VIA_EXTENSION"] = "1";
162
172
  // hide pi's own "Run pi update" banner; users should run `u1s1 update` instead
@@ -177,14 +187,6 @@ async function runAgent(cfg, args) {
177
187
  let imageGenEnabled = false;
178
188
  const endpointsReady = loadCustomEndpoints(cfg);
179
189
  const modelsReady = fetchModels(cfg);
180
- modelsReady.catch(() => { });
181
- // pi 的模块图 import 要 300~800 ms,且 ESM 求值是同步的、会把事件循环卡住;
182
- // 若在请求发出前就 import,请求要等它求值完才真正上线,重叠白做(实测总耗时不变)。
183
- // 先让 DNS/TLS/写出在事件循环里推进一段,再发起 import,让它落在等服务端应答的空档里。
184
- await new Promise((resolve) => setTimeout(resolve, AGENT_IMPORT_DEFER_MS));
185
- const agentReady = import("@earendil-works/pi-coding-agent");
186
- // 失败留到下面 await 时再抛;这里只是避免在等网络期间被当成未处理的 rejection。
187
- agentReady.catch(() => { });
188
190
  let modelsResp;
189
191
  try {
190
192
  modelsResp = await modelsReady;
@@ -289,7 +291,11 @@ async function runAgent(cfg, args) {
289
291
  });
290
292
  };
291
293
  await searchToolsReady;
292
- const { CustomEditor, main } = await agentReady;
294
+ // Module evaluation can block the event loop for seconds on slow filesystems.
295
+ // Finish startup requests and fallback/login handling before loading the kernel.
296
+ const { CustomEditor, main } = await import("@earendil-works/pi-coding-agent");
297
+ // Start this optional request after module evaluation so its timeout can progress.
298
+ void checkForUpdate();
293
299
  // 网页终端(如 Taikula)把 Shift+Enter 发成 ESC+CR(\x1b\r)。pi 在没开 Kitty
294
300
  // 协议时把它当成 Alt+Enter:空闲就直接发送,忙碌才排队。先改成 CSI-u 的
295
301
  // Shift+Enter,输入框就会换行,真·Alt+Enter(\x1b[13;3u)不受影响。
@@ -686,6 +692,10 @@ async function run() {
686
692
  const args = process.argv.slice(2);
687
693
  const cmd = args[0];
688
694
  const rest = args.slice(1);
695
+ // Must precede ensureAuth/login, fetchModels, and every command that can
696
+ // issue network requests. No proxy configured means direct connections via
697
+ // EnvHttpProxyAgent, preserving the existing fallback behavior.
698
+ await configureStartupHttp();
689
699
  if (cmd === "--version" || cmd === "-v") {
690
700
  console.log(`u1s1 v${VERSION}`);
691
701
  return;
@@ -708,8 +718,6 @@ async function run() {
708
718
  return;
709
719
  const { ensureAuth } = await import("./login.js");
710
720
  const cfg = await ensureAuth();
711
- // 在后台检查更新(非阻塞,不影响启动速度)
712
- void checkForUpdate();
713
721
  await runAgent(cfg, args);
714
722
  // autoUpdate 开着且启动时发现了新版:现在装(TUI 已退出,原地重铺安全)
715
723
  installPendingUpdate();
package/dist/tools.d.ts CHANGED
@@ -77,12 +77,12 @@ export declare function createImageTool(cfg: Pick<CliConfig, "baseUrl" | "apiKey
77
77
  export interface FetchToolConfig {
78
78
  baseUrl: string;
79
79
  apiKey?: string;
80
- /** 网关配了 Browser Rendering(features.web_fetch_render)才开;老网关没有 /v1/fetch。 */
80
+ /** 网关配了云端浏览器渲染(features.web_fetch_render)才开;老网关没有 /v1/fetch。 */
81
81
  renderFallback?: boolean;
82
82
  }
83
83
  /**
84
84
  * 抓网页工具:先直连(免费、快),打不开或疑似被反爬/JS 空壳时,回退到
85
- * 网关的 Cloudflare Browser Rendering 渲染(真无头浏览器,按次计费)。
85
+ * 网关的云端真浏览器渲染(能过人机验证挑战页,按次计费)。
86
86
  */
87
87
  export declare function createFetchTool(cfg: FetchToolConfig): import("@earendil-works/pi-coding-agent").ToolDefinition<Type.TObject<{
88
88
  url: Type.TString;
package/dist/tools.js CHANGED
@@ -568,7 +568,7 @@ async function fetchDirect(url, signal) {
568
568
  }
569
569
  /**
570
570
  * 抓网页工具:先直连(免费、快),打不开或疑似被反爬/JS 空壳时,回退到
571
- * 网关的 Cloudflare Browser Rendering 渲染(真无头浏览器,按次计费)。
571
+ * 网关的云端真浏览器渲染(能过人机验证挑战页,按次计费)。
572
572
  */
573
573
  export function createFetchTool(cfg) {
574
574
  const canRender = () => !!(cfg.renderFallback && cfg.apiKey);
@@ -581,7 +581,7 @@ export function createFetchTool(cfg) {
581
581
  return defineTool({
582
582
  name: "web_fetch",
583
583
  label: "读取网页",
584
- description: "Fetch a URL and return its readable text content (HTML is stripped to text; JSON and plain text are returned as-is). Use it to read a page found via web_search, or any URL the user pasted. If the direct fetch fails or the page needs JavaScript, it automatically retries through a cloud headless browser. Long pages are returned in 30,000-character windows: when the result says it was truncated, call again with the same url and the offset it gives to read the next part.",
584
+ description: "Fetch a URL and return its readable text content (HTML is stripped to text; JSON and plain text are returned as-is). Use it to read a page found via web_search, or any URL the user pasted. If the direct fetch fails or the page needs JavaScript, it automatically retries through a cloud browser that can also clear bot-check interstitials. Long pages are returned in 30,000-character windows: when the result says it was truncated, call again with the same url and the offset it gives to read the next part.",
585
585
  promptSnippet: "Fetch a URL and convert it to readable text",
586
586
  promptGuidelines: [
587
587
  "Use web_fetch to read a specific URL, and web_search when you still need to find the URL.",
package/dist/update.d.ts CHANGED
@@ -5,6 +5,8 @@ export declare function detectPackageManager(): string;
5
5
  /** Fetch the latest published version from npm registry. */
6
6
  export declare function getLatestVersion(): Promise<string | undefined>;
7
7
  export declare function compareVersions(a: string, b: string): number;
8
+ /** 生成 Windows 更新启动器:优先使用 PowerShell 7,没有时回退 Windows PowerShell。 */
9
+ export declare function buildWindowsUpdateLauncherScript(): string;
8
10
  /** 从固定发布源拉一个文本文件:强制 https、拒绝重定向、限长。 */
9
11
  export declare function fetchReleaseText(name: string, maxBytes: number, timeoutMs?: number): Promise<string>;
10
12
  /** 在 sha256sum 格式的清单里找某个文件的哈希("<hex> <name>" 或 "<hex> *<name>")。 */
package/dist/update.js CHANGED
@@ -69,6 +69,17 @@ export function compareVersions(a, b) {
69
69
  const RELEASE_ORIGIN = "https://u1s1.io";
70
70
  const INSTALL_SCRIPT_NAME = "install.sh";
71
71
  const MAX_SUMS_BYTES = 64 * 1024;
72
+ /** 生成 Windows 更新启动器:优先使用 PowerShell 7,没有时回退 Windows PowerShell。 */
73
+ export function buildWindowsUpdateLauncherScript() {
74
+ return [
75
+ "$inner = '-NoProfile -ExecutionPolicy Bypass -NoExit -File \"' + $PSScriptRoot + '\\update.ps1\"'",
76
+ "$ps = (Get-Command pwsh -ErrorAction SilentlyContinue).Source",
77
+ "if (-not $ps) { $ps = (Get-Command powershell -ErrorAction SilentlyContinue).Source }",
78
+ "if (-not $ps) { throw '找不到可用的 PowerShell' }",
79
+ "Start-Process -FilePath $ps -ArgumentList $inner",
80
+ "",
81
+ ].join("\n");
82
+ }
72
83
  /** 从固定发布源拉一个文本文件:强制 https、拒绝重定向、限长。 */
73
84
  export async function fetchReleaseText(name, maxBytes, timeoutMs = 30_000) {
74
85
  const url = new URL(`/releases/${name}`, RELEASE_ORIGIN);
@@ -135,8 +146,7 @@ async function portableSelfUpdate(latest) {
135
146
  // 拼字符串,没有引号二次解析问题。
136
147
  const dir = mkdtempSync(join(tmpdir(), "u1s1-update-"));
137
148
  writeFileSync(join(dir, "update.ps1"), "irm https://u1s1.io/releases/install.ps1 | iex\n");
138
- writeFileSync(join(dir, "launch.ps1"), "$inner = '-NoProfile -ExecutionPolicy Bypass -NoExit -File \"' + $PSScriptRoot + '\\update.ps1\"'\n" +
139
- "Start-Process powershell -ArgumentList $inner\n");
149
+ writeFileSync(join(dir, "launch.ps1"), buildWindowsUpdateLauncherScript());
140
150
  const res = spawnSync("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(dir, "launch.ps1")], { stdio: "ignore", windowsHide: true, timeout: 30_000 });
141
151
  if (res.error || res.status !== 0) {
142
152
  console.error("无法拉起更新窗口。请手动在 PowerShell 里运行:");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "1.11.1",
3
+ "version": "1.11.5",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {