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 +2 -2
- package/dist/api.js +39 -26
- package/dist/config.d.ts +2 -0
- package/dist/index.js +24 -16
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +2 -2
- package/dist/update.d.ts +2 -0
- package/dist/update.js +12 -2
- package/package.json +1 -1
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
|
-
/**
|
|
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
|
|
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
|
-
|
|
70
|
+
const signal = AbortSignal.timeout(15_000);
|
|
71
71
|
try {
|
|
72
|
-
resp
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
158
|
-
//
|
|
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
|
-
|
|
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
|
-
/**
|
|
80
|
+
/** 网关配了云端浏览器渲染(features.web_fetch_render)才开;老网关没有 /v1/fetch。 */
|
|
81
81
|
renderFallback?: boolean;
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
84
|
* 抓网页工具:先直连(免费、快),打不开或疑似被反爬/JS 空壳时,回退到
|
|
85
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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"),
|
|
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 里运行:");
|