hiwork-core 0.0.24 → 0.0.30

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/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.24";
3140
+ var PKG_VERSION = false ? "dev" : "0.0.30";
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 version = typeof raw === "string" ? raw.trim() : "";
4222
- return version === "" ? null : version;
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 isAborted4(signal) {
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 (isAborted4(signal)) return failure4("cancelled", "MCP \u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
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 failure4("SESSION_EXPIRED", MANAGED_MCP_SESSION_EXPIRED_MESSAGE);
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 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");
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 (isAborted4(signal)) return failure4("cancelled", "MCP \u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
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 failure4("internal", MANAGED_MCP_GENERIC_FAILURE_MESSAGE);
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 failure4(code, message) {
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 isAborted5(signal) {
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 (isAborted5(signal)) return failure5("cancelled", "\u6280\u80FD\u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
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 failure5("SESSION_EXPIRED", MANAGED_SKILLS_SESSION_EXPIRED_MESSAGE);
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 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");
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 (isAborted5(signal)) return failure5("cancelled", "\u6280\u80FD\u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
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 failure5("internal", MANAGED_SKILLS_GENERIC_FAILURE_MESSAGE);
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 failure5(MANAGED_SKILLS_PACKAGE_TOO_LARGE_CODE, MANAGED_SKILLS_PACKAGE_TOO_LARGE_MESSAGE);
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 failure5(code, message) {
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 isAborted6(signal) {
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 (isAborted6(signal)) return failure6("cancelled", "\u5DE5\u4F5C\u53F0\u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
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 failure6(code, message) {
5655
+ function failure7(code, message) {
5439
5656
  return { ok: false, error: { code, message, details: {} } };
5440
5657
  }
5441
5658
  function registerWorkbenchRpc(ctx, deps, store) {
@@ -5695,6 +5912,10 @@ var PmsClient = class {
5695
5912
  * 必须与其余调用逐字相同**(spec §7:统一 10s、令牌只在有身份时发),所以两条路共用这一处,
5696
5913
  * 而不是各抄一份、将来各自漂移。
5697
5914
  *
5915
+ * **重定向自己跟**(`redirect: 'manual'` + {@link redirectTargetOf}):`fetch` 的默认 `follow` 会把
5916
+ * 重定向后的请求按 fetch 语义**把 POST 改成 GET**,于是请求落在一个只认 POST 的映射上、拿到一个
5917
+ * 「200 + 业务错误信封」,看上去像上游坏了。判据与修法见 {@link PMS_MAX_REDIRECTS}。
5918
+ *
5698
5919
  * @param url - 相对路径(拼 {@link PmsClient.baseURL})或**绝对 URL**(原样使用)。
5699
5920
  * @param opts - 方法 / 可选 JSON 体 / 可选 Bearer 令牌。
5700
5921
  * @returns 原始 `Response`:状态码由调用方判定,本方法不把任何状态码当成错误。
@@ -5710,10 +5931,19 @@ var PmsClient = class {
5710
5931
  const init = {
5711
5932
  method: opts.method,
5712
5933
  headers,
5713
- signal: AbortSignal.timeout(this.timeoutMs)
5934
+ // 手动跟随:默认的 `follow` 会把重定向后的 POST 降级成 GET(见本方法的抬头)。
5935
+ redirect: "manual"
5714
5936
  };
5715
5937
  if (opts.body !== void 0) init.body = JSON.stringify(opts.body);
5716
- return await fetch(isAbsoluteUrl(url) ? url : `${this.baseURL}${url}`, init);
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
+ }
5717
5947
  } catch {
5718
5948
  throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
5719
5949
  }
@@ -5767,6 +5997,30 @@ function isRecord3(value) {
5767
5997
  function isAbsoluteUrl(url) {
5768
5998
  return /^https?:\/\//i.test(url);
5769
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
+ }
5770
6024
  function expiresAtOr(value, fallbackMs) {
5771
6025
  if (typeof value === "string" && value !== "" && Number.isFinite(Date.parse(value))) return value;
5772
6026
  return new Date(fallbackMs).toISOString();
@@ -6297,7 +6551,7 @@ var PmsTodoStore = class {
6297
6551
  };
6298
6552
  async function handlePmsTodoEndpoint(endpoint, rawPayload, deps, store, signal) {
6299
6553
  try {
6300
- if (signal?.aborted === true) return failure7("BAD_REQUEST", PMS_TODO_CANCELLED_MESSAGE);
6554
+ if (signal?.aborted === true) return failure8("BAD_REQUEST", PMS_TODO_CANCELLED_MESSAGE);
6301
6555
  if (!isPmsTodoRpcEndpoint(endpoint)) {
6302
6556
  throw new PmsTodoRequestError(`\u672A\u77E5\u7684 PMS \u5F85\u529E\u7AEF\u70B9 '${endpoint}'`);
6303
6557
  }
@@ -6321,7 +6575,7 @@ async function handlePmsTodoEndpoint(endpoint, rawPayload, deps, store, signal)
6321
6575
  return { ok: false, error: pmsTodoErrorOf(error) };
6322
6576
  }
6323
6577
  }
6324
- function failure7(code, message) {
6578
+ function failure8(code, message) {
6325
6579
  return { ok: false, error: { code, message, details: {} } };
6326
6580
  }
6327
6581
  function registerPmsTodoRpc(ctx, deps, store) {
@@ -6352,7 +6606,7 @@ function isRecord4(value) {
6352
6606
  function textOrUndefined4(value) {
6353
6607
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
6354
6608
  }
6355
- function isAborted7(signal) {
6609
+ function isAborted8(signal) {
6356
6610
  return signal?.aborted === true;
6357
6611
  }
6358
6612
  function parsePluginUpdatesPayload(endpoint, raw) {
@@ -6395,7 +6649,7 @@ function toSnapshot(raw) {
6395
6649
  }
6396
6650
  async function handlePluginUpdatesEndpoint(endpoint, rawPayload, deps, signal) {
6397
6651
  try {
6398
- if (isAborted7(signal)) return failure8("cancelled", "\u63D2\u4EF6\u66F4\u65B0\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
6652
+ if (isAborted8(signal)) return failure9("cancelled", "\u63D2\u4EF6\u66F4\u65B0\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
6399
6653
  if (!isPluginUpdatesRpcEndpoint(endpoint)) {
6400
6654
  throw new PluginUpdatesRequestError("bad-request", `\u672A\u77E5\u7684\u63D2\u4EF6\u66F4\u65B0\u7AEF\u70B9 '${endpoint}'`);
6401
6655
  }
@@ -6411,12 +6665,12 @@ async function handlePluginUpdatesEndpoint(endpoint, rawPayload, deps, signal) {
6411
6665
  await plugins.applyNow();
6412
6666
  return { ok: true, value: { requested: true } };
6413
6667
  } catch (error) {
6414
- if (error instanceof PluginUpdatesRequestError) return failure8(error.code, error.message);
6668
+ if (error instanceof PluginUpdatesRequestError) return failure9(error.code, error.message);
6415
6669
  deps.log?.(`hiwork-core: \u63D2\u4EF6\u66F4\u65B0 RPC '${endpoint}' \u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
6416
- return failure8("internal", PLUGIN_UPDATES_GENERIC_FAILURE_MESSAGE);
6670
+ return failure9("internal", PLUGIN_UPDATES_GENERIC_FAILURE_MESSAGE);
6417
6671
  }
6418
6672
  }
6419
- function failure8(code, message) {
6673
+ function failure9(code, message) {
6420
6674
  return { ok: false, error: { code, message, details: {} } };
6421
6675
  }
6422
6676
  function registerPluginUpdatesRpc(ctx, deps) {
@@ -6487,16 +6741,15 @@ function provideIdentity(ctx, deps) {
6487
6741
  };
6488
6742
  }
6489
6743
 
6490
- // src/memory-config.ts
6491
- var HIWORK_MEMORY_SERVICE = "hiworkMemory";
6492
- var ME_MEMORY_CONFIG_PATH = "/api/me/memory-config";
6493
- var MEMORY_CONFIG_TTL_MS = 5 * 60 * 1e3;
6494
- var MEMORY_CONFIG_TIMEOUT_MS = 1e4;
6495
- var MESSAGE_NOT_CONFIGURED = "\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\u56E2\u961F\u8BB0\u5FC6\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u5F00\u901A";
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";
6496
6750
  var MESSAGE_UNAUTHENTICATED = "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55";
6497
- var MESSAGE_UNREACHABLE = "\u8BB0\u5FC6\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
6498
- var MESSAGE_NO_GATEWAY = "\u8FD8\u6CA1\u8FDE\u4E0A\u4F01\u4E1A\u7F51\u5173\uFF0C\u8BF7\u5148\u767B\u5F55\u540E\u518D\u8BD5";
6499
- 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";
6500
6753
  function messageFor(reason) {
6501
6754
  switch (reason) {
6502
6755
  case "not-configured":
@@ -6509,7 +6762,7 @@ function messageFor(reason) {
6509
6762
  return MESSAGE_GENERIC;
6510
6763
  }
6511
6764
  }
6512
- function failure9(reason, message) {
6765
+ function failure10(reason, message) {
6513
6766
  return { ok: false, reason, message: message ?? messageFor(reason) };
6514
6767
  }
6515
6768
  function textOrUndefined6(value) {
@@ -6517,19 +6770,172 @@ function textOrUndefined6(value) {
6517
6770
  const text = value.trim();
6518
6771
  return text === "" ? void 0 : text;
6519
6772
  }
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;
6777
+ return {
6778
+ ok: true,
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"]) ?? ""
6784
+ };
6785
+ }
6786
+ function errorName2(error) {
6787
+ return error instanceof Error ? error.name : typeof error;
6788
+ }
6789
+ function reasonOfErrorCode(error) {
6790
+ const code = error?.code;
6791
+ if (typeof code !== "string") return void 0;
6792
+ switch (code) {
6793
+ case "SESSION_EXPIRED":
6794
+ return "unauthenticated";
6795
+ case "KNOWLEDGE_NOT_CONFIGURED":
6796
+ return "not-configured";
6797
+ case "KNOWLEDGE_UNREACHABLE":
6798
+ case "GATEWAY_UNREACHABLE":
6799
+ return "unreachable";
6800
+ default:
6801
+ return void 0;
6802
+ }
6803
+ }
6804
+ function reasonOfStatus(status) {
6805
+ if (status === 401) return "unauthenticated";
6806
+ if (status === 503) return "not-configured";
6807
+ if (status === 504) return "unreachable";
6808
+ return "error";
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
+ }
6520
6926
  function teamIdsOf(value) {
6521
6927
  if (!Array.isArray(value)) return [];
6522
6928
  const ids = [];
6523
6929
  for (const item of value) {
6524
- const text = textOrUndefined6(item);
6930
+ const text = textOrUndefined7(item);
6525
6931
  if (text !== void 0) ids.push(text);
6526
6932
  }
6527
6933
  return ids;
6528
6934
  }
6529
6935
  function projectMemoryConfig(body) {
6530
- const panelUrl = textOrUndefined6(body["panel_url"] ?? body["panelUrl"]);
6531
- const serviceId = textOrUndefined6(body["service_id"] ?? body["serviceId"]);
6532
- const apiKey = textOrUndefined6(body["api_key"] ?? body["apiKey"] ?? body["userKey"]);
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"]);
6533
6939
  if (panelUrl === void 0 || serviceId === void 0 || apiKey === void 0) return void 0;
6534
6940
  return {
6535
6941
  ok: true,
@@ -6539,10 +6945,10 @@ function projectMemoryConfig(body) {
6539
6945
  apiKey
6540
6946
  };
6541
6947
  }
6542
- function errorName2(error) {
6948
+ function errorName3(error) {
6543
6949
  return error instanceof Error ? error.name : typeof error;
6544
6950
  }
6545
- function reasonOfErrorCode(error) {
6951
+ function reasonOfErrorCode2(error) {
6546
6952
  const code = error?.code;
6547
6953
  if (typeof code !== "string") return void 0;
6548
6954
  switch (code) {
@@ -6557,7 +6963,7 @@ function reasonOfErrorCode(error) {
6557
6963
  return void 0;
6558
6964
  }
6559
6965
  }
6560
- function reasonOfStatus(status) {
6966
+ function reasonOfStatus2(status) {
6561
6967
  if (status === 401) return "unauthenticated";
6562
6968
  if (status === 503) return "not-configured";
6563
6969
  if (status === 504) return "unreachable";
@@ -6574,10 +6980,10 @@ var MemoryConfigProvider = class {
6574
6980
  try {
6575
6981
  sessionCookie = await this.deps.resolveSession();
6576
6982
  } catch (error) {
6577
- this.deps.log?.(`hiwork-core: \u8BB0\u5FC6\u914D\u7F6E\u83B7\u53D6\u5931\u8D25\uFF08${errorName2(error)}\uFF09`);
6578
- return failure9("error");
6983
+ this.deps.log?.(`hiwork-core: \u8BB0\u5FC6\u914D\u7F6E\u83B7\u53D6\u5931\u8D25\uFF08${errorName3(error)}\uFF09`);
6984
+ return failure11("error");
6579
6985
  }
6580
- if (sessionCookie === void 0 || sessionCookie === "") return failure9("unauthenticated");
6986
+ if (sessionCookie === void 0 || sessionCookie === "") return failure11("unauthenticated");
6581
6987
  if (!force) {
6582
6988
  const hit = this.cached;
6583
6989
  if (hit !== void 0 && hit.sessionCookie === sessionCookie && this.now() - hit.at < MEMORY_CONFIG_TTL_MS) {
@@ -6585,25 +6991,25 @@ var MemoryConfigProvider = class {
6585
6991
  }
6586
6992
  }
6587
6993
  const gateway = this.deps.gateway();
6588
- if (gateway === void 0) return failure9("unreachable", MESSAGE_NO_GATEWAY);
6994
+ if (gateway === void 0) return failure11("unreachable", MESSAGE_NO_GATEWAY);
6589
6995
  let outcome;
6590
6996
  try {
6591
6997
  outcome = await gateway.getMyMemoryConfig(sessionCookie);
6592
6998
  } catch (error) {
6593
- const mapped = reasonOfErrorCode(error);
6594
- if (mapped !== void 0) return failure9(mapped);
6595
- this.deps.log?.(`hiwork-core: \u8BB0\u5FC6\u914D\u7F6E\u83B7\u53D6\u5931\u8D25\uFF08${errorName2(error)}\uFF09`);
6596
- return failure9("error");
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");
6597
7003
  }
6598
7004
  if (!outcome.ok) {
6599
- if (outcome.kind === "network") return failure9("unreachable");
6600
- if (outcome.kind === "http") return failure9(reasonOfStatus(outcome.status));
6601
- return failure9("error");
7005
+ if (outcome.kind === "network") return failure11("unreachable");
7006
+ if (outcome.kind === "http") return failure11(reasonOfStatus2(outcome.status));
7007
+ return failure11("error");
6602
7008
  }
6603
7009
  const view = projectMemoryConfig(outcome.body);
6604
7010
  if (view === void 0) {
6605
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");
6606
- return failure9("error");
7012
+ return failure11("error");
6607
7013
  }
6608
7014
  this.cached = { sessionCookie, view, at: this.now() };
6609
7015
  return view;
@@ -6796,31 +7202,1374 @@ function provideOfficeRelay(ctx, deps) {
6796
7202
  };
6797
7203
  }
6798
7204
 
6799
- // src/index.ts
6800
- var name = "hiwork-core";
6801
- var inject = ["settings", "credentials", "workspaceRegistry", "connection"];
6802
- var NS = "ai-router";
6803
- var PI_AI_NS = "llm-pi-ai";
6804
- var lastErrorSchema = Schema.object({ code: Schema.string().required(), message: Schema.string().required() }).default(void 0);
6805
- var Config = Schema.object({
6806
- baseURL: Schema.string(),
6807
- keyId: Schema.string(),
6808
- username: Schema.string(),
6809
- displayName: Schema.string(),
6810
- lastLogin: Schema.object({ at: Schema.string().required(), modelCount: Schema.number().required(), gatewayName: Schema.string().required() }).default(void 0),
6811
- lastError: lastErrorSchema,
6812
- pendingLogin: Schema.object({ username: Schema.string().required(), passwordRef: Schema.string().required() }).default(void 0),
6813
- rememberPassword: Schema.boolean().default(false),
6814
- refreshRequest: Schema.object({ at: Schema.string().required() }).default(void 0),
6815
- logoutRequest: Schema.object({ at: Schema.string().required() }).default(void 0),
6816
- browserLoginRequest: Schema.object({ at: Schema.string().required() }).default(void 0),
6817
- browserLogin: Schema.object({ authorizeUrl: Schema.string().required(), startedAt: Schema.string().required() }).default(void 0),
6818
- // 三值枚举:非法值写入直接被 schema 拒绝(客户端还会再归一一次,双保险)。
6819
- loginMode: Schema.union([Schema.const("browser"), Schema.const("form"), Schema.const("both")]),
6820
- passwordLogin: Schema.boolean(),
6821
- budgets: Schema.array(Schema.object({
6822
- model_name: Schema.string().required(),
6823
- total_budget: Schema.number(),
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
+
8548
+ // src/index.ts
8549
+ var name = "hiwork-core";
8550
+ var inject = ["settings", "credentials", "workspaceRegistry", "connection", "web"];
8551
+ var NS = "ai-router";
8552
+ var PI_AI_NS = "llm-pi-ai";
8553
+ var lastErrorSchema = Schema.object({ code: Schema.string().required(), message: Schema.string().required() }).default(void 0);
8554
+ var Config = Schema.object({
8555
+ baseURL: Schema.string(),
8556
+ keyId: Schema.string(),
8557
+ username: Schema.string(),
8558
+ displayName: Schema.string(),
8559
+ lastLogin: Schema.object({ at: Schema.string().required(), modelCount: Schema.number().required(), gatewayName: Schema.string().required() }).default(void 0),
8560
+ lastError: lastErrorSchema,
8561
+ pendingLogin: Schema.object({ username: Schema.string().required(), passwordRef: Schema.string().required() }).default(void 0),
8562
+ rememberPassword: Schema.boolean().default(false),
8563
+ refreshRequest: Schema.object({ at: Schema.string().required() }).default(void 0),
8564
+ logoutRequest: Schema.object({ at: Schema.string().required() }).default(void 0),
8565
+ browserLoginRequest: Schema.object({ at: Schema.string().required() }).default(void 0),
8566
+ browserLogin: Schema.object({ authorizeUrl: Schema.string().required(), startedAt: Schema.string().required() }).default(void 0),
8567
+ // 三值枚举:非法值写入直接被 schema 拒绝(客户端还会再归一一次,双保险)。
8568
+ loginMode: Schema.union([Schema.const("browser"), Schema.const("form"), Schema.const("both")]),
8569
+ passwordLogin: Schema.boolean(),
8570
+ budgets: Schema.array(Schema.object({
8571
+ model_name: Schema.string().required(),
8572
+ total_budget: Schema.number(),
6824
8573
  consumed_budget: Schema.number(),
6825
8574
  remaining_budget: Schema.number()
6826
8575
  })).default(void 0),
@@ -6921,14 +8670,14 @@ function parseDeviceInfo(message) {
6921
8670
  const id = deviceId.value?.trim();
6922
8671
  if (id === void 0 || id === "") return void 0;
6923
8672
  const name2 = deviceName.value?.trim();
6924
- const version = appVersion.value?.trim();
8673
+ const version2 = appVersion.value?.trim();
6925
8674
  return {
6926
8675
  deviceId: id,
6927
8676
  ...name2 === void 0 || name2 === "" ? {} : { deviceName: name2 },
6928
- ...version === void 0 || version === "" ? {} : { appVersion: version }
8677
+ ...version2 === void 0 || version2 === "" ? {} : { appVersion: version2 }
6929
8678
  };
6930
8679
  }
6931
- function errorName3(error) {
8680
+ function errorName5(error) {
6932
8681
  return error instanceof Error ? error.name : typeof error;
6933
8682
  }
6934
8683
  function registerIpcMessageHandler(ports) {
@@ -6955,12 +8704,12 @@ function registerIpcMessageHandler(ports) {
6955
8704
  authInFlight = true;
6956
8705
  try {
6957
8706
  void Promise.resolve(ports.complete(input)).catch((error) => {
6958
- log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5904\u7406\u5F02\u5E38\uFF08${errorName3(error)}\uFF09`);
8707
+ log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5904\u7406\u5F02\u5E38\uFF08${errorName5(error)}\uFF09`);
6959
8708
  }).finally(() => {
6960
8709
  authInFlight = false;
6961
8710
  });
6962
8711
  } catch (error) {
6963
- log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5206\u53D1\u5931\u8D25\uFF08${errorName3(error)}\uFF09`);
8712
+ log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5206\u53D1\u5931\u8D25\uFF08${errorName5(error)}\uFF09`);
6964
8713
  authInFlight = false;
6965
8714
  }
6966
8715
  return;
@@ -7008,12 +8757,12 @@ async function projectLoginMode(opts) {
7008
8757
  }
7009
8758
  }
7010
8759
  } catch (error) {
7011
- opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u62C9\u53D6\u5931\u8D25\uFF0C\u767B\u5F55\u65B9\u5F0F\u56DE\u9000 both\uFF08${errorName3(error)}\uFF09`);
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`);
7012
8761
  }
7013
8762
  try {
7014
8763
  await opts.state.patch({ set: { loginMode, passwordLogin } });
7015
8764
  } catch (error) {
7016
- opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u5199\u5165\u5931\u8D25\uFF08${errorName3(error)}\uFF09`);
8765
+ opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u5199\u5165\u5931\u8D25\uFF08${errorName5(error)}\uFF09`);
7017
8766
  }
7018
8767
  return loginMode;
7019
8768
  }
@@ -7151,6 +8900,16 @@ function apply(ctx, config) {
7151
8900
  ctx.logger.warn(message);
7152
8901
  }
7153
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");
7154
8913
  const managedMcpDeps = {
7155
8914
  resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
7156
8915
  gateway: () => {
@@ -7283,6 +9042,16 @@ function apply(ctx, config) {
7283
9042
  ctx.logger.warn(message);
7284
9043
  }
7285
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");
7286
9055
  ctx.effect(() => provideOfficeRelay(ctx, {
7287
9056
  resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
7288
9057
  gateway: () => {
@@ -7293,6 +9062,19 @@ function apply(ctx, config) {
7293
9062
  ctx.logger.warn(message);
7294
9063
  }
7295
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
+ }
7296
9078
  ctx.effect(() => registerAppVersionRpc({ connection, logger: ctx.logger }, {
7297
9079
  appVersion: () => pushedAppVersion(),
7298
9080
  log: (message) => {