u1s1-cli 1.11.1 → 1.11.2

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/index.js CHANGED
@@ -149,14 +149,11 @@ function ensureTmuxKeyboardProtocol() {
149
149
  runTmux(["bind-key", "-n", "S-Enter", "send-keys", "-l", "\x1b[13;2u"]);
150
150
  runTmux(["bind-key", "-n", "C-Enter", "send-keys", "-l", "\x1b[13;5u"]);
151
151
  }
152
- /** 取模型列表的请求发出后、发起 pi import 前的让路时间(见 runAgent 内注释)。 */
153
- const AGENT_IMPORT_DEFER_MS = 200;
154
152
  async function runAgent(cfg, args) {
155
153
  cleanupBrandThemes();
156
154
  ensureDefaultSettings();
157
- // pi import/初始化阶段可能读到的常量环境变量先设好(import 在下面网络请求
158
- // 发出之后才发起);依赖网络结果的变量(签名代理地址/密钥、端点密钥)仍在进 pi
159
- // 之前才设置——pi 只在调用时读它们。
155
+ // Set constant environment values before startup I/O; values derived from
156
+ // responses are configured below, before loading the kernel.
160
157
  process.env["PI_CODING_AGENT_DIR"] = agentDir;
161
158
  process.env["U1S1_TOOLS_VIA_EXTENSION"] = "1";
162
159
  // hide pi's own "Run pi update" banner; users should run `u1s1 update` instead
@@ -177,14 +174,6 @@ async function runAgent(cfg, args) {
177
174
  let imageGenEnabled = false;
178
175
  const endpointsReady = loadCustomEndpoints(cfg);
179
176
  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
177
  let modelsResp;
189
178
  try {
190
179
  modelsResp = await modelsReady;
@@ -289,7 +278,11 @@ async function runAgent(cfg, args) {
289
278
  });
290
279
  };
291
280
  await searchToolsReady;
292
- const { CustomEditor, main } = await agentReady;
281
+ // Module evaluation can block the event loop for seconds on slow filesystems.
282
+ // Finish startup requests and fallback/login handling before loading the kernel.
283
+ const { CustomEditor, main } = await import("@earendil-works/pi-coding-agent");
284
+ // Start this optional request after module evaluation so its timeout can progress.
285
+ void checkForUpdate();
293
286
  // 网页终端(如 Taikula)把 Shift+Enter 发成 ESC+CR(\x1b\r)。pi 在没开 Kitty
294
287
  // 协议时把它当成 Alt+Enter:空闲就直接发送,忙碌才排队。先改成 CSI-u 的
295
288
  // Shift+Enter,输入框就会换行,真·Alt+Enter(\x1b[13;3u)不受影响。
@@ -708,8 +701,6 @@ async function run() {
708
701
  return;
709
702
  const { ensureAuth } = await import("./login.js");
710
703
  const cfg = await ensureAuth();
711
- // 在后台检查更新(非阻塞,不影响启动速度)
712
- void checkForUpdate();
713
704
  await runAgent(cfg, args);
714
705
  // autoUpdate 开着且启动时发现了新版:现在装(TUI 已退出,原地重铺安全)
715
706
  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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "1.11.1",
3
+ "version": "1.11.2",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {