u1s1-cli 1.2.8 → 1.3.0
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 -0
- package/dist/api.js +21 -16
- package/dist/device-auth.d.ts +2 -1
- package/dist/device-auth.js +12 -3
- package/dist/index.js +1 -1
- package/dist/web.js +4 -2
- package/package.json +3 -3
- package/scripts/patch-pi.js +4 -4
package/dist/api.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface ModelsResponse {
|
|
|
64
64
|
models: ApiModel[];
|
|
65
65
|
features: ApiFeatures;
|
|
66
66
|
announcement?: ApiAnnouncement | null;
|
|
67
|
+
/** Opaque, device-bound canary echoed only by the official signing proxy. */
|
|
68
|
+
clientAttestation?: string;
|
|
67
69
|
}
|
|
68
70
|
/** 401:凭证失效(设备被移除/换过钥匙),调用方应引导重新登录而不是继续硬跑。 */
|
|
69
71
|
export declare class AuthError extends Error {
|
package/dist/api.js
CHANGED
|
@@ -3,6 +3,11 @@ import { authorizedFetch } from "./device-auth.js";
|
|
|
3
3
|
/** 401:凭证失效(设备被移除/换过钥匙),调用方应引导重新登录而不是继续硬跑。 */
|
|
4
4
|
export class AuthError extends Error {
|
|
5
5
|
}
|
|
6
|
+
/** 网关错误壳统一是 { error: { message } };解析不出来时回退到状态码提示。 */
|
|
7
|
+
async function errorMessageFromResponse(resp, fallback) {
|
|
8
|
+
const body = (await resp.json().catch(() => null));
|
|
9
|
+
return body?.error?.message ?? fallback;
|
|
10
|
+
}
|
|
6
11
|
export async function fetchModels(cfg) {
|
|
7
12
|
let resp;
|
|
8
13
|
try {
|
|
@@ -17,9 +22,15 @@ export async function fetchModels(cfg) {
|
|
|
17
22
|
if (resp.status === 401)
|
|
18
23
|
throw new AuthError("登录已失效,请重新运行 u1s1 login");
|
|
19
24
|
if (!resp.ok)
|
|
20
|
-
throw new Error(`服务端返回 ${resp.status},稍后再试`);
|
|
25
|
+
throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status},稍后再试`));
|
|
21
26
|
const body = (await resp.json());
|
|
22
|
-
|
|
27
|
+
const token = body.client_attestation?.token;
|
|
28
|
+
return {
|
|
29
|
+
models: body.data,
|
|
30
|
+
features: body.features ?? {},
|
|
31
|
+
announcement: body.announcement,
|
|
32
|
+
clientAttestation: typeof token === "string" && token.length <= 1024 ? token : undefined,
|
|
33
|
+
};
|
|
23
34
|
}
|
|
24
35
|
/**
|
|
25
36
|
* 拉取用户在云端配置的自定义模型端点(dashboard「自定义模型端点」卡片)。
|
|
@@ -37,7 +48,7 @@ export async function fetchUserEndpoints(cfg) {
|
|
|
37
48
|
throw new Error(`连不上 ${cfg.baseUrl}`);
|
|
38
49
|
}
|
|
39
50
|
if (!resp.ok)
|
|
40
|
-
throw new Error(`服务端返回 ${resp.status}`);
|
|
51
|
+
throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status}`));
|
|
41
52
|
const body = (await resp.json());
|
|
42
53
|
return Array.isArray(body.endpoints) ? body.endpoints : [];
|
|
43
54
|
}
|
|
@@ -76,10 +87,8 @@ export async function searchWeb(cfg, input) {
|
|
|
76
87
|
}
|
|
77
88
|
if (resp.status === 401)
|
|
78
89
|
throw new Error("登录已失效,请重新运行 u1s1 login");
|
|
79
|
-
if (!resp.ok)
|
|
80
|
-
|
|
81
|
-
throw new Error(body?.error?.message ?? `搜索服务返回 ${resp.status},稍后再试`);
|
|
82
|
-
}
|
|
90
|
+
if (!resp.ok)
|
|
91
|
+
throw new Error(await errorMessageFromResponse(resp, `搜索服务返回 ${resp.status},稍后再试`));
|
|
83
92
|
return (await resp.json());
|
|
84
93
|
}
|
|
85
94
|
/** web_fetch 直连失败时的回退:网关用 Cloudflare Browser Rendering 渲染后转 markdown。 */
|
|
@@ -103,10 +112,8 @@ export async function renderPage(cfg, url, signal) {
|
|
|
103
112
|
}
|
|
104
113
|
if (resp.status === 401)
|
|
105
114
|
throw new Error("登录已失效,请重新运行 u1s1 login");
|
|
106
|
-
if (!resp.ok)
|
|
107
|
-
|
|
108
|
-
throw new Error(body?.error?.message ?? `渲染服务返回 ${resp.status}`);
|
|
109
|
-
}
|
|
115
|
+
if (!resp.ok)
|
|
116
|
+
throw new Error(await errorMessageFromResponse(resp, `渲染服务返回 ${resp.status}`));
|
|
110
117
|
return (await resp.json());
|
|
111
118
|
}
|
|
112
119
|
const IMAGE_URL_HEADER = "x-u1s1-image-url";
|
|
@@ -178,10 +185,8 @@ export async function generateImage(cfg, req, signal) {
|
|
|
178
185
|
}
|
|
179
186
|
if (resp.status === 401)
|
|
180
187
|
throw new Error("登录已失效,请重新运行 u1s1 login");
|
|
181
|
-
if (!resp.ok)
|
|
182
|
-
|
|
183
|
-
throw new Error(body?.error?.message ?? `图片生成服务返回 ${resp.status},稍后再试`);
|
|
184
|
-
}
|
|
188
|
+
if (!resp.ok)
|
|
189
|
+
throw new Error(await errorMessageFromResponse(resp, `图片生成服务返回 ${resp.status},稍后再试`));
|
|
185
190
|
return readGeneratedImageResponse(resp);
|
|
186
191
|
}
|
|
187
192
|
export async function fetchMe(cfg) {
|
|
@@ -200,6 +205,6 @@ export async function fetchMe(cfg) {
|
|
|
200
205
|
if (resp.status === 401)
|
|
201
206
|
throw new Error("登录已失效,请重新运行 u1s1 login");
|
|
202
207
|
if (!resp.ok)
|
|
203
|
-
throw new Error(`服务端返回 ${resp.status},稍后再试`);
|
|
208
|
+
throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status},稍后再试`));
|
|
204
209
|
return (await resp.json());
|
|
205
210
|
}
|
package/dist/device-auth.d.ts
CHANGED
|
@@ -13,11 +13,12 @@ interface SigningProxy {
|
|
|
13
13
|
localKey: string;
|
|
14
14
|
token: string;
|
|
15
15
|
client: ClientSurface;
|
|
16
|
+
clientAttestation?: string;
|
|
16
17
|
}
|
|
17
18
|
export type ClientSurface = "terminal" | "web" | "desktop" | "cloud";
|
|
18
19
|
/**
|
|
19
20
|
* pi accepts static provider headers only. Keep it behind a loopback proxy that
|
|
20
21
|
* replaces the local bearer credential with a fresh DPoP proof per request.
|
|
21
22
|
*/
|
|
22
|
-
export declare function ensureSigningProxy(cfg: CliConfig, fallbackClient?: ClientSurface): Promise<SigningProxy>;
|
|
23
|
+
export declare function ensureSigningProxy(cfg: CliConfig, fallbackClient?: ClientSurface, clientAttestation?: string): Promise<SigningProxy>;
|
|
23
24
|
export {};
|
package/dist/device-auth.js
CHANGED
|
@@ -98,12 +98,13 @@ function clientSurface(fallback) {
|
|
|
98
98
|
* pi accepts static provider headers only. Keep it behind a loopback proxy that
|
|
99
99
|
* replaces the local bearer credential with a fresh DPoP proof per request.
|
|
100
100
|
*/
|
|
101
|
-
export async function ensureSigningProxy(cfg, fallbackClient = "terminal") {
|
|
101
|
+
export async function ensureSigningProxy(cfg, fallbackClient = "terminal", clientAttestation) {
|
|
102
102
|
if (!hasDeviceCredential(cfg))
|
|
103
103
|
throw new Error("当前安装需要重新登录,以创建设备凭证");
|
|
104
104
|
const client = clientSurface(fallbackClient);
|
|
105
105
|
const current = signingProxy;
|
|
106
|
-
if (current && current.token === cfg.deviceToken && current.client === client
|
|
106
|
+
if (current && current.token === cfg.deviceToken && current.client === client
|
|
107
|
+
&& current.clientAttestation === clientAttestation)
|
|
107
108
|
return current;
|
|
108
109
|
const localKey = `local-${randomBytes(32).toString("hex")}`;
|
|
109
110
|
const upstreamOrigin = new URL(cfg.baseUrl).origin;
|
|
@@ -133,6 +134,8 @@ export async function ensureSigningProxy(cfg, fallbackClient = "terminal") {
|
|
|
133
134
|
outboundHeaders.set("x-u1s1-client", client);
|
|
134
135
|
outboundHeaders.set("x-u1s1-version", VERSION);
|
|
135
136
|
outboundHeaders.set("x-u1s1-platform", `${process.platform}-${process.arch}`);
|
|
137
|
+
if (clientAttestation)
|
|
138
|
+
outboundHeaders.set("x-u1s1-attestation", clientAttestation);
|
|
136
139
|
const upstream = await authorizedFetch(cfg, target, {
|
|
137
140
|
method: req.method ?? "GET",
|
|
138
141
|
headers: outboundHeaders,
|
|
@@ -166,6 +169,12 @@ export async function ensureSigningProxy(cfg, fallbackClient = "terminal") {
|
|
|
166
169
|
});
|
|
167
170
|
server.unref();
|
|
168
171
|
const port = server.address().port;
|
|
169
|
-
signingProxy = {
|
|
172
|
+
signingProxy = {
|
|
173
|
+
baseUrl: `http://127.0.0.1:${port}/v1`,
|
|
174
|
+
localKey,
|
|
175
|
+
token: cfg.deviceToken,
|
|
176
|
+
client,
|
|
177
|
+
clientAttestation,
|
|
178
|
+
};
|
|
170
179
|
return signingProxy;
|
|
171
180
|
}
|
package/dist/index.js
CHANGED
|
@@ -193,7 +193,7 @@ async function runAgent(cfg, args) {
|
|
|
193
193
|
// it after the live model list arrives; explicit user choices remain intact.
|
|
194
194
|
ensureDefaultSettings(MODELS);
|
|
195
195
|
// pi provider 只支持静态 header;指向本机 signing proxy,由它逐请求附 DPoP proof。
|
|
196
|
-
const signing = await ensureSigningProxy(cfg);
|
|
196
|
+
const signing = await ensureSigningProxy(cfg, "terminal", modelsResp?.clientAttestation);
|
|
197
197
|
const officialCfg = { ...cfg, baseUrl: signing.baseUrl, apiKey: signing.localKey };
|
|
198
198
|
ensureBrandPrompt(await shellReady);
|
|
199
199
|
ensureProviderModels(officialCfg);
|
package/dist/web.js
CHANGED
|
@@ -54,20 +54,22 @@ export async function prepareWebEnv(cfg) {
|
|
|
54
54
|
let webFetchRenderEnabled = false;
|
|
55
55
|
// 老网关没有 /v1/image,image_gen 缺失时按关闭处理,不注册生图工具
|
|
56
56
|
let imageGenEnabled = false;
|
|
57
|
+
let clientAttestation;
|
|
57
58
|
const endpointsReady = loadCustomEndpoints(cfg);
|
|
58
59
|
try {
|
|
59
|
-
const { models, features } = await fetchModels(cfg);
|
|
60
|
+
const { models, features, clientAttestation: attestation } = await fetchModels(cfg);
|
|
60
61
|
setModelsFromApi(models.map(apiModelToDef));
|
|
61
62
|
webSearchEnabled = features.web_search !== false;
|
|
62
63
|
webFetchRenderEnabled = features.web_fetch_render === true;
|
|
63
64
|
imageGenEnabled = features.image_gen === true;
|
|
65
|
+
clientAttestation = attestation;
|
|
64
66
|
}
|
|
65
67
|
catch (e) {
|
|
66
68
|
console.error(" 获取模型列表失败,使用内置列表:", e.message);
|
|
67
69
|
}
|
|
68
70
|
await endpointsReady;
|
|
69
71
|
ensureDefaultSettings(MODELS);
|
|
70
|
-
const signing = await ensureSigningProxy(cfg, "desktop");
|
|
72
|
+
const signing = await ensureSigningProxy(cfg, "desktop", clientAttestation);
|
|
71
73
|
const officialCfg = { ...cfg, baseUrl: signing.baseUrl, apiKey: signing.localKey };
|
|
72
74
|
webOfficialCfg = officialCfg;
|
|
73
75
|
const modelsPath = refreshWebModels();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u1s1-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"homepage": "https://u1s1.io",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
46
|
-
"@earendil-works/pi-tui": "0.84.
|
|
45
|
+
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
46
|
+
"@earendil-works/pi-tui": "0.84.4",
|
|
47
47
|
"typebox": "1.3.7"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
package/scripts/patch-pi.js
CHANGED
|
@@ -133,13 +133,13 @@ const ENABLE_AUTOWRAP = "\\x1b[?7h";`,
|
|
|
133
133
|
expected: 1,
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
|
-
from: '
|
|
137
|
-
to: '
|
|
136
|
+
from: 'output.append("\\x1b[?2026h");',
|
|
137
|
+
to: 'output.append("\\x1b[?2026h" + DISABLE_AUTOWRAP);',
|
|
138
138
|
expected: 3,
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
|
-
from: '
|
|
142
|
-
to: '
|
|
141
|
+
from: 'output.append("\\x1b[?2026l");',
|
|
142
|
+
to: 'output.append(ENABLE_AUTOWRAP + "\\x1b[?2026l");',
|
|
143
143
|
expected: 3,
|
|
144
144
|
},
|
|
145
145
|
];
|