hiwork-core 0.0.22 → 0.0.29
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/cordis.patch.yml +20 -4
- package/lib/ai-router-client.js +142 -1
- package/lib/client.js +961 -33
- package/lib/index.js +2185 -353
- package/lib/knowledge-config.js +273 -0
- package/lib/pms-client.js +216 -61
- package/lib/pms-todo-bridge.js +332 -313
- package/lib/types/ai-router-client.d.ts +61 -0
- package/lib/types/client/contracts.d.ts +10 -8
- package/lib/types/client/hero-greeting.d.ts +0 -11
- package/lib/types/client/pms-todo.d.ts +67 -49
- package/lib/types/client/styles.d.ts +28 -0
- package/lib/types/client/types.d.ts +13 -1
- package/lib/types/client/voice/MicButton.d.ts +24 -0
- package/lib/types/client/voice/VoiceOverlay.d.ts +37 -0
- package/lib/types/client/voice/composer.d.ts +98 -0
- package/lib/types/client/voice/entry.d.ts +46 -0
- package/lib/types/client/voice/recording.d.ts +57 -0
- package/lib/types/client/voice/voice-data.d.ts +101 -0
- package/lib/types/client/voice/voice-styles.d.ts +9 -0
- package/lib/types/index.d.ts +3 -1
- package/lib/types/knowledge-config.d.ts +115 -0
- package/lib/types/pms-client.d.ts +99 -34
- package/lib/types/pms-todo-bridge.d.ts +210 -184
- package/lib/types/voice-rpc.d.ts +133 -0
- package/lib/types/web-search.d.ts +123 -0
- package/lib/voice-rpc.js +211 -0
- package/lib/web-search.js +259 -0
- package/package.json +5 -3
package/lib/index.js
CHANGED
|
@@ -3137,7 +3137,7 @@ 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.29";
|
|
3141
3141
|
|
|
3142
3142
|
// src/ai-router-client.ts
|
|
3143
3143
|
var AUTH_AUTHORIZE_PATH = "/api/auth/authorize";
|
|
@@ -3157,6 +3157,19 @@ var ME_SKILL_PACKAGES_PATH = "/api/me/skill-packages";
|
|
|
3157
3157
|
var ME_WORKBENCH_APPS_PATH = "/api/me/workbench-apps";
|
|
3158
3158
|
var ME_OFFICE_CONFIG_PATH = "/api/me/office-config";
|
|
3159
3159
|
var ME_OFFICE_RELAY_DOCUMENTS_PATH = "/api/me/office-relay/documents";
|
|
3160
|
+
var ME_VOICE_CONFIG_PATH = "/api/me/voice-config";
|
|
3161
|
+
var ME_VOICE_TRANSCRIBE_PATH = "/api/me/voice-transcribe";
|
|
3162
|
+
var VOICE_TRANSCRIBE_TIMEOUT_MS = 6e4;
|
|
3163
|
+
var VOICE_CONFIG_FALLBACK_MAX_SECONDS = 60;
|
|
3164
|
+
var VOICE_ERROR_FALLBACK_MESSAGE = {
|
|
3165
|
+
VOICE_DISABLED: "\u672C\u90E8\u7F72\u672A\u542F\u7528\u8BED\u97F3\u8BC6\u522B\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
|
|
3166
|
+
VOICE_NOT_CONFIGURED: "\u672C\u90E8\u7F72\u5C1A\u672A\u914D\u7F6E\u8BED\u97F3\u8BC6\u522B\u670D\u52A1\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
|
|
3167
|
+
VOICE_LIMIT_REACHED: "\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
|
|
3168
|
+
VOICE_AUDIO_TOO_LARGE: "\u5F55\u97F3\u8FC7\u957F\u6216\u6587\u4EF6\u8FC7\u5927\uFF0C\u8BF7\u7F29\u77ED\u540E\u91CD\u8BD5",
|
|
3169
|
+
VOICE_BAD_REQUEST: "\u5F55\u97F3\u6570\u636E\u4E0D\u5408\u6CD5\uFF0C\u8BF7\u91CD\u65B0\u5F55\u5236",
|
|
3170
|
+
VOICE_UPSTREAM: "\u8BED\u97F3\u8BC6\u522B\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
|
|
3171
|
+
};
|
|
3172
|
+
var VOICE_ERROR_DEFAULT_MESSAGE = "\u8BED\u97F3\u8BC6\u522B\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
3160
3173
|
var DEFAULT_CLIENT_ID = "hiwork-desktop";
|
|
3161
3174
|
var DEFAULT_REDIRECT_URI = "hiwork://auth/callback";
|
|
3162
3175
|
var GatewayError = class extends Error {
|
|
@@ -3548,6 +3561,81 @@ var AiRouterClient = class {
|
|
|
3548
3561
|
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3549
3562
|
return await parseMeJson(res);
|
|
3550
3563
|
}
|
|
3564
|
+
/**
|
|
3565
|
+
* GET /api/me/voice-config:当前账号的语音识别开关与录音时长上限。
|
|
3566
|
+
*
|
|
3567
|
+
* 与每日一句的 `getMyDailyQuote` 同款,**这两个方法不在 {@link GatewayClient} 接口上**:
|
|
3568
|
+
* 那个接口是编排层的端口,「语音识别」与它无关,加进去只会逼着每处编排层假网关
|
|
3569
|
+
* (tests/orchestration.spec.ts 里一堆)都补两个用不到的桩。频道侧改用结构子集
|
|
3570
|
+
* `VoiceGatewayPort`(见 `src/voice-rpc.ts`),与 usage / daily 三处一致。
|
|
3571
|
+
*
|
|
3572
|
+
* 与 {@link getMyMcpAssignments} 同样的理由**不复用 `getMeJson`**:那条路径把 403 映射成
|
|
3573
|
+
* `NO_KEY_ASSIGNED`(「尚未被分配虚拟 KEY」),而本端点的鉴权只有会话——一个还没被分配
|
|
3574
|
+
* Key 的账号照样应该有语音识别(分能力与分 Key 是两件事)。沿用它的文案会把「没有 Key」
|
|
3575
|
+
* 错报成原因。
|
|
3576
|
+
*
|
|
3577
|
+
* 本端点契约**恒 200**(开关开与关都是 200 + `enabled` 布尔),因此除 401 以外没有任何
|
|
3578
|
+
* 需要单独解释的拒绝语义:403 / 404 / 5xx / 反代错误页一律收敛成 `GATEWAY_UNREACHABLE`。
|
|
3579
|
+
*
|
|
3580
|
+
* 形状收敛(**本层负责**,消费方拿到的永远是一个合法的 {@link GatewayVoiceConfig}):
|
|
3581
|
+
* 网关线上是 snake_case `max_audio_seconds`,这里转 camelCase;字段缺失 / 类型不符
|
|
3582
|
+
* 按「禁用 + 60 秒」处理——配置读不出来时话筒本就该消失,用一个 undefined 让消费方
|
|
3583
|
+
* 去猜是把形状问题推给下游。真正的「读不到就回落」再往上一层(`src/voice-rpc.ts`)。
|
|
3584
|
+
*/
|
|
3585
|
+
async getMyVoiceConfig(sessionCookie) {
|
|
3586
|
+
const res = await this.requestOrUnreachable(ME_VOICE_CONFIG_PATH, { method: "GET", sessionCookie });
|
|
3587
|
+
if (res.status === 401) throw new GatewayError("SESSION_EXPIRED", "\u7F51\u5173\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 401);
|
|
3588
|
+
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3589
|
+
const data = await parseMeJson(res);
|
|
3590
|
+
const seconds = data.max_audio_seconds;
|
|
3591
|
+
return {
|
|
3592
|
+
enabled: data.enabled === true,
|
|
3593
|
+
maxDurationSeconds: typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0 ? seconds : VOICE_CONFIG_FALLBACK_MAX_SECONDS
|
|
3594
|
+
};
|
|
3595
|
+
}
|
|
3596
|
+
/**
|
|
3597
|
+
* POST /api/me/voice-transcribe:把一段录音(base64)交给网关转发上游做识别。
|
|
3598
|
+
*
|
|
3599
|
+
* 状态码映射与服务端 handler 一一对应,**不许合并**:
|
|
3600
|
+
* - 401 = 会话过期(唯一一条能让人做点什么——重新登录——的拒绝);
|
|
3601
|
+
* - 503 = 网关自己发的「功能不可用」,再按响应体 `code` 细分:
|
|
3602
|
+
* `VOICE_NOT_CONFIGURED`(部署没配上游)/ `VOICE_DISABLED`(管理员把开关关了)。
|
|
3603
|
+
* 两者给的提示不同(该找管理员配服务 vs 该找管理员开开关)。
|
|
3604
|
+
* ⚠️ 响应体没有 `code`(非 JSON / 反代错误页)也归 `VOICE_DISABLED`——这条路由的
|
|
3605
|
+
* 503 语义就是「没开」,而**上游故障走 502**(见下),不会伪装成 503;
|
|
3606
|
+
* - 429 / 413 / 400 = 配额 / 超长 / 请求体不合法,三种不同的事,分开映射(合并后
|
|
3607
|
+
* 客户端只会看到一句「识别失败」,413 的人不知道该录短一点);
|
|
3608
|
+
* - 其余(含 502 = 上游出错)→ `VOICE_UPSTREAM`。
|
|
3609
|
+
*
|
|
3610
|
+
* message 一律取网关响应体里的 `message`(网关侧是冻结的中文文案,会直接展示给用户);
|
|
3611
|
+
* 响应体不是合法 JSON / 缺 message 时退回本地兜底文案。**绝不把上游 / 网关原文回显**:
|
|
3612
|
+
* 读取响应体走 {@link readVoiceError}(解析失败即空对象),与 `parseMeJson` 不回显原文同源。
|
|
3613
|
+
*
|
|
3614
|
+
* 超时**单独放宽到 60s**(见 {@link VOICE_TRANSCRIBE_TIMEOUT_MS}):这条要上传录音 +
|
|
3615
|
+
* 等上游识别,构造时的 10s 默认值在这里是误报源。
|
|
3616
|
+
*/
|
|
3617
|
+
async transcribeVoice(sessionCookie, audioBase64) {
|
|
3618
|
+
const res = await this.requestOrUnreachable(ME_VOICE_TRANSCRIBE_PATH, {
|
|
3619
|
+
method: "POST",
|
|
3620
|
+
sessionCookie,
|
|
3621
|
+
body: { audio_base64: audioBase64 },
|
|
3622
|
+
timeoutMs: VOICE_TRANSCRIBE_TIMEOUT_MS
|
|
3623
|
+
});
|
|
3624
|
+
if (res.status === 401) throw new GatewayError("SESSION_EXPIRED", "\u7F51\u5173\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 401);
|
|
3625
|
+
if (!res.ok) {
|
|
3626
|
+
const body = await readVoiceError(res);
|
|
3627
|
+
if (res.status === 503) {
|
|
3628
|
+
const code = body.code === "VOICE_NOT_CONFIGURED" ? "VOICE_NOT_CONFIGURED" : "VOICE_DISABLED";
|
|
3629
|
+
throw new GatewayError(code, voiceMessage(code, body), 503);
|
|
3630
|
+
}
|
|
3631
|
+
if (res.status === 429) throw new GatewayError("VOICE_LIMIT_REACHED", voiceMessage("VOICE_LIMIT_REACHED", body), 429);
|
|
3632
|
+
if (res.status === 413) throw new GatewayError("VOICE_AUDIO_TOO_LARGE", voiceMessage("VOICE_AUDIO_TOO_LARGE", body), 413);
|
|
3633
|
+
if (res.status === 400) throw new GatewayError("VOICE_BAD_REQUEST", voiceMessage("VOICE_BAD_REQUEST", body), 400);
|
|
3634
|
+
throw new GatewayError("VOICE_UPSTREAM", voiceMessage("VOICE_UPSTREAM", body), res.status);
|
|
3635
|
+
}
|
|
3636
|
+
const data = await parseMeJson(res);
|
|
3637
|
+
return { text: typeof data.text === "string" ? data.text : "" };
|
|
3638
|
+
}
|
|
3551
3639
|
/**
|
|
3552
3640
|
* /api/me 自助端点的 GET 公共路径:网络异常 → GATEWAY_UNREACHABLE,
|
|
3553
3641
|
* 401/403/400 映射为稳定 code,响应体非 JSON 时不把原文带进异常。
|
|
@@ -3584,7 +3672,7 @@ var AiRouterClient = class {
|
|
|
3584
3672
|
const init = {
|
|
3585
3673
|
method: opts.method,
|
|
3586
3674
|
headers,
|
|
3587
|
-
signal: AbortSignal.timeout(this.timeoutMs)
|
|
3675
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? this.timeoutMs)
|
|
3588
3676
|
};
|
|
3589
3677
|
if (opts.rawBody !== void 0) {
|
|
3590
3678
|
init.body = opts.rawBody.buffer.slice(
|
|
@@ -3652,6 +3740,18 @@ async function parseMeJson(res) {
|
|
|
3652
3740
|
}
|
|
3653
3741
|
return data;
|
|
3654
3742
|
}
|
|
3743
|
+
async function readVoiceError(res) {
|
|
3744
|
+
const data = await readJson(res);
|
|
3745
|
+
if (typeof data !== "object" || data === null) return {};
|
|
3746
|
+
const record = data;
|
|
3747
|
+
return {
|
|
3748
|
+
...typeof record.code === "string" && record.code !== "" ? { code: record.code } : {},
|
|
3749
|
+
...typeof record.message === "string" && record.message.trim() !== "" ? { message: record.message.trim() } : {}
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
function voiceMessage(code, body) {
|
|
3753
|
+
return body.message ?? VOICE_ERROR_FALLBACK_MESSAGE[code] ?? VOICE_ERROR_DEFAULT_MESSAGE;
|
|
3754
|
+
}
|
|
3655
3755
|
function isInvalidGrant(payload) {
|
|
3656
3756
|
return typeof payload === "object" && payload !== null && payload.error === "invalid_grant";
|
|
3657
3757
|
}
|
|
@@ -4218,8 +4318,8 @@ function parseAppVersionPayload(raw) {
|
|
|
4218
4318
|
if (keys.length > 0) throw new AppVersionRequestError("bad-request", `\u684C\u9762\u7AEF\u7248\u672C\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${keys[0]}'\u3002`);
|
|
4219
4319
|
}
|
|
4220
4320
|
function normalizedVersion(raw) {
|
|
4221
|
-
const
|
|
4222
|
-
return
|
|
4321
|
+
const version2 = typeof raw === "string" ? raw.trim() : "";
|
|
4322
|
+
return version2 === "" ? null : version2;
|
|
4223
4323
|
}
|
|
4224
4324
|
async function handleAppVersionEndpoint(endpoint, rawPayload, deps, signal) {
|
|
4225
4325
|
try {
|
|
@@ -4652,6 +4752,123 @@ function registerUsageRpc(ctx, deps) {
|
|
|
4652
4752
|
};
|
|
4653
4753
|
}
|
|
4654
4754
|
|
|
4755
|
+
// src/voice-rpc.ts
|
|
4756
|
+
var VOICE_RPC_CHANNEL = "/hiwork-voice";
|
|
4757
|
+
var VOICE_RPC_ENDPOINTS = ["status", "transcribe"];
|
|
4758
|
+
var VoiceRequestError = class extends Error {
|
|
4759
|
+
constructor(code, message) {
|
|
4760
|
+
super(message);
|
|
4761
|
+
this.code = code;
|
|
4762
|
+
this.name = "VoiceRequestError";
|
|
4763
|
+
}
|
|
4764
|
+
};
|
|
4765
|
+
var VOICE_MESSAGE_FALLBACK = {
|
|
4766
|
+
SESSION_EXPIRED: "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55",
|
|
4767
|
+
GATEWAY_UNREACHABLE: "\u7F51\u5173\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
4768
|
+
VOICE_DISABLED: "\u672C\u90E8\u7F72\u672A\u542F\u7528\u8BED\u97F3\u8BC6\u522B\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
|
|
4769
|
+
VOICE_NOT_CONFIGURED: "\u672C\u90E8\u7F72\u5C1A\u672A\u914D\u7F6E\u8BED\u97F3\u8BC6\u522B\u670D\u52A1\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
|
|
4770
|
+
VOICE_LIMIT_REACHED: "\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
|
|
4771
|
+
VOICE_AUDIO_TOO_LARGE: "\u5F55\u97F3\u8FC7\u957F\uFF0C\u8BF7\u7F29\u77ED\u540E\u91CD\u8BD5",
|
|
4772
|
+
VOICE_BAD_REQUEST: "\u5F55\u97F3\u6570\u636E\u4E0D\u5408\u6CD5\uFF0C\u8BF7\u91CD\u65B0\u5F55\u5236",
|
|
4773
|
+
VOICE_UPSTREAM: "\u8BED\u97F3\u8BC6\u522B\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
4774
|
+
RATE_LIMITED: "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
|
|
4775
|
+
AUTH_FAILED: "\u767B\u5F55\u72B6\u6001\u5F02\u5E38\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55"
|
|
4776
|
+
};
|
|
4777
|
+
var VOICE_GENERIC_FAILURE_MESSAGE = "\u8BED\u97F3\u8BC6\u522B\u6682\u65F6\u65E0\u6CD5\u4F7F\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
4778
|
+
var VOICE_SESSION_EXPIRED_MESSAGE = "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55";
|
|
4779
|
+
var VOICE_FALLBACK_CONFIG = { enabled: false, maxDurationSeconds: 60 };
|
|
4780
|
+
function displayVoiceGatewayMessage(code, message) {
|
|
4781
|
+
const text = typeof message === "string" ? message.trim() : "";
|
|
4782
|
+
if (text === "") return VOICE_MESSAGE_FALLBACK[code] ?? VOICE_GENERIC_FAILURE_MESSAGE;
|
|
4783
|
+
const leaksImplementationDetail = /https?:\/\//i.test(text) || /\bHTTP\s*\d{3}\b/i.test(text) || /\bbaseURL\b/i.test(text);
|
|
4784
|
+
if (leaksImplementationDetail) return VOICE_MESSAGE_FALLBACK[code] ?? VOICE_GENERIC_FAILURE_MESSAGE;
|
|
4785
|
+
return text;
|
|
4786
|
+
}
|
|
4787
|
+
function parseVoicePayload(endpoint, raw) {
|
|
4788
|
+
if (raw === void 0 || raw === null) {
|
|
4789
|
+
if (endpoint === "transcribe") throw new VoiceRequestError("bad-request", "\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u7F3A\u5C11 audioBase64\u3002");
|
|
4790
|
+
return {};
|
|
4791
|
+
}
|
|
4792
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
4793
|
+
throw new VoiceRequestError("bad-request", "\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u53C2\u6570\u4E0D\u5408\u6CD5\u3002");
|
|
4794
|
+
}
|
|
4795
|
+
const record = raw;
|
|
4796
|
+
const keys = Object.keys(record);
|
|
4797
|
+
if (endpoint === "status") {
|
|
4798
|
+
if (keys.length > 0) throw new VoiceRequestError("bad-request", `\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${keys[0]}'\u3002`);
|
|
4799
|
+
return {};
|
|
4800
|
+
}
|
|
4801
|
+
for (const key of keys) {
|
|
4802
|
+
if (key !== "audioBase64") throw new VoiceRequestError("bad-request", `\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${key}'\u3002`);
|
|
4803
|
+
}
|
|
4804
|
+
const rawAudio = record["audioBase64"];
|
|
4805
|
+
if (typeof rawAudio !== "string" || rawAudio.trim() === "") {
|
|
4806
|
+
throw new VoiceRequestError("bad-request", "\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u7684 audioBase64 \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u3002");
|
|
4807
|
+
}
|
|
4808
|
+
return { audioBase64: rawAudio };
|
|
4809
|
+
}
|
|
4810
|
+
function isVoiceRpcEndpoint(endpoint) {
|
|
4811
|
+
return VOICE_RPC_ENDPOINTS.includes(endpoint);
|
|
4812
|
+
}
|
|
4813
|
+
function isAborted4(signal) {
|
|
4814
|
+
return signal?.aborted === true;
|
|
4815
|
+
}
|
|
4816
|
+
async function handleVoiceEndpoint(endpoint, rawPayload, deps, signal) {
|
|
4817
|
+
try {
|
|
4818
|
+
if (isAborted4(signal)) return failure4("cancelled", "\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4819
|
+
if (!isVoiceRpcEndpoint(endpoint)) {
|
|
4820
|
+
throw new VoiceRequestError("bad-request", `\u672A\u77E5\u7684\u8BED\u97F3\u8BC6\u522B\u7AEF\u70B9 '${endpoint}'`);
|
|
4821
|
+
}
|
|
4822
|
+
const payload = parseVoicePayload(endpoint, rawPayload);
|
|
4823
|
+
const sessionCookie = await deps.resolveSession();
|
|
4824
|
+
if (sessionCookie === void 0 || sessionCookie === "") {
|
|
4825
|
+
return failure4("SESSION_EXPIRED", VOICE_SESSION_EXPIRED_MESSAGE);
|
|
4826
|
+
}
|
|
4827
|
+
const gateway = deps.gateway();
|
|
4828
|
+
if (gateway === void 0) {
|
|
4829
|
+
return failure4("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5730\u5740\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u7F51\u5173\u6A21\u578B\u300D\u91CC\u5B8C\u6210\u767B\u5F55");
|
|
4830
|
+
}
|
|
4831
|
+
if (isAborted4(signal)) return failure4("cancelled", "\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4832
|
+
if (endpoint === "status") return { ok: true, value: await readVoiceConfig(gateway, sessionCookie, deps.log) };
|
|
4833
|
+
return { ok: true, value: await transcribe(gateway, sessionCookie, payload) };
|
|
4834
|
+
} catch (error) {
|
|
4835
|
+
if (error instanceof VoiceRequestError) return failure4(error.code, error.message);
|
|
4836
|
+
if (error instanceof GatewayError) {
|
|
4837
|
+
return failure4(error.code, displayVoiceGatewayMessage(error.code, error.message));
|
|
4838
|
+
}
|
|
4839
|
+
deps.log?.(`hiwork-core: \u8BED\u97F3\u8BC6\u522B RPC '${endpoint}' \u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
4840
|
+
return failure4("internal", VOICE_GENERIC_FAILURE_MESSAGE);
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
async function readVoiceConfig(gateway, sessionCookie, log) {
|
|
4844
|
+
try {
|
|
4845
|
+
const config = await gateway.getMyVoiceConfig(sessionCookie);
|
|
4846
|
+
return {
|
|
4847
|
+
enabled: config.enabled === true,
|
|
4848
|
+
maxDurationSeconds: config.maxDurationSeconds > 0 ? config.maxDurationSeconds : VOICE_FALLBACK_CONFIG.maxDurationSeconds
|
|
4849
|
+
};
|
|
4850
|
+
} catch (error) {
|
|
4851
|
+
log?.(`hiwork-core: \u8BED\u97F3\u914D\u7F6E\u8BFB\u53D6\u5931\u8D25\uFF0C\u6309\u7981\u7528\u56DE\u843D\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
4852
|
+
return { ...VOICE_FALLBACK_CONFIG };
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
async function transcribe(gateway, sessionCookie, payload) {
|
|
4856
|
+
const audioBase64 = payload.audioBase64;
|
|
4857
|
+
if (audioBase64 === void 0) throw new VoiceRequestError("bad-request", "\u8BED\u97F3\u8BC6\u522B\u8BF7\u6C42\u7F3A\u5C11 audioBase64\u3002");
|
|
4858
|
+
const result = await gateway.transcribeVoice(sessionCookie, audioBase64);
|
|
4859
|
+
return { text: result.text };
|
|
4860
|
+
}
|
|
4861
|
+
function failure4(code, message) {
|
|
4862
|
+
return { ok: false, error: { code, message, details: {} } };
|
|
4863
|
+
}
|
|
4864
|
+
function registerVoiceRpc(ctx, deps) {
|
|
4865
|
+
const handler = (endpoint, payload, signal) => handleVoiceEndpoint(endpoint, payload, deps, signal);
|
|
4866
|
+
const remove = ctx.connection.rpc.handle(VOICE_RPC_CHANNEL, handler, { authority: "loopback" });
|
|
4867
|
+
return async () => {
|
|
4868
|
+
await remove();
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
|
|
4655
4872
|
// src/managed-mcp.ts
|
|
4656
4873
|
var MANAGED_MCP_RPC_CHANNEL = "/hiwork-managed-mcp";
|
|
4657
4874
|
var MANAGED_MCP_RPC_ENDPOINTS = ["snapshot", "refresh", "assertion"];
|
|
@@ -4816,12 +5033,12 @@ var ManagedMcpStore = class {
|
|
|
4816
5033
|
}
|
|
4817
5034
|
}
|
|
4818
5035
|
};
|
|
4819
|
-
function
|
|
5036
|
+
function isAborted5(signal) {
|
|
4820
5037
|
return signal?.aborted === true;
|
|
4821
5038
|
}
|
|
4822
5039
|
async function handleManagedMcpEndpoint(endpoint, rawPayload, deps, store, signal) {
|
|
4823
5040
|
try {
|
|
4824
|
-
if (
|
|
5041
|
+
if (isAborted5(signal)) return failure5("cancelled", "MCP \u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4825
5042
|
if (!isManagedMcpRpcEndpoint(endpoint)) {
|
|
4826
5043
|
throw new ManagedMcpRequestError("bad-request", `\u672A\u77E5\u7684 MCP \u6E05\u5355\u7AEF\u70B9 '${endpoint}'`);
|
|
4827
5044
|
}
|
|
@@ -4842,19 +5059,19 @@ async function handleManagedMcpEndpoint(endpoint, rawPayload, deps, store, signa
|
|
|
4842
5059
|
async function handleAssertion(assignmentId, deps, signal) {
|
|
4843
5060
|
const sessionCookie = await deps.resolveSession();
|
|
4844
5061
|
if (sessionCookie === void 0 || sessionCookie === "") {
|
|
4845
|
-
return
|
|
5062
|
+
return failure5("SESSION_EXPIRED", MANAGED_MCP_SESSION_EXPIRED_MESSAGE);
|
|
4846
5063
|
}
|
|
4847
5064
|
const gateway = deps.gateway();
|
|
4848
5065
|
if (gateway === void 0) {
|
|
4849
|
-
return
|
|
5066
|
+
return failure5("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5730\u5740\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u7F51\u5173\u6A21\u578B\u300D\u91CC\u5B8C\u6210\u767B\u5F55");
|
|
4850
5067
|
}
|
|
4851
|
-
if (
|
|
5068
|
+
if (isAborted5(signal)) return failure5("cancelled", "MCP \u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4852
5069
|
const ticket = await gateway.issueMyMcpAssertion(sessionCookie, assignmentId);
|
|
4853
5070
|
const assertion = textOrUndefined(ticket?.assertion);
|
|
4854
5071
|
const mintUrl = textOrUndefined(ticket?.mintUrl);
|
|
4855
5072
|
if (assertion === void 0 || mintUrl === void 0) {
|
|
4856
5073
|
deps.log?.("hiwork-core: \u53D6\u65AD\u8A00\u6536\u5230\u65E0\u6548\u54CD\u5E94\uFF08\u7F3A assertion / mintUrl\uFF09");
|
|
4857
|
-
return
|
|
5074
|
+
return failure5("internal", MANAGED_MCP_GENERIC_FAILURE_MESSAGE);
|
|
4858
5075
|
}
|
|
4859
5076
|
return {
|
|
4860
5077
|
ok: true,
|
|
@@ -4867,7 +5084,7 @@ async function handleAssertion(assignmentId, deps, signal) {
|
|
|
4867
5084
|
}
|
|
4868
5085
|
};
|
|
4869
5086
|
}
|
|
4870
|
-
function
|
|
5087
|
+
function failure5(code, message) {
|
|
4871
5088
|
return { ok: false, error: { code, message, details: {} } };
|
|
4872
5089
|
}
|
|
4873
5090
|
function registerManagedMcpRpc(ctx, deps, store) {
|
|
@@ -5098,12 +5315,12 @@ var ManagedSkillsStore = class {
|
|
|
5098
5315
|
}
|
|
5099
5316
|
}
|
|
5100
5317
|
};
|
|
5101
|
-
function
|
|
5318
|
+
function isAborted6(signal) {
|
|
5102
5319
|
return signal?.aborted === true;
|
|
5103
5320
|
}
|
|
5104
5321
|
async function handleManagedSkillsEndpoint(endpoint, rawPayload, deps, store, signal) {
|
|
5105
5322
|
try {
|
|
5106
|
-
if (
|
|
5323
|
+
if (isAborted6(signal)) return failure6("cancelled", "\u6280\u80FD\u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
5107
5324
|
if (!isManagedSkillsRpcEndpoint(endpoint)) {
|
|
5108
5325
|
throw new ManagedSkillsRequestError("bad-request", `\u672A\u77E5\u7684\u6280\u80FD\u6E05\u5355\u7AEF\u70B9 '${endpoint}'`);
|
|
5109
5326
|
}
|
|
@@ -5124,24 +5341,24 @@ async function handleManagedSkillsEndpoint(endpoint, rawPayload, deps, store, si
|
|
|
5124
5341
|
async function handlePackage(assignmentId, deps, signal) {
|
|
5125
5342
|
const sessionCookie = await deps.resolveSession();
|
|
5126
5343
|
if (sessionCookie === void 0 || sessionCookie === "") {
|
|
5127
|
-
return
|
|
5344
|
+
return failure6("SESSION_EXPIRED", MANAGED_SKILLS_SESSION_EXPIRED_MESSAGE);
|
|
5128
5345
|
}
|
|
5129
5346
|
const gateway = deps.gateway();
|
|
5130
5347
|
if (gateway === void 0) {
|
|
5131
|
-
return
|
|
5348
|
+
return failure6("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5730\u5740\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u7F51\u5173\u6A21\u578B\u300D\u91CC\u5B8C\u6210\u767B\u5F55");
|
|
5132
5349
|
}
|
|
5133
|
-
if (
|
|
5350
|
+
if (isAborted6(signal)) return failure6("cancelled", "\u6280\u80FD\u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
5134
5351
|
const pkg = await gateway.getMySkillPackage(sessionCookie, assignmentId);
|
|
5135
5352
|
const bytes = pkg?.bytes;
|
|
5136
5353
|
const skillName = textOrUndefined2(pkg?.skillName);
|
|
5137
5354
|
const contentSha256 = textOrUndefined2(pkg?.contentSha256);
|
|
5138
5355
|
if (!(bytes instanceof Uint8Array) || bytes.byteLength === 0 || skillName === void 0 || contentSha256 === void 0) {
|
|
5139
5356
|
deps.log?.("hiwork-core: \u53D6\u6280\u80FD\u5305\u6536\u5230\u65E0\u6548\u54CD\u5E94\uFF08\u7F3A skillName / contentSha256 \u6216\u7A7A\u5305\u4F53\uFF09");
|
|
5140
|
-
return
|
|
5357
|
+
return failure6("internal", MANAGED_SKILLS_GENERIC_FAILURE_MESSAGE);
|
|
5141
5358
|
}
|
|
5142
5359
|
if (bytes.byteLength > MAX_MANAGED_SKILL_ARCHIVE_BYTES) {
|
|
5143
5360
|
deps.log?.("hiwork-core: \u6280\u80FD\u5305\u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650\uFF0C\u5DF2\u62D2\u7EDD\u642C\u8FD0");
|
|
5144
|
-
return
|
|
5361
|
+
return failure6(MANAGED_SKILLS_PACKAGE_TOO_LARGE_CODE, MANAGED_SKILLS_PACKAGE_TOO_LARGE_MESSAGE);
|
|
5145
5362
|
}
|
|
5146
5363
|
return {
|
|
5147
5364
|
ok: true,
|
|
@@ -5155,7 +5372,7 @@ async function handlePackage(assignmentId, deps, signal) {
|
|
|
5155
5372
|
}
|
|
5156
5373
|
};
|
|
5157
5374
|
}
|
|
5158
|
-
function
|
|
5375
|
+
function failure6(code, message) {
|
|
5159
5376
|
return { ok: false, error: { code, message, details: {} } };
|
|
5160
5377
|
}
|
|
5161
5378
|
function registerManagedSkillsRpc(ctx, deps, store) {
|
|
@@ -5297,7 +5514,7 @@ function failureOf3(error) {
|
|
|
5297
5514
|
function normalizeSession2(sessionCookie) {
|
|
5298
5515
|
return sessionCookie === void 0 || sessionCookie === "" ? void 0 : sessionCookie;
|
|
5299
5516
|
}
|
|
5300
|
-
function
|
|
5517
|
+
function isAborted7(signal) {
|
|
5301
5518
|
return signal?.aborted === true;
|
|
5302
5519
|
}
|
|
5303
5520
|
var WorkbenchStore = class {
|
|
@@ -5422,7 +5639,7 @@ var WorkbenchStore = class {
|
|
|
5422
5639
|
};
|
|
5423
5640
|
async function handleWorkbenchEndpoint(endpoint, rawPayload, deps, store, signal) {
|
|
5424
5641
|
try {
|
|
5425
|
-
if (
|
|
5642
|
+
if (isAborted7(signal)) return failure7("cancelled", "\u5DE5\u4F5C\u53F0\u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
5426
5643
|
if (!isWorkbenchRpcEndpoint(endpoint)) {
|
|
5427
5644
|
throw new WorkbenchRequestError("bad-request", `\u672A\u77E5\u7684\u5DE5\u4F5C\u53F0\u6E05\u5355\u7AEF\u70B9 '${endpoint}'`);
|
|
5428
5645
|
}
|
|
@@ -5435,7 +5652,7 @@ async function handleWorkbenchEndpoint(endpoint, rawPayload, deps, store, signal
|
|
|
5435
5652
|
return { ok: false, error: failureOf3(error) };
|
|
5436
5653
|
}
|
|
5437
5654
|
}
|
|
5438
|
-
function
|
|
5655
|
+
function failure7(code, message) {
|
|
5439
5656
|
return { ok: false, error: { code, message, details: {} } };
|
|
5440
5657
|
}
|
|
5441
5658
|
function registerWorkbenchRpc(ctx, deps, store) {
|
|
@@ -5448,8 +5665,6 @@ function registerWorkbenchRpc(ctx, deps, store) {
|
|
|
5448
5665
|
|
|
5449
5666
|
// src/pms-client.ts
|
|
5450
5667
|
var PMS_BASE_URL = "https://pms.hivery.cn";
|
|
5451
|
-
var AUTH_LOGIN_PATH = "/api/auth/login";
|
|
5452
|
-
var AUTH_REFRESH_PATH = "/api/auth/refresh";
|
|
5453
5668
|
var TODO_TODAY_PATH = "/api/todo/today";
|
|
5454
5669
|
var TODO_STATISTICS_PATH = "/api/todo/statistics";
|
|
5455
5670
|
var TASK_LIST_PATH = "/api/task/tasks";
|
|
@@ -5501,26 +5716,60 @@ var PmsClient = class {
|
|
|
5501
5716
|
this.log = options.log;
|
|
5502
5717
|
}
|
|
5503
5718
|
/**
|
|
5504
|
-
* `POST
|
|
5719
|
+
* `POST {mintUrl}`:用一张网关签发的 **120 秒身份断言**换一把 **10 小时 PMS JWT**
|
|
5720
|
+
* (spec `2026-09-21-pms-zero-input-login-design.md` §4.1b)。
|
|
5505
5721
|
*
|
|
5506
|
-
*
|
|
5507
|
-
* -
|
|
5508
|
-
*
|
|
5509
|
-
*
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
*
|
|
5722
|
+
* 与其余方法的两处形状差异都是契约给定的,不是本层偷懒:
|
|
5723
|
+
* - **`mintUrl` 是绝对地址**,来自网关的连接器分配(`auth.mintUrl`)——它**不拼** {@link PMS_BASE_URL}:
|
|
5724
|
+
* 那个常量是「PMS 部署地址」,而兑换端点由网关的分配清单说了算(本层不猜也不改写,见
|
|
5725
|
+
* {@link PmsClient.send});
|
|
5726
|
+
* - **不解 `Result` 信封**:成功体就是 `{token, expiresAt, userId, username}`,失败体是
|
|
5727
|
+
* `{error:"assertion_rejected"}` 这类——两者都不是业务信封。
|
|
5728
|
+
*
|
|
5729
|
+
* 失败语义:
|
|
5730
|
+
* - HTTP 401 / 403(断言过期、`aud` 不符、签名不过、工号在 PMS 不存在)→ `UNAUTHORIZED`:用户该做的
|
|
5731
|
+
* 是**重新登录 HiWork 客户端**(断言来自网关会话),不是再输一次 PMS 密码(spec D8);
|
|
5732
|
+
* - 其余(网络 / 超时 / 400 / 503 / 非 JSON / 2xx 却形状不符)→ `UPSTREAM_UNAVAILABLE`。
|
|
5517
5733
|
*
|
|
5518
|
-
*
|
|
5519
|
-
*
|
|
5734
|
+
* **本方法不重试**:断言是一次性的(PMS 侧拿 `jti` 做 2 分钟防重放),要重试必须由调用方**换一张新
|
|
5735
|
+
* 断言**再调——重发同一张只会换来第二个 403(spec §6)。
|
|
5736
|
+
*
|
|
5737
|
+
* @param mintUrl - 网关分配里的 `auth.mintUrl`(绝对 URL)。
|
|
5738
|
+
* @param assertion - 网关签发的短命断言(**不进日志、不落盘**,用完即弃)。
|
|
5739
|
+
* @param deviceRef - 设备标识串(可空;只进 PMS 侧审计,不参与鉴权)。
|
|
5740
|
+
* @returns 令牌与它的元数据(`token` 交给 Host 内存,见 `src/pms-todo-bridge.ts`)。
|
|
5520
5741
|
*/
|
|
5521
|
-
async
|
|
5522
|
-
const
|
|
5523
|
-
|
|
5742
|
+
async exchangeAssertion(mintUrl, assertion, deviceRef) {
|
|
5743
|
+
const response = await this.send(mintUrl, { method: "POST", body: { assertion, deviceRef } });
|
|
5744
|
+
if (response.status === 401 || response.status === 403) {
|
|
5745
|
+
this.log?.(`hiwork-core: PMS \u6362\u4EE4\u724C\u88AB\u62D2\uFF08HTTP ${response.status}\uFF09`);
|
|
5746
|
+
throw new PmsError("UNAUTHORIZED", PMS_UNAUTHORIZED_MESSAGE, response.status);
|
|
5747
|
+
}
|
|
5748
|
+
if (response.status < 200 || response.status >= 300) {
|
|
5749
|
+
this.log?.(`hiwork-core: PMS \u6362\u4EE4\u724C\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
|
|
5750
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE, response.status);
|
|
5751
|
+
}
|
|
5752
|
+
const payload = await readJson2(response);
|
|
5753
|
+
if (!isRecord3(payload)) {
|
|
5754
|
+
this.log?.("hiwork-core: PMS \u6362\u4EE4\u724C\u8FD4\u56DE\u4E86\u975E JSON \u54CD\u5E94\uFF08\u53EF\u80FD\u7684\u4E2D\u95F4\u5C42\uFF09");
|
|
5755
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE, response.status);
|
|
5756
|
+
}
|
|
5757
|
+
const token = textOf(payload["token"]);
|
|
5758
|
+
if (token === void 0) {
|
|
5759
|
+
this.log?.("hiwork-core: PMS \u6362\u4EE4\u724C\u54CD\u5E94\u7F3A token\uFF08\u5951\u7EA6\u7834\u574F\uFF09\uFF0C\u6309\u4E0A\u6E38\u4E0D\u53EF\u7528\u5904\u7406");
|
|
5760
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE, response.status);
|
|
5761
|
+
}
|
|
5762
|
+
if (!isInteger(payload["userId"])) {
|
|
5763
|
+
this.log?.("hiwork-core: PMS \u6362\u4EE4\u724C\u54CD\u5E94\u7F3A userId / \u5F62\u6001\u4E0D\u5BF9\uFF08\u5951\u7EA6\u7834\u574F\uFF09\uFF0C\u6309\u4E0A\u6E38\u4E0D\u53EF\u7528\u5904\u7406");
|
|
5764
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE, response.status);
|
|
5765
|
+
}
|
|
5766
|
+
return {
|
|
5767
|
+
token,
|
|
5768
|
+
// 两个非关键字段按容错处理:令牌已经拿到了,不为一个坏字段让用户重来一次。
|
|
5769
|
+
expiresAt: expiresAtOr(payload["expiresAt"], this.now() + PMS_ACCESS_TOKEN_TTL_MS),
|
|
5770
|
+
userId: payload["userId"],
|
|
5771
|
+
username: textOf(payload["username"]) ?? ""
|
|
5772
|
+
};
|
|
5524
5773
|
}
|
|
5525
5774
|
/**
|
|
5526
5775
|
* `GET /api/todo/today`:今日待办(⚠️ 口径比名字宽:**今天 + 明天 + 无截止日期**都算,spec §D4)。
|
|
@@ -5656,15 +5905,22 @@ var PmsClient = class {
|
|
|
5656
5905
|
};
|
|
5657
5906
|
}
|
|
5658
5907
|
/**
|
|
5659
|
-
*
|
|
5908
|
+
* 发一次请求:拼地址、塞请求头、按 {@link PmsClientOptions.timeoutMs} 超时。**只有传输**。
|
|
5660
5909
|
*
|
|
5661
|
-
*
|
|
5662
|
-
*
|
|
5663
|
-
*
|
|
5664
|
-
*
|
|
5910
|
+
* 与 {@link PmsClient.call} 的分工:`call` 负责 `Result` 信封与业务码,本方法不管响应体是什么形状
|
|
5911
|
+
* ——换令牌那条路({@link PmsClient.exchangeAssertion})既不解信封也不认业务码,但**超时口径与请求头
|
|
5912
|
+
* 必须与其余调用逐字相同**(spec §7:统一 10s、令牌只在有身份时发),所以两条路共用这一处,
|
|
5913
|
+
* 而不是各抄一份、将来各自漂移。
|
|
5914
|
+
*
|
|
5915
|
+
* **重定向自己跟**(`redirect: 'manual'` + {@link redirectTargetOf}):`fetch` 的默认 `follow` 会把
|
|
5916
|
+
* 重定向后的请求按 fetch 语义**把 POST 改成 GET**,于是请求落在一个只认 POST 的映射上、拿到一个
|
|
5917
|
+
* 「200 + 业务错误信封」,看上去像上游坏了。判据与修法见 {@link PMS_MAX_REDIRECTS}。
|
|
5918
|
+
*
|
|
5919
|
+
* @param url - 相对路径(拼 {@link PmsClient.baseURL})或**绝对 URL**(原样使用)。
|
|
5920
|
+
* @param opts - 方法 / 可选 JSON 体 / 可选 Bearer 令牌。
|
|
5921
|
+
* @returns 原始 `Response`:状态码由调用方判定,本方法不把任何状态码当成错误。
|
|
5665
5922
|
*/
|
|
5666
|
-
async
|
|
5667
|
-
let response;
|
|
5923
|
+
async send(url, opts) {
|
|
5668
5924
|
try {
|
|
5669
5925
|
const headers = {
|
|
5670
5926
|
"user-agent": `hiwork-core/${PKG_VERSION}`,
|
|
@@ -5675,13 +5931,34 @@ var PmsClient = class {
|
|
|
5675
5931
|
const init = {
|
|
5676
5932
|
method: opts.method,
|
|
5677
5933
|
headers,
|
|
5678
|
-
|
|
5934
|
+
// 手动跟随:默认的 `follow` 会把重定向后的 POST 降级成 GET(见本方法的抬头)。
|
|
5935
|
+
redirect: "manual"
|
|
5679
5936
|
};
|
|
5680
5937
|
if (opts.body !== void 0) init.body = JSON.stringify(opts.body);
|
|
5681
|
-
|
|
5938
|
+
const deadline = Date.now() + this.timeoutMs;
|
|
5939
|
+
let target = isAbsoluteUrl(url) ? url : `${this.baseURL}${url}`;
|
|
5940
|
+
let response = await fetchWithinDeadline(target, init, deadline);
|
|
5941
|
+
for (let hop = 0; ; hop += 1) {
|
|
5942
|
+
const next = redirectTargetOf(target, response, hop);
|
|
5943
|
+
if (next === void 0) return response;
|
|
5944
|
+
target = next;
|
|
5945
|
+
response = await fetchWithinDeadline(target, init, deadline);
|
|
5946
|
+
}
|
|
5682
5947
|
} catch {
|
|
5683
5948
|
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
|
|
5684
5949
|
}
|
|
5950
|
+
}
|
|
5951
|
+
/**
|
|
5952
|
+
* 发一次调用并解开 `Result` 信封。**PMS 的业务错误都在这里收敛**,返回值只有 `data`
|
|
5953
|
+
* (传输层的失败已在 {@link PmsClient.send} 收敛)。
|
|
5954
|
+
*
|
|
5955
|
+
* 顺序是刻意的:**先看 HTTP 401,再解信封**——PMS 未登录时回的是 Spring Security 形状
|
|
5956
|
+
* (`{path, code:"AUTH_001", message}`),它的 `message` 是给浏览器看跳登录页的,既不是业务文案
|
|
5957
|
+
* 也可能带 `path` 等实现细节。把它当业务信封解析,轻则文案变成一句英文,重则把「凭据失效」
|
|
5958
|
+
* 误判成「上游故障」而让用户不知道要重新输入密码。
|
|
5959
|
+
*/
|
|
5960
|
+
async call(path, opts) {
|
|
5961
|
+
const response = await this.send(path, opts);
|
|
5685
5962
|
if (response.status === 401) {
|
|
5686
5963
|
throw new PmsError("UNAUTHORIZED", PMS_UNAUTHORIZED_MESSAGE, 401);
|
|
5687
5964
|
}
|
|
@@ -5713,32 +5990,41 @@ var PmsClient = class {
|
|
|
5713
5990
|
}
|
|
5714
5991
|
return { code, data: payload["data"] };
|
|
5715
5992
|
}
|
|
5716
|
-
/** 登录 / 刷新的 `data` → {@link PmsTokenSet}。200 却不给 token 视为契约破坏(上游不可用)。 */
|
|
5717
|
-
tokenSet(data) {
|
|
5718
|
-
if (!isRecord3(data)) {
|
|
5719
|
-
this.log?.("hiwork-core: PMS \u8BA4\u8BC1\u54CD\u5E94\u7F3A data\uFF08\u5951\u7EA6\u7834\u574F\uFF09\uFF0C\u6309\u4E0A\u6E38\u4E0D\u53EF\u7528\u5904\u7406");
|
|
5720
|
-
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
|
|
5721
|
-
}
|
|
5722
|
-
const accessToken = textOf(data["token"]);
|
|
5723
|
-
if (accessToken === void 0) {
|
|
5724
|
-
this.log?.("hiwork-core: PMS \u8BA4\u8BC1\u54CD\u5E94\u7F3A token\uFF08\u5951\u7EA6\u7834\u574F\uFF09\uFF0C\u6309\u4E0A\u6E38\u4E0D\u53EF\u7528\u5904\u7406");
|
|
5725
|
-
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
|
|
5726
|
-
}
|
|
5727
|
-
const refreshToken = textOf(data["refreshToken"]);
|
|
5728
|
-
const expiresIn = data["expiresIn"];
|
|
5729
|
-
const ttlMs = typeof expiresIn === "number" && Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn * 1e3 : PMS_ACCESS_TOKEN_TTL_MS;
|
|
5730
|
-
const userId = isInteger(data["userId"]) ? data["userId"] : void 0;
|
|
5731
|
-
return {
|
|
5732
|
-
accessToken,
|
|
5733
|
-
...refreshToken === void 0 ? {} : { refreshToken },
|
|
5734
|
-
...userId === void 0 ? {} : { userId },
|
|
5735
|
-
expiresAt: this.now() + ttlMs
|
|
5736
|
-
};
|
|
5737
|
-
}
|
|
5738
5993
|
};
|
|
5739
5994
|
function isRecord3(value) {
|
|
5740
5995
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5741
5996
|
}
|
|
5997
|
+
function isAbsoluteUrl(url) {
|
|
5998
|
+
return /^https?:\/\//i.test(url);
|
|
5999
|
+
}
|
|
6000
|
+
var PMS_MAX_REDIRECTS = 2;
|
|
6001
|
+
var METHOD_PRESERVING_REDIRECTS = /* @__PURE__ */ new Set([301, 302, 307, 308]);
|
|
6002
|
+
function redirectTargetOf(current, response, hop) {
|
|
6003
|
+
if (!METHOD_PRESERVING_REDIRECTS.has(response.status)) return void 0;
|
|
6004
|
+
if (hop >= PMS_MAX_REDIRECTS) return void 0;
|
|
6005
|
+
const location = response.headers.get("location");
|
|
6006
|
+
if (location === null || location.trim() === "") return void 0;
|
|
6007
|
+
let from2;
|
|
6008
|
+
let next;
|
|
6009
|
+
try {
|
|
6010
|
+
from2 = new URL(current);
|
|
6011
|
+
next = new URL(location.trim(), current);
|
|
6012
|
+
} catch {
|
|
6013
|
+
return void 0;
|
|
6014
|
+
}
|
|
6015
|
+
if (next.protocol !== "https:" && next.protocol !== from2.protocol) return void 0;
|
|
6016
|
+
if (next.hostname !== from2.hostname) return void 0;
|
|
6017
|
+
return next.toString();
|
|
6018
|
+
}
|
|
6019
|
+
async function fetchWithinDeadline(target, init, deadline) {
|
|
6020
|
+
const remaining = deadline - Date.now();
|
|
6021
|
+
if (remaining <= 0) throw new Error("pms request deadline exceeded");
|
|
6022
|
+
return await fetch(target, { ...init, signal: AbortSignal.timeout(remaining) });
|
|
6023
|
+
}
|
|
6024
|
+
function expiresAtOr(value, fallbackMs) {
|
|
6025
|
+
if (typeof value === "string" && value !== "" && Number.isFinite(Date.parse(value))) return value;
|
|
6026
|
+
return new Date(fallbackMs).toISOString();
|
|
6027
|
+
}
|
|
5742
6028
|
function textOf(value) {
|
|
5743
6029
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
5744
6030
|
}
|
|
@@ -5831,18 +6117,18 @@ async function readJson2(response) {
|
|
|
5831
6117
|
var PMS_TODO_RPC_CHANNEL = "/hiwork-pms-todo";
|
|
5832
6118
|
var PMS_TODO_RPC_ENDPOINTS = [
|
|
5833
6119
|
"status",
|
|
5834
|
-
"login",
|
|
5835
|
-
"logout",
|
|
5836
6120
|
"today",
|
|
5837
6121
|
"statistics",
|
|
5838
6122
|
"setStatus",
|
|
5839
6123
|
"myTasks",
|
|
5840
6124
|
"completeTask"
|
|
5841
6125
|
];
|
|
6126
|
+
var PMS_ASSIGNMENT_SLUG = "pms";
|
|
5842
6127
|
var PMS_CREDENTIALS_REF = "HIWORK_PMS_CREDENTIALS";
|
|
5843
6128
|
var PMS_TODO_ACCESS_TOKEN_TTL_MS = 10 * 60 * 60 * 1e3;
|
|
5844
|
-
var
|
|
5845
|
-
var
|
|
6129
|
+
var PMS_TODO_REUSE_MIN_REMAINING_RATIO = 0.5;
|
|
6130
|
+
var PMS_TODO_NOT_CONFIGURED_MESSAGE = "\u7BA1\u7406\u5458\u5C1A\u672A\u628A PMS \u63A5\u5165\u8FD9\u53F0\u5BA2\u6237\u7AEF\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458";
|
|
6131
|
+
var PMS_TODO_UNAUTHORIZED_MESSAGE = "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 HiWork \u5BA2\u6237\u7AEF";
|
|
5846
6132
|
var PMS_TODO_FORBIDDEN_MESSAGE = PMS_FORBIDDEN_MESSAGE;
|
|
5847
6133
|
var PMS_TODO_GENERIC_FAILURE_MESSAGE = PMS_GENERIC_FAILURE_MESSAGE;
|
|
5848
6134
|
var PMS_TODO_CANCELLED_MESSAGE = "PMS \u5F85\u529E\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002";
|
|
@@ -5853,9 +6139,19 @@ var PmsTodoRequestError = class extends Error {
|
|
|
5853
6139
|
this.name = "PmsTodoRequestError";
|
|
5854
6140
|
}
|
|
5855
6141
|
};
|
|
6142
|
+
var PmsTodoError = class extends Error {
|
|
6143
|
+
constructor(code, message) {
|
|
6144
|
+
super(message);
|
|
6145
|
+
this.code = code;
|
|
6146
|
+
this.name = "PmsTodoError";
|
|
6147
|
+
}
|
|
6148
|
+
};
|
|
5856
6149
|
function isPmsTodoRpcEndpoint(endpoint) {
|
|
5857
6150
|
return PMS_TODO_RPC_ENDPOINTS.includes(endpoint);
|
|
5858
6151
|
}
|
|
6152
|
+
function textOf2(value) {
|
|
6153
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
6154
|
+
}
|
|
5859
6155
|
function asRecord(endpoint, raw) {
|
|
5860
6156
|
if (raw === void 0 || raw === null) return {};
|
|
5861
6157
|
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -5872,24 +6168,10 @@ function rejectUnknownKeys(endpoint, record, allowed) {
|
|
|
5872
6168
|
}
|
|
5873
6169
|
function parsePmsTodoPayload(endpoint, raw) {
|
|
5874
6170
|
const record = asRecord(endpoint, raw);
|
|
5875
|
-
if (endpoint === "status" || endpoint === "
|
|
6171
|
+
if (endpoint === "status" || endpoint === "today" || endpoint === "statistics" || endpoint === "myTasks") {
|
|
5876
6172
|
rejectUnknownKeys(endpoint, record, []);
|
|
5877
6173
|
return { endpoint };
|
|
5878
6174
|
}
|
|
5879
|
-
if (endpoint === "login") {
|
|
5880
|
-
rejectUnknownKeys(endpoint, record, ["username", "password", "remember"]);
|
|
5881
|
-
const username = typeof record["username"] === "string" ? record["username"].trim() : "";
|
|
5882
|
-
if (username === "") throw new PmsTodoRequestError("PMS \u767B\u5F55\u8BF7\u6C42\u7F3A\u5C11 username\u3002");
|
|
5883
|
-
const password = record["password"];
|
|
5884
|
-
if (typeof password !== "string" || password === "") {
|
|
5885
|
-
throw new PmsTodoRequestError("PMS \u767B\u5F55\u8BF7\u6C42\u7F3A\u5C11 password\u3002");
|
|
5886
|
-
}
|
|
5887
|
-
const remember = record["remember"];
|
|
5888
|
-
if (remember !== void 0 && typeof remember !== "boolean") {
|
|
5889
|
-
throw new PmsTodoRequestError("PMS \u767B\u5F55\u8BF7\u6C42\u7684 remember \u5FC5\u987B\u662F\u5E03\u5C14\u503C\u3002");
|
|
5890
|
-
}
|
|
5891
|
-
return { endpoint, username, password, remember: remember === true };
|
|
5892
|
-
}
|
|
5893
6175
|
if (endpoint === "completeTask") {
|
|
5894
6176
|
rejectUnknownKeys(endpoint, record, ["id"]);
|
|
5895
6177
|
const id2 = record["id"];
|
|
@@ -5937,139 +6219,95 @@ function displayPmsTodoMessage(code, message) {
|
|
|
5937
6219
|
if (leaksImplementationDetail) return PMS_TODO_MESSAGE_FALLBACK[code] ?? PMS_TODO_GENERIC_FAILURE_MESSAGE;
|
|
5938
6220
|
return text;
|
|
5939
6221
|
}
|
|
5940
|
-
var PmsTodoError = class extends Error {
|
|
5941
|
-
constructor(code, message) {
|
|
5942
|
-
super(message);
|
|
5943
|
-
this.code = code;
|
|
5944
|
-
this.name = "PmsTodoError";
|
|
5945
|
-
}
|
|
5946
|
-
};
|
|
5947
6222
|
function errorName(error) {
|
|
5948
6223
|
return error instanceof Error ? error.name : typeof error;
|
|
5949
6224
|
}
|
|
5950
|
-
function
|
|
5951
|
-
|
|
6225
|
+
function gatewayCodeOf(error) {
|
|
6226
|
+
return error instanceof GatewayError ? error.code : errorName(error);
|
|
6227
|
+
}
|
|
6228
|
+
async function clearLegacyCredentials(credentials, log) {
|
|
5952
6229
|
try {
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6230
|
+
const hit = await credentials.resolve(PMS_CREDENTIALS_REF);
|
|
6231
|
+
if (hit === void 0 || hit.value === "") return;
|
|
6232
|
+
await credentials.unset(PMS_CREDENTIALS_REF);
|
|
6233
|
+
log?.("hiwork-core: \u5DF2\u6E05\u9664 v1 \u9057\u7559\u7684 PMS \u51ED\u636E\u8BB0\u5F55");
|
|
6234
|
+
} catch (error) {
|
|
6235
|
+
log?.(`hiwork-core: PMS \u9057\u7559\u51ED\u636E\u6E05\u9664\u5931\u8D25\uFF08${errorName(error)}\uFF09`);
|
|
5956
6236
|
}
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
const
|
|
5960
|
-
if (username === "") return void 0;
|
|
5961
|
-
const password = typeof record["password"] === "string" && record["password"] !== "" ? record["password"] : void 0;
|
|
5962
|
-
const refreshToken = typeof record["refreshToken"] === "string" && record["refreshToken"] !== "" ? record["refreshToken"] : void 0;
|
|
5963
|
-
const expiresAt = typeof record["expiresAt"] === "number" && Number.isFinite(record["expiresAt"]) ? record["expiresAt"] : void 0;
|
|
5964
|
-
const userId = typeof record["userId"] === "number" && Number.isSafeInteger(record["userId"]) ? record["userId"] : void 0;
|
|
6237
|
+
}
|
|
6238
|
+
function sessionOf(result, now) {
|
|
6239
|
+
const parsed = Date.parse(result.expiresAt);
|
|
5965
6240
|
return {
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
6241
|
+
token: result.token,
|
|
6242
|
+
// `PmsClient` 已保证 `expiresAt` 可解析(缺失时它自己兜底成 now + 10 小时);这里再兜一次
|
|
6243
|
+
// 是为了「上游回了一个解析不了的串」时不至于把过期时刻变成 NaN(那会让复用判据永远为假)。
|
|
6244
|
+
expiresAt: Number.isFinite(parsed) ? parsed : now + PMS_TODO_ACCESS_TOKEN_TTL_MS,
|
|
6245
|
+
issuedAt: now,
|
|
6246
|
+
userId: result.userId,
|
|
6247
|
+
username: result.username
|
|
5971
6248
|
};
|
|
5972
6249
|
}
|
|
5973
|
-
function serializeCredentialsRecord(record) {
|
|
5974
|
-
return JSON.stringify({
|
|
5975
|
-
username: record.username,
|
|
5976
|
-
...record.password === void 0 ? {} : { password: record.password },
|
|
5977
|
-
...record.refreshToken === void 0 ? {} : { refreshToken: record.refreshToken },
|
|
5978
|
-
...record.expiresAt === void 0 ? {} : { expiresAt: record.expiresAt },
|
|
5979
|
-
...record.userId === void 0 ? {} : { userId: record.userId }
|
|
5980
|
-
});
|
|
5981
|
-
}
|
|
5982
6250
|
var PmsTodoStore = class {
|
|
5983
6251
|
constructor(deps) {
|
|
5984
6252
|
this.deps = deps;
|
|
5985
6253
|
}
|
|
5986
|
-
/**
|
|
5987
|
-
|
|
5988
|
-
/**
|
|
5989
|
-
|
|
5990
|
-
/**
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
* refresh token 写给下一个人(登出后磁盘上仍有可用凭据,这正是不 remember 的相反面)。
|
|
5994
|
-
*/
|
|
5995
|
-
persisted = false;
|
|
5996
|
-
/** access token(**只在内存**)。 */
|
|
5997
|
-
access;
|
|
5998
|
-
/** 世代号:`login` / `logout` 各 +1,用来作废它之前发起的在飞请求(换账号的语义同工作台)。 */
|
|
5999
|
-
generation = 0;
|
|
6000
|
-
/** 在飞请求**连同世代与端点**记账:同一世代 + 同一端点才允许合并。 */
|
|
6254
|
+
/** 内存里的会话(**唯一的家**,不落盘;重启后自然重换)。 */
|
|
6255
|
+
session;
|
|
6256
|
+
/** 网关分配清单里那条 `pms` 分配(只在内存里记住,免得每次取数都拉一遍清单)。 */
|
|
6257
|
+
assignment;
|
|
6258
|
+
/** 最近一次「为什么还没就绪」(进 `status` 的 `reason`;成功换到令牌时清空)。 */
|
|
6259
|
+
reason;
|
|
6260
|
+
/** 在飞请求按端点记账:同一端点才允许合并。 */
|
|
6001
6261
|
inFlight;
|
|
6002
6262
|
now() {
|
|
6003
6263
|
return this.deps.now?.() ?? Date.now();
|
|
6004
6264
|
}
|
|
6005
|
-
/** 当前状态(同步语义的取数,异步实现:可能读一次 credentials)。**永不抛**。 */
|
|
6006
|
-
async status() {
|
|
6007
|
-
const record = await this.currentRecord();
|
|
6008
|
-
return this.statusOf(record);
|
|
6009
|
-
}
|
|
6010
6265
|
/**
|
|
6011
|
-
*
|
|
6266
|
+
* 当前状态(spec §4.4)。**永不抛**:失败在快照里表现为 `configured=false` + `reason`。
|
|
6012
6267
|
*
|
|
6013
|
-
*
|
|
6014
|
-
*
|
|
6015
|
-
*
|
|
6268
|
+
* 顺序是刻意的:
|
|
6269
|
+
* 1. 手上有一张未过期的 JWT → 直接报 `loggedIn`,**一次网络都不发**(面板每次挂载都读它);
|
|
6270
|
+
* 2. 没有 → 问一次网关「到底有没有 `pms` 分配」。`configured` 的判据只能是它:用户填没填过
|
|
6271
|
+
* 账号密码这件事已经不存在了(D3),而「有没有分配」只有网关知道。第二次起走内存里的那条
|
|
6272
|
+
* 分配,不再打网关。
|
|
6016
6273
|
*
|
|
6017
|
-
* `
|
|
6018
|
-
*
|
|
6274
|
+
* `reason` 只在「还没就绪」时出现:`no-assignment` / `gateway-signed-out` / `unavailable` 三种,
|
|
6275
|
+
* 分别对应「找管理员」/「重新登录 HiWork」/「稍后重试」(spec §3 D8 的 UI 动作各不相同)。
|
|
6019
6276
|
*/
|
|
6020
|
-
async
|
|
6021
|
-
const
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
await this.forgetStored();
|
|
6277
|
+
async status() {
|
|
6278
|
+
const session = this.session;
|
|
6279
|
+
if (session !== void 0 && this.now() < session.expiresAt) {
|
|
6280
|
+
return {
|
|
6281
|
+
configured: true,
|
|
6282
|
+
loggedIn: true,
|
|
6283
|
+
username: session.username,
|
|
6284
|
+
expiresAt: new Date(session.expiresAt).toISOString(),
|
|
6285
|
+
userId: session.userId,
|
|
6286
|
+
...this.reason === void 0 ? {} : { reason: this.reason }
|
|
6287
|
+
};
|
|
6288
|
+
}
|
|
6289
|
+
try {
|
|
6290
|
+
const cookie = await this.sessionCookie();
|
|
6291
|
+
const gateway = this.gatewayPort();
|
|
6292
|
+
await this.ensureAssignment(gateway, cookie);
|
|
6293
|
+
return { configured: true, loggedIn: false, ...this.reason === void 0 ? {} : { reason: this.reason } };
|
|
6294
|
+
} catch {
|
|
6295
|
+
return { configured: false, loggedIn: false, reason: this.reason ?? "unavailable" };
|
|
6040
6296
|
}
|
|
6041
|
-
this.deps.log?.("hiwork-core: PMS \u8D26\u53F7\u5DF2\u767B\u5F55\uFF08\u51ED\u636E\u53EA\u5B58\u4E8E Host\uFF09");
|
|
6042
|
-
return this.statusOf(record);
|
|
6043
|
-
}
|
|
6044
|
-
/**
|
|
6045
|
-
* 登出:清内存 + 清凭据记录(spec §4.1 的 `logout`)。
|
|
6046
|
-
*
|
|
6047
|
-
* 不清 PMS 侧的服务端会话:PMS 的 JWT 是无状态签发的(没有「撤销单个 token」的接口),
|
|
6048
|
-
* 我们能做且该做的是**不再持有**它——删掉本地凭据后,下一次取数一定要求重新输入。
|
|
6049
|
-
*/
|
|
6050
|
-
async logout() {
|
|
6051
|
-
this.generation += 1;
|
|
6052
|
-
this.record = void 0;
|
|
6053
|
-
this.loaded = true;
|
|
6054
|
-
this.persisted = false;
|
|
6055
|
-
this.access = void 0;
|
|
6056
|
-
await this.forgetStored();
|
|
6057
|
-
this.deps.log?.("hiwork-core: PMS \u51ED\u636E\u5DF2\u6E05\u9664");
|
|
6058
|
-
return this.statusOf(void 0);
|
|
6059
6297
|
}
|
|
6060
6298
|
/** `GET /api/todo/today` + 本次拉取时刻。同一时刻的并发调用合并成一次 PMS 请求。 */
|
|
6061
6299
|
async today() {
|
|
6062
6300
|
return await this.dedupe("today", async () => {
|
|
6063
|
-
const
|
|
6064
|
-
const items = await this.deps.pms.today(token);
|
|
6301
|
+
const session = await this.authorize();
|
|
6302
|
+
const items = await this.deps.pms.today(session.token);
|
|
6065
6303
|
return { items, fetchedAt: new Date(this.now()).toISOString() };
|
|
6066
6304
|
});
|
|
6067
6305
|
}
|
|
6068
6306
|
/** `GET /api/todo/statistics`。并发调用同样合并。 */
|
|
6069
6307
|
async statistics() {
|
|
6070
6308
|
return await this.dedupe("statistics", async () => {
|
|
6071
|
-
const
|
|
6072
|
-
return await this.deps.pms.statistics(token);
|
|
6309
|
+
const session = await this.authorize();
|
|
6310
|
+
return await this.deps.pms.statistics(session.token);
|
|
6073
6311
|
});
|
|
6074
6312
|
}
|
|
6075
6313
|
/**
|
|
@@ -6080,30 +6318,21 @@ var PmsTodoStore = class {
|
|
|
6080
6318
|
* 的顺序处理」——那个语义 PMS 也不提供。
|
|
6081
6319
|
*/
|
|
6082
6320
|
async setStatus(input) {
|
|
6083
|
-
const
|
|
6084
|
-
return await this.deps.pms.setStatus(token, input.id, input.status);
|
|
6321
|
+
const session = await this.authorize();
|
|
6322
|
+
return await this.deps.pms.setStatus(session.token, input.id, input.status);
|
|
6085
6323
|
}
|
|
6086
6324
|
/**
|
|
6087
6325
|
* `GET /api/task/tasks`:**我的未完成项目任务**(spec §3 D7)。
|
|
6088
6326
|
*
|
|
6089
|
-
* `userId`
|
|
6327
|
+
* `userId` 只从 mint 响应里取(客户端传不进来,见 `parsePmsTodoPayload` 的注释)——这是
|
|
6090
6328
|
* 「只能查自己」唯一可靠的落点:端点白名单里也没有任何「按 id 查别人」的形状。
|
|
6091
6329
|
*
|
|
6092
|
-
* 记录里**没有** `userId`(2026-09-18 之前登录的旧记录)时收敛成 `UNAUTHORIZED`:用户重新登录
|
|
6093
|
-
* 一次即可拿到它。这里刻意**不**回退成「按用户名去查用户列表」之类的动作——那需要额外的接口与
|
|
6094
|
-
* 权限,而且会在 Host 里凭空多一条拿别人数据的路径。
|
|
6095
|
-
*
|
|
6096
6330
|
* 并发调用合并(同 `today`):这是**取数**,同一时刻问两遍没有意义。
|
|
6097
6331
|
*/
|
|
6098
6332
|
async myTasks(opts = {}) {
|
|
6099
6333
|
return await this.dedupe("myTasks", async () => {
|
|
6100
|
-
const
|
|
6101
|
-
|
|
6102
|
-
if (userId === void 0) {
|
|
6103
|
-
this.deps.log?.("hiwork-core: PMS \u51ED\u636E\u8BB0\u5F55\u91CC\u6CA1\u6709 userId\uFF0C\u6211\u7684\u4EFB\u52A1\u9700\u8981\u91CD\u65B0\u767B\u5F55\u4E00\u6B21");
|
|
6104
|
-
throw new PmsTodoError("UNAUTHORIZED", PMS_TODO_UNAUTHORIZED_MESSAGE);
|
|
6105
|
-
}
|
|
6106
|
-
return await this.deps.pms.myTasks(token, userId, opts);
|
|
6334
|
+
const session = await this.authorize();
|
|
6335
|
+
return await this.deps.pms.myTasks(session.token, session.userId, opts);
|
|
6107
6336
|
});
|
|
6108
6337
|
}
|
|
6109
6338
|
/**
|
|
@@ -6116,132 +6345,203 @@ var PmsTodoStore = class {
|
|
|
6116
6345
|
* UI 按 §3 D7 提示「请到 PMS 操作」——**不是**重新登录。
|
|
6117
6346
|
*/
|
|
6118
6347
|
async completeTask(input) {
|
|
6119
|
-
const
|
|
6120
|
-
return await this.deps.pms.completeTask(token, input.id);
|
|
6348
|
+
const session = await this.authorize();
|
|
6349
|
+
return await this.deps.pms.completeTask(session.token, input.id);
|
|
6121
6350
|
}
|
|
6122
6351
|
/**
|
|
6123
|
-
* 取一个能用的
|
|
6352
|
+
* 取一个能用的 PMS JWT(凭据获取的**唯一入口**,spec §3 D10)。
|
|
6124
6353
|
*
|
|
6125
6354
|
* 顺序是刻意的:
|
|
6126
|
-
* 1.
|
|
6127
|
-
* 2.
|
|
6128
|
-
*
|
|
6129
|
-
* 3. refresh 失败(401 / 记录里根本没有 refresh token)→ `UNAUTHORIZED`,UI 要求重新输入。
|
|
6355
|
+
* 1. 内存里有一张「剩余寿命 ≥ 50%」的令牌 → 直接用(零网络请求);
|
|
6356
|
+
* 2. 否则:解析网关会话 → 从分配清单里取 `slug='pms'` 那条(D9)→ 签一张 120 秒断言 →
|
|
6357
|
+
* 打分配的 mint 端点换一张 10 小时 JWT → 存回内存({@link PmsTodoStore.mint})。
|
|
6130
6358
|
*
|
|
6131
|
-
* ⚠️
|
|
6132
|
-
*
|
|
6133
|
-
*
|
|
6134
|
-
* 提交一次 `login` 时才用(那时它也确实是用户刚输入的)。
|
|
6359
|
+
* ⚠️ **断言绝不缓存**:它只有 120 秒、且 PMS 侧拿 `jti` 做一次性防重放,缓存下来只会是一张废纸。
|
|
6360
|
+
* ⚠️ 这里**没有**「拿存下来的账号密码重新登一次」这一支,而且是结构上没有:账号密码通道已删
|
|
6361
|
+
* (spec §3 D3),本文件里也搜不到任何密码字段(§7 的验收项)。
|
|
6135
6362
|
*/
|
|
6136
6363
|
async authorize() {
|
|
6137
|
-
const
|
|
6138
|
-
if (
|
|
6139
|
-
const
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
const refreshToken = record.refreshToken;
|
|
6144
|
-
if (refreshToken === void 0) {
|
|
6145
|
-
throw new PmsTodoError("UNAUTHORIZED", PMS_TODO_UNAUTHORIZED_MESSAGE);
|
|
6146
|
-
}
|
|
6147
|
-
const tokens = await this.deps.pms.refresh(refreshToken);
|
|
6148
|
-
const next = {
|
|
6149
|
-
...record,
|
|
6150
|
-
...tokens.refreshToken === void 0 ? {} : { refreshToken: tokens.refreshToken },
|
|
6151
|
-
expiresAt: tokens.expiresAt
|
|
6152
|
-
};
|
|
6153
|
-
this.record = next;
|
|
6154
|
-
this.access = { token: tokens.accessToken, expiresAt: tokens.expiresAt };
|
|
6155
|
-
if (this.persisted) await this.persist(next);
|
|
6156
|
-
return tokens.accessToken;
|
|
6364
|
+
const cached = this.session;
|
|
6365
|
+
if (cached !== void 0 && this.reusable(cached)) return cached;
|
|
6366
|
+
const cookie = await this.sessionCookie();
|
|
6367
|
+
const gateway = this.gatewayPort();
|
|
6368
|
+
const assignment = await this.ensureAssignment(gateway, cookie);
|
|
6369
|
+
return await this.mint(gateway, cookie, assignment);
|
|
6157
6370
|
}
|
|
6158
6371
|
/**
|
|
6159
|
-
*
|
|
6372
|
+
* 这张令牌还能不能接着用(spec §4.5:剩余寿命 ≥ 50% 才复用)。
|
|
6160
6373
|
*
|
|
6161
|
-
* `
|
|
6162
|
-
*
|
|
6163
|
-
*
|
|
6374
|
+
* 判据与 `hiwork-capabilities/src/connectors/assignments.ts` 的 `isCredentialReusable` 同口径:
|
|
6375
|
+
* 分母是**这张令牌自己的寿命**(`expiresAt - issuedAt`),不是某个写死的 10 小时——服务端将来
|
|
6376
|
+
* 改短有效期,复用口径跟着它走。
|
|
6164
6377
|
*/
|
|
6165
|
-
|
|
6378
|
+
reusable(session) {
|
|
6166
6379
|
const now = this.now();
|
|
6167
|
-
|
|
6168
|
-
const
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6380
|
+
if (now >= session.expiresAt) return false;
|
|
6381
|
+
const lifetime = session.expiresAt - session.issuedAt;
|
|
6382
|
+
if (lifetime <= 0) return false;
|
|
6383
|
+
return session.expiresAt - now >= lifetime * PMS_TODO_REUSE_MIN_REMAINING_RATIO;
|
|
6384
|
+
}
|
|
6385
|
+
/** 网关会话 cookie;缺失即 `UNAUTHORIZED`(动作在 HiWork:重新登录客户端)。 */
|
|
6386
|
+
async sessionCookie() {
|
|
6387
|
+
const cookie = await this.deps.resolveSession();
|
|
6388
|
+
if (cookie === void 0 || cookie === "") {
|
|
6389
|
+
this.fail("gateway-signed-out");
|
|
6390
|
+
throw new PmsTodoError("UNAUTHORIZED", PMS_TODO_UNAUTHORIZED_MESSAGE);
|
|
6391
|
+
}
|
|
6392
|
+
return cookie;
|
|
6393
|
+
}
|
|
6394
|
+
/** 当前 baseURL 对应的网关客户端;没配置 baseURL 时同样是「去 HiWork 登录」这一类动作。 */
|
|
6395
|
+
gatewayPort() {
|
|
6396
|
+
const gateway = this.deps.gateway();
|
|
6397
|
+
if (gateway === void 0) {
|
|
6398
|
+
this.fail("gateway-signed-out");
|
|
6399
|
+
throw new PmsTodoError("UNAUTHORIZED", PMS_TODO_UNAUTHORIZED_MESSAGE);
|
|
6400
|
+
}
|
|
6401
|
+
return gateway;
|
|
6178
6402
|
}
|
|
6179
6403
|
/**
|
|
6180
|
-
*
|
|
6404
|
+
* 取出这条客户端**被分配**的 `pms` 服务(spec §3 D9)。
|
|
6181
6405
|
*
|
|
6182
|
-
*
|
|
6183
|
-
*
|
|
6184
|
-
*
|
|
6406
|
+
* 匹配依据只有 slug(逐字等于 {@link PMS_ASSIGNMENT_SLUG}):清单里还有 visio-mcp 之类的分配,
|
|
6407
|
+
* 「取第一条断言分配」在分配多起来之后会随机串台。**不看 `auth.kind`**:那是服务端演进的开放
|
|
6408
|
+
* 集合,我们只认自己写死的 slug(D9 的原文就是「唯一匹配依据」)。
|
|
6409
|
+
*
|
|
6410
|
+
* 没有这条分配 → `NOT_CONFIGURED`:UI 提示「找管理员」,**不给任何手工配置入口**(spec §6)。
|
|
6185
6411
|
*/
|
|
6186
|
-
async
|
|
6187
|
-
|
|
6188
|
-
|
|
6412
|
+
async ensureAssignment(gateway, cookie) {
|
|
6413
|
+
const cached = this.assignment;
|
|
6414
|
+
if (cached !== void 0) return cached;
|
|
6415
|
+
let view;
|
|
6189
6416
|
try {
|
|
6190
|
-
|
|
6191
|
-
if (hit === void 0 || hit.value === "") return void 0;
|
|
6192
|
-
const record = parseCredentialsRecord(hit.value);
|
|
6193
|
-
this.record = record;
|
|
6194
|
-
this.persisted = record !== void 0;
|
|
6195
|
-
return record;
|
|
6417
|
+
view = await gateway.getMyMcpAssignments(cookie);
|
|
6196
6418
|
} catch (error) {
|
|
6197
|
-
this.
|
|
6198
|
-
return void 0;
|
|
6419
|
+
throw this.gatewayFailure(error);
|
|
6199
6420
|
}
|
|
6421
|
+
const list = Array.isArray(view?.assignments) ? view.assignments : [];
|
|
6422
|
+
for (const item of list) {
|
|
6423
|
+
if (item?.slug !== PMS_ASSIGNMENT_SLUG) continue;
|
|
6424
|
+
const assignmentId = textOf2(item.assignmentId);
|
|
6425
|
+
if (assignmentId === void 0) continue;
|
|
6426
|
+
const assignment = { assignmentId };
|
|
6427
|
+
this.assignment = assignment;
|
|
6428
|
+
this.reason = void 0;
|
|
6429
|
+
return assignment;
|
|
6430
|
+
}
|
|
6431
|
+
this.fail("no-assignment");
|
|
6432
|
+
throw new PmsTodoError("NOT_CONFIGURED", PMS_TODO_NOT_CONFIGURED_MESSAGE);
|
|
6200
6433
|
}
|
|
6201
6434
|
/**
|
|
6202
|
-
*
|
|
6435
|
+
* 签一张断言(120 秒、一次性),并取出它的 `mintUrl`。
|
|
6203
6436
|
*
|
|
6204
|
-
*
|
|
6205
|
-
* 「重启后要重新输入」。反过来把登录判为失败会让用户以为密码错了。
|
|
6437
|
+
* `mintUrl` 由**门票**给(`auth.mintUrl` 的权威副本在网关侧),本层不猜也不改写它。
|
|
6206
6438
|
*/
|
|
6207
|
-
async
|
|
6439
|
+
async assertionTicket(gateway, cookie, assignment) {
|
|
6440
|
+
let ticket;
|
|
6208
6441
|
try {
|
|
6209
|
-
await
|
|
6210
|
-
this.persisted = true;
|
|
6442
|
+
ticket = await gateway.issueMyMcpAssertion(cookie, assignment.assignmentId);
|
|
6211
6443
|
} catch (error) {
|
|
6212
|
-
this.
|
|
6444
|
+
throw this.gatewayFailure(error);
|
|
6445
|
+
}
|
|
6446
|
+
const assertion = textOf2(ticket?.assertion);
|
|
6447
|
+
const mintUrl = textOf2(ticket?.mintUrl);
|
|
6448
|
+
if (assertion === void 0 || mintUrl === void 0) {
|
|
6449
|
+
this.deps.log?.("hiwork-core: PMS \u53D6\u65AD\u8A00\u6536\u5230\u65E0\u6548\u54CD\u5E94\uFF08\u7F3A assertion / mintUrl\uFF09");
|
|
6450
|
+
this.fail("unavailable");
|
|
6451
|
+
throw new PmsTodoError("INTERNAL", PMS_TODO_GENERIC_FAILURE_MESSAGE);
|
|
6213
6452
|
}
|
|
6453
|
+
return { assertion, mintUrl };
|
|
6214
6454
|
}
|
|
6215
6455
|
/**
|
|
6216
|
-
*
|
|
6456
|
+
* 断言 → 令牌(spec §4.1b 的客户端侧),失败语义按 §6 / plan 全局约束 2 分两类:
|
|
6217
6457
|
*
|
|
6218
|
-
*
|
|
6219
|
-
*
|
|
6220
|
-
*
|
|
6458
|
+
* - **被拒(401 / 403)→ 不重试**:断言过期 / `aud` 不符 / 签名不过 / 工号在 PMS 不存在,
|
|
6459
|
+
* 四种原因在 PMS 侧都是同一个 403(防账号探测),重发只会连打;动作是「重新登录 HiWork」。
|
|
6460
|
+
* - **网络 / 5xx → 换一张新断言重试一次**:断言可能是在途耗掉的(120 秒的门票),而同一张断言
|
|
6461
|
+
* 是一次性的(`jti` 防重放),所以重试必须重新签一张。仍失败 → `UPSTREAM_UNAVAILABLE`。
|
|
6462
|
+
*
|
|
6463
|
+
* 日志只记 assignmentId 与事件(**不记断言、不记令牌**,spec §7)。
|
|
6221
6464
|
*/
|
|
6222
|
-
async
|
|
6465
|
+
async mint(gateway, cookie, assignment) {
|
|
6466
|
+
const deviceRef = await this.deviceRef();
|
|
6467
|
+
let attempt = 0;
|
|
6468
|
+
while (attempt < 2) {
|
|
6469
|
+
attempt += 1;
|
|
6470
|
+
const ticket = await this.assertionTicket(gateway, cookie, assignment);
|
|
6471
|
+
try {
|
|
6472
|
+
const session = sessionOf(
|
|
6473
|
+
await this.deps.pms.exchangeAssertion(ticket.mintUrl, ticket.assertion, deviceRef),
|
|
6474
|
+
this.now()
|
|
6475
|
+
);
|
|
6476
|
+
this.session = session;
|
|
6477
|
+
this.reason = void 0;
|
|
6478
|
+
return session;
|
|
6479
|
+
} catch (error) {
|
|
6480
|
+
if (error instanceof PmsError && error.code === "UNAUTHORIZED") {
|
|
6481
|
+
this.deps.log?.(`hiwork-core: PMS \u6362\u4EE4\u724C\u88AB\u62D2\uFF08\u5206\u914D ${assignment.assignmentId}\uFF09`);
|
|
6482
|
+
this.fail("gateway-signed-out");
|
|
6483
|
+
throw new PmsTodoError("UNAUTHORIZED", PMS_TODO_UNAUTHORIZED_MESSAGE);
|
|
6484
|
+
}
|
|
6485
|
+
if (error instanceof PmsError && error.code === "UPSTREAM_UNAVAILABLE" && attempt === 1) {
|
|
6486
|
+
this.deps.log?.(`hiwork-core: PMS \u6362\u4EE4\u724C\u5931\u8D25\uFF08\u5206\u914D ${assignment.assignmentId}\uFF09\uFF0C\u6362\u4E00\u5F20\u65B0\u65AD\u8A00\u91CD\u8BD5\u4E00\u6B21`);
|
|
6487
|
+
continue;
|
|
6488
|
+
}
|
|
6489
|
+
this.fail("unavailable");
|
|
6490
|
+
throw error instanceof PmsError ? error : new PmsTodoError("INTERNAL", PMS_TODO_GENERIC_FAILURE_MESSAGE);
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
this.fail("unavailable");
|
|
6494
|
+
throw new PmsTodoError("UPSTREAM_UNAVAILABLE", PMS_TODO_GENERIC_FAILURE_MESSAGE);
|
|
6495
|
+
}
|
|
6496
|
+
/** 设备标识(只进 PMS 侧审计、不参与鉴权):解析失败留空,不拦登录。 */
|
|
6497
|
+
async deviceRef() {
|
|
6223
6498
|
try {
|
|
6224
|
-
|
|
6225
|
-
if (hit === void 0 || hit.value === "") return;
|
|
6226
|
-
await this.deps.credentials.unset(PMS_CREDENTIALS_REF);
|
|
6499
|
+
return await this.deps.resolveDeviceId();
|
|
6227
6500
|
} catch (error) {
|
|
6228
|
-
this.deps.log?.(`hiwork-core:
|
|
6501
|
+
this.deps.log?.(`hiwork-core: \u8BBE\u5907\u6807\u8BC6\u89E3\u6790\u5931\u8D25\uFF08${errorName(error)}\uFF09\uFF0CdeviceRef \u7559\u7A7A`);
|
|
6502
|
+
return "";
|
|
6503
|
+
}
|
|
6504
|
+
}
|
|
6505
|
+
/**
|
|
6506
|
+
* 网关异常 → 本频道的稳定错误(spec §3 D8)。
|
|
6507
|
+
*
|
|
6508
|
+
* 关键的分法:**会话失效**与**分配被撤销/停用**要分开。前者是用户自己的动作(重新登录 HiWork),
|
|
6509
|
+
* 后者是管理员那边的动作(重新分配)——归到同一句话,用户就会对着一个自己修不了的问题反复重试。
|
|
6510
|
+
*/
|
|
6511
|
+
gatewayFailure(error) {
|
|
6512
|
+
this.deps.log?.(`hiwork-core: PMS \u7F51\u5173\u8C03\u7528\u5931\u8D25\uFF08${gatewayCodeOf(error)}\uFF09`);
|
|
6513
|
+
if (error instanceof GatewayError) {
|
|
6514
|
+
if (error.code === "SESSION_EXPIRED") {
|
|
6515
|
+
this.fail("gateway-signed-out");
|
|
6516
|
+
return new PmsTodoError("UNAUTHORIZED", PMS_TODO_UNAUTHORIZED_MESSAGE);
|
|
6517
|
+
}
|
|
6518
|
+
if (error.code === "MCP_ASSIGNMENT_GONE" || error.code === "MCP_NOT_DELIVERABLE") {
|
|
6519
|
+
this.assignment = void 0;
|
|
6520
|
+
this.fail("no-assignment");
|
|
6521
|
+
return new PmsTodoError("NOT_CONFIGURED", PMS_TODO_NOT_CONFIGURED_MESSAGE);
|
|
6522
|
+
}
|
|
6229
6523
|
}
|
|
6524
|
+
this.fail("unavailable");
|
|
6525
|
+
return new PmsTodoError("UPSTREAM_UNAVAILABLE", PMS_TODO_GENERIC_FAILURE_MESSAGE);
|
|
6526
|
+
}
|
|
6527
|
+
/** 记下「为什么还没就绪」(只影响 `status` 的 `reason`,不影响错误码)。 */
|
|
6528
|
+
fail(reason) {
|
|
6529
|
+
this.reason = reason;
|
|
6230
6530
|
}
|
|
6231
6531
|
/**
|
|
6232
|
-
*
|
|
6532
|
+
* 并发合并:同一端点的在飞请求共用一次调用与同一个返回值(面板挂载与手动刷新常常同拍)。
|
|
6233
6533
|
*
|
|
6234
|
-
*
|
|
6235
|
-
*
|
|
6534
|
+
* v1 还带一个「世代号」(`login` / `logout` 之后账上那条属于上一个人);现在**身份只有一个来源**
|
|
6535
|
+
* (网关会话),进程内没有换人的入口,所以按端点名合并即可。换账号的清理由客户端半边的
|
|
6536
|
+
* `clear()`(镜像)与会话本身负责。
|
|
6236
6537
|
*/
|
|
6237
6538
|
async dedupe(key, task) {
|
|
6238
6539
|
const inFlight = this.inFlight;
|
|
6239
|
-
if (inFlight !== void 0 && inFlight.
|
|
6540
|
+
if (inFlight !== void 0 && inFlight.key === key) {
|
|
6240
6541
|
return await inFlight.task;
|
|
6241
6542
|
}
|
|
6242
|
-
const generation = this.generation;
|
|
6243
6543
|
const promise = task();
|
|
6244
|
-
this.inFlight = {
|
|
6544
|
+
this.inFlight = { key, task: promise };
|
|
6245
6545
|
try {
|
|
6246
6546
|
return await promise;
|
|
6247
6547
|
} finally {
|
|
@@ -6251,7 +6551,7 @@ var PmsTodoStore = class {
|
|
|
6251
6551
|
};
|
|
6252
6552
|
async function handlePmsTodoEndpoint(endpoint, rawPayload, deps, store, signal) {
|
|
6253
6553
|
try {
|
|
6254
|
-
if (signal?.aborted === true) return
|
|
6554
|
+
if (signal?.aborted === true) return failure8("BAD_REQUEST", PMS_TODO_CANCELLED_MESSAGE);
|
|
6255
6555
|
if (!isPmsTodoRpcEndpoint(endpoint)) {
|
|
6256
6556
|
throw new PmsTodoRequestError(`\u672A\u77E5\u7684 PMS \u5F85\u529E\u7AEF\u70B9 '${endpoint}'`);
|
|
6257
6557
|
}
|
|
@@ -6259,10 +6559,6 @@ async function handlePmsTodoEndpoint(endpoint, rawPayload, deps, store, signal)
|
|
|
6259
6559
|
switch (payload.endpoint) {
|
|
6260
6560
|
case "status":
|
|
6261
6561
|
return { ok: true, value: await store.status() };
|
|
6262
|
-
case "login":
|
|
6263
|
-
return { ok: true, value: await store.login({ username: payload.username, password: payload.password, remember: payload.remember }) };
|
|
6264
|
-
case "logout":
|
|
6265
|
-
return { ok: true, value: await store.logout() };
|
|
6266
6562
|
case "today":
|
|
6267
6563
|
return { ok: true, value: await store.today() };
|
|
6268
6564
|
case "statistics":
|
|
@@ -6279,7 +6575,7 @@ async function handlePmsTodoEndpoint(endpoint, rawPayload, deps, store, signal)
|
|
|
6279
6575
|
return { ok: false, error: pmsTodoErrorOf(error) };
|
|
6280
6576
|
}
|
|
6281
6577
|
}
|
|
6282
|
-
function
|
|
6578
|
+
function failure8(code, message) {
|
|
6283
6579
|
return { ok: false, error: { code, message, details: {} } };
|
|
6284
6580
|
}
|
|
6285
6581
|
function registerPmsTodoRpc(ctx, deps, store) {
|
|
@@ -6310,7 +6606,7 @@ function isRecord4(value) {
|
|
|
6310
6606
|
function textOrUndefined4(value) {
|
|
6311
6607
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
6312
6608
|
}
|
|
6313
|
-
function
|
|
6609
|
+
function isAborted8(signal) {
|
|
6314
6610
|
return signal?.aborted === true;
|
|
6315
6611
|
}
|
|
6316
6612
|
function parsePluginUpdatesPayload(endpoint, raw) {
|
|
@@ -6353,7 +6649,7 @@ function toSnapshot(raw) {
|
|
|
6353
6649
|
}
|
|
6354
6650
|
async function handlePluginUpdatesEndpoint(endpoint, rawPayload, deps, signal) {
|
|
6355
6651
|
try {
|
|
6356
|
-
if (
|
|
6652
|
+
if (isAborted8(signal)) return failure9("cancelled", "\u63D2\u4EF6\u66F4\u65B0\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
6357
6653
|
if (!isPluginUpdatesRpcEndpoint(endpoint)) {
|
|
6358
6654
|
throw new PluginUpdatesRequestError("bad-request", `\u672A\u77E5\u7684\u63D2\u4EF6\u66F4\u65B0\u7AEF\u70B9 '${endpoint}'`);
|
|
6359
6655
|
}
|
|
@@ -6369,12 +6665,12 @@ async function handlePluginUpdatesEndpoint(endpoint, rawPayload, deps, signal) {
|
|
|
6369
6665
|
await plugins.applyNow();
|
|
6370
6666
|
return { ok: true, value: { requested: true } };
|
|
6371
6667
|
} catch (error) {
|
|
6372
|
-
if (error instanceof PluginUpdatesRequestError) return
|
|
6668
|
+
if (error instanceof PluginUpdatesRequestError) return failure9(error.code, error.message);
|
|
6373
6669
|
deps.log?.(`hiwork-core: \u63D2\u4EF6\u66F4\u65B0 RPC '${endpoint}' \u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
6374
|
-
return
|
|
6670
|
+
return failure9("internal", PLUGIN_UPDATES_GENERIC_FAILURE_MESSAGE);
|
|
6375
6671
|
}
|
|
6376
6672
|
}
|
|
6377
|
-
function
|
|
6673
|
+
function failure9(code, message) {
|
|
6378
6674
|
return { ok: false, error: { code, message, details: {} } };
|
|
6379
6675
|
}
|
|
6380
6676
|
function registerPluginUpdatesRpc(ctx, deps) {
|
|
@@ -6445,16 +6741,15 @@ function provideIdentity(ctx, deps) {
|
|
|
6445
6741
|
};
|
|
6446
6742
|
}
|
|
6447
6743
|
|
|
6448
|
-
// src/
|
|
6449
|
-
var
|
|
6450
|
-
var
|
|
6451
|
-
var
|
|
6452
|
-
var
|
|
6453
|
-
var MESSAGE_NOT_CONFIGURED = "\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\
|
|
6744
|
+
// src/knowledge-config.ts
|
|
6745
|
+
var HIWORK_KNOWLEDGE_SERVICE = "hiworkKnowledge";
|
|
6746
|
+
var ME_KNOWLEDGE_CONFIG_PATH = "/api/me/knowledge-config";
|
|
6747
|
+
var KNOWLEDGE_CONFIG_TTL_MS = 5 * 60 * 1e3;
|
|
6748
|
+
var KNOWLEDGE_CONFIG_TIMEOUT_MS = 1e4;
|
|
6749
|
+
var MESSAGE_NOT_CONFIGURED = "\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\u77E5\u8BC6\u5E93\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u5F00\u901A";
|
|
6454
6750
|
var MESSAGE_UNAUTHENTICATED = "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55";
|
|
6455
|
-
var MESSAGE_UNREACHABLE = "\
|
|
6456
|
-
var
|
|
6457
|
-
var MESSAGE_GENERIC = "\u8BB0\u5FC6\u914D\u7F6E\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
6751
|
+
var MESSAGE_UNREACHABLE = "\u77E5\u8BC6\u5E93\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
6752
|
+
var MESSAGE_GENERIC = "\u77E5\u8BC6\u5E93\u914D\u7F6E\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
6458
6753
|
function messageFor(reason) {
|
|
6459
6754
|
switch (reason) {
|
|
6460
6755
|
case "not-configured":
|
|
@@ -6467,7 +6762,7 @@ function messageFor(reason) {
|
|
|
6467
6762
|
return MESSAGE_GENERIC;
|
|
6468
6763
|
}
|
|
6469
6764
|
}
|
|
6470
|
-
function
|
|
6765
|
+
function failure10(reason, message) {
|
|
6471
6766
|
return { ok: false, reason, message: message ?? messageFor(reason) };
|
|
6472
6767
|
}
|
|
6473
6768
|
function textOrUndefined6(value) {
|
|
@@ -6475,26 +6770,17 @@ function textOrUndefined6(value) {
|
|
|
6475
6770
|
const text = value.trim();
|
|
6476
6771
|
return text === "" ? void 0 : text;
|
|
6477
6772
|
}
|
|
6478
|
-
function
|
|
6479
|
-
|
|
6480
|
-
const
|
|
6481
|
-
|
|
6482
|
-
const text = textOrUndefined6(item);
|
|
6483
|
-
if (text !== void 0) ids.push(text);
|
|
6484
|
-
}
|
|
6485
|
-
return ids;
|
|
6486
|
-
}
|
|
6487
|
-
function projectMemoryConfig(body) {
|
|
6488
|
-
const panelUrl = textOrUndefined6(body["panel_url"] ?? body["panelUrl"]);
|
|
6489
|
-
const serviceId = textOrUndefined6(body["service_id"] ?? body["serviceId"]);
|
|
6490
|
-
const apiKey = textOrUndefined6(body["api_key"] ?? body["apiKey"] ?? body["userKey"]);
|
|
6491
|
-
if (panelUrl === void 0 || serviceId === void 0 || apiKey === void 0) return void 0;
|
|
6773
|
+
function projectKnowledgeConfig(body) {
|
|
6774
|
+
const baseUrl = textOrUndefined6(body["base_url"] ?? body["baseUrl"]);
|
|
6775
|
+
const apiKey = textOrUndefined6(body["api_key"] ?? body["apiKey"]);
|
|
6776
|
+
if (baseUrl === void 0 || apiKey === void 0) return void 0;
|
|
6492
6777
|
return {
|
|
6493
6778
|
ok: true,
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6779
|
+
baseUrl,
|
|
6780
|
+
apiKey,
|
|
6781
|
+
agentId: textOrUndefined6(body["agent_id"] ?? body["agentId"]) ?? "",
|
|
6782
|
+
chatModelId: textOrUndefined6(body["chat_model_id"] ?? body["chatModelId"]) ?? "",
|
|
6783
|
+
tenantId: textOrUndefined6(body["tenant_id"] ?? body["tenantId"]) ?? ""
|
|
6498
6784
|
};
|
|
6499
6785
|
}
|
|
6500
6786
|
function errorName2(error) {
|
|
@@ -6506,9 +6792,9 @@ function reasonOfErrorCode(error) {
|
|
|
6506
6792
|
switch (code) {
|
|
6507
6793
|
case "SESSION_EXPIRED":
|
|
6508
6794
|
return "unauthenticated";
|
|
6509
|
-
case "
|
|
6795
|
+
case "KNOWLEDGE_NOT_CONFIGURED":
|
|
6510
6796
|
return "not-configured";
|
|
6511
|
-
case "
|
|
6797
|
+
case "KNOWLEDGE_UNREACHABLE":
|
|
6512
6798
|
case "GATEWAY_UNREACHABLE":
|
|
6513
6799
|
return "unreachable";
|
|
6514
6800
|
default:
|
|
@@ -6521,6 +6807,168 @@ function reasonOfStatus(status) {
|
|
|
6521
6807
|
if (status === 504) return "unreachable";
|
|
6522
6808
|
return "error";
|
|
6523
6809
|
}
|
|
6810
|
+
var KnowledgeConfigProvider = class {
|
|
6811
|
+
constructor(deps) {
|
|
6812
|
+
this.deps = deps;
|
|
6813
|
+
}
|
|
6814
|
+
cached;
|
|
6815
|
+
async config(opts) {
|
|
6816
|
+
const force = opts?.force === true;
|
|
6817
|
+
let sessionCookie;
|
|
6818
|
+
try {
|
|
6819
|
+
sessionCookie = await this.deps.resolveSession();
|
|
6820
|
+
} catch (error) {
|
|
6821
|
+
this.deps.log?.(`hiwork-core: \u77E5\u8BC6\u5E93\u914D\u7F6E\u83B7\u53D6\u5931\u8D25\uFF08${errorName2(error)}\uFF09`);
|
|
6822
|
+
return failure10("error");
|
|
6823
|
+
}
|
|
6824
|
+
if (sessionCookie === void 0 || sessionCookie === "") return failure10("unauthenticated");
|
|
6825
|
+
if (!force) {
|
|
6826
|
+
const hit = this.cached;
|
|
6827
|
+
if (hit !== void 0 && hit.sessionCookie === sessionCookie && this.now() - hit.at < KNOWLEDGE_CONFIG_TTL_MS) {
|
|
6828
|
+
return hit.view;
|
|
6829
|
+
}
|
|
6830
|
+
}
|
|
6831
|
+
const gateway = this.deps.gateway();
|
|
6832
|
+
if (gateway === void 0) return failure10("unreachable");
|
|
6833
|
+
let outcome;
|
|
6834
|
+
try {
|
|
6835
|
+
outcome = await gateway.getMyKnowledgeConfig(sessionCookie);
|
|
6836
|
+
} catch (error) {
|
|
6837
|
+
const mapped = reasonOfErrorCode(error);
|
|
6838
|
+
if (mapped !== void 0) return failure10(mapped);
|
|
6839
|
+
this.deps.log?.(`hiwork-core: \u77E5\u8BC6\u5E93\u914D\u7F6E\u83B7\u53D6\u5931\u8D25\uFF08${errorName2(error)}\uFF09`);
|
|
6840
|
+
return failure10("error");
|
|
6841
|
+
}
|
|
6842
|
+
if (!outcome.ok) {
|
|
6843
|
+
if (outcome.kind === "network") return failure10("unreachable");
|
|
6844
|
+
if (outcome.kind === "http") return failure10(reasonOfStatus(outcome.status));
|
|
6845
|
+
return failure10("error");
|
|
6846
|
+
}
|
|
6847
|
+
const view = projectKnowledgeConfig(outcome.body);
|
|
6848
|
+
if (view === void 0) {
|
|
6849
|
+
this.deps.log?.("hiwork-core: \u7F51\u5173\u8FD4\u56DE\u7684 knowledge-config \u4E0D\u5B8C\u6574\uFF08\u7F3A base_url / api_key \u4E4B\u4E00\uFF09");
|
|
6850
|
+
return failure10("error");
|
|
6851
|
+
}
|
|
6852
|
+
this.cached = { sessionCookie, view, at: this.now() };
|
|
6853
|
+
return view;
|
|
6854
|
+
}
|
|
6855
|
+
now() {
|
|
6856
|
+
return (this.deps.now ?? Date.now)();
|
|
6857
|
+
}
|
|
6858
|
+
};
|
|
6859
|
+
function provideKnowledgeConfig(ctx, deps) {
|
|
6860
|
+
const service = new KnowledgeConfigProvider(deps);
|
|
6861
|
+
const dispose = ctx.provide(HIWORK_KNOWLEDGE_SERVICE, service);
|
|
6862
|
+
return typeof dispose === "function" ? dispose : () => {
|
|
6863
|
+
};
|
|
6864
|
+
}
|
|
6865
|
+
function createKnowledgeConfigGateway(baseURL, opts = {}) {
|
|
6866
|
+
const root = baseURL.replace(/\/+$/, "");
|
|
6867
|
+
const timeoutMs = opts.timeoutMs ?? KNOWLEDGE_CONFIG_TIMEOUT_MS;
|
|
6868
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
6869
|
+
return {
|
|
6870
|
+
async getMyKnowledgeConfig(sessionCookie) {
|
|
6871
|
+
try {
|
|
6872
|
+
const res = await doFetch(`${root}${ME_KNOWLEDGE_CONFIG_PATH}`, {
|
|
6873
|
+
method: "GET",
|
|
6874
|
+
headers: {
|
|
6875
|
+
"user-agent": `hiwork-core/${PKG_VERSION}`,
|
|
6876
|
+
cookie: sessionCookie
|
|
6877
|
+
},
|
|
6878
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
6879
|
+
});
|
|
6880
|
+
if (!res.ok) return { ok: false, kind: "http", status: res.status };
|
|
6881
|
+
let body;
|
|
6882
|
+
try {
|
|
6883
|
+
body = await res.json();
|
|
6884
|
+
} catch {
|
|
6885
|
+
return { ok: false, kind: "invalid" };
|
|
6886
|
+
}
|
|
6887
|
+
if (body === null || typeof body !== "object") return { ok: false, kind: "invalid" };
|
|
6888
|
+
return { ok: true, body };
|
|
6889
|
+
} catch {
|
|
6890
|
+
return { ok: false, kind: "network" };
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6893
|
+
};
|
|
6894
|
+
}
|
|
6895
|
+
|
|
6896
|
+
// src/memory-config.ts
|
|
6897
|
+
var HIWORK_MEMORY_SERVICE = "hiworkMemory";
|
|
6898
|
+
var ME_MEMORY_CONFIG_PATH = "/api/me/memory-config";
|
|
6899
|
+
var MEMORY_CONFIG_TTL_MS = 5 * 60 * 1e3;
|
|
6900
|
+
var MEMORY_CONFIG_TIMEOUT_MS = 1e4;
|
|
6901
|
+
var MESSAGE_NOT_CONFIGURED2 = "\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\u56E2\u961F\u8BB0\u5FC6\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u5F00\u901A";
|
|
6902
|
+
var MESSAGE_UNAUTHENTICATED2 = "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55";
|
|
6903
|
+
var MESSAGE_UNREACHABLE2 = "\u8BB0\u5FC6\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
6904
|
+
var MESSAGE_NO_GATEWAY = "\u8FD8\u6CA1\u8FDE\u4E0A\u4F01\u4E1A\u7F51\u5173\uFF0C\u8BF7\u5148\u767B\u5F55\u540E\u518D\u8BD5";
|
|
6905
|
+
var MESSAGE_GENERIC2 = "\u8BB0\u5FC6\u914D\u7F6E\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
6906
|
+
function messageFor2(reason) {
|
|
6907
|
+
switch (reason) {
|
|
6908
|
+
case "not-configured":
|
|
6909
|
+
return MESSAGE_NOT_CONFIGURED2;
|
|
6910
|
+
case "unauthenticated":
|
|
6911
|
+
return MESSAGE_UNAUTHENTICATED2;
|
|
6912
|
+
case "unreachable":
|
|
6913
|
+
return MESSAGE_UNREACHABLE2;
|
|
6914
|
+
case "error":
|
|
6915
|
+
return MESSAGE_GENERIC2;
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
function failure11(reason, message) {
|
|
6919
|
+
return { ok: false, reason, message: message ?? messageFor2(reason) };
|
|
6920
|
+
}
|
|
6921
|
+
function textOrUndefined7(value) {
|
|
6922
|
+
if (typeof value !== "string") return void 0;
|
|
6923
|
+
const text = value.trim();
|
|
6924
|
+
return text === "" ? void 0 : text;
|
|
6925
|
+
}
|
|
6926
|
+
function teamIdsOf(value) {
|
|
6927
|
+
if (!Array.isArray(value)) return [];
|
|
6928
|
+
const ids = [];
|
|
6929
|
+
for (const item of value) {
|
|
6930
|
+
const text = textOrUndefined7(item);
|
|
6931
|
+
if (text !== void 0) ids.push(text);
|
|
6932
|
+
}
|
|
6933
|
+
return ids;
|
|
6934
|
+
}
|
|
6935
|
+
function projectMemoryConfig(body) {
|
|
6936
|
+
const panelUrl = textOrUndefined7(body["panel_url"] ?? body["panelUrl"]);
|
|
6937
|
+
const serviceId = textOrUndefined7(body["service_id"] ?? body["serviceId"]);
|
|
6938
|
+
const apiKey = textOrUndefined7(body["api_key"] ?? body["apiKey"] ?? body["userKey"]);
|
|
6939
|
+
if (panelUrl === void 0 || serviceId === void 0 || apiKey === void 0) return void 0;
|
|
6940
|
+
return {
|
|
6941
|
+
ok: true,
|
|
6942
|
+
panelUrl,
|
|
6943
|
+
serviceId,
|
|
6944
|
+
teamIds: teamIdsOf(body["team_ids"] ?? body["teamIds"]),
|
|
6945
|
+
apiKey
|
|
6946
|
+
};
|
|
6947
|
+
}
|
|
6948
|
+
function errorName3(error) {
|
|
6949
|
+
return error instanceof Error ? error.name : typeof error;
|
|
6950
|
+
}
|
|
6951
|
+
function reasonOfErrorCode2(error) {
|
|
6952
|
+
const code = error?.code;
|
|
6953
|
+
if (typeof code !== "string") return void 0;
|
|
6954
|
+
switch (code) {
|
|
6955
|
+
case "SESSION_EXPIRED":
|
|
6956
|
+
return "unauthenticated";
|
|
6957
|
+
case "MEMORY_NOT_CONFIGURED":
|
|
6958
|
+
return "not-configured";
|
|
6959
|
+
case "MEMORY_UNREACHABLE":
|
|
6960
|
+
case "GATEWAY_UNREACHABLE":
|
|
6961
|
+
return "unreachable";
|
|
6962
|
+
default:
|
|
6963
|
+
return void 0;
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
function reasonOfStatus2(status) {
|
|
6967
|
+
if (status === 401) return "unauthenticated";
|
|
6968
|
+
if (status === 503) return "not-configured";
|
|
6969
|
+
if (status === 504) return "unreachable";
|
|
6970
|
+
return "error";
|
|
6971
|
+
}
|
|
6524
6972
|
var MemoryConfigProvider = class {
|
|
6525
6973
|
constructor(deps) {
|
|
6526
6974
|
this.deps = deps;
|
|
@@ -6532,10 +6980,10 @@ var MemoryConfigProvider = class {
|
|
|
6532
6980
|
try {
|
|
6533
6981
|
sessionCookie = await this.deps.resolveSession();
|
|
6534
6982
|
} catch (error) {
|
|
6535
|
-
this.deps.log?.(`hiwork-core: \u8BB0\u5FC6\u914D\u7F6E\u83B7\u53D6\u5931\u8D25\uFF08${
|
|
6536
|
-
return
|
|
6983
|
+
this.deps.log?.(`hiwork-core: \u8BB0\u5FC6\u914D\u7F6E\u83B7\u53D6\u5931\u8D25\uFF08${errorName3(error)}\uFF09`);
|
|
6984
|
+
return failure11("error");
|
|
6537
6985
|
}
|
|
6538
|
-
if (sessionCookie === void 0 || sessionCookie === "") return
|
|
6986
|
+
if (sessionCookie === void 0 || sessionCookie === "") return failure11("unauthenticated");
|
|
6539
6987
|
if (!force) {
|
|
6540
6988
|
const hit = this.cached;
|
|
6541
6989
|
if (hit !== void 0 && hit.sessionCookie === sessionCookie && this.now() - hit.at < MEMORY_CONFIG_TTL_MS) {
|
|
@@ -6543,25 +6991,25 @@ var MemoryConfigProvider = class {
|
|
|
6543
6991
|
}
|
|
6544
6992
|
}
|
|
6545
6993
|
const gateway = this.deps.gateway();
|
|
6546
|
-
if (gateway === void 0) return
|
|
6994
|
+
if (gateway === void 0) return failure11("unreachable", MESSAGE_NO_GATEWAY);
|
|
6547
6995
|
let outcome;
|
|
6548
6996
|
try {
|
|
6549
6997
|
outcome = await gateway.getMyMemoryConfig(sessionCookie);
|
|
6550
6998
|
} catch (error) {
|
|
6551
|
-
const mapped =
|
|
6552
|
-
if (mapped !== void 0) return
|
|
6553
|
-
this.deps.log?.(`hiwork-core: \u8BB0\u5FC6\u914D\u7F6E\u83B7\u53D6\u5931\u8D25\uFF08${
|
|
6554
|
-
return
|
|
6999
|
+
const mapped = reasonOfErrorCode2(error);
|
|
7000
|
+
if (mapped !== void 0) return failure11(mapped);
|
|
7001
|
+
this.deps.log?.(`hiwork-core: \u8BB0\u5FC6\u914D\u7F6E\u83B7\u53D6\u5931\u8D25\uFF08${errorName3(error)}\uFF09`);
|
|
7002
|
+
return failure11("error");
|
|
6555
7003
|
}
|
|
6556
7004
|
if (!outcome.ok) {
|
|
6557
|
-
if (outcome.kind === "network") return
|
|
6558
|
-
if (outcome.kind === "http") return
|
|
6559
|
-
return
|
|
7005
|
+
if (outcome.kind === "network") return failure11("unreachable");
|
|
7006
|
+
if (outcome.kind === "http") return failure11(reasonOfStatus2(outcome.status));
|
|
7007
|
+
return failure11("error");
|
|
6560
7008
|
}
|
|
6561
7009
|
const view = projectMemoryConfig(outcome.body);
|
|
6562
7010
|
if (view === void 0) {
|
|
6563
7011
|
this.deps.log?.("hiwork-core: \u7F51\u5173\u8FD4\u56DE\u7684 memory-config \u4E0D\u5B8C\u6574\uFF08\u7F3A panel_url / service_id / api_key \u4E4B\u4E00\uFF09");
|
|
6564
|
-
return
|
|
7012
|
+
return failure11("error");
|
|
6565
7013
|
}
|
|
6566
7014
|
this.cached = { sessionCookie, view, at: this.now() };
|
|
6567
7015
|
return view;
|
|
@@ -6754,9 +7202,1352 @@ function provideOfficeRelay(ctx, deps) {
|
|
|
6754
7202
|
};
|
|
6755
7203
|
}
|
|
6756
7204
|
|
|
7205
|
+
// node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-llm/lib/index.js
|
|
7206
|
+
import { createRequire } from "node:module";
|
|
7207
|
+
|
|
7208
|
+
// node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
|
|
7209
|
+
var RemoteError = class extends Error {
|
|
7210
|
+
code;
|
|
7211
|
+
details;
|
|
7212
|
+
/** Structural marker: cross-realm/bundle identification never uses instanceof. */
|
|
7213
|
+
isDSHRemoteError = true;
|
|
7214
|
+
/**
|
|
7215
|
+
* @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
|
|
7216
|
+
* @param message - human diagnostic carried across the wire.
|
|
7217
|
+
* @param details - structured payload typed by the code.
|
|
7218
|
+
* @param options - standard Error options (`cause` survives in-process only).
|
|
7219
|
+
*/
|
|
7220
|
+
constructor(code, message, details, options) {
|
|
7221
|
+
super(message, options);
|
|
7222
|
+
this.code = code;
|
|
7223
|
+
this.details = details;
|
|
7224
|
+
this.name = "RemoteError";
|
|
7225
|
+
}
|
|
7226
|
+
};
|
|
7227
|
+
var TYPERT_REMOTE_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
|
|
7228
|
+
function isTypertRemoteSegment(value) {
|
|
7229
|
+
return value !== "." && value !== ".." && TYPERT_REMOTE_SEGMENT_PATTERN.test(value);
|
|
7230
|
+
}
|
|
7231
|
+
var REMOTE_METHOD_DESCRIPTOR = "@deepseek-ai/dsh-typert-protocol/remote-methods";
|
|
7232
|
+
function bindTypertRemote(service, serviceKey, options = {}) {
|
|
7233
|
+
validateName("service key", serviceKey);
|
|
7234
|
+
const namespace = options.namespace ?? serviceKey;
|
|
7235
|
+
validateName("namespace", namespace);
|
|
7236
|
+
return Object.freeze({
|
|
7237
|
+
service,
|
|
7238
|
+
serviceKey,
|
|
7239
|
+
namespace
|
|
7240
|
+
});
|
|
7241
|
+
}
|
|
7242
|
+
var TypertRemoteService = class extends Service {
|
|
7243
|
+
/** Visible binding consumed by the Gateway's source-mode discovery. */
|
|
7244
|
+
typertRemote;
|
|
7245
|
+
/**
|
|
7246
|
+
* Register the Service and bind the same key to Typert Gateway.
|
|
7247
|
+
* @param ctx - owning Cordis Context.
|
|
7248
|
+
* @param serviceKey - exact Cordis service key and default wire namespace.
|
|
7249
|
+
* @param options - optional distinct wire namespace.
|
|
7250
|
+
*/
|
|
7251
|
+
constructor(ctx, serviceKey, options = {}) {
|
|
7252
|
+
super(ctx, serviceKey);
|
|
7253
|
+
this.typertRemote = bindTypertRemote(this, this.name, options);
|
|
7254
|
+
}
|
|
7255
|
+
};
|
|
7256
|
+
function Remote(methodExportOrOptions, context) {
|
|
7257
|
+
if (typeof methodExportOrOptions === "string") {
|
|
7258
|
+
validateName("Remote export name", methodExportOrOptions);
|
|
7259
|
+
return remoteDecorator({ kind: "direct" }, void 0, methodExportOrOptions);
|
|
7260
|
+
}
|
|
7261
|
+
if (typeof methodExportOrOptions === "object") {
|
|
7262
|
+
if (remoteOptionMode(methodExportOrOptions) !== "stream" || Reflect.ownKeys(methodExportOrOptions).length !== 1) throw new TypeError('typert-protocol: Remote options must contain exactly mode: "stream"');
|
|
7263
|
+
return remoteDecorator({ kind: "direct" }, "stream");
|
|
7264
|
+
}
|
|
7265
|
+
if (context === void 0) throw new TypeError("typert-protocol: Remote decorator context is missing");
|
|
7266
|
+
addMarkerInitializer(context, { kind: "direct" });
|
|
7267
|
+
}
|
|
7268
|
+
function remoteOptionMode(options) {
|
|
7269
|
+
return Reflect.get(options, "mode");
|
|
7270
|
+
}
|
|
7271
|
+
function remoteDecorator(invocation, mode, exportName) {
|
|
7272
|
+
return function(_method, context) {
|
|
7273
|
+
addMarkerInitializer(context, invocation, mode, exportName);
|
|
7274
|
+
};
|
|
7275
|
+
}
|
|
7276
|
+
function readRemoteMethodDescriptor(prototype) {
|
|
7277
|
+
const property2 = Object.getOwnPropertyDescriptor(prototype, REMOTE_METHOD_DESCRIPTOR);
|
|
7278
|
+
if (property2 === void 0) return void 0;
|
|
7279
|
+
const descriptor = property2.value;
|
|
7280
|
+
if (descriptor === null || typeof descriptor !== "object") throw new TypeError("typert-protocol: Remote method descriptor must be an object");
|
|
7281
|
+
const version2 = Reflect.get(descriptor, "version");
|
|
7282
|
+
if (version2 !== 1) throw new TypeError(`typert-protocol: unsupported Remote method descriptor version ${String(version2)}`);
|
|
7283
|
+
const methods = Reflect.get(descriptor, "methods");
|
|
7284
|
+
if (!Array.isArray(methods)) throw new TypeError("typert-protocol: Remote method descriptor methods must be an array");
|
|
7285
|
+
return descriptor;
|
|
7286
|
+
}
|
|
7287
|
+
function addMarkerInitializer(context, invocation, mode, exportName) {
|
|
7288
|
+
if (context.private || context.static || typeof context.name !== "string") throw new TypeError("typert-protocol: Remote decorators require a public instance method with a string name");
|
|
7289
|
+
const method = context.name;
|
|
7290
|
+
context.addInitializer(function() {
|
|
7291
|
+
const prototype = Object.getPrototypeOf(this);
|
|
7292
|
+
if (prototype === null) throw new TypeError(`typert-protocol: cannot mark Remote method "${method}" on an object without a prototype`);
|
|
7293
|
+
mark(prototype, method, invocation, mode, exportName);
|
|
7294
|
+
});
|
|
7295
|
+
}
|
|
7296
|
+
function mark(prototype, method, invocation, mode, exportName) {
|
|
7297
|
+
const descriptor = readRemoteMethodDescriptor(prototype);
|
|
7298
|
+
const marker = Object.freeze({
|
|
7299
|
+
method,
|
|
7300
|
+
...exportName === void 0 || exportName === method ? {} : { exportName },
|
|
7301
|
+
...mode === void 0 ? {} : { mode },
|
|
7302
|
+
invocation: Object.freeze(invocation)
|
|
7303
|
+
});
|
|
7304
|
+
const current = descriptor?.methods.find((candidate) => candidate.method === method);
|
|
7305
|
+
if (current !== void 0) {
|
|
7306
|
+
if (current.exportName === marker.exportName && current.mode === marker.mode && sameInvocation(current.invocation, invocation)) return;
|
|
7307
|
+
throw new Error(`typert-protocol: Remote method "${method}" has conflicting invocation markers`);
|
|
7308
|
+
}
|
|
7309
|
+
Object.defineProperty(prototype, REMOTE_METHOD_DESCRIPTOR, {
|
|
7310
|
+
configurable: true,
|
|
7311
|
+
value: Object.freeze({
|
|
7312
|
+
version: 1,
|
|
7313
|
+
methods: Object.freeze([...descriptor?.methods ?? [], marker])
|
|
7314
|
+
})
|
|
7315
|
+
});
|
|
7316
|
+
}
|
|
7317
|
+
function sameInvocation(left, right) {
|
|
7318
|
+
if (left.kind === "direct") return right.kind === "direct";
|
|
7319
|
+
if (right.kind === "direct") return false;
|
|
7320
|
+
return left.context === right.context;
|
|
7321
|
+
}
|
|
7322
|
+
function validateName(subject, value) {
|
|
7323
|
+
if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
|
|
7324
|
+
}
|
|
7325
|
+
|
|
7326
|
+
// node_modules/.pnpm/@deepseek-ai+dsh-timeout@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-timeout/lib/index.js
|
|
7327
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
7328
|
+
|
|
7329
|
+
// node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-llm/lib/index.js
|
|
7330
|
+
function freezeMessage(message) {
|
|
7331
|
+
return deepFreeze(structuredClone(message));
|
|
7332
|
+
}
|
|
7333
|
+
var HarnessError = class extends Error {
|
|
7334
|
+
/** Stable machine-routable failure class (e.g. `RATE_LIMIT`); route on this, never by parsing `message`. */
|
|
7335
|
+
code;
|
|
7336
|
+
constructor(message, code, options) {
|
|
7337
|
+
super(message, options);
|
|
7338
|
+
this.code = code;
|
|
7339
|
+
this.name = new.target.name;
|
|
7340
|
+
}
|
|
7341
|
+
};
|
|
7342
|
+
var EMPTY_RESPONSE_CODE = "EMPTY_RESPONSE";
|
|
7343
|
+
var STRUCTURED_CONTEXT_OVERFLOW = new RegExp(String.raw`(?:^|[^a-z0-9])context[\s_-](?:length|window)[\s_-]` + String.raw`(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\s_-]exceeded)(?:$|[^a-z0-9])`, "i");
|
|
7344
|
+
var TOO_LARGE_FOR_CONTEXT = new RegExp(String.raw`\b(?:request|prompt|input|messages?)\s+(?:is\s+|are\s+)?` + String.raw`too\s+(?:large|long)\s+for\s+(?:(?:this|the)\s+)?` + String.raw`(?:model(?:'s)?\s+)?context(?:\s+window)?\b`, "i");
|
|
7345
|
+
var EXCEEDS_MODEL_CONTEXT = new RegExp(String.raw`\b(?:input|prompt|request|messages?)\b.{0,40}` + String.raw`\b(?:exceed(?:s|ed)?|overflows?|is\s+larger\s+than)\b.{0,40}` + String.raw`\b(?:the\s+)?(?:model(?:'s)?\s+)?context(?:\s+(?:length|window))?\b`, "i");
|
|
7346
|
+
var DEFAULT_MAX_RETRIES = 5;
|
|
7347
|
+
var DEFAULT_INITIAL_DELAY_MS = 500;
|
|
7348
|
+
var DEFAULT_MAX_DELAY_MS = 1e4;
|
|
7349
|
+
var DEFAULT_JITTER_RATIO = 0.1;
|
|
7350
|
+
var DEFAULT_RETRYABLE_CODES = Object.freeze([
|
|
7351
|
+
EMPTY_RESPONSE_CODE,
|
|
7352
|
+
"RATE_LIMIT",
|
|
7353
|
+
"SERVER",
|
|
7354
|
+
"TIMEOUT",
|
|
7355
|
+
"TRANSPORT"
|
|
7356
|
+
]);
|
|
7357
|
+
var backoffSchema = Schema.object({
|
|
7358
|
+
initialDelayMs: Schema.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_INITIAL_DELAY_MS),
|
|
7359
|
+
maxDelayMs: Schema.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_MAX_DELAY_MS),
|
|
7360
|
+
jitterRatio: Schema.number().min(0).max(1).default(DEFAULT_JITTER_RATIO)
|
|
7361
|
+
});
|
|
7362
|
+
var normalPolicySchema = Schema.object({
|
|
7363
|
+
mode: Schema.const("normal").required(),
|
|
7364
|
+
maxRetries: Schema.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_RETRIES),
|
|
7365
|
+
retryableCodes: Schema.array(Schema.string()).default([...DEFAULT_RETRYABLE_CODES]),
|
|
7366
|
+
backoff: backoffSchema
|
|
7367
|
+
});
|
|
7368
|
+
var alwaysPolicySchema = Schema.object({
|
|
7369
|
+
mode: Schema.const("always").required(),
|
|
7370
|
+
backoff: backoffSchema
|
|
7371
|
+
});
|
|
7372
|
+
var RetryPolicySchema = Schema.union([normalPolicySchema, alwaysPolicySchema]);
|
|
7373
|
+
var NORMAL_POLICY_KEYS = /* @__PURE__ */ new Set([
|
|
7374
|
+
"mode",
|
|
7375
|
+
"maxRetries",
|
|
7376
|
+
"retryableCodes",
|
|
7377
|
+
"backoff"
|
|
7378
|
+
]);
|
|
7379
|
+
var ALWAYS_POLICY_KEYS = /* @__PURE__ */ new Set([
|
|
7380
|
+
"mode",
|
|
7381
|
+
"maxRetries",
|
|
7382
|
+
"retryableCodes",
|
|
7383
|
+
"backoff"
|
|
7384
|
+
]);
|
|
7385
|
+
var BACKOFF_KEYS = /* @__PURE__ */ new Set([
|
|
7386
|
+
"initialDelayMs",
|
|
7387
|
+
"maxDelayMs",
|
|
7388
|
+
"jitterRatio"
|
|
7389
|
+
]);
|
|
7390
|
+
function validateKeys(value, allowed, path) {
|
|
7391
|
+
for (const key of Object.keys(value)) if (!allowed.has(key)) throw new Error(`${path}: unknown key "${key}"`);
|
|
7392
|
+
}
|
|
7393
|
+
function resolveBackoff(config, path) {
|
|
7394
|
+
if (config !== void 0) validateKeys(config, BACKOFF_KEYS, path);
|
|
7395
|
+
const initialDelayMs = config?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
|
|
7396
|
+
const maxDelayMs = config?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
7397
|
+
const jitterRatio = config?.jitterRatio ?? DEFAULT_JITTER_RATIO;
|
|
7398
|
+
if (!Number.isFinite(initialDelayMs) || initialDelayMs <= 0 || initialDelayMs > MAX_TIMER_DELAY_MS) throw new Error(`${path}.initialDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
7399
|
+
if (!Number.isFinite(maxDelayMs) || maxDelayMs <= 0 || maxDelayMs > MAX_TIMER_DELAY_MS) throw new Error(`${path}.maxDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
7400
|
+
if (initialDelayMs > maxDelayMs) throw new Error(`${path}.initialDelayMs must be less than or equal to maxDelayMs`);
|
|
7401
|
+
if (!Number.isFinite(jitterRatio) || jitterRatio < 0 || jitterRatio > 1) throw new Error(`${path}.jitterRatio must be between 0 and 1`);
|
|
7402
|
+
return Object.freeze({
|
|
7403
|
+
initialDelayMs,
|
|
7404
|
+
maxDelayMs,
|
|
7405
|
+
jitterRatio
|
|
7406
|
+
});
|
|
7407
|
+
}
|
|
7408
|
+
function resolveRetryPolicy(config, path) {
|
|
7409
|
+
if (config === void 0) return Object.freeze({
|
|
7410
|
+
mode: "normal",
|
|
7411
|
+
maxRetries: DEFAULT_MAX_RETRIES,
|
|
7412
|
+
retryableCodes: DEFAULT_RETRYABLE_CODES,
|
|
7413
|
+
...resolveBackoff(void 0, `${path}.backoff`)
|
|
7414
|
+
});
|
|
7415
|
+
switch (config.mode) {
|
|
7416
|
+
case "normal": {
|
|
7417
|
+
validateKeys(config, NORMAL_POLICY_KEYS, path);
|
|
7418
|
+
const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
7419
|
+
const retryableCodes = config.retryableCodes ?? [...DEFAULT_RETRYABLE_CODES];
|
|
7420
|
+
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0) throw new Error(`${path}.maxRetries must be a non-negative safe integer`);
|
|
7421
|
+
if (retryableCodes.length === 0) throw new Error(`${path}.retryableCodes must not be empty`);
|
|
7422
|
+
if (retryableCodes.some((code) => typeof code !== "string" || code.length === 0)) throw new Error(`${path}.retryableCodes must contain only non-empty strings`);
|
|
7423
|
+
if (new Set(retryableCodes).size !== retryableCodes.length) throw new Error(`${path}.retryableCodes must not contain duplicates`);
|
|
7424
|
+
return Object.freeze({
|
|
7425
|
+
mode: "normal",
|
|
7426
|
+
maxRetries,
|
|
7427
|
+
retryableCodes: Object.freeze([...retryableCodes]),
|
|
7428
|
+
...resolveBackoff(config.backoff, `${path}.backoff`)
|
|
7429
|
+
});
|
|
7430
|
+
}
|
|
7431
|
+
case "always":
|
|
7432
|
+
validateKeys(config, ALWAYS_POLICY_KEYS, path);
|
|
7433
|
+
return Object.freeze({
|
|
7434
|
+
mode: "always",
|
|
7435
|
+
...resolveBackoff(config.backoff, `${path}.backoff`)
|
|
7436
|
+
});
|
|
7437
|
+
default:
|
|
7438
|
+
throw new Error(`${path}.mode must be "normal" or "always"`);
|
|
7439
|
+
}
|
|
7440
|
+
}
|
|
7441
|
+
function callConfigEquals(a, b) {
|
|
7442
|
+
if (a.provider !== b.provider || a.model !== b.model || a.reasoningEffort !== b.reasoningEffort || a.temperature !== b.temperature || a.maxTokens !== b.maxTokens) return false;
|
|
7443
|
+
if (a.stop === void 0 || b.stop === void 0) return a.stop === b.stop;
|
|
7444
|
+
return a.stop.length === b.stop.length && a.stop.every((s, i) => s === b.stop?.[i]);
|
|
7445
|
+
}
|
|
7446
|
+
function normalizeLlmFailure(value) {
|
|
7447
|
+
const error = value instanceof Error ? value : new HarnessError(thrownMessage(value), "UNKNOWN", { cause: value });
|
|
7448
|
+
const carried = ownFailureSnapshot(error);
|
|
7449
|
+
if (carried !== void 0 && carried.code === ownErrorCode(error)) return carried;
|
|
7450
|
+
return Object.freeze({
|
|
7451
|
+
message: errorMessage(error),
|
|
7452
|
+
code: harnessErrorCode(error)
|
|
7453
|
+
});
|
|
7454
|
+
}
|
|
7455
|
+
function thrownMessage(value) {
|
|
7456
|
+
try {
|
|
7457
|
+
const message = String(value);
|
|
7458
|
+
return message.length > 0 ? message : "LLM adapter failed";
|
|
7459
|
+
} catch (_hostileThrownValue) {
|
|
7460
|
+
return "LLM adapter failed";
|
|
7461
|
+
}
|
|
7462
|
+
}
|
|
7463
|
+
function ownErrorCode(error) {
|
|
7464
|
+
try {
|
|
7465
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "code");
|
|
7466
|
+
return descriptor !== void 0 && "value" in descriptor ? descriptor.value : void 0;
|
|
7467
|
+
} catch (_sdkPropertyTrap) {
|
|
7468
|
+
return;
|
|
7469
|
+
}
|
|
7470
|
+
}
|
|
7471
|
+
function ownFailureSnapshot(error) {
|
|
7472
|
+
try {
|
|
7473
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "failure");
|
|
7474
|
+
return descriptor !== void 0 && "value" in descriptor ? failureSnapshot(descriptor.value) : void 0;
|
|
7475
|
+
} catch (_sdkPropertyTrap) {
|
|
7476
|
+
return;
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
function failureSnapshot(value) {
|
|
7480
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
7481
|
+
try {
|
|
7482
|
+
const candidate = value;
|
|
7483
|
+
const message = candidate.message;
|
|
7484
|
+
const code = candidate.code;
|
|
7485
|
+
const status = candidate.status;
|
|
7486
|
+
const providerRetryAfterMs = candidate.providerRetryAfterMs;
|
|
7487
|
+
const requestId = candidate.requestId;
|
|
7488
|
+
if (typeof message !== "string" || message.length === 0 || typeof code !== "string" || code.length === 0 || status !== void 0 && (!Number.isInteger(status) || status < 100 || status > 599) || providerRetryAfterMs !== void 0 && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0) || requestId !== void 0 && (typeof requestId !== "string" || requestId.length === 0)) return void 0;
|
|
7489
|
+
return Object.freeze({
|
|
7490
|
+
message,
|
|
7491
|
+
code,
|
|
7492
|
+
...status === void 0 ? {} : { status },
|
|
7493
|
+
...providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs },
|
|
7494
|
+
...requestId === void 0 ? {} : { requestId }
|
|
7495
|
+
});
|
|
7496
|
+
} catch (_sdkFailureGetter) {
|
|
7497
|
+
return;
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
function errorMessage(error) {
|
|
7501
|
+
try {
|
|
7502
|
+
const message = error.message;
|
|
7503
|
+
if (typeof message === "string" && message.length > 0) return message;
|
|
7504
|
+
} catch (_sdkMessageGetter) {
|
|
7505
|
+
}
|
|
7506
|
+
return "LLM adapter failed";
|
|
7507
|
+
}
|
|
7508
|
+
function harnessErrorCode(error) {
|
|
7509
|
+
return error instanceof HarnessError ? error.code : "UNKNOWN";
|
|
7510
|
+
}
|
|
7511
|
+
function quoted(value) {
|
|
7512
|
+
return JSON.stringify(value);
|
|
7513
|
+
}
|
|
7514
|
+
function textOnlyImageText(ref) {
|
|
7515
|
+
return `[image omitted because this model accepts text only; attachment sha256:${String(ref.attachmentId).slice(7, 15)}]`;
|
|
7516
|
+
}
|
|
7517
|
+
function contentHasImage(content) {
|
|
7518
|
+
return content.some((block) => block.type === "image" || block.type === "tool-result" && contentHasImage(block.content));
|
|
7519
|
+
}
|
|
7520
|
+
function contentHasFile(content) {
|
|
7521
|
+
for (const block of content) if (block.type === "file" || block.type === "tool-result" && contentHasFile(block.content)) return true;
|
|
7522
|
+
return false;
|
|
7523
|
+
}
|
|
7524
|
+
function fileHandleText(ref, readonlyPath) {
|
|
7525
|
+
const digest = String(ref.attachmentId).slice(7, 15);
|
|
7526
|
+
const identity = `File ${quoted(ref.name)} (${ref.bytes} bytes, sha256:${digest})`;
|
|
7527
|
+
if (readonlyPath === void 0) return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
|
|
7528
|
+
return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
|
|
7529
|
+
}
|
|
7530
|
+
function replaceFilesWithHandles(blocks, resolvePath) {
|
|
7531
|
+
let next;
|
|
7532
|
+
for (const [index, block] of blocks.entries()) {
|
|
7533
|
+
if (block.type === "file") {
|
|
7534
|
+
next ??= blocks.slice(0, index);
|
|
7535
|
+
next.push({
|
|
7536
|
+
type: "text",
|
|
7537
|
+
text: fileHandleText(block.attachment, resolvePath(block.attachment))
|
|
7538
|
+
});
|
|
7539
|
+
continue;
|
|
7540
|
+
}
|
|
7541
|
+
if (block.type === "tool-result") {
|
|
7542
|
+
const content = replaceFilesWithHandles(block.content, resolvePath);
|
|
7543
|
+
if (content !== block.content) {
|
|
7544
|
+
next ??= blocks.slice(0, index);
|
|
7545
|
+
next.push({
|
|
7546
|
+
...block,
|
|
7547
|
+
content
|
|
7548
|
+
});
|
|
7549
|
+
continue;
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
next?.push(block);
|
|
7553
|
+
}
|
|
7554
|
+
return next ?? blocks;
|
|
7555
|
+
}
|
|
7556
|
+
function projectFilesToText(messages, resolvePath) {
|
|
7557
|
+
if (!messages.some((message) => contentHasFile(message.content))) return messages;
|
|
7558
|
+
return messages.map((message) => {
|
|
7559
|
+
const content = replaceFilesWithHandles(message.content, resolvePath);
|
|
7560
|
+
return content === message.content ? message : {
|
|
7561
|
+
...message,
|
|
7562
|
+
content
|
|
7563
|
+
};
|
|
7564
|
+
});
|
|
7565
|
+
}
|
|
7566
|
+
function replaceImagesForTextModel(blocks) {
|
|
7567
|
+
let next;
|
|
7568
|
+
for (const [index, block] of blocks.entries()) {
|
|
7569
|
+
if (block.type === "image") {
|
|
7570
|
+
next ??= blocks.slice(0, index);
|
|
7571
|
+
next.push({
|
|
7572
|
+
type: "text",
|
|
7573
|
+
text: textOnlyImageText(block.attachment)
|
|
7574
|
+
});
|
|
7575
|
+
continue;
|
|
7576
|
+
}
|
|
7577
|
+
if (block.type === "tool-result") {
|
|
7578
|
+
const content = replaceImagesForTextModel(block.content);
|
|
7579
|
+
if (content !== block.content) {
|
|
7580
|
+
next ??= blocks.slice(0, index);
|
|
7581
|
+
next.push({
|
|
7582
|
+
...block,
|
|
7583
|
+
content
|
|
7584
|
+
});
|
|
7585
|
+
continue;
|
|
7586
|
+
}
|
|
7587
|
+
}
|
|
7588
|
+
next?.push(block);
|
|
7589
|
+
}
|
|
7590
|
+
return next ?? blocks;
|
|
7591
|
+
}
|
|
7592
|
+
function projectImagesForTextModel(messages) {
|
|
7593
|
+
if (!messages.some((message) => contentHasImage(message.content))) return messages;
|
|
7594
|
+
return messages.map((message) => {
|
|
7595
|
+
const content = replaceImagesForTextModel(message.content);
|
|
7596
|
+
return content === message.content ? message : {
|
|
7597
|
+
...message,
|
|
7598
|
+
content
|
|
7599
|
+
};
|
|
7600
|
+
});
|
|
7601
|
+
}
|
|
7602
|
+
var { version } = createRequire(import.meta.url)("../package.json");
|
|
7603
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
7604
|
+
var useValue = arguments.length > 2;
|
|
7605
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
7606
|
+
return useValue ? value : void 0;
|
|
7607
|
+
};
|
|
7608
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
7609
|
+
function accept(f) {
|
|
7610
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
7611
|
+
return f;
|
|
7612
|
+
}
|
|
7613
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
7614
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
7615
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
7616
|
+
var _, done = false;
|
|
7617
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
7618
|
+
var context = {};
|
|
7619
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
7620
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
7621
|
+
context.addInitializer = function(f) {
|
|
7622
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
7623
|
+
extraInitializers.push(accept(f || null));
|
|
7624
|
+
};
|
|
7625
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
7626
|
+
get: descriptor.get,
|
|
7627
|
+
set: descriptor.set
|
|
7628
|
+
} : descriptor[key], context);
|
|
7629
|
+
if (kind === "accessor") {
|
|
7630
|
+
if (result === void 0) continue;
|
|
7631
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
7632
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
7633
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
7634
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
7635
|
+
} else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
|
|
7636
|
+
else descriptor[key] = _;
|
|
7637
|
+
}
|
|
7638
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
7639
|
+
done = true;
|
|
7640
|
+
};
|
|
7641
|
+
var LlmError = class extends HarnessError {
|
|
7642
|
+
/** Serializable facts retained beside this live Error. */
|
|
7643
|
+
failure;
|
|
7644
|
+
/**
|
|
7645
|
+
* @param message - non-empty human-readable failure summary.
|
|
7646
|
+
* @param code - non-empty stable provider-neutral machine code.
|
|
7647
|
+
* @param options - optional cause and validated serializable provider facts.
|
|
7648
|
+
*/
|
|
7649
|
+
constructor(message, code, options) {
|
|
7650
|
+
if (typeof message !== "string" || message.length === 0) throw new Error("LlmError message must be a non-empty string");
|
|
7651
|
+
if (typeof code !== "string" || code.length === 0) throw new Error("LlmError code must be a non-empty string");
|
|
7652
|
+
if (options?.status !== void 0 && (!Number.isInteger(options.status) || options.status < 100 || options.status > 599)) throw new Error("LlmError status must be an integer from 100 through 599");
|
|
7653
|
+
if (options?.providerRetryAfterMs !== void 0 && (!Number.isFinite(options.providerRetryAfterMs) || options.providerRetryAfterMs <= 0)) throw new Error("LlmError providerRetryAfterMs must be a positive finite number");
|
|
7654
|
+
if (options?.requestId !== void 0 && (typeof options.requestId !== "string" || options.requestId.length === 0)) throw new Error("LlmError requestId must be a non-empty string");
|
|
7655
|
+
super(message, code, options);
|
|
7656
|
+
this.name = "LlmError";
|
|
7657
|
+
this.failure = Object.freeze({
|
|
7658
|
+
message,
|
|
7659
|
+
code,
|
|
7660
|
+
...options?.status === void 0 ? {} : { status: options.status },
|
|
7661
|
+
...options?.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: options.providerRetryAfterMs },
|
|
7662
|
+
...options?.requestId === void 0 ? {} : { requestId: options.requestId }
|
|
7663
|
+
});
|
|
7664
|
+
}
|
|
7665
|
+
};
|
|
7666
|
+
var LlmRuntime = (() => {
|
|
7667
|
+
let _classSuper = TypertRemoteService;
|
|
7668
|
+
let _instanceExtraInitializers = [];
|
|
7669
|
+
let _listProviders_decorators;
|
|
7670
|
+
let _listConfigurableProviders_decorators;
|
|
7671
|
+
let _remoteDiscoverModels_decorators;
|
|
7672
|
+
return class LlmRuntime extends _classSuper {
|
|
7673
|
+
static {
|
|
7674
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
7675
|
+
_listProviders_decorators = [Remote];
|
|
7676
|
+
_listConfigurableProviders_decorators = [Remote];
|
|
7677
|
+
_remoteDiscoverModels_decorators = [Remote("discoverModels")];
|
|
7678
|
+
__esDecorate(this, null, _listProviders_decorators, {
|
|
7679
|
+
kind: "method",
|
|
7680
|
+
name: "listProviders",
|
|
7681
|
+
static: false,
|
|
7682
|
+
private: false,
|
|
7683
|
+
access: {
|
|
7684
|
+
has: (obj) => "listProviders" in obj,
|
|
7685
|
+
get: (obj) => obj.listProviders
|
|
7686
|
+
},
|
|
7687
|
+
metadata: _metadata
|
|
7688
|
+
}, null, _instanceExtraInitializers);
|
|
7689
|
+
__esDecorate(this, null, _listConfigurableProviders_decorators, {
|
|
7690
|
+
kind: "method",
|
|
7691
|
+
name: "listConfigurableProviders",
|
|
7692
|
+
static: false,
|
|
7693
|
+
private: false,
|
|
7694
|
+
access: {
|
|
7695
|
+
has: (obj) => "listConfigurableProviders" in obj,
|
|
7696
|
+
get: (obj) => obj.listConfigurableProviders
|
|
7697
|
+
},
|
|
7698
|
+
metadata: _metadata
|
|
7699
|
+
}, null, _instanceExtraInitializers);
|
|
7700
|
+
__esDecorate(this, null, _remoteDiscoverModels_decorators, {
|
|
7701
|
+
kind: "method",
|
|
7702
|
+
name: "remoteDiscoverModels",
|
|
7703
|
+
static: false,
|
|
7704
|
+
private: false,
|
|
7705
|
+
access: {
|
|
7706
|
+
has: (obj) => "remoteDiscoverModels" in obj,
|
|
7707
|
+
get: (obj) => obj.remoteDiscoverModels
|
|
7708
|
+
},
|
|
7709
|
+
metadata: _metadata
|
|
7710
|
+
}, null, _instanceExtraInitializers);
|
|
7711
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
7712
|
+
enumerable: true,
|
|
7713
|
+
configurable: true,
|
|
7714
|
+
writable: true,
|
|
7715
|
+
value: _metadata
|
|
7716
|
+
});
|
|
7717
|
+
}
|
|
7718
|
+
adapters = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Map());
|
|
7719
|
+
directory = /* @__PURE__ */ new Map();
|
|
7720
|
+
discoveries = /* @__PURE__ */ new Map();
|
|
7721
|
+
constructor(ctx) {
|
|
7722
|
+
super(ctx, "llm");
|
|
7723
|
+
}
|
|
7724
|
+
/** Notify topology observers without letting one broken listener veto the commit. */
|
|
7725
|
+
emitAdaptersUpdated() {
|
|
7726
|
+
let invariantFailure;
|
|
7727
|
+
for (const listener of this.ctx.events.dispatch("emit", ["llm/adapters-updated"])) try {
|
|
7728
|
+
const returned = listener();
|
|
7729
|
+
if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
|
|
7730
|
+
this.warnAdaptersListenerFailure(error);
|
|
7731
|
+
});
|
|
7732
|
+
} catch (error) {
|
|
7733
|
+
if (error?.code === "INVARIANT") {
|
|
7734
|
+
invariantFailure ??= error;
|
|
7735
|
+
continue;
|
|
7736
|
+
}
|
|
7737
|
+
this.warnAdaptersListenerFailure(error);
|
|
7738
|
+
}
|
|
7739
|
+
if (invariantFailure !== void 0) throw invariantFailure;
|
|
7740
|
+
}
|
|
7741
|
+
/** Contained-listener diagnostic shared by the sync and async failure paths. */
|
|
7742
|
+
warnAdaptersListenerFailure(error) {
|
|
7743
|
+
this.ctx.logger.warn("llm: an llm/adapters-updated listener failed");
|
|
7744
|
+
this.ctx.logger.warn(error);
|
|
7745
|
+
}
|
|
7746
|
+
/**
|
|
7747
|
+
* Register an adapter for the given provider routes. Throws `LlmError` with code
|
|
7748
|
+
* `DUPLICATE_ADAPTER` if any provider already has an adapter (all-or-nothing).
|
|
7749
|
+
* Disposed with the fiber.
|
|
7750
|
+
* @param providers - every provider route this adapter should serve.
|
|
7751
|
+
* @param adapter - the adapter that streams calls for those providers.
|
|
7752
|
+
* @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}.
|
|
7753
|
+
*/
|
|
7754
|
+
registerAdapter(providers, adapter) {
|
|
7755
|
+
const owned = /* @__PURE__ */ new Set();
|
|
7756
|
+
let released = false;
|
|
7757
|
+
const dispose = this.ctx.effect(function* () {
|
|
7758
|
+
if (providers.length === 0) throw new LlmError("an adapter must register at least one provider", "INVALID_ADAPTER");
|
|
7759
|
+
this.commitRoutes(owned, this.prepareRoutes(providers, adapter, owned));
|
|
7760
|
+
yield () => {
|
|
7761
|
+
released = true;
|
|
7762
|
+
for (const provider of owned) this.adapters.delete(provider);
|
|
7763
|
+
owned.clear();
|
|
7764
|
+
this.emitAdaptersUpdated();
|
|
7765
|
+
};
|
|
7766
|
+
}.bind(this), "llm.registerAdapter()");
|
|
7767
|
+
const handle = () => void dispose();
|
|
7768
|
+
handle.replace = (next) => {
|
|
7769
|
+
if (released) throw new LlmError("a disposed adapter registration cannot replace its routes", "REGISTRATION_DISPOSED");
|
|
7770
|
+
this.commitRoutes(owned, this.prepareRoutes(next, adapter, owned));
|
|
7771
|
+
};
|
|
7772
|
+
return handle;
|
|
7773
|
+
}
|
|
7774
|
+
/**
|
|
7775
|
+
* Validate one candidate route set for `adapter`, treating routes this
|
|
7776
|
+
* registration already holds as available. Nothing is mutated: a rejected
|
|
7777
|
+
* candidate leaves the registry exactly as it was.
|
|
7778
|
+
*/
|
|
7779
|
+
prepareRoutes(providers, adapter, owned) {
|
|
7780
|
+
const unique = /* @__PURE__ */ new Set();
|
|
7781
|
+
const registrations = [];
|
|
7782
|
+
for (const provider of providers) {
|
|
7783
|
+
if (provider.length === 0) throw new LlmError("adapter provider names must be non-empty", "INVALID_ADAPTER");
|
|
7784
|
+
if (unique.has(provider) || this.adapters.has(provider) && !owned.has(provider)) throw new LlmError(`an adapter for provider "${provider}" is already registered`, "DUPLICATE_ADAPTER");
|
|
7785
|
+
const info = adapter.providerInfo(provider);
|
|
7786
|
+
if (typeof info.id !== "string" || info.id !== provider || typeof info.name !== "string" || info.name.length === 0) throw new LlmError(`adapter metadata for provider "${provider}" must preserve its id and have a non-empty name`, "INVALID_ADAPTER");
|
|
7787
|
+
unique.add(provider);
|
|
7788
|
+
const retryPolicy = adapter.providerRetryPolicy(provider) ?? resolveRetryPolicy(void 0, `llm: provider "${provider}" retryPolicy`);
|
|
7789
|
+
registrations.push({
|
|
7790
|
+
adapter,
|
|
7791
|
+
provider: {
|
|
7792
|
+
id: info.id,
|
|
7793
|
+
name: info.name
|
|
7794
|
+
},
|
|
7795
|
+
retryPolicy
|
|
7796
|
+
});
|
|
7797
|
+
}
|
|
7798
|
+
return registrations;
|
|
7799
|
+
}
|
|
7800
|
+
/**
|
|
7801
|
+
* Swap this registration's routes for the prepared ones in one synchronous
|
|
7802
|
+
* section, so no observer can see the registry between the release and the
|
|
7803
|
+
* re-registration. The route set's one mutation point is also where
|
|
7804
|
+
* `llm/adapters-updated` is published, so a `replace` announces itself
|
|
7805
|
+
* exactly like a first registration.
|
|
7806
|
+
*/
|
|
7807
|
+
commitRoutes(owned, registrations) {
|
|
7808
|
+
for (const provider of owned) this.adapters.delete(provider);
|
|
7809
|
+
owned.clear();
|
|
7810
|
+
for (const registration of registrations) {
|
|
7811
|
+
this.adapters.set(registration.provider.id, registration);
|
|
7812
|
+
owned.add(registration.provider.id);
|
|
7813
|
+
}
|
|
7814
|
+
this.emitAdaptersUpdated();
|
|
7815
|
+
}
|
|
7816
|
+
/**
|
|
7817
|
+
* Describe provider routes with a registered adapter.
|
|
7818
|
+
* @returns detached provider metadata in registration order.
|
|
7819
|
+
*/
|
|
7820
|
+
listProviders() {
|
|
7821
|
+
return [...this.adapters.values()].map(({ provider }) => ({ ...provider }));
|
|
7822
|
+
}
|
|
7823
|
+
/**
|
|
7824
|
+
* Declare provider routes an adapter plugin can activate through
|
|
7825
|
+
* configuration. Registration is all-or-nothing: an empty list, invalid
|
|
7826
|
+
* entry, or a provider already declared by any registration throws
|
|
7827
|
+
* `LlmError` without registering the rest. Disposed with the fiber.
|
|
7828
|
+
* @param entries - every configurable provider this plugin owns.
|
|
7829
|
+
* @returns a handle that withdraws all of them, and can atomically replace them.
|
|
7830
|
+
*/
|
|
7831
|
+
registerConfigurableProviders(entries) {
|
|
7832
|
+
let held = [];
|
|
7833
|
+
let disposed = false;
|
|
7834
|
+
const commit = (candidates) => {
|
|
7835
|
+
const detached = [];
|
|
7836
|
+
const own = new Set(held.map((entry) => entry.provider));
|
|
7837
|
+
for (const entry of candidates) {
|
|
7838
|
+
if (entry.provider.length === 0 || entry.displayName.length === 0 || entry.settingsNs.length === 0) throw new LlmError("configurable providers need a non-empty provider, displayName, and settingsNs", "INVALID_DIRECTORY");
|
|
7839
|
+
if (entry.settingsPath.some((segment) => segment.length === 0)) throw new LlmError(`configurable provider "${entry.provider}" has an empty settingsPath segment`, "INVALID_DIRECTORY");
|
|
7840
|
+
if (this.directory.has(entry.provider) && !own.has(entry.provider) || detached.some((seen) => seen.provider === entry.provider)) throw new LlmError(`configurable provider "${entry.provider}" is already declared`, "DUPLICATE_DIRECTORY");
|
|
7841
|
+
detached.push({
|
|
7842
|
+
...entry,
|
|
7843
|
+
settingsPath: [...entry.settingsPath]
|
|
7844
|
+
});
|
|
7845
|
+
}
|
|
7846
|
+
for (const entry of held) this.directory.delete(entry.provider);
|
|
7847
|
+
for (const entry of detached) this.directory.set(entry.provider, entry);
|
|
7848
|
+
held = detached;
|
|
7849
|
+
this.emitAdaptersUpdated();
|
|
7850
|
+
};
|
|
7851
|
+
const dispose = this.ctx.effect(function* () {
|
|
7852
|
+
if (entries.length === 0) throw new LlmError("a configurable-provider registration must declare at least one provider", "INVALID_DIRECTORY");
|
|
7853
|
+
commit(entries);
|
|
7854
|
+
yield () => {
|
|
7855
|
+
disposed = true;
|
|
7856
|
+
for (const entry of held) this.directory.delete(entry.provider);
|
|
7857
|
+
held = [];
|
|
7858
|
+
this.emitAdaptersUpdated();
|
|
7859
|
+
};
|
|
7860
|
+
}.bind(this), "llm.registerConfigurableProviders()");
|
|
7861
|
+
const handle = () => void dispose();
|
|
7862
|
+
handle.replace = (next) => {
|
|
7863
|
+
if (disposed) throw new LlmError("this configurable-provider registration was disposed", "REGISTRATION_DISPOSED");
|
|
7864
|
+
commit(next);
|
|
7865
|
+
};
|
|
7866
|
+
return handle;
|
|
7867
|
+
}
|
|
7868
|
+
/**
|
|
7869
|
+
* List every declared configurable provider, registered or dormant.
|
|
7870
|
+
* @returns detached directory entries in declaration order.
|
|
7871
|
+
*/
|
|
7872
|
+
listConfigurableProviders() {
|
|
7873
|
+
return [...this.directory.values()].map((entry) => ({
|
|
7874
|
+
...entry,
|
|
7875
|
+
settingsPath: [...entry.settingsPath]
|
|
7876
|
+
}));
|
|
7877
|
+
}
|
|
7878
|
+
/**
|
|
7879
|
+
* Offer to interrogate provider endpoints on behalf of the settings
|
|
7880
|
+
* namespace this plugin owns. The namespace is the key because that is what
|
|
7881
|
+
* a configuration surface already holds from the configurable-provider
|
|
7882
|
+
* directory, and because a provider being *added* has no route to name yet.
|
|
7883
|
+
* Disposed with the fiber.
|
|
7884
|
+
* @param settingsNs - the namespace whose profiles this discovery serves.
|
|
7885
|
+
* @param discover - interrogates one endpoint and must honor the supplied signal.
|
|
7886
|
+
* @returns the disposer that withdraws the offer.
|
|
7887
|
+
*/
|
|
7888
|
+
registerModelDiscovery(settingsNs, discover) {
|
|
7889
|
+
const dispose = this.ctx.effect(function* () {
|
|
7890
|
+
if (settingsNs.length === 0) throw new LlmError("model discovery needs a non-empty settings namespace", "INVALID_DISCOVERY");
|
|
7891
|
+
if (this.discoveries.has(settingsNs)) throw new LlmError(`model discovery for "${settingsNs}" is already registered`, "DUPLICATE_DISCOVERY");
|
|
7892
|
+
this.discoveries.set(settingsNs, discover);
|
|
7893
|
+
yield () => {
|
|
7894
|
+
this.discoveries.delete(settingsNs);
|
|
7895
|
+
};
|
|
7896
|
+
}.bind(this), "llm.registerModelDiscovery()");
|
|
7897
|
+
return () => void dispose();
|
|
7898
|
+
}
|
|
7899
|
+
/**
|
|
7900
|
+
* Interrogate one provider endpoint for the models it advertises. The
|
|
7901
|
+
* request describes a draft, not a stored route, so nothing here reads or
|
|
7902
|
+
* writes settings or credentials — the caller owns both, and the reply is
|
|
7903
|
+
* candidate metadata a surface may offer for adoption.
|
|
7904
|
+
* @param settingsNs - namespace whose registered discovery serves this draft.
|
|
7905
|
+
* @param request - the endpoint, protocol, and one-shot credential to use.
|
|
7906
|
+
* @param signal - caller cancellation.
|
|
7907
|
+
* @returns the advertised models, deduplicated in endpoint order.
|
|
7908
|
+
*/
|
|
7909
|
+
async discoverModels(settingsNs, request, signal) {
|
|
7910
|
+
const discover = this.discoveries.get(settingsNs);
|
|
7911
|
+
if (discover === void 0) throw new LlmError(`no model discovery is registered for "${settingsNs}"`, "NO_DISCOVERY");
|
|
7912
|
+
if ((request.provider ?? "").length === 0 && (request.baseURL ?? "").length === 0) throw new LlmError("model discovery needs a provider route or a baseURL", "INVALID_DISCOVERY");
|
|
7913
|
+
const discovered = signal === void 0 ? await discover(request) : await discover(request, signal);
|
|
7914
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7915
|
+
const models = [];
|
|
7916
|
+
for (const model of discovered) {
|
|
7917
|
+
if (typeof model.id !== "string" || model.id.length === 0 || seen.has(model.id)) continue;
|
|
7918
|
+
seen.add(model.id);
|
|
7919
|
+
models.push({
|
|
7920
|
+
id: model.id,
|
|
7921
|
+
...model.name === void 0 ? {} : { name: model.name },
|
|
7922
|
+
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
7923
|
+
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
|
|
7924
|
+
});
|
|
7925
|
+
}
|
|
7926
|
+
return models;
|
|
7927
|
+
}
|
|
7928
|
+
/**
|
|
7929
|
+
* Remote adapter for one draft provider interrogation.
|
|
7930
|
+
* @param settingsNs - namespace whose registered discovery serves this draft.
|
|
7931
|
+
* @param request - endpoint, protocol, and one-shot credential to use.
|
|
7932
|
+
* @param signal - caller cancellation supplied by the Remote carrier.
|
|
7933
|
+
* @returns advertised models in endpoint order.
|
|
7934
|
+
* @throws RemoteError with `llm/model-discovery-rejected` when discovery refuses or fails.
|
|
7935
|
+
*/
|
|
7936
|
+
async remoteDiscoverModels(settingsNs, request, signal) {
|
|
7937
|
+
try {
|
|
7938
|
+
return await this.discoverModels(settingsNs, request, signal);
|
|
7939
|
+
} catch (error) {
|
|
7940
|
+
throw new RemoteError("llm/model-discovery-rejected", error instanceof Error ? error.message : String(error), {
|
|
7941
|
+
settingsNs,
|
|
7942
|
+
...request.baseURL === void 0 ? {} : { baseURL: request.baseURL }
|
|
7943
|
+
}, { cause: error });
|
|
7944
|
+
}
|
|
7945
|
+
}
|
|
7946
|
+
/**
|
|
7947
|
+
* Resolve the retry policy captured when one provider route was registered.
|
|
7948
|
+
* @param provider - registered provider route to inspect.
|
|
7949
|
+
* @returns the provider-owned policy, with normal defaults already resolved.
|
|
7950
|
+
*/
|
|
7951
|
+
providerRetryPolicy(provider) {
|
|
7952
|
+
return this.registration(provider).retryPolicy;
|
|
7953
|
+
}
|
|
7954
|
+
/**
|
|
7955
|
+
* Resolve provider-side request-image pricing for one exact route, or
|
|
7956
|
+
* `undefined` when the provider is unregistered or declares none. Unknown
|
|
7957
|
+
* providers degrade to `undefined` rather than throwing because callers
|
|
7958
|
+
* price durable history whose route may no longer be mounted.
|
|
7959
|
+
* @param provider - provider route named by a request header.
|
|
7960
|
+
* @param model - exact model id named by the same header.
|
|
7961
|
+
* @returns the owning adapter's image pricing for the route, when declared.
|
|
7962
|
+
*/
|
|
7963
|
+
imageRequestPricing(provider, model) {
|
|
7964
|
+
return this.adapters.get(provider)?.adapter.imageRequestPricing(provider, model);
|
|
7965
|
+
}
|
|
7966
|
+
/**
|
|
7967
|
+
* Resolve the exact text one durable file occurrence contributes to every
|
|
7968
|
+
* provider request in the current execution environment.
|
|
7969
|
+
* @param ref - durable verbatim file reference from model history.
|
|
7970
|
+
* @returns the same deterministic handle text used at adapter dispatch.
|
|
7971
|
+
*/
|
|
7972
|
+
fileRequestText(ref) {
|
|
7973
|
+
return fileHandleText(ref, this.fileReadPath(ref));
|
|
7974
|
+
}
|
|
7975
|
+
/** Detach typed adapter-owned modality metadata. */
|
|
7976
|
+
detachedModalities(modalities) {
|
|
7977
|
+
return modalities === void 0 ? void 0 : [...modalities];
|
|
7978
|
+
}
|
|
7979
|
+
/**
|
|
7980
|
+
* Discover models advertised by one registered provider. Catalog membership
|
|
7981
|
+
* is advisory and never changes routing or request validation.
|
|
7982
|
+
* @param provider - registered provider route to inspect.
|
|
7983
|
+
* @returns detached model metadata in adapter-preferred order.
|
|
7984
|
+
*/
|
|
7985
|
+
async listModels(provider) {
|
|
7986
|
+
const models = await this.registration(provider).adapter.listModels(provider);
|
|
7987
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7988
|
+
return models.map((model) => {
|
|
7989
|
+
if (typeof model.provider !== "string" || model.provider !== provider || typeof model.id !== "string" || model.id.length === 0 || typeof model.name !== "string" || model.name.length === 0 || model.description !== void 0 && typeof model.description !== "string" || seen.has(model.id)) throw new LlmError(`adapter returned invalid or duplicate model metadata for provider "${provider}"`, "INVALID_CATALOG");
|
|
7990
|
+
seen.add(model.id);
|
|
7991
|
+
const inputModalities = this.detachedModalities(model.inputModalities);
|
|
7992
|
+
return {
|
|
7993
|
+
provider: model.provider,
|
|
7994
|
+
id: model.id,
|
|
7995
|
+
name: model.name,
|
|
7996
|
+
...model.description === void 0 ? {} : { description: model.description },
|
|
7997
|
+
...inputModalities === void 0 ? {} : { inputModalities }
|
|
7998
|
+
};
|
|
7999
|
+
});
|
|
8000
|
+
}
|
|
8001
|
+
/**
|
|
8002
|
+
* Resolve and validate all metadata from the adapter that owns one exact
|
|
8003
|
+
* route. The result is detached from adapter-owned objects; catalog
|
|
8004
|
+
* membership remains advisory and does not control request routing.
|
|
8005
|
+
* @param provider - registered provider route to inspect.
|
|
8006
|
+
* @param model - exact model id passed to the adapter.
|
|
8007
|
+
* @param signal - optional cancellation for adapter-owned asynchronous lookup.
|
|
8008
|
+
* @returns exact model identity plus available context and reasoning metadata.
|
|
8009
|
+
*/
|
|
8010
|
+
async resolveModelInfo(provider, model, signal) {
|
|
8011
|
+
return this.resolveModelInfoFor(this.registration(provider), model, signal);
|
|
8012
|
+
}
|
|
8013
|
+
async resolveModelInfoFor(registration, model, signal) {
|
|
8014
|
+
const resolved = await registration.adapter.resolveModel(registration.provider.id, model, signal);
|
|
8015
|
+
return this.normalizeModelInfo(registration, model, resolved);
|
|
8016
|
+
}
|
|
8017
|
+
/** Validate and detach one adapter-returned exact model result. */
|
|
8018
|
+
normalizeModelInfo(registration, model, resolved) {
|
|
8019
|
+
const provider = registration.provider.id;
|
|
8020
|
+
if (typeof resolved.provider !== "string" || resolved.provider !== provider || typeof resolved.id !== "string" || resolved.id !== model || typeof resolved.name !== "string" || resolved.name.length === 0 || resolved.description !== void 0 && typeof resolved.description !== "string") throw new LlmError(`adapter returned invalid exact model metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
|
|
8021
|
+
const context = resolved.context;
|
|
8022
|
+
if (context !== void 0 && (!Number.isInteger(context.contextWindow) || context.contextWindow <= 0)) throw new LlmError(`adapter returned invalid context metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_CONTEXT");
|
|
8023
|
+
const inputModalities = this.detachedModalities(resolved.inputModalities);
|
|
8024
|
+
const systemPromptUpdate = resolved.systemPromptUpdate;
|
|
8025
|
+
if (systemPromptUpdate !== void 0 && systemPromptUpdate !== "in-history") throw new LlmError(`adapter returned invalid system prompt update mode for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
|
|
8026
|
+
const defaultMaxTokens = resolved.defaultMaxTokens;
|
|
8027
|
+
if (defaultMaxTokens !== void 0 && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) throw new LlmError(`adapter returned invalid default maxTokens for provider "${provider}" model "${model}"`, "INVALID_MODEL_MAX_TOKENS");
|
|
8028
|
+
const info = {
|
|
8029
|
+
provider,
|
|
8030
|
+
id: model,
|
|
8031
|
+
name: resolved.name,
|
|
8032
|
+
...resolved.description === void 0 ? {} : { description: resolved.description },
|
|
8033
|
+
...inputModalities === void 0 ? {} : { inputModalities },
|
|
8034
|
+
...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },
|
|
8035
|
+
...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens },
|
|
8036
|
+
...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate }
|
|
8037
|
+
};
|
|
8038
|
+
const reasoning = resolved.reasoning;
|
|
8039
|
+
if (reasoning === void 0) return info;
|
|
8040
|
+
if (reasoning.efforts.length === 0) throw new LlmError(`adapter returned invalid reasoning metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
8041
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8042
|
+
const efforts = reasoning.efforts.map((effort) => {
|
|
8043
|
+
if (typeof effort.id !== "string" || effort.id.length === 0 || typeof effort.name !== "string" || effort.name.length === 0 || effort.description !== void 0 && typeof effort.description !== "string" || seen.has(effort.id)) throw new LlmError(`adapter returned invalid or duplicate reasoning effort metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
8044
|
+
seen.add(effort.id);
|
|
8045
|
+
return {
|
|
8046
|
+
id: effort.id,
|
|
8047
|
+
name: effort.name,
|
|
8048
|
+
...effort.description === void 0 ? {} : { description: effort.description }
|
|
8049
|
+
};
|
|
8050
|
+
});
|
|
8051
|
+
if (reasoning.defaultEffort !== void 0 && !seen.has(reasoning.defaultEffort)) throw new LlmError(`adapter returned an unknown default reasoning effort for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
8052
|
+
return {
|
|
8053
|
+
...info,
|
|
8054
|
+
reasoning: {
|
|
8055
|
+
efforts,
|
|
8056
|
+
...reasoning.defaultEffort === void 0 ? {} : { defaultEffort: reasoning.defaultEffort }
|
|
8057
|
+
}
|
|
8058
|
+
};
|
|
8059
|
+
}
|
|
8060
|
+
/**
|
|
8061
|
+
* Validate a conversation call config against its exact model capability and
|
|
8062
|
+
* materialize adapter-configured defaults. Unsupported explicit efforts
|
|
8063
|
+
* reject before provider I/O; no clamping or aliasing is performed. This
|
|
8064
|
+
* standalone query does not bind a later dispatch; use {@link prepareCall}
|
|
8065
|
+
* when logging and streaming must share one adapter registration.
|
|
8066
|
+
* @param config - provider/model route and optional request controls.
|
|
8067
|
+
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
8068
|
+
* @returns a detached config only when a default must be materialized.
|
|
8069
|
+
*/
|
|
8070
|
+
async resolveCallConfig(config, signal) {
|
|
8071
|
+
return (await this.resolveCallFor(this.registration(config.provider), config, signal)).config;
|
|
8072
|
+
}
|
|
8073
|
+
async resolveCallFor(registration, config, signal) {
|
|
8074
|
+
const info = await this.resolveModelInfoFor(registration, config.model, signal);
|
|
8075
|
+
return this.resolveCallWithInfo(config, info);
|
|
8076
|
+
}
|
|
8077
|
+
/** Validate request controls against one already-bound exact model result. */
|
|
8078
|
+
resolveCallWithInfo(config, info) {
|
|
8079
|
+
const defaulted = config.maxTokens === void 0 && info.defaultMaxTokens !== void 0 ? {
|
|
8080
|
+
...config,
|
|
8081
|
+
maxTokens: info.defaultMaxTokens
|
|
8082
|
+
} : config;
|
|
8083
|
+
const reasoning = info.reasoning;
|
|
8084
|
+
const requested = defaulted.reasoningEffort;
|
|
8085
|
+
let resolvedConfig = defaulted;
|
|
8086
|
+
if (reasoning === void 0) {
|
|
8087
|
+
if (requested !== void 0) throw new LlmError(`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${requested}"`, "UNSUPPORTED_REASONING_EFFORT");
|
|
8088
|
+
} else {
|
|
8089
|
+
const effective = requested ?? reasoning.defaultEffort;
|
|
8090
|
+
if (effective !== void 0) {
|
|
8091
|
+
if (!reasoning.efforts.some((effort) => effort.id === effective)) throw new LlmError(`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${effective}"`, "UNSUPPORTED_REASONING_EFFORT");
|
|
8092
|
+
if (requested !== effective) resolvedConfig = {
|
|
8093
|
+
...defaulted,
|
|
8094
|
+
reasoningEffort: effective
|
|
8095
|
+
};
|
|
8096
|
+
}
|
|
8097
|
+
}
|
|
8098
|
+
return {
|
|
8099
|
+
config: resolvedConfig,
|
|
8100
|
+
...info.context === void 0 ? {} : { context: info.context },
|
|
8101
|
+
modelInfo: info
|
|
8102
|
+
};
|
|
8103
|
+
}
|
|
8104
|
+
/**
|
|
8105
|
+
* Resolve one call under its current adapter registration. The returned
|
|
8106
|
+
* one-shot handle keeps that registration across header logging and dispatch,
|
|
8107
|
+
* so HMR cannot combine one adapter's capability result with another adapter.
|
|
8108
|
+
* @param config - provider/model route and optional request controls.
|
|
8109
|
+
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
8110
|
+
* @returns a prepared config and its registration-bound stream entry point.
|
|
8111
|
+
*/
|
|
8112
|
+
async prepareCall(config, signal) {
|
|
8113
|
+
const registration = this.registration(config.provider);
|
|
8114
|
+
const adapterCall = await registration.adapter.prepareCall(config.provider, config.model, signal);
|
|
8115
|
+
const modelInfo = this.normalizeModelInfo(registration, config.model, adapterCall.model);
|
|
8116
|
+
const resolved = this.resolveCallWithInfo(config, modelInfo);
|
|
8117
|
+
const resolvedConfig = deepFreeze(structuredClone(resolved.config));
|
|
8118
|
+
const context = resolved.context === void 0 ? void 0 : deepFreeze(structuredClone(resolved.context));
|
|
8119
|
+
const adapterDefaults = deepFreeze({
|
|
8120
|
+
...config.reasoningEffort === void 0 && resolvedConfig.reasoningEffort !== void 0 ? { reasoningEffort: true } : {},
|
|
8121
|
+
...config.maxTokens === void 0 && resolvedConfig.maxTokens !== void 0 ? { maxTokens: true } : {}
|
|
8122
|
+
});
|
|
8123
|
+
let dispatched = false;
|
|
8124
|
+
return Object.freeze({
|
|
8125
|
+
config: resolvedConfig,
|
|
8126
|
+
retryPolicy: registration.retryPolicy,
|
|
8127
|
+
adapterDefaults,
|
|
8128
|
+
...context === void 0 ? {} : { context },
|
|
8129
|
+
...modelInfo.inputModalities === void 0 ? {} : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },
|
|
8130
|
+
...modelInfo.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: modelInfo.systemPromptUpdate },
|
|
8131
|
+
stream: (options) => {
|
|
8132
|
+
if (dispatched) throw new LlmError("a prepared LLM call can only be dispatched once", "INVALID_PREPARED_CALL");
|
|
8133
|
+
if (!callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
|
|
8134
|
+
dispatched = true;
|
|
8135
|
+
return this.streamWithRegistration(options, {
|
|
8136
|
+
registration,
|
|
8137
|
+
config: resolvedConfig,
|
|
8138
|
+
modelInfo,
|
|
8139
|
+
dispatch: (options2) => adapterCall.stream(options2)
|
|
8140
|
+
});
|
|
8141
|
+
}
|
|
8142
|
+
});
|
|
8143
|
+
}
|
|
8144
|
+
registration(provider) {
|
|
8145
|
+
const registration = this.adapters.get(provider);
|
|
8146
|
+
if (!registration) throw new LlmError(`no adapter registered for provider "${provider}"`, "NO_ADAPTER");
|
|
8147
|
+
return registration;
|
|
8148
|
+
}
|
|
8149
|
+
/** Remove replay state whose historical route is owned by another adapter. */
|
|
8150
|
+
forAdapter(options, adapter) {
|
|
8151
|
+
const messages = options.messages.map((message) => {
|
|
8152
|
+
const source = message.source;
|
|
8153
|
+
if (message.role !== "assistant" || source.kind !== "model" || source.replayState === void 0) return message;
|
|
8154
|
+
if (this.adapters.get(source.provider)?.adapter === adapter) return message;
|
|
8155
|
+
return freezeMessage({
|
|
8156
|
+
...message,
|
|
8157
|
+
source: {
|
|
8158
|
+
kind: "model",
|
|
8159
|
+
provider: source.provider,
|
|
8160
|
+
model: source.model
|
|
8161
|
+
}
|
|
8162
|
+
});
|
|
8163
|
+
});
|
|
8164
|
+
if (messages.every((message, index) => message === options.messages[index])) return options;
|
|
8165
|
+
const filtered = {
|
|
8166
|
+
...options,
|
|
8167
|
+
messages
|
|
8168
|
+
};
|
|
8169
|
+
return Object.isFrozen(options) ? deepFreeze(filtered) : filtered;
|
|
8170
|
+
}
|
|
8171
|
+
/**
|
|
8172
|
+
* Resolve the current execution-world read path of one durable file
|
|
8173
|
+
* reference through the mounted attachment and filesystem providers.
|
|
8174
|
+
*/
|
|
8175
|
+
fileReadPath(ref) {
|
|
8176
|
+
let hostPath;
|
|
8177
|
+
try {
|
|
8178
|
+
hostPath = this.ctx.get("attachments")?.fileHostPath(ref);
|
|
8179
|
+
} catch {
|
|
8180
|
+
return;
|
|
8181
|
+
}
|
|
8182
|
+
if (hostPath === void 0) return void 0;
|
|
8183
|
+
return this.ctx.get("fs")?.processPathFromHostPath(hostPath);
|
|
8184
|
+
}
|
|
8185
|
+
/**
|
|
8186
|
+
* Final adapter boundary. Adapter selection, dispatch, iterator construction,
|
|
8187
|
+
* and iteration failures become one terminal failure chunk. Middleware and
|
|
8188
|
+
* downstream consumer failures remain thrown plugin or consumer errors.
|
|
8189
|
+
*/
|
|
8190
|
+
async *adapterStream(options, prepared) {
|
|
8191
|
+
let iterator;
|
|
8192
|
+
try {
|
|
8193
|
+
const registration = prepared?.registration ?? this.registration(options.provider);
|
|
8194
|
+
const adapter = registration.adapter;
|
|
8195
|
+
let modelInfo;
|
|
8196
|
+
let resolvedConfig;
|
|
8197
|
+
let dispatch;
|
|
8198
|
+
if (prepared === void 0) {
|
|
8199
|
+
const adapterCall = await adapter.prepareCall(options.provider, options.model, options.signal);
|
|
8200
|
+
modelInfo = this.normalizeModelInfo(registration, options.model, adapterCall.model);
|
|
8201
|
+
resolvedConfig = this.resolveCallWithInfo(options, modelInfo).config;
|
|
8202
|
+
dispatch = (options2) => adapterCall.stream(options2);
|
|
8203
|
+
} else {
|
|
8204
|
+
modelInfo = prepared.modelInfo;
|
|
8205
|
+
resolvedConfig = prepared.config;
|
|
8206
|
+
dispatch = prepared.dispatch;
|
|
8207
|
+
}
|
|
8208
|
+
if (prepared !== void 0 && !callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
|
|
8209
|
+
const resolvedOptions = callConfigEquals(options, resolvedConfig) ? options : Object.isFrozen(options) ? deepFreeze({
|
|
8210
|
+
...options,
|
|
8211
|
+
...resolvedConfig
|
|
8212
|
+
}) : {
|
|
8213
|
+
...options,
|
|
8214
|
+
...resolvedConfig
|
|
8215
|
+
};
|
|
8216
|
+
let projectedMessages = resolvedOptions.messages;
|
|
8217
|
+
if (projectedMessages.some((message) => contentHasFile(message.content))) projectedMessages = projectFilesToText(projectedMessages, (ref) => this.fileReadPath(ref));
|
|
8218
|
+
if (modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && projectedMessages.some((message) => contentHasImage(message.content))) projectedMessages = projectImagesForTextModel(projectedMessages);
|
|
8219
|
+
const projectedOptions = projectedMessages === resolvedOptions.messages ? resolvedOptions : Object.isFrozen(resolvedOptions) ? deepFreeze({
|
|
8220
|
+
...resolvedOptions,
|
|
8221
|
+
messages: projectedMessages
|
|
8222
|
+
}) : {
|
|
8223
|
+
...resolvedOptions,
|
|
8224
|
+
messages: projectedMessages
|
|
8225
|
+
};
|
|
8226
|
+
iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
|
|
8227
|
+
} catch (error) {
|
|
8228
|
+
yield adapterFailureChunk(error, options.signal);
|
|
8229
|
+
return;
|
|
8230
|
+
}
|
|
8231
|
+
let completed = false;
|
|
8232
|
+
try {
|
|
8233
|
+
while (true) {
|
|
8234
|
+
let item;
|
|
8235
|
+
try {
|
|
8236
|
+
const next = await iterator.next();
|
|
8237
|
+
item = next.done ? { done: true } : {
|
|
8238
|
+
done: false,
|
|
8239
|
+
value: next.value
|
|
8240
|
+
};
|
|
8241
|
+
} catch (error) {
|
|
8242
|
+
completed = true;
|
|
8243
|
+
yield adapterFailureChunk(error, options.signal);
|
|
8244
|
+
return;
|
|
8245
|
+
}
|
|
8246
|
+
if (item.done) {
|
|
8247
|
+
completed = true;
|
|
8248
|
+
return;
|
|
8249
|
+
}
|
|
8250
|
+
yield item.value;
|
|
8251
|
+
}
|
|
8252
|
+
} finally {
|
|
8253
|
+
if (!completed) {
|
|
8254
|
+
const close = iterator.return?.bind(iterator);
|
|
8255
|
+
if (close) await close();
|
|
8256
|
+
}
|
|
8257
|
+
}
|
|
8258
|
+
}
|
|
8259
|
+
/**
|
|
8260
|
+
* Stream one model call as raw chunks (token-level deltas). Replay state is
|
|
8261
|
+
* retained only when the same adapter instance owns its historical provider
|
|
8262
|
+
* and the target provider. Final adapter selection remains fixed through
|
|
8263
|
+
* asynchronous exact-model resolution and dispatch. Adapter selection,
|
|
8264
|
+
* dispatch, and iteration failures become terminal `error` or `aborted`
|
|
8265
|
+
* finish chunks; middleware, nested-call, cleanup, and consumer failures
|
|
8266
|
+
* remain thrown.
|
|
8267
|
+
* @param options - the full request; `options.provider` selects the adapter.
|
|
8268
|
+
* @returns the chunk stream, possibly wrapped by `llm/stream` listeners.
|
|
8269
|
+
*/
|
|
8270
|
+
stream(options) {
|
|
8271
|
+
return this.streamWithRegistration(options);
|
|
8272
|
+
}
|
|
8273
|
+
streamWithRegistration(options, prepared) {
|
|
8274
|
+
return this.ctx.waterfall(this, "llm/stream", options, () => this.adapterStream(options, prepared));
|
|
8275
|
+
}
|
|
8276
|
+
};
|
|
8277
|
+
})();
|
|
8278
|
+
function adapterFailureChunk(error, signal) {
|
|
8279
|
+
const failure12 = normalizeLlmFailure(error);
|
|
8280
|
+
return {
|
|
8281
|
+
type: "finish",
|
|
8282
|
+
reason: signal?.aborted || failure12.code === "ABORTED" ? {
|
|
8283
|
+
kind: "aborted",
|
|
8284
|
+
failure: failure12
|
|
8285
|
+
} : {
|
|
8286
|
+
kind: "error",
|
|
8287
|
+
failure: failure12
|
|
8288
|
+
}
|
|
8289
|
+
};
|
|
8290
|
+
}
|
|
8291
|
+
|
|
8292
|
+
// node_modules/.pnpm/@deepseek-ai+dsh-web@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-llm@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2_/node_modules/@deepseek-ai/dsh-web/lib/index.js
|
|
8293
|
+
var WebError = class extends HarnessError {
|
|
8294
|
+
};
|
|
8295
|
+
var WebRuntime = class extends Service {
|
|
8296
|
+
/**
|
|
8297
|
+
* Provider selection config. Operational env overrides feed the SAME fields:
|
|
8298
|
+
* `$DSH_WEB_SEARCH_PROVIDER` / `$DSH_WEB_FETCH_PROVIDER` are equivalent to
|
|
8299
|
+
* `searchProvider` / `fetchProvider` and are NOT a hidden priority chain.
|
|
8300
|
+
*/
|
|
8301
|
+
static Config = Schema.object({
|
|
8302
|
+
searchProvider: Schema.string(),
|
|
8303
|
+
fetchProvider: Schema.string()
|
|
8304
|
+
});
|
|
8305
|
+
searchProviders = /* @__PURE__ */ new Map();
|
|
8306
|
+
fetchProviders = /* @__PURE__ */ new Map();
|
|
8307
|
+
searchProviderId;
|
|
8308
|
+
fetchProviderId;
|
|
8309
|
+
constructor(ctx, config = {}) {
|
|
8310
|
+
super(ctx, "web");
|
|
8311
|
+
this.searchProviderId = config.searchProvider ?? process.env.DSH_WEB_SEARCH_PROVIDER;
|
|
8312
|
+
this.fetchProviderId = config.fetchProvider ?? process.env.DSH_WEB_FETCH_PROVIDER;
|
|
8313
|
+
}
|
|
8314
|
+
/**
|
|
8315
|
+
* Register a search provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER`
|
|
8316
|
+
* if its id is already registered for search. Returns a disposer; disposed
|
|
8317
|
+
* with the calling fiber.
|
|
8318
|
+
* @param provider - the provider; its `id` is the registry key.
|
|
8319
|
+
* @returns the disposer that unregisters the provider.
|
|
8320
|
+
*/
|
|
8321
|
+
registerSearchProvider(provider) {
|
|
8322
|
+
return this.registerProvider(this.searchProviders, provider);
|
|
8323
|
+
}
|
|
8324
|
+
/**
|
|
8325
|
+
* Register a fetch provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER`
|
|
8326
|
+
* if its id is already registered for fetch. Returns a disposer; disposed
|
|
8327
|
+
* with the calling fiber.
|
|
8328
|
+
* @param provider - the provider; its `id` is the registry key.
|
|
8329
|
+
* @returns the disposer that unregisters the provider.
|
|
8330
|
+
*/
|
|
8331
|
+
registerFetchProvider(provider) {
|
|
8332
|
+
return this.registerProvider(this.fetchProviders, provider);
|
|
8333
|
+
}
|
|
8334
|
+
registerProvider(store, provider) {
|
|
8335
|
+
if (store.has(provider.id)) throw new WebError(`a web provider with id "${provider.id}" is already registered`, "WEB_DUPLICATE_PROVIDER");
|
|
8336
|
+
const dispose = this.ctx.effect(function* () {
|
|
8337
|
+
store.set(provider.id, provider);
|
|
8338
|
+
yield () => store.delete(provider.id);
|
|
8339
|
+
}, "web.registerProvider()");
|
|
8340
|
+
return () => void dispose();
|
|
8341
|
+
}
|
|
8342
|
+
/**
|
|
8343
|
+
* Run one search through the selected provider. Resolves the provider at call
|
|
8344
|
+
* time with the selection rules above; throws {@link WebError} when the
|
|
8345
|
+
* capability cannot run. The seam enforces `request.maxResults` on the result:
|
|
8346
|
+
* if the provider over-returns, `sources[]` is truncated and `truncated` set.
|
|
8347
|
+
* @param request - the query and optional result limit.
|
|
8348
|
+
* @param signal - optional cancellation signal forwarded to the provider.
|
|
8349
|
+
* @returns the provider's results, capped to `request.maxResults`.
|
|
8350
|
+
*/
|
|
8351
|
+
async search(request, signal) {
|
|
8352
|
+
return capSources(await resolveProvider({
|
|
8353
|
+
providers: this.searchProviders,
|
|
8354
|
+
...this.searchProviderId !== void 0 ? { configuredId: this.searchProviderId } : {}
|
|
8355
|
+
}).search(request, signal), request.maxResults);
|
|
8356
|
+
}
|
|
8357
|
+
/**
|
|
8358
|
+
* Retrieve one URL through the selected provider. Resolves the provider at
|
|
8359
|
+
* call time with the selection rules above; throws {@link WebError} when the
|
|
8360
|
+
* capability cannot run. A non-2xx response is a result, not a throw.
|
|
8361
|
+
* @param request - the URL plus retrieval options.
|
|
8362
|
+
* @param signal - optional cancellation signal forwarded to the provider.
|
|
8363
|
+
* @returns the retrieval outcome; non-2xx responses resolve descriptively.
|
|
8364
|
+
*/
|
|
8365
|
+
async fetch(request, signal) {
|
|
8366
|
+
return resolveProvider({
|
|
8367
|
+
providers: this.fetchProviders,
|
|
8368
|
+
...this.fetchProviderId !== void 0 ? { configuredId: this.fetchProviderId } : {}
|
|
8369
|
+
}).fetch(request, signal);
|
|
8370
|
+
}
|
|
8371
|
+
};
|
|
8372
|
+
function resolveProvider(selection) {
|
|
8373
|
+
const { configuredId, providers } = selection;
|
|
8374
|
+
if (configuredId !== void 0) {
|
|
8375
|
+
const provider = providers.get(configuredId);
|
|
8376
|
+
if (!provider) throw new WebError(`configured web provider "${configuredId}" is not registered`, "WEB_PROVIDER_CONFIGURED_MISSING");
|
|
8377
|
+
if (!provider.available()) throw new WebError(`configured web provider "${configuredId}" is registered but unavailable`, "WEB_PROVIDER_CONFIGURED_UNAVAILABLE");
|
|
8378
|
+
return provider;
|
|
8379
|
+
}
|
|
8380
|
+
const usable = [...providers.values()].filter((provider) => provider.available());
|
|
8381
|
+
const [single] = usable;
|
|
8382
|
+
if (single === void 0) throw new WebError("no usable web provider is registered", "WEB_PROVIDER_UNAVAILABLE");
|
|
8383
|
+
if (usable.length > 1) throw new WebError(`multiple usable web providers are registered (${usable.map((provider) => provider.id).join(", ")}); configure one explicitly`, "WEB_PROVIDER_AMBIGUOUS");
|
|
8384
|
+
return single;
|
|
8385
|
+
}
|
|
8386
|
+
function capSources(result, maxResults) {
|
|
8387
|
+
if (maxResults === void 0 || result.sources.length <= maxResults) return result;
|
|
8388
|
+
return {
|
|
8389
|
+
...result,
|
|
8390
|
+
sources: result.sources.slice(0, maxResults),
|
|
8391
|
+
truncated: true
|
|
8392
|
+
};
|
|
8393
|
+
}
|
|
8394
|
+
|
|
8395
|
+
// src/web-search.ts
|
|
8396
|
+
var HIWORK_SEARCH_PROVIDER_ID = "hiwork-managed-search";
|
|
8397
|
+
var ME_WEB_SEARCH_PATH = "/api/me/web-search";
|
|
8398
|
+
var WEB_SEARCH_TIMEOUT_MS = 12e3;
|
|
8399
|
+
var HiworkSearchProvider = class {
|
|
8400
|
+
constructor(deps) {
|
|
8401
|
+
this.deps = deps;
|
|
8402
|
+
}
|
|
8403
|
+
id = HIWORK_SEARCH_PROVIDER_ID;
|
|
8404
|
+
available() {
|
|
8405
|
+
return true;
|
|
8406
|
+
}
|
|
8407
|
+
async search(request, signal) {
|
|
8408
|
+
if (signal?.aborted) throw aborted();
|
|
8409
|
+
let sessionCookie;
|
|
8410
|
+
try {
|
|
8411
|
+
sessionCookie = await this.deps.resolveSession();
|
|
8412
|
+
} catch (error) {
|
|
8413
|
+
this.deps.log?.(`hiwork-core: \u8054\u7F51\u641C\u7D22\u4F1A\u8BDD\u89E3\u6790\u5931\u8D25\uFF08${errorName4(error)}\uFF09`);
|
|
8414
|
+
throw new WebError("\u8054\u7F51\u641C\u7D22\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", HIWORK_SEARCH_UNREACHABLE, { cause: error });
|
|
8415
|
+
}
|
|
8416
|
+
if (sessionCookie === void 0 || sessionCookie === "") {
|
|
8417
|
+
throw new WebError("\u767B\u5F55\u540E\u624D\u80FD\u4F7F\u7528\u8054\u7F51\u641C\u7D22", HIWORK_SEARCH_UNAUTHENTICATED);
|
|
8418
|
+
}
|
|
8419
|
+
const gateway = this.deps.gateway();
|
|
8420
|
+
if (gateway === void 0) {
|
|
8421
|
+
throw new WebError("\u8054\u7F51\u641C\u7D22\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", HIWORK_SEARCH_UNREACHABLE);
|
|
8422
|
+
}
|
|
8423
|
+
const outcome = await gateway.searchWeb(
|
|
8424
|
+
sessionCookie,
|
|
8425
|
+
// maxResults 是 tool-web 传进来的上限,透传给网关少拉数据(费用按次不按条,
|
|
8426
|
+
// 但少拉条数能压响应体)。seam 仍会在结果侧强制截断,这里只是配合。
|
|
8427
|
+
{ query: request.query, ...request.maxResults !== void 0 ? { max_results: request.maxResults } : {} },
|
|
8428
|
+
signal
|
|
8429
|
+
);
|
|
8430
|
+
if (!outcome.ok) {
|
|
8431
|
+
throw outcomeToError(outcome);
|
|
8432
|
+
}
|
|
8433
|
+
const view = projectWebSearch(outcome.body);
|
|
8434
|
+
if (view === void 0) {
|
|
8435
|
+
this.deps.log?.("hiwork-core: \u7F51\u5173\u8FD4\u56DE\u7684 web-search \u54CD\u5E94\u4E0D\u7B26\u5408\u5951\u7EA6\uFF08results \u7F3A\u5931\u6216\u5F62\u72B6\u4E0D\u5BF9\uFF09");
|
|
8436
|
+
throw new WebError("\u8054\u7F51\u641C\u7D22\u8FD4\u56DE\u4E86\u65E0\u6CD5\u89E3\u6790\u7684\u7ED3\u679C", "WEB_PROVIDER_ERROR");
|
|
8437
|
+
}
|
|
8438
|
+
return view;
|
|
8439
|
+
}
|
|
8440
|
+
};
|
|
8441
|
+
var HIWORK_SEARCH_UNAUTHENTICATED = "HIWORK_SEARCH_UNAUTHENTICATED";
|
|
8442
|
+
var HIWORK_SEARCH_DISABLED = "HIWORK_SEARCH_DISABLED";
|
|
8443
|
+
var HIWORK_SEARCH_NOT_CONFIGURED = "HIWORK_SEARCH_NOT_CONFIGURED";
|
|
8444
|
+
var HIWORK_SEARCH_LIMIT = "HIWORK_SEARCH_LIMIT";
|
|
8445
|
+
var HIWORK_SEARCH_UNREACHABLE = "HIWORK_SEARCH_UNREACHABLE";
|
|
8446
|
+
var HIWORK_SEARCH_UPSTREAM = "HIWORK_SEARCH_UPSTREAM";
|
|
8447
|
+
function aborted() {
|
|
8448
|
+
return new WebError("\u8054\u7F51\u641C\u7D22\u5DF2\u53D6\u6D88", "WEB_ABORTED");
|
|
8449
|
+
}
|
|
8450
|
+
function outcomeToError(outcome) {
|
|
8451
|
+
switch (outcome.kind) {
|
|
8452
|
+
case "aborted":
|
|
8453
|
+
return aborted();
|
|
8454
|
+
case "network":
|
|
8455
|
+
return new WebError("\u8054\u7F51\u641C\u7D22\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", HIWORK_SEARCH_UNREACHABLE);
|
|
8456
|
+
case "invalid":
|
|
8457
|
+
return new WebError("\u8054\u7F51\u641C\u7D22\u8FD4\u56DE\u4E86\u65E0\u6CD5\u89E3\u6790\u7684\u7ED3\u679C", "WEB_PROVIDER_ERROR");
|
|
8458
|
+
case "http":
|
|
8459
|
+
return httpOutcomeToError(outcome.status, outcome.code);
|
|
8460
|
+
}
|
|
8461
|
+
}
|
|
8462
|
+
function httpOutcomeToError(status, code) {
|
|
8463
|
+
if (status === 401) return new WebError("\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", HIWORK_SEARCH_UNAUTHENTICATED);
|
|
8464
|
+
if (code === "SEARCH_DISABLED") return new WebError("\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\u8054\u7F51\u641C\u7D22", HIWORK_SEARCH_DISABLED);
|
|
8465
|
+
if (code === "SEARCH_NOT_CONFIGURED") return new WebError("\u8054\u7F51\u641C\u7D22\u8FD8\u6CA1\u6709\u914D\u7F6E\u597D\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458", HIWORK_SEARCH_NOT_CONFIGURED);
|
|
8466
|
+
if (status === 429 || code === "SEARCH_LIMIT_EXCEEDED") return new WebError("\u4ECA\u5929\u7684\u8054\u7F51\u641C\u7D22\u6B21\u6570\u5DF2\u7528\u5B8C\uFF0C\u660E\u5929\u518D\u6765", HIWORK_SEARCH_LIMIT);
|
|
8467
|
+
if (status === 502 || code === "SEARCH_UPSTREAM_ERROR") return new WebError("\u641C\u7D22\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", HIWORK_SEARCH_UPSTREAM);
|
|
8468
|
+
return new WebError("\u8054\u7F51\u641C\u7D22\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", "WEB_PROVIDER_ERROR");
|
|
8469
|
+
}
|
|
8470
|
+
function textOrUndefined8(value) {
|
|
8471
|
+
if (typeof value !== "string") return void 0;
|
|
8472
|
+
const text = value.trim();
|
|
8473
|
+
return text === "" ? void 0 : text;
|
|
8474
|
+
}
|
|
8475
|
+
function projectWebSearch(body) {
|
|
8476
|
+
const raw = body["results"];
|
|
8477
|
+
if (!Array.isArray(raw)) return void 0;
|
|
8478
|
+
const sources = [];
|
|
8479
|
+
for (const item of raw) {
|
|
8480
|
+
if (item === null || typeof item !== "object") continue;
|
|
8481
|
+
const record = item;
|
|
8482
|
+
const url = textOrUndefined8(record["url"]);
|
|
8483
|
+
if (url === void 0) continue;
|
|
8484
|
+
const title = textOrUndefined8(record["title"]);
|
|
8485
|
+
const snippet = textOrUndefined8(record["snippet"]) ?? textOrUndefined8(record["content"]);
|
|
8486
|
+
const publishedAt = textOrUndefined8(record["published_at"]);
|
|
8487
|
+
sources.push({
|
|
8488
|
+
url,
|
|
8489
|
+
...title !== void 0 ? { title } : {},
|
|
8490
|
+
...snippet !== void 0 ? { snippet } : {},
|
|
8491
|
+
...publishedAt !== void 0 ? { publishedAt } : {}
|
|
8492
|
+
});
|
|
8493
|
+
}
|
|
8494
|
+
return { sources, truncated: false };
|
|
8495
|
+
}
|
|
8496
|
+
function errorName4(error) {
|
|
8497
|
+
return error instanceof Error ? error.name : typeof error;
|
|
8498
|
+
}
|
|
8499
|
+
function createWebSearchGateway(baseURL, opts = {}) {
|
|
8500
|
+
const root = baseURL.replace(/\/+$/, "");
|
|
8501
|
+
const timeoutMs = opts.timeoutMs ?? WEB_SEARCH_TIMEOUT_MS;
|
|
8502
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
8503
|
+
return {
|
|
8504
|
+
async searchWeb(sessionCookie, body, signal) {
|
|
8505
|
+
const signals = [AbortSignal.timeout(timeoutMs)];
|
|
8506
|
+
if (signal !== void 0) signals.push(signal);
|
|
8507
|
+
const merged = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
8508
|
+
let res;
|
|
8509
|
+
try {
|
|
8510
|
+
res = await doFetch(`${root}${ME_WEB_SEARCH_PATH}`, {
|
|
8511
|
+
method: "POST",
|
|
8512
|
+
headers: {
|
|
8513
|
+
"user-agent": `hiwork-core/${PKG_VERSION}`,
|
|
8514
|
+
cookie: sessionCookie,
|
|
8515
|
+
"content-type": "application/json"
|
|
8516
|
+
},
|
|
8517
|
+
body: JSON.stringify(body),
|
|
8518
|
+
signal: merged
|
|
8519
|
+
});
|
|
8520
|
+
} catch (error) {
|
|
8521
|
+
if (signal?.aborted) return { ok: false, kind: "aborted" };
|
|
8522
|
+
return { ok: false, kind: "network" };
|
|
8523
|
+
}
|
|
8524
|
+
if (!res.ok) {
|
|
8525
|
+
let code;
|
|
8526
|
+
try {
|
|
8527
|
+
const parsed2 = await res.json();
|
|
8528
|
+
if (typeof parsed2?.code === "string" && parsed2.code !== "") code = parsed2.code;
|
|
8529
|
+
} catch {
|
|
8530
|
+
}
|
|
8531
|
+
return { ok: false, kind: "http", status: res.status, ...code !== void 0 ? { code } : {} };
|
|
8532
|
+
}
|
|
8533
|
+
let parsed;
|
|
8534
|
+
try {
|
|
8535
|
+
parsed = await res.json();
|
|
8536
|
+
} catch {
|
|
8537
|
+
return { ok: false, kind: "invalid" };
|
|
8538
|
+
}
|
|
8539
|
+
if (parsed === null || typeof parsed !== "object") return { ok: false, kind: "invalid" };
|
|
8540
|
+
return { ok: true, body: parsed };
|
|
8541
|
+
}
|
|
8542
|
+
};
|
|
8543
|
+
}
|
|
8544
|
+
function registerManagedSearchProvider(web, deps) {
|
|
8545
|
+
return web.registerSearchProvider(new HiworkSearchProvider(deps));
|
|
8546
|
+
}
|
|
8547
|
+
|
|
6757
8548
|
// src/index.ts
|
|
6758
8549
|
var name = "hiwork-core";
|
|
6759
|
-
var inject = ["settings", "credentials", "workspaceRegistry", "connection"];
|
|
8550
|
+
var inject = ["settings", "credentials", "workspaceRegistry", "connection", "web"];
|
|
6760
8551
|
var NS = "ai-router";
|
|
6761
8552
|
var PI_AI_NS = "llm-pi-ai";
|
|
6762
8553
|
var lastErrorSchema = Schema.object({ code: Schema.string().required(), message: Schema.string().required() }).default(void 0);
|
|
@@ -6879,14 +8670,14 @@ function parseDeviceInfo(message) {
|
|
|
6879
8670
|
const id = deviceId.value?.trim();
|
|
6880
8671
|
if (id === void 0 || id === "") return void 0;
|
|
6881
8672
|
const name2 = deviceName.value?.trim();
|
|
6882
|
-
const
|
|
8673
|
+
const version2 = appVersion.value?.trim();
|
|
6883
8674
|
return {
|
|
6884
8675
|
deviceId: id,
|
|
6885
8676
|
...name2 === void 0 || name2 === "" ? {} : { deviceName: name2 },
|
|
6886
|
-
...
|
|
8677
|
+
...version2 === void 0 || version2 === "" ? {} : { appVersion: version2 }
|
|
6887
8678
|
};
|
|
6888
8679
|
}
|
|
6889
|
-
function
|
|
8680
|
+
function errorName5(error) {
|
|
6890
8681
|
return error instanceof Error ? error.name : typeof error;
|
|
6891
8682
|
}
|
|
6892
8683
|
function registerIpcMessageHandler(ports) {
|
|
@@ -6913,12 +8704,12 @@ function registerIpcMessageHandler(ports) {
|
|
|
6913
8704
|
authInFlight = true;
|
|
6914
8705
|
try {
|
|
6915
8706
|
void Promise.resolve(ports.complete(input)).catch((error) => {
|
|
6916
|
-
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5904\u7406\u5F02\u5E38\uFF08${
|
|
8707
|
+
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5904\u7406\u5F02\u5E38\uFF08${errorName5(error)}\uFF09`);
|
|
6917
8708
|
}).finally(() => {
|
|
6918
8709
|
authInFlight = false;
|
|
6919
8710
|
});
|
|
6920
8711
|
} catch (error) {
|
|
6921
|
-
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5206\u53D1\u5931\u8D25\uFF08${
|
|
8712
|
+
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5206\u53D1\u5931\u8D25\uFF08${errorName5(error)}\uFF09`);
|
|
6922
8713
|
authInFlight = false;
|
|
6923
8714
|
}
|
|
6924
8715
|
return;
|
|
@@ -6966,12 +8757,12 @@ async function projectLoginMode(opts) {
|
|
|
6966
8757
|
}
|
|
6967
8758
|
}
|
|
6968
8759
|
} catch (error) {
|
|
6969
|
-
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u62C9\u53D6\u5931\u8D25\uFF0C\u767B\u5F55\u65B9\u5F0F\u56DE\u9000 both\uFF08${
|
|
8760
|
+
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u62C9\u53D6\u5931\u8D25\uFF0C\u767B\u5F55\u65B9\u5F0F\u56DE\u9000 both\uFF08${errorName5(error)}\uFF09`);
|
|
6970
8761
|
}
|
|
6971
8762
|
try {
|
|
6972
8763
|
await opts.state.patch({ set: { loginMode, passwordLogin } });
|
|
6973
8764
|
} catch (error) {
|
|
6974
|
-
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u5199\u5165\u5931\u8D25\uFF08${
|
|
8765
|
+
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u5199\u5165\u5931\u8D25\uFF08${errorName5(error)}\uFF09`);
|
|
6975
8766
|
}
|
|
6976
8767
|
return loginMode;
|
|
6977
8768
|
}
|
|
@@ -7109,6 +8900,16 @@ function apply(ctx, config) {
|
|
|
7109
8900
|
ctx.logger.warn(message);
|
|
7110
8901
|
}
|
|
7111
8902
|
}), "hiwork-core: daily rpc");
|
|
8903
|
+
ctx.effect(() => registerVoiceRpc({ connection, logger: ctx.logger }, {
|
|
8904
|
+
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
8905
|
+
gateway: () => {
|
|
8906
|
+
const baseURL = current().baseURL;
|
|
8907
|
+
return baseURL === void 0 || baseURL === "" ? void 0 : gatewayFor(baseURL);
|
|
8908
|
+
},
|
|
8909
|
+
log: (message) => {
|
|
8910
|
+
ctx.logger.warn(message);
|
|
8911
|
+
}
|
|
8912
|
+
}), "hiwork-core: voice rpc");
|
|
7112
8913
|
const managedMcpDeps = {
|
|
7113
8914
|
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
7114
8915
|
gateway: () => {
|
|
@@ -7194,7 +8995,12 @@ function apply(ctx, config) {
|
|
|
7194
8995
|
};
|
|
7195
8996
|
}, "hiwork-core: workbench warmup");
|
|
7196
8997
|
const pmsTodoDeps = {
|
|
7197
|
-
|
|
8998
|
+
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
8999
|
+
gateway: () => {
|
|
9000
|
+
const baseURL = current().baseURL;
|
|
9001
|
+
return baseURL === void 0 || baseURL === "" ? void 0 : gatewayFor(baseURL);
|
|
9002
|
+
},
|
|
9003
|
+
resolveDeviceId: async () => (await resolveDeviceInfo(ports.credentials)).deviceId,
|
|
7198
9004
|
pms: new PmsClient({ log: (message) => {
|
|
7199
9005
|
ctx.logger.warn(message);
|
|
7200
9006
|
} }),
|
|
@@ -7207,6 +9013,9 @@ function apply(ctx, config) {
|
|
|
7207
9013
|
() => registerPmsTodoRpc({ connection, logger: ctx.logger }, pmsTodoDeps, pmsTodo),
|
|
7208
9014
|
"hiwork-core: pms todo rpc"
|
|
7209
9015
|
);
|
|
9016
|
+
void clearLegacyCredentials(ports.credentials, (message) => {
|
|
9017
|
+
ctx.logger.warn(message);
|
|
9018
|
+
});
|
|
7210
9019
|
ctx.effect(() => provideOfficeCredentials(ctx, {
|
|
7211
9020
|
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
7212
9021
|
gateway: () => {
|
|
@@ -7233,6 +9042,16 @@ function apply(ctx, config) {
|
|
|
7233
9042
|
ctx.logger.warn(message);
|
|
7234
9043
|
}
|
|
7235
9044
|
}), "hiwork-core: memory config service");
|
|
9045
|
+
ctx.effect(() => provideKnowledgeConfig(ctx, {
|
|
9046
|
+
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
9047
|
+
gateway: () => {
|
|
9048
|
+
const baseURL = current().baseURL;
|
|
9049
|
+
return baseURL === void 0 || baseURL === "" ? void 0 : createKnowledgeConfigGateway(baseURL);
|
|
9050
|
+
},
|
|
9051
|
+
log: (message) => {
|
|
9052
|
+
ctx.logger.warn(message);
|
|
9053
|
+
}
|
|
9054
|
+
}), "hiwork-core: knowledge config service");
|
|
7236
9055
|
ctx.effect(() => provideOfficeRelay(ctx, {
|
|
7237
9056
|
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
7238
9057
|
gateway: () => {
|
|
@@ -7243,6 +9062,19 @@ function apply(ctx, config) {
|
|
|
7243
9062
|
ctx.logger.warn(message);
|
|
7244
9063
|
}
|
|
7245
9064
|
}), "hiwork-core: office relay");
|
|
9065
|
+
const web = ctx.get("web");
|
|
9066
|
+
if (web !== void 0) {
|
|
9067
|
+
registerManagedSearchProvider(web, {
|
|
9068
|
+
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
9069
|
+
gateway: () => {
|
|
9070
|
+
const baseURL = current().baseURL;
|
|
9071
|
+
return baseURL === void 0 || baseURL === "" ? void 0 : createWebSearchGateway(baseURL);
|
|
9072
|
+
},
|
|
9073
|
+
log: (message) => {
|
|
9074
|
+
ctx.logger.warn(message);
|
|
9075
|
+
}
|
|
9076
|
+
});
|
|
9077
|
+
}
|
|
7246
9078
|
ctx.effect(() => registerAppVersionRpc({ connection, logger: ctx.logger }, {
|
|
7247
9079
|
appVersion: () => pushedAppVersion(),
|
|
7248
9080
|
log: (message) => {
|