hiwork-core 0.0.2 → 0.0.6
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/README.md +16 -7
- package/cordis.patch.yml +21 -0
- package/lib/ai-router-client.js +258 -2
- package/lib/browser-login.js +241 -0
- package/lib/client-policy.js +82 -0
- package/lib/client.js +1062 -1002
- package/lib/daily-rpc.js +161 -0
- package/lib/default-model.js +39 -0
- package/lib/index.js +1158 -71
- package/lib/orchestration.js +45 -75
- package/lib/provision-session.js +85 -0
- package/lib/provision.js +74 -24
- package/lib/types/ai-router-client.d.ts +255 -3
- package/lib/types/browser-login.d.ts +99 -0
- package/lib/types/client/account-cache.d.ts +46 -0
- package/lib/types/client/controller.d.ts +68 -0
- package/lib/types/client/daily-card.d.ts +29 -20
- package/lib/types/client/daily-data.d.ts +116 -0
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/login-gate.d.ts +9 -0
- package/lib/types/client/session-cost.d.ts +119 -0
- package/lib/types/client/styles.d.ts +12 -0
- package/lib/types/client/types.d.ts +1 -1
- package/lib/types/client/usage-data.d.ts +276 -0
- package/lib/types/client/usage-section.d.ts +18 -0
- package/lib/types/client-policy.d.ts +51 -0
- package/lib/types/daily-rpc.d.ts +110 -0
- package/lib/types/default-model.d.ts +37 -0
- package/lib/types/index.d.ts +113 -0
- package/lib/types/orchestration.d.ts +28 -55
- package/lib/types/provision-session.d.ts +106 -0
- package/lib/types/provision.d.ts +48 -9
- package/lib/types/types.d.ts +12 -1
- package/lib/types/usage-rpc.d.ts +196 -0
- package/lib/usage-rpc.js +386 -0
- package/package.json +6 -2
package/lib/index.js
CHANGED
|
@@ -3137,9 +3137,21 @@ function isUnloading(ctx) {
|
|
|
3137
3137
|
}
|
|
3138
3138
|
|
|
3139
3139
|
// src/version.ts
|
|
3140
|
-
var PKG_VERSION = false ? "dev" : "0.0.
|
|
3140
|
+
var PKG_VERSION = false ? "dev" : "0.0.6";
|
|
3141
3141
|
|
|
3142
3142
|
// src/ai-router-client.ts
|
|
3143
|
+
var AUTH_AUTHORIZE_PATH = "/api/auth/authorize";
|
|
3144
|
+
var AUTH_TOKEN_PATH = "/api/auth/token";
|
|
3145
|
+
var AUTH_SESSIONS_PATH = "/api/auth/sessions";
|
|
3146
|
+
var ME_ANALYTICS_SUMMARY_PATH = "/api/me/analytics/summary";
|
|
3147
|
+
var ME_ANALYTICS_TRENDS_PATH = "/api/me/analytics/trends";
|
|
3148
|
+
var ME_ANALYTICS_MODEL_TRENDS_PATH = "/api/me/analytics/model-trends";
|
|
3149
|
+
var ME_KEY_RECHARGES_PATH = "/api/me/key/recharges";
|
|
3150
|
+
var ME_COST_ESTIMATE_PATH = "/api/me/cost-estimate";
|
|
3151
|
+
var ME_DAILY_QUOTE_PATH = "/api/me/daily-quote";
|
|
3152
|
+
var ME_DAILY_QUOTE_RANDOM_PATH = "/api/me/daily-quote/random";
|
|
3153
|
+
var DEFAULT_CLIENT_ID = "hiwork-desktop";
|
|
3154
|
+
var DEFAULT_REDIRECT_URI = "hiwork://auth/callback";
|
|
3143
3155
|
var GatewayError = class extends Error {
|
|
3144
3156
|
constructor(code, message, status) {
|
|
3145
3157
|
super(message);
|
|
@@ -3148,13 +3160,32 @@ var GatewayError = class extends Error {
|
|
|
3148
3160
|
this.name = "GatewayError";
|
|
3149
3161
|
}
|
|
3150
3162
|
};
|
|
3163
|
+
function buildAuthorizeURL(opts) {
|
|
3164
|
+
const params = [
|
|
3165
|
+
["client_id", opts.clientId ?? DEFAULT_CLIENT_ID],
|
|
3166
|
+
["redirect_uri", opts.redirectUri ?? DEFAULT_REDIRECT_URI],
|
|
3167
|
+
["code_challenge", opts.codeChallenge],
|
|
3168
|
+
["code_challenge_method", "S256"],
|
|
3169
|
+
["state", opts.state],
|
|
3170
|
+
["device_id", opts.deviceId],
|
|
3171
|
+
["device_name", opts.deviceName],
|
|
3172
|
+
["platform", opts.platform],
|
|
3173
|
+
["app_version", opts.appVersion]
|
|
3174
|
+
];
|
|
3175
|
+
const query = params.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
|
|
3176
|
+
return `${opts.baseURL}${AUTH_AUTHORIZE_PATH}?${query}`;
|
|
3177
|
+
}
|
|
3151
3178
|
var AiRouterClient = class {
|
|
3152
3179
|
constructor(baseURL, timeoutMs = 1e4) {
|
|
3153
3180
|
this.baseURL = baseURL;
|
|
3154
3181
|
this.timeoutMs = timeoutMs;
|
|
3155
3182
|
}
|
|
3156
|
-
async login(username, password) {
|
|
3157
|
-
const res = await this.request("/api/admin/auth/login", {
|
|
3183
|
+
async login(username, password, device) {
|
|
3184
|
+
const res = await this.request("/api/admin/auth/login", {
|
|
3185
|
+
method: "POST",
|
|
3186
|
+
body: { username, password },
|
|
3187
|
+
headers: deviceHeaders(device)
|
|
3188
|
+
});
|
|
3158
3189
|
const sid = extractSessionId(res.headers);
|
|
3159
3190
|
if (res.status === 429) throw new GatewayError("RATE_LIMITED", "\u767B\u5F55\u5C1D\u8BD5\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5", 429);
|
|
3160
3191
|
if (res.status === 401) throw new GatewayError("AUTH_FAILED", "\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF", 401);
|
|
@@ -3163,6 +3194,45 @@ var AiRouterClient = class {
|
|
|
3163
3194
|
const user = await res.json();
|
|
3164
3195
|
return { sessionCookie: `session_id=${sid}`, user };
|
|
3165
3196
|
}
|
|
3197
|
+
/**
|
|
3198
|
+
* POST /api/auth/token:用一次性 code + PKCE verifier 换取会话。
|
|
3199
|
+
* 返回 `session_id=<sid>`(与 login 同形状,编排层可直接复用),user 原样返回。
|
|
3200
|
+
* 任何日志/异常信息都不得包含 code / code_verifier / session_id 明文。
|
|
3201
|
+
*/
|
|
3202
|
+
async exchangeCode(input) {
|
|
3203
|
+
const res = await this.requestOrUnreachable(AUTH_TOKEN_PATH, {
|
|
3204
|
+
method: "POST",
|
|
3205
|
+
body: { code: input.code, code_verifier: input.codeVerifier, device_id: input.deviceId }
|
|
3206
|
+
});
|
|
3207
|
+
if (res.status === 400) {
|
|
3208
|
+
const payload = await readJson(res);
|
|
3209
|
+
if (isInvalidGrant(payload)) throw new GatewayError("AUTH_FAILED", "\u6388\u6743\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 400);
|
|
3210
|
+
throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3211
|
+
}
|
|
3212
|
+
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3213
|
+
const data = await readJson(res);
|
|
3214
|
+
if (data === void 0 || data.session_id === void 0 || data.session_id === "") {
|
|
3215
|
+
throw new GatewayError("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5151\u6362\u6210\u529F\u4F46\u672A\u8FD4\u56DE\u4F1A\u8BDD\u6807\u8BC6", res.status);
|
|
3216
|
+
}
|
|
3217
|
+
return { sessionCookie: `session_id=${data.session_id}`, user: data.user };
|
|
3218
|
+
}
|
|
3219
|
+
/** GET /api/auth/sessions:当前账号的登录设备列表。 */
|
|
3220
|
+
async listSessions(sessionCookie) {
|
|
3221
|
+
const res = await this.requestOrUnreachable(AUTH_SESSIONS_PATH, { method: "GET", sessionCookie });
|
|
3222
|
+
if (res.status === 401) throw new GatewayError("SESSION_EXPIRED", "\u7F51\u5173\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 401);
|
|
3223
|
+
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3224
|
+
return await res.json();
|
|
3225
|
+
}
|
|
3226
|
+
/** DELETE /api/auth/sessions/:id:远程吊销指定会话(204 成功;404 表示已不存在)。 */
|
|
3227
|
+
async revokeSession(sessionCookie, sessionId) {
|
|
3228
|
+
const res = await this.requestOrUnreachable(`${AUTH_SESSIONS_PATH}/${encodeURIComponent(sessionId)}`, {
|
|
3229
|
+
method: "DELETE",
|
|
3230
|
+
sessionCookie
|
|
3231
|
+
});
|
|
3232
|
+
if (res.status === 401) throw new GatewayError("SESSION_EXPIRED", "\u7F51\u5173\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 401);
|
|
3233
|
+
if (res.status === 404) throw new GatewayError("NOT_FOUND", "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u540A\u9500", 404);
|
|
3234
|
+
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3235
|
+
}
|
|
3166
3236
|
async getMyKey(sessionCookie, keyId) {
|
|
3167
3237
|
const qs = keyId === void 0 ? "" : `?key_id=${encodeURIComponent(keyId)}`;
|
|
3168
3238
|
const res = await this.request(`/api/me/key${qs}`, { method: "GET", sessionCookie });
|
|
@@ -3192,10 +3262,85 @@ var AiRouterClient = class {
|
|
|
3192
3262
|
} catch {
|
|
3193
3263
|
}
|
|
3194
3264
|
}
|
|
3265
|
+
/** GET /api/me/analytics/summary:按模型聚合的用量汇总。 */
|
|
3266
|
+
async getMySummary(sessionCookie, keyId, range) {
|
|
3267
|
+
const query = meQuery([["key_id", keyId], ["range", range]]);
|
|
3268
|
+
return await this.getMeJson(`${ME_ANALYTICS_SUMMARY_PATH}${query}`, sessionCookie);
|
|
3269
|
+
}
|
|
3270
|
+
/** GET /api/me/analytics/trends:token/费用走势。 */
|
|
3271
|
+
async getMyTrends(sessionCookie, keyId, range) {
|
|
3272
|
+
const query = meQuery([["key_id", keyId], ["range", range]]);
|
|
3273
|
+
return await this.getMeJson(`${ME_ANALYTICS_TRENDS_PATH}${query}`, sessionCookie);
|
|
3274
|
+
}
|
|
3275
|
+
/** GET /api/me/analytics/model-trends:按模型拆分的走势。 */
|
|
3276
|
+
async getMyModelTrends(sessionCookie, keyId, range) {
|
|
3277
|
+
const query = meQuery([["key_id", keyId], ["range", range]]);
|
|
3278
|
+
return await this.getMeJson(`${ME_ANALYTICS_MODEL_TRENDS_PATH}${query}`, sessionCookie);
|
|
3279
|
+
}
|
|
3280
|
+
/** GET /api/me/key/recharges:当前 Key 的充值台账(可按模型过滤)。 */
|
|
3281
|
+
async getMyKeyRecharges(sessionCookie, keyId, modelName) {
|
|
3282
|
+
const query = meQuery([["key_id", keyId], ["model_name", modelName]]);
|
|
3283
|
+
return await this.getMeJson(`${ME_KEY_RECHARGES_PATH}${query}`, sessionCookie);
|
|
3284
|
+
}
|
|
3285
|
+
/** GET /api/me/daily-quote:当日条目(服务端东八区取模轮换,全网同一天同一条)。 */
|
|
3286
|
+
async getMyDailyQuote(sessionCookie) {
|
|
3287
|
+
return await this.getMeJson(ME_DAILY_QUOTE_PATH, sessionCookie);
|
|
3288
|
+
}
|
|
3289
|
+
/** GET /api/me/daily-quote/random:换一句(exclude 为当前条目 id;非法值由网关按缺省处理)。 */
|
|
3290
|
+
async getMyDailyQuoteRandom(sessionCookie, excludeId) {
|
|
3291
|
+
const query = meQuery([["exclude", excludeId === void 0 ? void 0 : String(excludeId)]]);
|
|
3292
|
+
return await this.getMeJson(`${ME_DAILY_QUOTE_RANDOM_PATH}${query}`, sessionCookie);
|
|
3293
|
+
}
|
|
3294
|
+
/**
|
|
3295
|
+
* POST /api/me/cost-estimate:网关权威计价。请求体里的四个 token 桶原样上报,
|
|
3296
|
+
* `input_tokens` 不含 cache;任何折算与单价计算都在网关侧完成。
|
|
3297
|
+
*/
|
|
3298
|
+
async estimateCost(sessionCookie, items) {
|
|
3299
|
+
const res = await this.requestOrUnreachable(ME_COST_ESTIMATE_PATH, {
|
|
3300
|
+
method: "POST",
|
|
3301
|
+
sessionCookie,
|
|
3302
|
+
body: { items }
|
|
3303
|
+
});
|
|
3304
|
+
if (res.status === 401) throw new GatewayError("SESSION_EXPIRED", "\u7F51\u5173\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 401);
|
|
3305
|
+
if (res.status === 403) {
|
|
3306
|
+
throw new GatewayError("NO_KEY_ASSIGNED", "\u5F53\u524D\u8D26\u53F7\u5C1A\u672A\u88AB\u5206\u914D\u865A\u62DF KEY\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u5206\u914D KEY \u540E\u518D\u767B\u5F55/\u5237\u65B0", 403);
|
|
3307
|
+
}
|
|
3308
|
+
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3309
|
+
return await parseMeJson(res);
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* /api/me 自助端点的 GET 公共路径:网络异常 → GATEWAY_UNREACHABLE,
|
|
3313
|
+
* 401/403/400 映射为稳定 code,响应体非 JSON 时不把原文带进异常。
|
|
3314
|
+
*/
|
|
3315
|
+
async getMeJson(path, sessionCookie) {
|
|
3316
|
+
const res = await this.requestOrUnreachable(path, { method: "GET", sessionCookie });
|
|
3317
|
+
if (res.status === 401) throw new GatewayError("SESSION_EXPIRED", "\u7F51\u5173\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 401);
|
|
3318
|
+
if (res.status === 403) {
|
|
3319
|
+
throw new GatewayError("NO_KEY_ASSIGNED", "\u5F53\u524D\u8D26\u53F7\u5C1A\u672A\u88AB\u5206\u914D\u865A\u62DF KEY\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u5206\u914D KEY \u540E\u518D\u767B\u5F55/\u5237\u65B0", 403);
|
|
3320
|
+
}
|
|
3321
|
+
if (res.status === 400) {
|
|
3322
|
+
throw new GatewayError("KEY_INVALID", "\u6240\u9009 KEY \u65E0\u6548\u6216\u5DF2\u88AB\u5220\u9664\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9 KEY \u540E\u5237\u65B0", 400);
|
|
3323
|
+
}
|
|
3324
|
+
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3325
|
+
return await parseMeJson(res);
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* 浏览器授权相关端点专用:网络层异常(连接失败 / 超时 / DNS)统一映射为
|
|
3329
|
+
* GATEWAY_UNREACHABLE,避免裸 fetch 错误漏到编排层。
|
|
3330
|
+
* login 等既有方法不经过此处,行为保持不变。
|
|
3331
|
+
*/
|
|
3332
|
+
async requestOrUnreachable(path, opts) {
|
|
3333
|
+
try {
|
|
3334
|
+
return await this.request(path, opts);
|
|
3335
|
+
} catch {
|
|
3336
|
+
throw new GatewayError("GATEWAY_UNREACHABLE", "\u7F51\u5173\u4E0D\u53EF\u8FBE\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5");
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3195
3339
|
async request(path, opts) {
|
|
3196
3340
|
const headers = { "user-agent": `hiwork-core/${PKG_VERSION}` };
|
|
3197
3341
|
if (opts.body !== void 0) headers["content-type"] = "application/json";
|
|
3198
3342
|
if (opts.sessionCookie !== void 0) headers["cookie"] = opts.sessionCookie;
|
|
3343
|
+
Object.assign(headers, opts.headers ?? {});
|
|
3199
3344
|
const init = {
|
|
3200
3345
|
method: opts.method,
|
|
3201
3346
|
headers,
|
|
@@ -3205,6 +3350,33 @@ var AiRouterClient = class {
|
|
|
3205
3350
|
return fetch(`${this.baseURL}${path}`, init);
|
|
3206
3351
|
}
|
|
3207
3352
|
};
|
|
3353
|
+
function headerSafeValue(value) {
|
|
3354
|
+
if (typeof value !== "string") return void 0;
|
|
3355
|
+
let out = "";
|
|
3356
|
+
for (const char of value) {
|
|
3357
|
+
const code = char.codePointAt(0) ?? 0;
|
|
3358
|
+
if (code >= 32 && code <= 126) out += char;
|
|
3359
|
+
}
|
|
3360
|
+
const trimmed = out.trim();
|
|
3361
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
3362
|
+
}
|
|
3363
|
+
function safeDeviceId(value) {
|
|
3364
|
+
if (typeof value !== "string") return void 0;
|
|
3365
|
+
const trimmed = value.trim();
|
|
3366
|
+
return /^[\x21-\x7e]+$/.test(trimmed) ? trimmed : void 0;
|
|
3367
|
+
}
|
|
3368
|
+
function deviceHeaders(device) {
|
|
3369
|
+
const deviceId = device?.deviceId === void 0 ? void 0 : safeDeviceId(device.deviceId);
|
|
3370
|
+
if (deviceId === void 0) return {};
|
|
3371
|
+
const headers = { "X-HiWork-Device-Id": deviceId };
|
|
3372
|
+
const deviceName = device?.deviceName === void 0 ? void 0 : headerSafeValue(device.deviceName);
|
|
3373
|
+
if (deviceName !== void 0) headers["X-HiWork-Device-Name"] = deviceName;
|
|
3374
|
+
const platform = device?.platform === void 0 ? void 0 : headerSafeValue(device.platform);
|
|
3375
|
+
if (platform !== void 0) headers["X-HiWork-Platform"] = platform;
|
|
3376
|
+
const appVersion = device?.appVersion === void 0 ? void 0 : headerSafeValue(device.appVersion);
|
|
3377
|
+
if (appVersion !== void 0) headers["X-HiWork-App-Version"] = appVersion;
|
|
3378
|
+
return headers;
|
|
3379
|
+
}
|
|
3208
3380
|
function extractSessionId(headers) {
|
|
3209
3381
|
const raws = typeof headers.getSetCookie === "function" ? headers.getSetCookie() : [headers.get("set-cookie")].filter((raw) => raw !== null);
|
|
3210
3382
|
for (const raw of raws) {
|
|
@@ -3213,22 +3385,35 @@ function extractSessionId(headers) {
|
|
|
3213
3385
|
}
|
|
3214
3386
|
return void 0;
|
|
3215
3387
|
}
|
|
3216
|
-
|
|
3217
|
-
// src/default-workspace.ts
|
|
3218
|
-
import { mkdir } from "node:fs/promises";
|
|
3219
|
-
import { homedir } from "node:os";
|
|
3220
|
-
import { join } from "node:path";
|
|
3221
|
-
var DEFAULT_WORKSPACE_DIR = join(homedir(), "HiWork", "Default");
|
|
3222
|
-
var DEFAULT_WORKSPACE_TITLE = "\u9ED8\u8BA4\u5DE5\u4F5C\u533A";
|
|
3223
|
-
async function ensureDefaultWorkspace(registry, warn, dir = DEFAULT_WORKSPACE_DIR) {
|
|
3388
|
+
async function readJson(res) {
|
|
3224
3389
|
try {
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
}
|
|
3229
|
-
|
|
3390
|
+
return await res.json();
|
|
3391
|
+
} catch {
|
|
3392
|
+
return void 0;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
function meQuery(params) {
|
|
3396
|
+
const pairs = [];
|
|
3397
|
+
for (const [key, value] of params) {
|
|
3398
|
+
if (value === void 0) continue;
|
|
3399
|
+
pairs.push(`${key}=${encodeURIComponent(value)}`);
|
|
3400
|
+
}
|
|
3401
|
+
return pairs.length === 0 ? "" : `?${pairs.join("&")}`;
|
|
3402
|
+
}
|
|
3403
|
+
async function parseMeJson(res) {
|
|
3404
|
+
const data = await readJson(res);
|
|
3405
|
+
if (data === void 0 || data === null) {
|
|
3406
|
+
throw new GatewayError("GATEWAY_UNREACHABLE", "\u7F51\u5173\u8FD4\u56DE\u7684\u54CD\u5E94\u65E0\u6CD5\u89E3\u6790\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", res.status);
|
|
3230
3407
|
}
|
|
3408
|
+
return data;
|
|
3231
3409
|
}
|
|
3410
|
+
function isInvalidGrant(payload) {
|
|
3411
|
+
return typeof payload === "object" && payload !== null && payload.error === "invalid_grant";
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
// src/browser-login.ts
|
|
3415
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
3416
|
+
import { hostname } from "node:os";
|
|
3232
3417
|
|
|
3233
3418
|
// src/provision.ts
|
|
3234
3419
|
var API_KEY_REF = "AI_ROUTER_API_KEY";
|
|
@@ -3236,22 +3421,34 @@ var SESSION_REF = "AI_ROUTER_SESSION";
|
|
|
3236
3421
|
var PENDING_PASSWORD_REF = "AI_ROUTER_PENDING_PASSWORD";
|
|
3237
3422
|
var REMEMBERED_PASSWORD_REF = "AI_ROUTER_REMEMBERED_PASSWORD";
|
|
3238
3423
|
var PROVIDER_ROUTES = {
|
|
3239
|
-
"ai-router": "openai-
|
|
3240
|
-
"ai-router-anthropic": "anthropic-messages"
|
|
3424
|
+
"ai-router-chat": "openai-completions",
|
|
3425
|
+
"ai-router-anthropic": "anthropic-messages",
|
|
3426
|
+
"ai-router": "openai-responses"
|
|
3241
3427
|
};
|
|
3242
3428
|
var DSH_SUPPORTED_MODALITIES = ["text", "image"];
|
|
3243
|
-
var PROVIDER_ROUTE_NAMES = [
|
|
3429
|
+
var PROVIDER_ROUTE_NAMES = [
|
|
3430
|
+
"ai-router-chat",
|
|
3431
|
+
"ai-router-anthropic",
|
|
3432
|
+
"ai-router"
|
|
3433
|
+
];
|
|
3244
3434
|
var LEGACY_PROVIDER_ROUTE_NAMES = ["ai-router-responses"];
|
|
3245
3435
|
var ROUTE_DISPLAY_NAMES = {
|
|
3246
|
-
"ai-router": "
|
|
3247
|
-
"ai-router-anthropic": "
|
|
3436
|
+
"ai-router-chat": "\u7EBF\u8DEFC",
|
|
3437
|
+
"ai-router-anthropic": "\u7EBF\u8DEFM",
|
|
3438
|
+
"ai-router": "\u7EBF\u8DEFR"
|
|
3248
3439
|
};
|
|
3440
|
+
var GATEWAY_PROTOCOLS = ["openai_chat_completions", "anthropic_messages", "openai_responses"];
|
|
3249
3441
|
var GATEWAY_PROTOCOL_TO_ROUTE = {
|
|
3250
|
-
|
|
3442
|
+
openai_responses: "ai-router",
|
|
3251
3443
|
anthropic_messages: "ai-router-anthropic",
|
|
3252
|
-
|
|
3444
|
+
openai_chat_completions: "ai-router-chat"
|
|
3253
3445
|
};
|
|
3254
|
-
|
|
3446
|
+
function routeToProtocol(route) {
|
|
3447
|
+
for (const protocol of GATEWAY_PROTOCOLS) {
|
|
3448
|
+
if (GATEWAY_PROTOCOL_TO_ROUTE[protocol] === route) return protocol;
|
|
3449
|
+
}
|
|
3450
|
+
return void 0;
|
|
3451
|
+
}
|
|
3255
3452
|
function computeModelIds(budgets) {
|
|
3256
3453
|
return [...new Set(
|
|
3257
3454
|
budgets.filter((b) => b.model_status !== "inactive").map((b) => b.model_name).filter((id) => typeof id === "string" && id.length > 0)
|
|
@@ -3296,17 +3493,24 @@ function computeRouteModels(budgets, mapping) {
|
|
|
3296
3493
|
const routes = {};
|
|
3297
3494
|
const unmatched = [];
|
|
3298
3495
|
for (const model of computeModelDescriptors(budgets)) {
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3496
|
+
let matched = false;
|
|
3497
|
+
for (const route of PROVIDER_ROUTE_NAMES) {
|
|
3498
|
+
if (!mapping.some((item) => matchesRoute(item, route, model.id))) continue;
|
|
3499
|
+
(routes[route] ??= []).push(model);
|
|
3500
|
+
matched = true;
|
|
3303
3501
|
}
|
|
3304
|
-
;
|
|
3305
|
-
(routes[route] ??= []).push(model);
|
|
3502
|
+
if (!matched) unmatched.push(model);
|
|
3306
3503
|
}
|
|
3307
|
-
if (unmatched.length > 0) routes["ai-router"] = [...routes["ai-router"] ?? [], ...unmatched];
|
|
3504
|
+
if (unmatched.length > 0) routes["ai-router-chat"] = [...routes["ai-router-chat"] ?? [], ...unmatched];
|
|
3308
3505
|
return routes;
|
|
3309
3506
|
}
|
|
3507
|
+
function pickFallbackDefaultModel(routeModels) {
|
|
3508
|
+
for (const route of PROVIDER_ROUTE_NAMES) {
|
|
3509
|
+
const first = routeModels[route]?.[0];
|
|
3510
|
+
if (first !== void 0) return { provider: route, model: first.id };
|
|
3511
|
+
}
|
|
3512
|
+
return void 0;
|
|
3513
|
+
}
|
|
3310
3514
|
function protocolBaseURL(baseURL, protocol) {
|
|
3311
3515
|
const bare = baseURL.replace(/\/+$/, "");
|
|
3312
3516
|
return protocol === "anthropic-messages" ? bare : `${bare}/v1`;
|
|
@@ -3333,7 +3537,7 @@ function buildPiAiProviderProfiles(opts) {
|
|
|
3333
3537
|
return profiles;
|
|
3334
3538
|
}
|
|
3335
3539
|
|
|
3336
|
-
// src/
|
|
3540
|
+
// src/provision-session.ts
|
|
3337
3541
|
function computeBudgets(budgets) {
|
|
3338
3542
|
return budgets.filter((b) => typeof b.model_name === "string" && b.model_name.length > 0).filter((b) => b.model_status !== "inactive").map((b) => ({
|
|
3339
3543
|
model_name: b.model_name,
|
|
@@ -3356,6 +3560,385 @@ function toLoginError(error, fallback) {
|
|
|
3356
3560
|
if (error instanceof GatewayError) return { code: error.code, message: error.message };
|
|
3357
3561
|
return { code: "GATEWAY_UNREACHABLE", message: fallback };
|
|
3358
3562
|
}
|
|
3563
|
+
async function provisionSession(opts) {
|
|
3564
|
+
const keys = await opts.gateway.getMyKeys(opts.sessionCookie);
|
|
3565
|
+
const key = await opts.gateway.getMyKey(opts.sessionCookie, opts.value.keyId);
|
|
3566
|
+
const modelIds = computeModelIds(key.budgets);
|
|
3567
|
+
await opts.credentials.set(API_KEY_REF, key.key.key);
|
|
3568
|
+
await opts.onApiKeyWritten?.();
|
|
3569
|
+
const routeModels = modelIds.length > 0 ? computeRouteModels(key.budgets, key.model_mapping ?? []) : void 0;
|
|
3570
|
+
await opts.piAi.applyAiRouterProfiles(
|
|
3571
|
+
routeModels === void 0 ? null : buildPiAiProviderProfiles({ baseURL: opts.baseURL, routeModels })
|
|
3572
|
+
);
|
|
3573
|
+
if (routeModels !== void 0) await opts.defaultModel?.ensure(routeModels);
|
|
3574
|
+
const displayName = opts.user.display_name?.trim() || void 0;
|
|
3575
|
+
const set2 = {
|
|
3576
|
+
...opts.username === void 0 ? {} : { username: opts.username },
|
|
3577
|
+
...displayName === void 0 ? {} : { displayName },
|
|
3578
|
+
lastLogin: {
|
|
3579
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3580
|
+
modelCount: modelIds.length,
|
|
3581
|
+
gatewayName: key.gateway_name ?? ""
|
|
3582
|
+
},
|
|
3583
|
+
budgets: computeBudgets(key.budgets),
|
|
3584
|
+
keys: computeKeys(keys)
|
|
3585
|
+
};
|
|
3586
|
+
const unset = [...opts.requestUnset ?? []];
|
|
3587
|
+
if (displayName === void 0) unset.push("displayName");
|
|
3588
|
+
if (key.key.enabled === false) {
|
|
3589
|
+
set2.lastError = { code: "KEY_DISABLED", message: "Key \u5F53\u524D\u88AB\u7981\u7528\uFF0C\u8C03\u7528\u4F1A\u5931\u8D25" };
|
|
3590
|
+
} else {
|
|
3591
|
+
unset.push("lastError");
|
|
3592
|
+
}
|
|
3593
|
+
await opts.state.patch({ set: set2, unset });
|
|
3594
|
+
await opts.ensureWorkspace?.();
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
// src/browser-login.ts
|
|
3598
|
+
var BROWSER_LOGIN_TTL_MS = 10 * 60 * 1e3;
|
|
3599
|
+
var DEVICE_ID_REF = "hiwork_device_id";
|
|
3600
|
+
var NO_BASE_URL_ERROR = {
|
|
3601
|
+
code: "GATEWAY_UNREACHABLE",
|
|
3602
|
+
message: "\u7F51\u5173\u5730\u5740\u672A\u914D\u7F6E\uFF0C\u65E0\u6CD5\u53D1\u8D77\u6D4F\u89C8\u5668\u767B\u5F55"
|
|
3603
|
+
};
|
|
3604
|
+
var pushedDeviceInfo;
|
|
3605
|
+
var pendingAuth;
|
|
3606
|
+
function setDeviceInfo(info) {
|
|
3607
|
+
const deviceId = typeof info.deviceId === "string" ? info.deviceId.trim() : "";
|
|
3608
|
+
if (deviceId === "") return;
|
|
3609
|
+
const deviceName = typeof info.deviceName === "string" ? info.deviceName.trim() : "";
|
|
3610
|
+
const appVersion = typeof info.appVersion === "string" ? info.appVersion.trim() : "";
|
|
3611
|
+
pushedDeviceInfo = {
|
|
3612
|
+
deviceId,
|
|
3613
|
+
...deviceName === "" ? {} : { deviceName },
|
|
3614
|
+
...appVersion === "" ? {} : { appVersion }
|
|
3615
|
+
};
|
|
3616
|
+
}
|
|
3617
|
+
function hostName() {
|
|
3618
|
+
try {
|
|
3619
|
+
return hostname().trim();
|
|
3620
|
+
} catch {
|
|
3621
|
+
return "";
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
async function resolveDeviceInfo(credentials) {
|
|
3625
|
+
const platform = process.platform;
|
|
3626
|
+
const appVersion = PKG_VERSION;
|
|
3627
|
+
const pushed = pushedDeviceInfo;
|
|
3628
|
+
if (pushed !== void 0) {
|
|
3629
|
+
const stored2 = await credentials.resolve(DEVICE_ID_REF).catch(() => void 0);
|
|
3630
|
+
if (stored2?.value.trim() !== pushed.deviceId) {
|
|
3631
|
+
await credentials.set(DEVICE_ID_REF, pushed.deviceId).catch(() => {
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
return { deviceId: pushed.deviceId, ...pushed.deviceName === void 0 ? {} : { deviceName: pushed.deviceName }, platform, appVersion };
|
|
3635
|
+
}
|
|
3636
|
+
const stored = await credentials.resolve(DEVICE_ID_REF).catch(() => void 0);
|
|
3637
|
+
const storedId = stored?.value.trim();
|
|
3638
|
+
if (storedId !== void 0 && storedId !== "") {
|
|
3639
|
+
return { deviceId: storedId, deviceName: hostName(), platform, appVersion };
|
|
3640
|
+
}
|
|
3641
|
+
const generated = randomUUID();
|
|
3642
|
+
await credentials.set(DEVICE_ID_REF, generated).catch(() => {
|
|
3643
|
+
});
|
|
3644
|
+
return { deviceId: generated, deviceName: hostName(), platform, appVersion };
|
|
3645
|
+
}
|
|
3646
|
+
function randomToken(bytes) {
|
|
3647
|
+
return randomBytes(bytes).toString("base64url");
|
|
3648
|
+
}
|
|
3649
|
+
function pkceChallenge(codeVerifier) {
|
|
3650
|
+
return createHash("sha256").update(codeVerifier).digest("base64url");
|
|
3651
|
+
}
|
|
3652
|
+
async function startBrowserLogin(opts) {
|
|
3653
|
+
const baseURL = opts.value.baseURL;
|
|
3654
|
+
if (baseURL === void 0 || baseURL === "") {
|
|
3655
|
+
await opts.state.patch({
|
|
3656
|
+
set: { lastError: NO_BASE_URL_ERROR },
|
|
3657
|
+
unset: ["browserLoginRequest", "browserLogin"]
|
|
3658
|
+
}).catch(() => {
|
|
3659
|
+
});
|
|
3660
|
+
return void 0;
|
|
3661
|
+
}
|
|
3662
|
+
const now = opts.now ?? Date.now;
|
|
3663
|
+
const startedAt = now();
|
|
3664
|
+
const codeVerifier = randomToken(32);
|
|
3665
|
+
const state = randomToken(16);
|
|
3666
|
+
const device = await resolveDeviceInfo(opts.credentials);
|
|
3667
|
+
pendingAuth = { state, codeVerifier, deviceId: device.deviceId, startedAt };
|
|
3668
|
+
const authorizeUrl = buildAuthorizeURL({
|
|
3669
|
+
baseURL,
|
|
3670
|
+
codeChallenge: pkceChallenge(codeVerifier),
|
|
3671
|
+
state,
|
|
3672
|
+
deviceId: device.deviceId,
|
|
3673
|
+
deviceName: device.deviceName ?? "",
|
|
3674
|
+
platform: device.platform ?? process.platform,
|
|
3675
|
+
appVersion: device.appVersion ?? PKG_VERSION
|
|
3676
|
+
});
|
|
3677
|
+
try {
|
|
3678
|
+
await opts.state.patch({
|
|
3679
|
+
set: { browserLogin: { authorizeUrl, startedAt: new Date(startedAt).toISOString() } },
|
|
3680
|
+
unset: ["browserLoginRequest", "lastError"]
|
|
3681
|
+
});
|
|
3682
|
+
} catch (error) {
|
|
3683
|
+
pendingAuth = void 0;
|
|
3684
|
+
throw error;
|
|
3685
|
+
}
|
|
3686
|
+
return { authorizeUrl, state };
|
|
3687
|
+
}
|
|
3688
|
+
async function failBrowserLogin(state, loginError) {
|
|
3689
|
+
await state.patch({
|
|
3690
|
+
set: { lastError: loginError },
|
|
3691
|
+
unset: ["browserLoginRequest", "browserLogin"]
|
|
3692
|
+
}).catch(() => {
|
|
3693
|
+
});
|
|
3694
|
+
return loginError;
|
|
3695
|
+
}
|
|
3696
|
+
async function reconcileStaleBrowserLogin(state, value) {
|
|
3697
|
+
if (value.browserLogin === void 0) return;
|
|
3698
|
+
await state.patch({
|
|
3699
|
+
set: { lastError: { code: "AUTH_FAILED", message: "\u4E0A\u6B21\u6D4F\u89C8\u5668\u6388\u6743\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" } },
|
|
3700
|
+
unset: ["browserLoginRequest", "browserLogin"]
|
|
3701
|
+
}).catch(() => {
|
|
3702
|
+
});
|
|
3703
|
+
}
|
|
3704
|
+
async function completeBrowserLogin(input, opts) {
|
|
3705
|
+
const now = opts.now ?? Date.now;
|
|
3706
|
+
const log = opts.log ?? (() => {
|
|
3707
|
+
});
|
|
3708
|
+
const pending = pendingAuth;
|
|
3709
|
+
if (pending === void 0) {
|
|
3710
|
+
log("hiwork-core: \u6536\u5230\u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u4F46\u6CA1\u6709\u5F85\u5B8C\u6210\u7684\u6388\u6743\u8BF7\u6C42\uFF0C\u5DF2\u5FFD\u7565");
|
|
3711
|
+
return null;
|
|
3712
|
+
}
|
|
3713
|
+
if (typeof input.state !== "string" || input.state !== pending.state) {
|
|
3714
|
+
log("hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03 state \u6821\u9A8C\u5931\u8D25\uFF08\u7591\u4F3C\u52AB\u6301\uFF09\uFF0C\u5DF2\u4E22\u5F03\u672C\u6B21\u56DE\u8C03");
|
|
3715
|
+
return null;
|
|
3716
|
+
}
|
|
3717
|
+
try {
|
|
3718
|
+
if (now() - pending.startedAt > BROWSER_LOGIN_TTL_MS) {
|
|
3719
|
+
log("hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5DF2\u8D85\u65F6\uFF08\u8D85\u8FC7 10 \u5206\u949F\uFF09\uFF0C\u5DF2\u62D2\u7EDD");
|
|
3720
|
+
return await failBrowserLogin(opts.state, { code: "AUTH_FAILED", message: "\u6388\u6743\u5DF2\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" });
|
|
3721
|
+
}
|
|
3722
|
+
if (typeof input.error === "string" && input.error !== "") {
|
|
3723
|
+
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u88AB\u62D2\u7EDD\uFF08${input.error.slice(0, 64)}\uFF09`);
|
|
3724
|
+
return await failBrowserLogin(opts.state, { code: "AUTH_FAILED", message: "\u6388\u6743\u88AB\u53D6\u6D88\u6216\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" });
|
|
3725
|
+
}
|
|
3726
|
+
if (typeof input.code !== "string" || input.code === "") {
|
|
3727
|
+
log("hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u7F3A\u5C11 code\uFF0C\u5DF2\u4E22\u5F03");
|
|
3728
|
+
return await failBrowserLogin(opts.state, { code: "AUTH_FAILED", message: "\u6388\u6743\u56DE\u8C03\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" });
|
|
3729
|
+
}
|
|
3730
|
+
const baseURL = opts.value.baseURL;
|
|
3731
|
+
if (baseURL === void 0 || baseURL === "") return await failBrowserLogin(opts.state, NO_BASE_URL_ERROR);
|
|
3732
|
+
const { sessionCookie, user } = await opts.gateway.exchangeCode({
|
|
3733
|
+
code: input.code,
|
|
3734
|
+
codeVerifier: pending.codeVerifier,
|
|
3735
|
+
deviceId: pending.deviceId
|
|
3736
|
+
});
|
|
3737
|
+
await opts.credentials.set(SESSION_REF, sessionCookie);
|
|
3738
|
+
const username = typeof user.username === "string" && user.username.trim() !== "" ? user.username.trim() : void 0;
|
|
3739
|
+
await provisionSession({
|
|
3740
|
+
gateway: opts.gateway,
|
|
3741
|
+
credentials: opts.credentials,
|
|
3742
|
+
piAi: opts.piAi,
|
|
3743
|
+
state: opts.state,
|
|
3744
|
+
baseURL,
|
|
3745
|
+
value: opts.value,
|
|
3746
|
+
sessionCookie,
|
|
3747
|
+
user,
|
|
3748
|
+
username,
|
|
3749
|
+
requestUnset: ["browserLoginRequest", "browserLogin"],
|
|
3750
|
+
...opts.defaultModel === void 0 ? {} : { defaultModel: opts.defaultModel },
|
|
3751
|
+
...opts.ensureWorkspace === void 0 ? {} : { ensureWorkspace: opts.ensureWorkspace }
|
|
3752
|
+
});
|
|
3753
|
+
return null;
|
|
3754
|
+
} catch (error) {
|
|
3755
|
+
const loginError = toLoginError(error, "\u65E0\u6CD5\u8FDE\u63A5\u7F51\u5173\uFF0C\u8BF7\u68C0\u67E5 baseURL \u6216\u7F51\u7EDC");
|
|
3756
|
+
return await failBrowserLogin(opts.state, loginError);
|
|
3757
|
+
} finally {
|
|
3758
|
+
pendingAuth = void 0;
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
// src/client-policy.ts
|
|
3763
|
+
var DEFAULT_LOGIN_MODE = "both";
|
|
3764
|
+
var CLIENT_POLICY_PATH = "/api/client/policy";
|
|
3765
|
+
var CLIENT_POLICY_TIMEOUT_MS = 5e3;
|
|
3766
|
+
function buildClientPolicyURL(opts) {
|
|
3767
|
+
const baseURL = typeof opts.baseURL === "string" ? opts.baseURL.trim().replace(/\/+$/, "") : "";
|
|
3768
|
+
if (baseURL === "") return void 0;
|
|
3769
|
+
const params = [
|
|
3770
|
+
["platform", opts.platform],
|
|
3771
|
+
["arch", opts.arch],
|
|
3772
|
+
["version", opts.version],
|
|
3773
|
+
["device_id", opts.deviceId]
|
|
3774
|
+
];
|
|
3775
|
+
const query = params.filter(([, value]) => typeof value === "string" && value !== "").map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
|
|
3776
|
+
return query === "" ? `${baseURL}${CLIENT_POLICY_PATH}` : `${baseURL}${CLIENT_POLICY_PATH}?${query}`;
|
|
3777
|
+
}
|
|
3778
|
+
function parseLoginMode(payload) {
|
|
3779
|
+
if (typeof payload !== "object" || payload === null) return void 0;
|
|
3780
|
+
const value = payload.loginMode;
|
|
3781
|
+
return value === "browser" || value === "form" || value === "both" ? value : void 0;
|
|
3782
|
+
}
|
|
3783
|
+
async function fetchClientPolicy(opts) {
|
|
3784
|
+
const url = buildClientPolicyURL(opts);
|
|
3785
|
+
if (url === void 0) return void 0;
|
|
3786
|
+
try {
|
|
3787
|
+
const res = await fetch(url, {
|
|
3788
|
+
method: "GET",
|
|
3789
|
+
headers: { "user-agent": `hiwork-core/${PKG_VERSION}`, accept: "application/json" },
|
|
3790
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? CLIENT_POLICY_TIMEOUT_MS)
|
|
3791
|
+
});
|
|
3792
|
+
if (res.status !== 200) return void 0;
|
|
3793
|
+
let payload;
|
|
3794
|
+
try {
|
|
3795
|
+
payload = await res.json();
|
|
3796
|
+
} catch {
|
|
3797
|
+
return void 0;
|
|
3798
|
+
}
|
|
3799
|
+
const loginMode = parseLoginMode(payload);
|
|
3800
|
+
return loginMode === void 0 ? void 0 : { loginMode };
|
|
3801
|
+
} catch {
|
|
3802
|
+
return void 0;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
// src/default-model.ts
|
|
3807
|
+
var DEFAULT_MODEL_NS = "agent-default-model";
|
|
3808
|
+
async function ensureDefaultModel(routeModels, ports) {
|
|
3809
|
+
if (!ports.available()) return void 0;
|
|
3810
|
+
const current = ports.read();
|
|
3811
|
+
if (current !== void 0 && await routable(current, routeModels, ports)) return void 0;
|
|
3812
|
+
const next = pickFallbackDefaultModel(routeModels);
|
|
3813
|
+
if (next === void 0) return void 0;
|
|
3814
|
+
await ports.write(next);
|
|
3815
|
+
return next;
|
|
3816
|
+
}
|
|
3817
|
+
async function routable(selection, routeModels, ports) {
|
|
3818
|
+
const models = routeModels[selection.provider];
|
|
3819
|
+
if (models !== void 0) return models.some((model) => model.id === selection.model);
|
|
3820
|
+
return ports.isRoutableElsewhere(selection);
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
// src/default-workspace.ts
|
|
3824
|
+
import { mkdir } from "node:fs/promises";
|
|
3825
|
+
import { homedir } from "node:os";
|
|
3826
|
+
import { join } from "node:path";
|
|
3827
|
+
var DEFAULT_WORKSPACE_DIR = join(homedir(), "HiWork", "Default");
|
|
3828
|
+
var DEFAULT_WORKSPACE_TITLE = "\u9ED8\u8BA4\u5DE5\u4F5C\u533A";
|
|
3829
|
+
async function ensureDefaultWorkspace(registry, warn, dir = DEFAULT_WORKSPACE_DIR) {
|
|
3830
|
+
try {
|
|
3831
|
+
if (registry.list().length > 0) return;
|
|
3832
|
+
await mkdir(dir, { recursive: true });
|
|
3833
|
+
await registry.create(dir, DEFAULT_WORKSPACE_TITLE);
|
|
3834
|
+
} catch (error) {
|
|
3835
|
+
warn(`hiwork-core: ensure default workspace failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
// src/daily-rpc.ts
|
|
3840
|
+
var DAILY_RPC_CHANNEL = "/hiwork-daily";
|
|
3841
|
+
var DAILY_RPC_ENDPOINTS = ["today", "random"];
|
|
3842
|
+
var DailyRequestError = class extends Error {
|
|
3843
|
+
constructor(code, message) {
|
|
3844
|
+
super(message);
|
|
3845
|
+
this.code = code;
|
|
3846
|
+
this.name = "DailyRequestError";
|
|
3847
|
+
}
|
|
3848
|
+
};
|
|
3849
|
+
var DAILY_MESSAGE_FALLBACK = {
|
|
3850
|
+
SESSION_EXPIRED: "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55",
|
|
3851
|
+
GATEWAY_UNREACHABLE: "\u7F51\u5173\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
3852
|
+
RATE_LIMITED: "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
|
|
3853
|
+
NOT_FOUND: "\u7F51\u5173\u672A\u627E\u5230\u5BF9\u5E94\u7684\u6570\u636E",
|
|
3854
|
+
AUTH_FAILED: "\u767B\u5F55\u72B6\u6001\u5F02\u5E38\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55"
|
|
3855
|
+
};
|
|
3856
|
+
var DAILY_GENERIC_FAILURE_MESSAGE = "\u6BCF\u65E5\u4E00\u53E5\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
3857
|
+
var DAILY_SESSION_EXPIRED_MESSAGE = "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55";
|
|
3858
|
+
function displayDailyGatewayMessage(code, message) {
|
|
3859
|
+
const text = typeof message === "string" ? message.trim() : "";
|
|
3860
|
+
if (text === "") return DAILY_MESSAGE_FALLBACK[code] ?? DAILY_GENERIC_FAILURE_MESSAGE;
|
|
3861
|
+
const leaksImplementationDetail = /https?:\/\//i.test(text) || /\bHTTP\s*\d{3}\b/i.test(text) || /\bbaseURL\b/i.test(text);
|
|
3862
|
+
if (leaksImplementationDetail) return DAILY_MESSAGE_FALLBACK[code] ?? DAILY_GENERIC_FAILURE_MESSAGE;
|
|
3863
|
+
return text;
|
|
3864
|
+
}
|
|
3865
|
+
function parseDailyPayload(endpoint, raw) {
|
|
3866
|
+
if (raw === void 0 || raw === null) return {};
|
|
3867
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
3868
|
+
throw new DailyRequestError("bad-request", "\u6BCF\u65E5\u4E00\u53E5\u8BF7\u6C42\u53C2\u6570\u4E0D\u5408\u6CD5\u3002");
|
|
3869
|
+
}
|
|
3870
|
+
const record = raw;
|
|
3871
|
+
const keys = Object.keys(record);
|
|
3872
|
+
if (endpoint === "today") {
|
|
3873
|
+
if (keys.length > 0) throw new DailyRequestError("bad-request", `\u6BCF\u65E5\u4E00\u53E5\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${keys[0]}'\u3002`);
|
|
3874
|
+
return {};
|
|
3875
|
+
}
|
|
3876
|
+
for (const key of keys) {
|
|
3877
|
+
if (key !== "excludeId") throw new DailyRequestError("bad-request", `\u6BCF\u65E5\u4E00\u53E5\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${key}'\u3002`);
|
|
3878
|
+
}
|
|
3879
|
+
const payload = {};
|
|
3880
|
+
const rawExcludeId = record["excludeId"];
|
|
3881
|
+
if (rawExcludeId !== void 0) {
|
|
3882
|
+
if (typeof rawExcludeId !== "number" || !Number.isInteger(rawExcludeId) || rawExcludeId <= 0) {
|
|
3883
|
+
throw new DailyRequestError("bad-request", "\u6BCF\u65E5\u4E00\u53E5\u8BF7\u6C42\u7684 excludeId \u5FC5\u987B\u662F\u6B63\u6574\u6570\u3002");
|
|
3884
|
+
}
|
|
3885
|
+
payload.excludeId = rawExcludeId;
|
|
3886
|
+
}
|
|
3887
|
+
return payload;
|
|
3888
|
+
}
|
|
3889
|
+
function isDailyRpcEndpoint(endpoint) {
|
|
3890
|
+
return DAILY_RPC_ENDPOINTS.includes(endpoint);
|
|
3891
|
+
}
|
|
3892
|
+
function isAborted(signal) {
|
|
3893
|
+
return signal?.aborted === true;
|
|
3894
|
+
}
|
|
3895
|
+
async function handleDailyEndpoint(endpoint, rawPayload, deps, signal) {
|
|
3896
|
+
try {
|
|
3897
|
+
if (isAborted(signal)) return failure("cancelled", "\u6BCF\u65E5\u4E00\u53E5\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
3898
|
+
if (!isDailyRpcEndpoint(endpoint)) {
|
|
3899
|
+
throw new DailyRequestError("bad-request", `\u672A\u77E5\u7684\u6BCF\u65E5\u4E00\u53E5\u7AEF\u70B9 '${endpoint}'`);
|
|
3900
|
+
}
|
|
3901
|
+
const payload = parseDailyPayload(endpoint, rawPayload);
|
|
3902
|
+
const sessionCookie = await deps.resolveSession();
|
|
3903
|
+
if (sessionCookie === void 0 || sessionCookie === "") {
|
|
3904
|
+
return failure("SESSION_EXPIRED", DAILY_SESSION_EXPIRED_MESSAGE);
|
|
3905
|
+
}
|
|
3906
|
+
const gateway = deps.gateway();
|
|
3907
|
+
if (gateway === void 0) {
|
|
3908
|
+
return failure("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5730\u5740\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u7F51\u5173\u6A21\u578B\u300D\u91CC\u5B8C\u6210\u767B\u5F55");
|
|
3909
|
+
}
|
|
3910
|
+
if (isAborted(signal)) return failure("cancelled", "\u6BCF\u65E5\u4E00\u53E5\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
3911
|
+
const value = await callEndpoint(gateway, sessionCookie, endpoint, payload);
|
|
3912
|
+
return { ok: true, value };
|
|
3913
|
+
} catch (error) {
|
|
3914
|
+
if (error instanceof DailyRequestError) return failure(error.code, error.message);
|
|
3915
|
+
if (error instanceof GatewayError) {
|
|
3916
|
+
return failure(error.code, displayDailyGatewayMessage(error.code, error.message));
|
|
3917
|
+
}
|
|
3918
|
+
deps.log?.(`hiwork-core: \u6BCF\u65E5\u4E00\u53E5 RPC '${endpoint}' \u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
3919
|
+
return failure("internal", DAILY_GENERIC_FAILURE_MESSAGE);
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
async function callEndpoint(gateway, sessionCookie, endpoint, payload) {
|
|
3923
|
+
switch (endpoint) {
|
|
3924
|
+
case "today":
|
|
3925
|
+
return await gateway.getMyDailyQuote(sessionCookie);
|
|
3926
|
+
case "random":
|
|
3927
|
+
return await gateway.getMyDailyQuoteRandom(sessionCookie, payload.excludeId);
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
function failure(code, message) {
|
|
3931
|
+
return { ok: false, error: { code, message, details: {} } };
|
|
3932
|
+
}
|
|
3933
|
+
function registerDailyRpc(ctx, deps) {
|
|
3934
|
+
const handler = (endpoint, payload, signal) => handleDailyEndpoint(endpoint, payload, deps, signal);
|
|
3935
|
+
const remove = ctx.connection.rpc.handle(DAILY_RPC_CHANNEL, handler, { authority: "loopback" });
|
|
3936
|
+
return async () => {
|
|
3937
|
+
await remove();
|
|
3938
|
+
};
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
// src/orchestration.ts
|
|
3359
3942
|
async function runLogin(opts) {
|
|
3360
3943
|
const pending = opts.value.pendingLogin;
|
|
3361
3944
|
const baseURL = opts.value.baseURL;
|
|
@@ -3369,40 +3952,23 @@ async function runLogin(opts) {
|
|
|
3369
3952
|
try {
|
|
3370
3953
|
const password = await opts.credentials.resolve(pending.passwordRef);
|
|
3371
3954
|
if (password === void 0) throw new GatewayError("AUTH_FAILED", "\u767B\u5F55\u51ED\u636E\u7F3A\u5931\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55");
|
|
3372
|
-
const
|
|
3955
|
+
const device = await resolveDeviceInfo(opts.credentials);
|
|
3956
|
+
const { sessionCookie, user } = await opts.gateway.login(pending.username, password.value, device);
|
|
3373
3957
|
await opts.credentials.set(SESSION_REF, sessionCookie);
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
else await opts.credentials.unset(REMEMBERED_PASSWORD_REF).catch(() => {
|
|
3380
|
-
});
|
|
3381
|
-
if (pending.passwordRef !== REMEMBERED_PASSWORD_REF) await opts.credentials.unset(pending.passwordRef);
|
|
3382
|
-
await opts.piAi.applyAiRouterProfiles(
|
|
3383
|
-
modelIds.length > 0 ? buildPiAiProviderProfiles({ baseURL, routeModels: computeRouteModels(key.budgets, key.model_mapping ?? []) }) : null
|
|
3384
|
-
);
|
|
3385
|
-
const displayName = user.display_name?.trim() || void 0;
|
|
3386
|
-
const set2 = {
|
|
3958
|
+
await provisionSession({
|
|
3959
|
+
...opts,
|
|
3960
|
+
baseURL,
|
|
3961
|
+
sessionCookie,
|
|
3962
|
+
user,
|
|
3387
3963
|
username: pending.username,
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
};
|
|
3397
|
-
const unset = ["pendingLogin"];
|
|
3398
|
-
if (displayName === void 0) unset.push("displayName");
|
|
3399
|
-
if (key.key.enabled === false) {
|
|
3400
|
-
set2.lastError = { code: "KEY_DISABLED", message: "Key \u5F53\u524D\u88AB\u7981\u7528\uFF0C\u8C03\u7528\u4F1A\u5931\u8D25" };
|
|
3401
|
-
} else {
|
|
3402
|
-
unset.push("lastError");
|
|
3403
|
-
}
|
|
3404
|
-
await opts.state.patch({ set: set2, unset });
|
|
3405
|
-
await opts.ensureWorkspace?.();
|
|
3964
|
+
requestUnset: ["pendingLogin"],
|
|
3965
|
+
onApiKeyWritten: async () => {
|
|
3966
|
+
if (opts.value.rememberPassword === true) await opts.credentials.set(REMEMBERED_PASSWORD_REF, password.value);
|
|
3967
|
+
else await opts.credentials.unset(REMEMBERED_PASSWORD_REF).catch(() => {
|
|
3968
|
+
});
|
|
3969
|
+
if (pending.passwordRef !== REMEMBERED_PASSWORD_REF) await opts.credentials.unset(pending.passwordRef);
|
|
3970
|
+
}
|
|
3971
|
+
});
|
|
3406
3972
|
return null;
|
|
3407
3973
|
} catch (error) {
|
|
3408
3974
|
const loginError = toLoginError(error, "\u65E0\u6CD5\u8FDE\u63A5\u7F51\u5173\uFF0C\u8BF7\u68C0\u67E5 baseURL \u6216\u7F51\u7EDC");
|
|
@@ -3430,9 +3996,11 @@ async function runRefresh(opts) {
|
|
|
3430
3996
|
const key = await opts.gateway.getMyKey(session.value, opts.value.keyId);
|
|
3431
3997
|
const modelIds = computeModelIds(key.budgets);
|
|
3432
3998
|
await opts.credentials.set(API_KEY_REF, key.key.key);
|
|
3999
|
+
const routeModels = modelIds.length > 0 ? computeRouteModels(key.budgets, key.model_mapping ?? []) : void 0;
|
|
3433
4000
|
await opts.piAi.applyAiRouterProfiles(
|
|
3434
|
-
|
|
4001
|
+
routeModels === void 0 ? null : buildPiAiProviderProfiles({ baseURL, routeModels })
|
|
3435
4002
|
);
|
|
4003
|
+
if (routeModels !== void 0) await opts.defaultModel?.ensure(routeModels);
|
|
3436
4004
|
const set2 = {
|
|
3437
4005
|
lastLogin: {
|
|
3438
4006
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3482,12 +4050,31 @@ async function runLogout(opts) {
|
|
|
3482
4050
|
});
|
|
3483
4051
|
await opts.piAi.applyAiRouterProfiles(null);
|
|
3484
4052
|
await opts.state.patch({
|
|
3485
|
-
unset: [
|
|
4053
|
+
unset: [
|
|
4054
|
+
"pendingLogin",
|
|
4055
|
+
"refreshRequest",
|
|
4056
|
+
"logoutRequest",
|
|
4057
|
+
"browserLoginRequest",
|
|
4058
|
+
"browserLogin",
|
|
4059
|
+
"username",
|
|
4060
|
+
"displayName",
|
|
4061
|
+
"lastLogin",
|
|
4062
|
+
"lastError",
|
|
4063
|
+
"keyId",
|
|
4064
|
+
"budgets",
|
|
4065
|
+
"keys"
|
|
4066
|
+
]
|
|
3486
4067
|
});
|
|
3487
4068
|
}
|
|
4069
|
+
async function handleBrowserLoginRequest(opts) {
|
|
4070
|
+
const started = await startBrowserLogin(opts);
|
|
4071
|
+
if (started !== void 0) return null;
|
|
4072
|
+
return NO_BASE_URL_ERROR;
|
|
4073
|
+
}
|
|
3488
4074
|
async function handleAiRouterSectionChange(opts) {
|
|
3489
4075
|
const v = opts.value;
|
|
3490
4076
|
if (v.pendingLogin !== void 0) return runLogin(opts);
|
|
4077
|
+
if (v.browserLoginRequest !== void 0) return handleBrowserLoginRequest(opts);
|
|
3491
4078
|
if (v.refreshRequest !== void 0) return runRefresh(opts);
|
|
3492
4079
|
if (v.logoutRequest !== void 0) {
|
|
3493
4080
|
await runLogout(opts);
|
|
@@ -3496,9 +4083,269 @@ async function handleAiRouterSectionChange(opts) {
|
|
|
3496
4083
|
return null;
|
|
3497
4084
|
}
|
|
3498
4085
|
|
|
4086
|
+
// src/usage-rpc.ts
|
|
4087
|
+
var USAGE_RPC_CHANNEL = "/hiwork-usage";
|
|
4088
|
+
var USAGE_RPC_ENDPOINTS = ["summary", "trends", "model-trends", "recharges", "keys", "key", "estimate"];
|
|
4089
|
+
var USAGE_RPC_RANGES = ["today", "yesterday", "24h", "7d", "30d", "mtd"];
|
|
4090
|
+
var USAGE_ESTIMATE_MAX_ITEMS = 50;
|
|
4091
|
+
var UsageRequestError = class extends Error {
|
|
4092
|
+
constructor(code, message) {
|
|
4093
|
+
super(message);
|
|
4094
|
+
this.code = code;
|
|
4095
|
+
this.name = "UsageRequestError";
|
|
4096
|
+
}
|
|
4097
|
+
};
|
|
4098
|
+
var GATEWAY_MESSAGE_FALLBACK = {
|
|
4099
|
+
SESSION_EXPIRED: "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55",
|
|
4100
|
+
GATEWAY_UNREACHABLE: "\u7F51\u5173\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
4101
|
+
NO_KEY_ASSIGNED: "\u5F53\u524D\u8D26\u53F7\u5C1A\u672A\u88AB\u5206\u914D\u865A\u62DF KEY\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
|
|
4102
|
+
KEY_INVALID: "\u6240\u9009 KEY \u65E0\u6548\u6216\u5DF2\u88AB\u5220\u9664\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9 KEY",
|
|
4103
|
+
RATE_LIMITED: "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
|
|
4104
|
+
NOT_FOUND: "\u7F51\u5173\u672A\u627E\u5230\u5BF9\u5E94\u7684\u6570\u636E",
|
|
4105
|
+
AUTH_FAILED: "\u767B\u5F55\u72B6\u6001\u5F02\u5E38\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55"
|
|
4106
|
+
};
|
|
4107
|
+
var USAGE_GENERIC_FAILURE_MESSAGE = "\u7528\u91CF\u6570\u636E\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
4108
|
+
var USAGE_SESSION_EXPIRED_MESSAGE = "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55";
|
|
4109
|
+
function displayGatewayMessage(code, message) {
|
|
4110
|
+
const text = typeof message === "string" ? message.trim() : "";
|
|
4111
|
+
if (text === "") return GATEWAY_MESSAGE_FALLBACK[code] ?? USAGE_GENERIC_FAILURE_MESSAGE;
|
|
4112
|
+
const leaksImplementationDetail = /https?:\/\//i.test(text) || /\bHTTP\s*\d{3}\b/i.test(text) || /\bbaseURL\b/i.test(text);
|
|
4113
|
+
if (leaksImplementationDetail) return GATEWAY_MESSAGE_FALLBACK[code] ?? USAGE_GENERIC_FAILURE_MESSAGE;
|
|
4114
|
+
return text;
|
|
4115
|
+
}
|
|
4116
|
+
function parseUsagePayload(raw) {
|
|
4117
|
+
if (raw === void 0 || raw === null) return {};
|
|
4118
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
4119
|
+
throw new UsageRequestError("bad-request", "\u7528\u91CF\u8BF7\u6C42\u53C2\u6570\u4E0D\u5408\u6CD5\u3002");
|
|
4120
|
+
}
|
|
4121
|
+
const record = raw;
|
|
4122
|
+
for (const key of Object.keys(record)) {
|
|
4123
|
+
if (key !== "keyId" && key !== "range") {
|
|
4124
|
+
throw new UsageRequestError("bad-request", `\u7528\u91CF\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${key}'\u3002`);
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
const payload = {};
|
|
4128
|
+
const rawKeyId = record["keyId"];
|
|
4129
|
+
if (rawKeyId !== void 0) {
|
|
4130
|
+
if (typeof rawKeyId !== "string") throw new UsageRequestError("bad-request", "\u7528\u91CF\u8BF7\u6C42\u7684 keyId \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002");
|
|
4131
|
+
const keyId = rawKeyId.trim();
|
|
4132
|
+
if (keyId !== "") payload.keyId = keyId;
|
|
4133
|
+
}
|
|
4134
|
+
const rawRange = record["range"];
|
|
4135
|
+
if (rawRange !== void 0) {
|
|
4136
|
+
if (typeof rawRange !== "string" || !USAGE_RPC_RANGES.includes(rawRange)) {
|
|
4137
|
+
throw new UsageRequestError("bad-request", `\u7528\u91CF\u8BF7\u6C42\u7684 range \u4E0D\u5408\u6CD5\uFF08\u53EF\u7528\u503C\uFF1A${USAGE_RPC_RANGES.join(" / ")}\uFF09\u3002`);
|
|
4138
|
+
}
|
|
4139
|
+
payload.range = rawRange;
|
|
4140
|
+
}
|
|
4141
|
+
return payload;
|
|
4142
|
+
}
|
|
4143
|
+
function isUsageRpcEndpoint(endpoint) {
|
|
4144
|
+
return USAGE_RPC_ENDPOINTS.includes(endpoint);
|
|
4145
|
+
}
|
|
4146
|
+
var ESTIMATE_ITEM_KEYS = [
|
|
4147
|
+
"route",
|
|
4148
|
+
"model",
|
|
4149
|
+
"input_tokens",
|
|
4150
|
+
"output_tokens",
|
|
4151
|
+
"cache_read_tokens",
|
|
4152
|
+
"cache_write_tokens",
|
|
4153
|
+
"at"
|
|
4154
|
+
];
|
|
4155
|
+
function parseEstimatePayload(raw) {
|
|
4156
|
+
if (raw === void 0 || raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
4157
|
+
throw new UsageRequestError("bad-request", "\u8BA1\u4EF7\u8BF7\u6C42\u53C2\u6570\u4E0D\u5408\u6CD5\u3002");
|
|
4158
|
+
}
|
|
4159
|
+
const record = raw;
|
|
4160
|
+
for (const key of Object.keys(record)) {
|
|
4161
|
+
if (key !== "items") throw new UsageRequestError("bad-request", `\u8BA1\u4EF7\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${key}'\u3002`);
|
|
4162
|
+
}
|
|
4163
|
+
const rawItems = record["items"];
|
|
4164
|
+
if (!Array.isArray(rawItems)) throw new UsageRequestError("bad-request", "\u8BA1\u4EF7\u8BF7\u6C42\u7684 items \u5FC5\u987B\u662F\u6570\u7EC4\u3002");
|
|
4165
|
+
if (rawItems.length === 0) throw new UsageRequestError("bad-request", "\u8BA1\u4EF7\u8BF7\u6C42\u7684 items \u4E0D\u80FD\u4E3A\u7A7A\u3002");
|
|
4166
|
+
return rawItems.map((item, index) => parseEstimateItem(item, index));
|
|
4167
|
+
}
|
|
4168
|
+
function parseEstimateItem(raw, index) {
|
|
4169
|
+
const position = index + 1;
|
|
4170
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
4171
|
+
throw new UsageRequestError("bad-request", `\u8BA1\u4EF7\u8BF7\u6C42\u7B2C ${position} \u9879\u5FC5\u987B\u662F\u5BF9\u8C61\u3002`);
|
|
4172
|
+
}
|
|
4173
|
+
const record = raw;
|
|
4174
|
+
for (const key of Object.keys(record)) {
|
|
4175
|
+
if (!ESTIMATE_ITEM_KEYS.includes(key)) {
|
|
4176
|
+
throw new UsageRequestError("bad-request", `\u8BA1\u4EF7\u8BF7\u6C42\u7B2C ${position} \u9879\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${key}'\u3002`);
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
const item = {
|
|
4180
|
+
route: requiredText(record["route"], position, "route"),
|
|
4181
|
+
model: requiredText(record["model"], position, "model"),
|
|
4182
|
+
input_tokens: tokenCount(record["input_tokens"], position, "input_tokens"),
|
|
4183
|
+
output_tokens: tokenCount(record["output_tokens"], position, "output_tokens")
|
|
4184
|
+
};
|
|
4185
|
+
const cacheRead = optionalTokenCount(record["cache_read_tokens"], position, "cache_read_tokens");
|
|
4186
|
+
if (cacheRead !== void 0) item.cache_read_tokens = cacheRead;
|
|
4187
|
+
const cacheWrite = optionalTokenCount(record["cache_write_tokens"], position, "cache_write_tokens");
|
|
4188
|
+
if (cacheWrite !== void 0) item.cache_write_tokens = cacheWrite;
|
|
4189
|
+
const at = record["at"];
|
|
4190
|
+
if (at !== void 0) {
|
|
4191
|
+
if (typeof at !== "string" || !isRfc3339(at)) {
|
|
4192
|
+
throw new UsageRequestError("bad-request", `\u8BA1\u4EF7\u8BF7\u6C42\u7B2C ${position} \u9879\u7684 at \u5FC5\u987B\u662F RFC3339 \u65F6\u95F4\u3002`);
|
|
4193
|
+
}
|
|
4194
|
+
item.at = at;
|
|
4195
|
+
}
|
|
4196
|
+
return item;
|
|
4197
|
+
}
|
|
4198
|
+
function isRfc3339(value) {
|
|
4199
|
+
const match = RFC3339_PATTERN.exec(value);
|
|
4200
|
+
if (match === null) return false;
|
|
4201
|
+
const field = (index) => Number(match[index] ?? Number.NaN);
|
|
4202
|
+
const year = field(1);
|
|
4203
|
+
const month = field(2);
|
|
4204
|
+
const day = field(3);
|
|
4205
|
+
const hour = field(4);
|
|
4206
|
+
const minute = field(5);
|
|
4207
|
+
const second = field(6);
|
|
4208
|
+
if (![year, month, day, hour, minute, second].every((part) => Number.isInteger(part))) return false;
|
|
4209
|
+
if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) return false;
|
|
4210
|
+
if (hour > 23 || minute > 59 || second > 59) return false;
|
|
4211
|
+
if (match[7] !== void 0) {
|
|
4212
|
+
const offsetHour = field(8);
|
|
4213
|
+
const offsetMinute = field(9);
|
|
4214
|
+
if (!Number.isInteger(offsetHour) || !Number.isInteger(offsetMinute)) return false;
|
|
4215
|
+
if (offsetHour > 23 || offsetMinute > 59) return false;
|
|
4216
|
+
}
|
|
4217
|
+
return true;
|
|
4218
|
+
}
|
|
4219
|
+
var RFC3339_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|([+-])(\d{2}):(\d{2}))$/;
|
|
4220
|
+
function daysInMonth(year, month) {
|
|
4221
|
+
if (month === 2) return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
|
|
4222
|
+
return [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] ?? 0;
|
|
4223
|
+
}
|
|
4224
|
+
function requiredText(value, position, field) {
|
|
4225
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
4226
|
+
throw new UsageRequestError("bad-request", `\u8BA1\u4EF7\u8BF7\u6C42\u7B2C ${position} \u9879\u7684 ${field} \u4E0D\u80FD\u4E3A\u7A7A\u3002`);
|
|
4227
|
+
}
|
|
4228
|
+
return value.trim();
|
|
4229
|
+
}
|
|
4230
|
+
function tokenCount(value, position, field) {
|
|
4231
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
4232
|
+
throw new UsageRequestError("bad-request", `\u8BA1\u4EF7\u8BF7\u6C42\u7B2C ${position} \u9879\u7684 ${field} \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570\u3002`);
|
|
4233
|
+
}
|
|
4234
|
+
return value;
|
|
4235
|
+
}
|
|
4236
|
+
function optionalTokenCount(value, position, field) {
|
|
4237
|
+
return value === void 0 ? void 0 : tokenCount(value, position, field);
|
|
4238
|
+
}
|
|
4239
|
+
function unpriced(reason) {
|
|
4240
|
+
return { cost: 0, priced: false, channelResolved: false, reason };
|
|
4241
|
+
}
|
|
4242
|
+
function summarizeEstimate(results, expected) {
|
|
4243
|
+
if (results.length !== expected) return unpriced("incomplete");
|
|
4244
|
+
let cost = 0;
|
|
4245
|
+
let channelResolved = true;
|
|
4246
|
+
for (const result of results) {
|
|
4247
|
+
if (result?.priced !== true) return unpriced(result?.reason ?? "unpriced");
|
|
4248
|
+
const value = result.cost;
|
|
4249
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return unpriced("invalid_cost");
|
|
4250
|
+
cost += value;
|
|
4251
|
+
if (result.channel_resolved !== true) channelResolved = false;
|
|
4252
|
+
}
|
|
4253
|
+
return { cost, priced: true, channelResolved };
|
|
4254
|
+
}
|
|
4255
|
+
async function handleEstimateEndpoint(items, deps, signal) {
|
|
4256
|
+
const sessionCookie = await deps.resolveSession();
|
|
4257
|
+
if (sessionCookie === void 0 || sessionCookie === "") return { ok: true, value: unpriced("no_session") };
|
|
4258
|
+
if (isAborted2(signal)) return failure2("cancelled", "\u7528\u91CF\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4259
|
+
if (items.length > USAGE_ESTIMATE_MAX_ITEMS) return { ok: true, value: unpriced("too_many_items") };
|
|
4260
|
+
const gatewayItems = [];
|
|
4261
|
+
for (const item of items) {
|
|
4262
|
+
const protocol = routeToProtocol(item.route);
|
|
4263
|
+
if (protocol === void 0) continue;
|
|
4264
|
+
gatewayItems.push({
|
|
4265
|
+
route: protocol,
|
|
4266
|
+
model: item.model,
|
|
4267
|
+
input_tokens: item.input_tokens,
|
|
4268
|
+
output_tokens: item.output_tokens,
|
|
4269
|
+
...item.cache_read_tokens === void 0 ? {} : { cache_read_tokens: item.cache_read_tokens },
|
|
4270
|
+
...item.cache_write_tokens === void 0 ? {} : { cache_write_tokens: item.cache_write_tokens },
|
|
4271
|
+
...item.at === void 0 ? {} : { at: item.at }
|
|
4272
|
+
});
|
|
4273
|
+
}
|
|
4274
|
+
if (gatewayItems.length === 0) return { ok: true, value: unpriced("no_route") };
|
|
4275
|
+
const gateway = deps.gateway();
|
|
4276
|
+
if (gateway === void 0) {
|
|
4277
|
+
return failure2("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5730\u5740\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u7F51\u5173\u6A21\u578B\u300D\u91CC\u5B8C\u6210\u767B\u5F55");
|
|
4278
|
+
}
|
|
4279
|
+
if (isAborted2(signal)) return failure2("cancelled", "\u7528\u91CF\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4280
|
+
const response = await gateway.estimateCost(sessionCookie, gatewayItems);
|
|
4281
|
+
const results = response?.items;
|
|
4282
|
+
if (!Array.isArray(results)) {
|
|
4283
|
+
deps.log?.("hiwork-core: \u8BA1\u4EF7 RPC \u6536\u5230\u65E0\u6548\u54CD\u5E94\uFF08items \u4E0D\u662F\u6570\u7EC4\uFF09");
|
|
4284
|
+
return failure2("internal", USAGE_GENERIC_FAILURE_MESSAGE);
|
|
4285
|
+
}
|
|
4286
|
+
return { ok: true, value: summarizeEstimate(results, gatewayItems.length) };
|
|
4287
|
+
}
|
|
4288
|
+
function isAborted2(signal) {
|
|
4289
|
+
return signal?.aborted === true;
|
|
4290
|
+
}
|
|
4291
|
+
async function handleUsageEndpoint(endpoint, rawPayload, deps, signal) {
|
|
4292
|
+
try {
|
|
4293
|
+
if (isAborted2(signal)) return failure2("cancelled", "\u7528\u91CF\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4294
|
+
if (!isUsageRpcEndpoint(endpoint)) {
|
|
4295
|
+
throw new UsageRequestError("bad-request", `\u672A\u77E5\u7684\u7528\u91CF\u7AEF\u70B9 '${endpoint}'`);
|
|
4296
|
+
}
|
|
4297
|
+
if (endpoint === "estimate") return await handleEstimateEndpoint(parseEstimatePayload(rawPayload), deps, signal);
|
|
4298
|
+
const payload = parseUsagePayload(rawPayload);
|
|
4299
|
+
const sessionCookie = await deps.resolveSession();
|
|
4300
|
+
if (sessionCookie === void 0 || sessionCookie === "") {
|
|
4301
|
+
return failure2("SESSION_EXPIRED", USAGE_SESSION_EXPIRED_MESSAGE);
|
|
4302
|
+
}
|
|
4303
|
+
const gateway = deps.gateway();
|
|
4304
|
+
if (gateway === void 0) {
|
|
4305
|
+
return failure2("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5730\u5740\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u7F51\u5173\u6A21\u578B\u300D\u91CC\u5B8C\u6210\u767B\u5F55");
|
|
4306
|
+
}
|
|
4307
|
+
if (isAborted2(signal)) return failure2("cancelled", "\u7528\u91CF\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4308
|
+
const value = await callEndpoint2(gateway, sessionCookie, endpoint, payload);
|
|
4309
|
+
return { ok: true, value };
|
|
4310
|
+
} catch (error) {
|
|
4311
|
+
if (error instanceof UsageRequestError) return failure2(error.code, error.message);
|
|
4312
|
+
if (error instanceof GatewayError) {
|
|
4313
|
+
return failure2(error.code, displayGatewayMessage(error.code, error.message));
|
|
4314
|
+
}
|
|
4315
|
+
deps.log?.(`hiwork-core: \u7528\u91CF RPC '${endpoint}' \u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
4316
|
+
return failure2("internal", USAGE_GENERIC_FAILURE_MESSAGE);
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
async function callEndpoint2(gateway, sessionCookie, endpoint, payload) {
|
|
4320
|
+
switch (endpoint) {
|
|
4321
|
+
case "summary":
|
|
4322
|
+
return await gateway.getMySummary(sessionCookie, payload.keyId, payload.range);
|
|
4323
|
+
case "trends":
|
|
4324
|
+
return await gateway.getMyTrends(sessionCookie, payload.keyId, payload.range);
|
|
4325
|
+
case "model-trends":
|
|
4326
|
+
return await gateway.getMyModelTrends(sessionCookie, payload.keyId, payload.range);
|
|
4327
|
+
case "recharges":
|
|
4328
|
+
return await gateway.getMyKeyRecharges(sessionCookie, payload.keyId);
|
|
4329
|
+
case "keys":
|
|
4330
|
+
return await gateway.getMyKeys(sessionCookie);
|
|
4331
|
+
case "key":
|
|
4332
|
+
return await gateway.getMyKey(sessionCookie, payload.keyId);
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
function failure2(code, message) {
|
|
4336
|
+
return { ok: false, error: { code, message, details: {} } };
|
|
4337
|
+
}
|
|
4338
|
+
function registerUsageRpc(ctx, deps) {
|
|
4339
|
+
const handler = (endpoint, payload, signal) => handleUsageEndpoint(endpoint, payload, deps, signal);
|
|
4340
|
+
const remove = ctx.connection.rpc.handle(USAGE_RPC_CHANNEL, handler, { authority: "loopback" });
|
|
4341
|
+
return async () => {
|
|
4342
|
+
await remove();
|
|
4343
|
+
};
|
|
4344
|
+
}
|
|
4345
|
+
|
|
3499
4346
|
// src/index.ts
|
|
3500
4347
|
var name = "hiwork-core";
|
|
3501
|
-
var inject = ["settings", "credentials", "workspaceRegistry"];
|
|
4348
|
+
var inject = ["settings", "credentials", "workspaceRegistry", "connection"];
|
|
3502
4349
|
var NS = "ai-router";
|
|
3503
4350
|
var PI_AI_NS = "llm-pi-ai";
|
|
3504
4351
|
var lastErrorSchema = Schema.object({ code: Schema.string().required(), message: Schema.string().required() }).default(void 0);
|
|
@@ -3513,6 +4360,10 @@ var Config = Schema.object({
|
|
|
3513
4360
|
rememberPassword: Schema.boolean().default(false),
|
|
3514
4361
|
refreshRequest: Schema.object({ at: Schema.string().required() }).default(void 0),
|
|
3515
4362
|
logoutRequest: Schema.object({ at: Schema.string().required() }).default(void 0),
|
|
4363
|
+
browserLoginRequest: Schema.object({ at: Schema.string().required() }).default(void 0),
|
|
4364
|
+
browserLogin: Schema.object({ authorizeUrl: Schema.string().required(), startedAt: Schema.string().required() }).default(void 0),
|
|
4365
|
+
// 三值枚举:非法值写入直接被 schema 拒绝(客户端还会再归一一次,双保险)。
|
|
4366
|
+
loginMode: Schema.union([Schema.const("browser"), Schema.const("form"), Schema.const("both")]),
|
|
3516
4367
|
budgets: Schema.array(Schema.object({
|
|
3517
4368
|
model_name: Schema.string().required(),
|
|
3518
4369
|
total_budget: Schema.number(),
|
|
@@ -3550,10 +4401,169 @@ async function writeNamespace(settings, ns, ops) {
|
|
|
3550
4401
|
}
|
|
3551
4402
|
throw new Error(`settings namespace "${ns}" write conflicted repeatedly`);
|
|
3552
4403
|
}
|
|
4404
|
+
function readDefaultSelection(settings) {
|
|
4405
|
+
const value = settings.get(DEFAULT_MODEL_NS);
|
|
4406
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
4407
|
+
const { provider, model } = value;
|
|
4408
|
+
if (typeof provider !== "string" || typeof model !== "string") return void 0;
|
|
4409
|
+
return { provider, model };
|
|
4410
|
+
}
|
|
4411
|
+
function createDefaultModelPort(opts) {
|
|
4412
|
+
const { settings, llm } = opts;
|
|
4413
|
+
return {
|
|
4414
|
+
ensure: async (routeModels) => {
|
|
4415
|
+
const next = await ensureDefaultModel(routeModels, {
|
|
4416
|
+
available: () => settings.describe().some((descriptor) => descriptor.ns === DEFAULT_MODEL_NS),
|
|
4417
|
+
read: () => readDefaultSelection(settings),
|
|
4418
|
+
isRoutableElsewhere: async ({ provider, model }) => {
|
|
4419
|
+
if (llm === void 0) return false;
|
|
4420
|
+
if (!llm.listProviders().some((entry) => entry.id === provider)) return false;
|
|
4421
|
+
try {
|
|
4422
|
+
return (await llm.listModels(provider)).some((entry) => entry.id === model);
|
|
4423
|
+
} catch {
|
|
4424
|
+
return false;
|
|
4425
|
+
}
|
|
4426
|
+
},
|
|
4427
|
+
write: async (selection) => {
|
|
4428
|
+
await writeNamespace(settings, DEFAULT_MODEL_NS, [
|
|
4429
|
+
{ op: "set", path: ["provider"], value: selection.provider },
|
|
4430
|
+
{ op: "set", path: ["model"], value: selection.model },
|
|
4431
|
+
// 换模型后旧的 effort 可能不被新模型支持,一并清掉。
|
|
4432
|
+
{ op: "unset", path: ["reasoningEffort"] }
|
|
4433
|
+
]);
|
|
4434
|
+
}
|
|
4435
|
+
});
|
|
4436
|
+
if (next !== void 0) opts.log?.(`hiwork-core: \u9ED8\u8BA4\u6A21\u578B\u6821\u6B63\u4E3A ${next.provider}/${next.model}`);
|
|
4437
|
+
}
|
|
4438
|
+
};
|
|
4439
|
+
}
|
|
4440
|
+
var IPC_AUTH_CALLBACK = "hiwork:auth-callback";
|
|
4441
|
+
var IPC_DEVICE_INFO = "hiwork:device-info";
|
|
4442
|
+
var IPC_FIELD_MAX_LENGTH = 4096;
|
|
4443
|
+
var IPC_DEVICE_ID_MAX_LENGTH = 128;
|
|
4444
|
+
var IPC_DEVICE_NAME_MAX_LENGTH = 256;
|
|
4445
|
+
var IPC_APP_VERSION_MAX_LENGTH = 32;
|
|
4446
|
+
function readOptionalString(value, maxLength) {
|
|
4447
|
+
if (value === void 0) return { ok: true, value: void 0 };
|
|
4448
|
+
if (typeof value !== "string" || value.length > maxLength) return { ok: false };
|
|
4449
|
+
return { ok: true, value };
|
|
4450
|
+
}
|
|
4451
|
+
function parseAuthCallback(message) {
|
|
4452
|
+
const code = readOptionalString(message.code, IPC_FIELD_MAX_LENGTH);
|
|
4453
|
+
const state = readOptionalString(message.state, IPC_FIELD_MAX_LENGTH);
|
|
4454
|
+
const error = readOptionalString(message.error, IPC_FIELD_MAX_LENGTH);
|
|
4455
|
+
if (!code.ok || !state.ok || !error.ok) return void 0;
|
|
4456
|
+
return {
|
|
4457
|
+
...code.value === void 0 ? {} : { code: code.value },
|
|
4458
|
+
...state.value === void 0 ? {} : { state: state.value },
|
|
4459
|
+
...error.value === void 0 ? {} : { error: error.value }
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
function parseDeviceInfo(message) {
|
|
4463
|
+
const deviceId = readOptionalString(message.deviceId, IPC_DEVICE_ID_MAX_LENGTH);
|
|
4464
|
+
const deviceName = readOptionalString(message.deviceName, IPC_DEVICE_NAME_MAX_LENGTH);
|
|
4465
|
+
const appVersion = readOptionalString(message.appVersion, IPC_APP_VERSION_MAX_LENGTH);
|
|
4466
|
+
if (!deviceId.ok || !deviceName.ok || !appVersion.ok) return void 0;
|
|
4467
|
+
const id = deviceId.value?.trim();
|
|
4468
|
+
if (id === void 0 || id === "") return void 0;
|
|
4469
|
+
const name2 = deviceName.value?.trim();
|
|
4470
|
+
const version = appVersion.value?.trim();
|
|
4471
|
+
return {
|
|
4472
|
+
deviceId: id,
|
|
4473
|
+
...name2 === void 0 || name2 === "" ? {} : { deviceName: name2 },
|
|
4474
|
+
...version === void 0 || version === "" ? {} : { appVersion: version }
|
|
4475
|
+
};
|
|
4476
|
+
}
|
|
4477
|
+
function errorName(error) {
|
|
4478
|
+
return error instanceof Error ? error.name : typeof error;
|
|
4479
|
+
}
|
|
4480
|
+
function registerIpcMessageHandler(ports) {
|
|
4481
|
+
const log = ports.log ?? (() => {
|
|
4482
|
+
});
|
|
4483
|
+
let authInFlight = false;
|
|
4484
|
+
let disposed = false;
|
|
4485
|
+
const handler = (message) => {
|
|
4486
|
+
if (disposed) return;
|
|
4487
|
+
if (typeof message !== "object" || message === null || Array.isArray(message)) return;
|
|
4488
|
+
const envelope = message;
|
|
4489
|
+
const type = envelope.type;
|
|
4490
|
+
if (typeof type !== "string") return;
|
|
4491
|
+
if (type === IPC_AUTH_CALLBACK) {
|
|
4492
|
+
const input = parseAuthCallback(envelope);
|
|
4493
|
+
if (input === void 0) {
|
|
4494
|
+
log("hiwork-core: \u6536\u5230\u5B57\u6BB5\u975E\u6CD5\u7684\u6D4F\u89C8\u5668\u6388\u6743 IPC \u6D88\u606F\uFF0C\u5DF2\u5FFD\u7565");
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4497
|
+
if (authInFlight) {
|
|
4498
|
+
log("hiwork-core: \u4E0A\u4E00\u6B21\u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u4ECD\u5728\u5904\u7406\u4E2D\uFF0C\u5DF2\u5FFD\u7565\u672C\u6B21\u91CD\u590D\u56DE\u8C03");
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
authInFlight = true;
|
|
4502
|
+
try {
|
|
4503
|
+
void Promise.resolve(ports.complete(input)).catch((error) => {
|
|
4504
|
+
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5904\u7406\u5F02\u5E38\uFF08${errorName(error)}\uFF09`);
|
|
4505
|
+
}).finally(() => {
|
|
4506
|
+
authInFlight = false;
|
|
4507
|
+
});
|
|
4508
|
+
} catch (error) {
|
|
4509
|
+
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5206\u53D1\u5931\u8D25\uFF08${errorName(error)}\uFF09`);
|
|
4510
|
+
authInFlight = false;
|
|
4511
|
+
}
|
|
4512
|
+
return;
|
|
4513
|
+
}
|
|
4514
|
+
if (type === IPC_DEVICE_INFO) {
|
|
4515
|
+
const info = parseDeviceInfo(envelope);
|
|
4516
|
+
if (info === void 0) {
|
|
4517
|
+
log("hiwork-core: \u6536\u5230\u5B57\u6BB5\u975E\u6CD5\u7684\u8BBE\u5907\u4FE1\u606F IPC \u6D88\u606F\uFF0C\u5DF2\u5FFD\u7565");
|
|
4518
|
+
return;
|
|
4519
|
+
}
|
|
4520
|
+
ports.setDevice(info);
|
|
4521
|
+
try {
|
|
4522
|
+
void Promise.resolve(ports.persistDeviceId(info.deviceId)).catch(() => {
|
|
4523
|
+
});
|
|
4524
|
+
} catch {
|
|
4525
|
+
}
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4528
|
+
};
|
|
4529
|
+
ports.source.on("message", handler);
|
|
4530
|
+
return () => {
|
|
4531
|
+
if (disposed) return;
|
|
4532
|
+
disposed = true;
|
|
4533
|
+
ports.source.off("message", handler);
|
|
4534
|
+
};
|
|
4535
|
+
}
|
|
4536
|
+
async function projectLoginMode(opts) {
|
|
4537
|
+
let loginMode = DEFAULT_LOGIN_MODE;
|
|
4538
|
+
try {
|
|
4539
|
+
const baseURL = opts.baseURL;
|
|
4540
|
+
if (baseURL !== void 0 && baseURL !== "") {
|
|
4541
|
+
const device = await resolveDeviceInfo(opts.credentials);
|
|
4542
|
+
const policy = await (opts.fetchPolicy ?? fetchClientPolicy)({
|
|
4543
|
+
baseURL,
|
|
4544
|
+
version: opts.version ?? PKG_VERSION,
|
|
4545
|
+
platform: opts.platform ?? process.platform,
|
|
4546
|
+
arch: opts.arch ?? process.arch,
|
|
4547
|
+
deviceId: device.deviceId,
|
|
4548
|
+
...opts.timeoutMs === void 0 ? {} : { timeoutMs: opts.timeoutMs }
|
|
4549
|
+
});
|
|
4550
|
+
if (policy !== void 0) loginMode = policy.loginMode;
|
|
4551
|
+
}
|
|
4552
|
+
} catch (error) {
|
|
4553
|
+
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u62C9\u53D6\u5931\u8D25\uFF0C\u767B\u5F55\u65B9\u5F0F\u56DE\u9000 both\uFF08${errorName(error)}\uFF09`);
|
|
4554
|
+
}
|
|
4555
|
+
try {
|
|
4556
|
+
await opts.state.patch({ set: { loginMode } });
|
|
4557
|
+
} catch (error) {
|
|
4558
|
+
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u5199\u5165\u5931\u8D25\uFF08${errorName(error)}\uFF09`);
|
|
4559
|
+
}
|
|
4560
|
+
return loginMode;
|
|
4561
|
+
}
|
|
3553
4562
|
function apply(ctx, config) {
|
|
3554
4563
|
let current = () => ({ ...config });
|
|
3555
4564
|
const settings = ctx.get("settings");
|
|
3556
4565
|
const credentials = ctx.get("credentials");
|
|
4566
|
+
const llm = ctx.get("llm");
|
|
3557
4567
|
if (settings === void 0) throw new Error("settings service unavailable");
|
|
3558
4568
|
const gatewayFor = (baseURL) => new AiRouterClient(baseURL.replace(/\/+$/, ""));
|
|
3559
4569
|
const ports = {
|
|
@@ -3587,6 +4597,11 @@ function apply(ctx, config) {
|
|
|
3587
4597
|
await writeNamespace(settings, PI_AI_NS, ops);
|
|
3588
4598
|
}
|
|
3589
4599
|
},
|
|
4600
|
+
defaultModel: createDefaultModelPort({
|
|
4601
|
+
settings,
|
|
4602
|
+
llm,
|
|
4603
|
+
log: (message) => ctx.logger.info(message)
|
|
4604
|
+
}),
|
|
3590
4605
|
state: {
|
|
3591
4606
|
patch: async (patch) => {
|
|
3592
4607
|
if (settings === void 0) throw new Error("settings service unavailable");
|
|
@@ -3602,26 +4617,50 @@ function apply(ctx, config) {
|
|
|
3602
4617
|
}
|
|
3603
4618
|
};
|
|
3604
4619
|
let inFlight;
|
|
4620
|
+
let policyRequested = false;
|
|
4621
|
+
const requestClientPolicy = () => {
|
|
4622
|
+
if (policyRequested) return;
|
|
4623
|
+
const baseURL = current().baseURL;
|
|
4624
|
+
if (baseURL === void 0 || baseURL === "") return;
|
|
4625
|
+
policyRequested = true;
|
|
4626
|
+
void projectLoginMode({
|
|
4627
|
+
baseURL,
|
|
4628
|
+
credentials: ports.credentials,
|
|
4629
|
+
state: ports.state,
|
|
4630
|
+
log: (message) => {
|
|
4631
|
+
ctx.logger.warn(message);
|
|
4632
|
+
}
|
|
4633
|
+
}).catch(() => {
|
|
4634
|
+
});
|
|
4635
|
+
};
|
|
3605
4636
|
settings.installSection(ctx, NS, Config, config, {
|
|
3606
4637
|
setSource: (source) => {
|
|
3607
4638
|
current = source;
|
|
3608
4639
|
},
|
|
3609
4640
|
onChange: () => {
|
|
4641
|
+
requestClientPolicy();
|
|
3610
4642
|
const value = current();
|
|
3611
|
-
const requested = value.pendingLogin ?? value.refreshRequest ?? value.logoutRequest;
|
|
4643
|
+
const requested = value.pendingLogin ?? value.browserLoginRequest ?? value.refreshRequest ?? value.logoutRequest;
|
|
3612
4644
|
if (requested === void 0) return;
|
|
3613
4645
|
if (inFlight !== void 0) return;
|
|
3614
4646
|
inFlight = (async () => {
|
|
3615
4647
|
try {
|
|
3616
4648
|
const gateway = gatewayFor(value.baseURL ?? "");
|
|
3617
|
-
await handleAiRouterSectionChange({
|
|
4649
|
+
await handleAiRouterSectionChange({
|
|
4650
|
+
gateway,
|
|
4651
|
+
...ports,
|
|
4652
|
+
value,
|
|
4653
|
+
log: (message) => {
|
|
4654
|
+
ctx.logger.warn(message);
|
|
4655
|
+
}
|
|
4656
|
+
});
|
|
3618
4657
|
} catch (error) {
|
|
3619
4658
|
ctx.logger.error("hiwork-core: ai-router section change failed", error);
|
|
3620
4659
|
await ports.credentials.unset(PENDING_PASSWORD_REF).catch(() => {
|
|
3621
4660
|
});
|
|
3622
4661
|
await ports.state.patch({
|
|
3623
4662
|
set: { lastError: { code: "GATEWAY_UNREACHABLE", message: "\u7F51\u5173\u63D2\u4EF6\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u914D\u7F6E\u4E0E\u7F51\u7EDC" } },
|
|
3624
|
-
unset: ["pendingLogin", "refreshRequest", "logoutRequest"]
|
|
4663
|
+
unset: ["pendingLogin", "browserLoginRequest", "browserLogin", "refreshRequest", "logoutRequest"]
|
|
3625
4664
|
}).catch(() => {
|
|
3626
4665
|
});
|
|
3627
4666
|
}
|
|
@@ -3630,6 +4669,51 @@ function apply(ctx, config) {
|
|
|
3630
4669
|
});
|
|
3631
4670
|
}
|
|
3632
4671
|
});
|
|
4672
|
+
requestClientPolicy();
|
|
4673
|
+
void reconcileStaleBrowserLogin(ports.state, current());
|
|
4674
|
+
const connection = ctx.get("connection");
|
|
4675
|
+
if (connection === void 0) throw new Error("connection service unavailable");
|
|
4676
|
+
ctx.effect(() => registerUsageRpc({ connection, logger: ctx.logger }, {
|
|
4677
|
+
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
4678
|
+
gateway: () => {
|
|
4679
|
+
const baseURL = current().baseURL;
|
|
4680
|
+
return baseURL === void 0 || baseURL === "" ? void 0 : gatewayFor(baseURL);
|
|
4681
|
+
},
|
|
4682
|
+
log: (message) => {
|
|
4683
|
+
ctx.logger.warn(message);
|
|
4684
|
+
}
|
|
4685
|
+
}), "hiwork-core: usage rpc");
|
|
4686
|
+
ctx.effect(() => registerDailyRpc({ connection, logger: ctx.logger }, {
|
|
4687
|
+
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
4688
|
+
gateway: () => {
|
|
4689
|
+
const baseURL = current().baseURL;
|
|
4690
|
+
return baseURL === void 0 || baseURL === "" ? void 0 : gatewayFor(baseURL);
|
|
4691
|
+
},
|
|
4692
|
+
log: (message) => {
|
|
4693
|
+
ctx.logger.warn(message);
|
|
4694
|
+
}
|
|
4695
|
+
}), "hiwork-core: daily rpc");
|
|
4696
|
+
ctx.effect(() => registerIpcMessageHandler({
|
|
4697
|
+
source: process,
|
|
4698
|
+
complete: (input) => {
|
|
4699
|
+
const value = current();
|
|
4700
|
+
return completeBrowserLogin(input, {
|
|
4701
|
+
...ports,
|
|
4702
|
+
value,
|
|
4703
|
+
gateway: gatewayFor(value.baseURL ?? ""),
|
|
4704
|
+
log: (message) => {
|
|
4705
|
+
ctx.logger.warn(message);
|
|
4706
|
+
}
|
|
4707
|
+
});
|
|
4708
|
+
},
|
|
4709
|
+
setDevice: (info) => {
|
|
4710
|
+
setDeviceInfo(info);
|
|
4711
|
+
},
|
|
4712
|
+
persistDeviceId: (deviceId) => ports.credentials.set(DEVICE_ID_REF, deviceId),
|
|
4713
|
+
log: (message) => {
|
|
4714
|
+
ctx.logger.warn(message);
|
|
4715
|
+
}
|
|
4716
|
+
}), "hiwork-core: ipc message listener");
|
|
3633
4717
|
if (config.lastLogin === void 0 && config.rememberPassword === true && config.username !== void 0 && config.baseURL !== void 0 && credentials !== void 0) {
|
|
3634
4718
|
void credentials.resolve(REMEMBERED_PASSWORD_REF).then((hit) => {
|
|
3635
4719
|
if (hit === void 0 || settings === void 0) return;
|
|
@@ -3641,6 +4725,9 @@ function apply(ctx, config) {
|
|
|
3641
4725
|
export {
|
|
3642
4726
|
Config,
|
|
3643
4727
|
apply,
|
|
4728
|
+
createDefaultModelPort,
|
|
3644
4729
|
inject,
|
|
3645
|
-
name
|
|
4730
|
+
name,
|
|
4731
|
+
projectLoginMode,
|
|
4732
|
+
registerIpcMessageHandler
|
|
3646
4733
|
};
|