@dsh-cove/dsh-web-all 0.4.3 → 0.4.4

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.
Files changed (25) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +5 -1
  3. package/README.zh.md +4 -1
  4. package/package.json +1 -1
  5. package/presets/code-vibe/skills/record-browser-gif/scripts/encode_gif.py +0 -0
  6. package/presets/code-vibe/skills/wish-programming/scripts/check_spec.py +0 -0
  7. package/presets/code-vibe/skills/wish-programming/scripts/coverage_check.py +0 -0
  8. package/presets/code-vibe/skills/wish-programming/scripts/gate-lock.sh +0 -0
  9. package/presets/code-vibe/skills/wish-programming/scripts/journey.sh +0 -0
  10. package/presets/code-vibe/skills/wish-programming/scripts/project.sh +0 -0
  11. package/presets/code-vibe/skills/wish-programming/scripts/replay.sh +0 -0
  12. package/presets/code-vibe/skills/wish-programming/scripts/verify.sh +0 -0
  13. package/presets/code-vibe/skills/wish-programming/scripts/wish.sh +0 -0
  14. package/lib/types/core/subagent-model/model-resolver.d.ts +0 -25
  15. package/lib/types/core/subagent-model/model-resolver.d.ts.map +0 -1
  16. package/lib/types/core/subagent-model/model-resolver.js +0 -60
  17. package/lib/types/host/services/web-search-kaon/contracts.d.ts +0 -114
  18. package/lib/types/host/services/web-search-kaon/contracts.d.ts.map +0 -1
  19. package/lib/types/host/services/web-search-kaon/contracts.js +0 -73
  20. package/lib/types/host/services/web-search-kaon/index.d.ts +0 -50
  21. package/lib/types/host/services/web-search-kaon/index.d.ts.map +0 -1
  22. package/lib/types/host/services/web-search-kaon/index.js +0 -114
  23. package/lib/types/host/services/web-search-kaon/provider.d.ts +0 -111
  24. package/lib/types/host/services/web-search-kaon/provider.d.ts.map +0 -1
  25. package/lib/types/host/services/web-search-kaon/provider.js +0 -244
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # node scripts/verify-docs.mjs --write <dir>
5
- README.md: 6f249597efea1432892e1131f28237318e0b9d6c
6
- README.zh.md: e211acee1437b94a4337027350edea4932aa242c
5
+ README.md: 2f89063b064015de6cf116470803300f2914ded7
6
+ README.zh.md: 8c2fa8a3f08546befad8d5a1cc6967f020af1c59
package/README.md CHANGED
@@ -25,8 +25,12 @@ The web profile runtime stack is `@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web
25
25
 
26
26
  ```sh
27
27
  dsh plugin --profile web add @dsh-cove/dsh-web-all@latest
28
+ # Stopped on ERR_PNPM_IGNORED_BUILDS? Add the printed keys to the profile's
29
+ # pnpm-workspace.yaml allowBuilds, then remove and add again: that failed run
30
+ # already wrote the dependency, and a plain re-run writes only the dependency,
31
+ # never the profile bundle row.
32
+ dsh --profile web --dump-config # a mounted web-all row is the proof it registered
28
33
  # Restart the managed dsh web process after installation.
29
- dsh --profile web --dump-config
30
34
  ```
31
35
 
32
36
  For DSH Desktop, replace `web` with `desktop`, verify with `dsh --profile desktop --dump-config`, then fully quit and reopen the desktop application.
package/README.zh.md CHANGED
@@ -25,8 +25,11 @@ Web profile 的运行时栈是 `@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web-a
25
25
 
26
26
  ```sh
27
27
  dsh plugin --profile web add @dsh-cove/dsh-web-all@latest
28
+ # 卡在 ERR_PNPM_IGNORED_BUILDS?把提示的键加进 profile 的 pnpm-workspace.yaml
29
+ # allowBuilds,然后先 remove 再 add:失败的那次已写入依赖,直接重跑只会写依赖,
30
+ # 不会登记 profile 的 bundle 行。
31
+ dsh --profile web --dump-config # 挂载出 web-all 行才算登记成功
28
32
  # 安装后重启受管的 dsh web 进程。
29
- dsh --profile web --dump-config
30
33
  ```
31
34
 
32
35
  DSH Desktop 请把 `web` 替换为 `desktop`,用 `dsh --profile desktop --dump-config` 验证,然后完全退出并重新打开桌面应用。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dsh-cove/dsh-web-all",
3
3
  "description": "DSH Web GUI 全家桶插件(实体版):按 dsh 注入位置组织的单一插件——侧栏(sidebar)/ 设置(settings)/ 会话区(conversation)/ 浮层(flyout)四个位置模块统一编排,host 半区按服务域承载各能力。安装一个插件即全家桶全部能力。",
4
- "version": "0.4.3",
4
+ "version": "0.4.4",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -1,25 +0,0 @@
1
- /**
2
- * Dependency-free model and reasoning-effort resolution for subagent history.
3
- * The browser position module feeds this resolver rows returned by the existing
4
- * read-only subagents/history RPC; malformed history is ignored defensively.
5
- */
6
- /** Minimal transcript row shape accepted by the resolver. */
7
- export interface SubagentModelHistoryEntry {
8
- readonly event?: unknown;
9
- }
10
- /** Model identity actually used by one subagent run. */
11
- export interface SubagentRunInfo {
12
- readonly model: string;
13
- readonly reasoningEffort?: string;
14
- }
15
- /**
16
- * Resolve the first request header model, including its reasoning effort when
17
- * present. A descriptor agentModel is retained only as a fallback because the
18
- * request header is the per-request ground truth.
19
- */
20
- export declare function resolveSubagentRun(entries: readonly SubagentModelHistoryEntry[]): SubagentRunInfo | undefined;
21
- /** Resolve only the model identifier for callers that do not need effort. */
22
- export declare function resolveSubagentModel(entries: readonly SubagentModelHistoryEntry[]): string | undefined;
23
- /** Format the visible badge as `model` or `model · effort`. */
24
- export declare function formatSubagentBadgeText(info: SubagentRunInfo): string;
25
- //# sourceMappingURL=model-resolver.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"model-resolver.d.ts","sourceRoot":"","sources":["../../../../src/core/subagent-model/model-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,6DAA6D;AAC7D,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAClC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,yBAAyB,EAAE,GAC5C,eAAe,GAAG,SAAS,CAU7B;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,yBAAyB,EAAE,GAC5C,MAAM,GAAG,SAAS,CAEpB;AAED,+DAA+D;AAC/D,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAIrE"}
@@ -1,60 +0,0 @@
1
- /**
2
- * Dependency-free model and reasoning-effort resolution for subagent history.
3
- * The browser position module feeds this resolver rows returned by the existing
4
- * read-only subagents/history RPC; malformed history is ignored defensively.
5
- */
6
- /**
7
- * Resolve the first request header model, including its reasoning effort when
8
- * present. A descriptor agentModel is retained only as a fallback because the
9
- * request header is the per-request ground truth.
10
- */
11
- export function resolveSubagentRun(entries) {
12
- let descriptorModel;
13
- for (const entry of entries) {
14
- const requestRun = readRequestHeaderRun(entry?.event);
15
- if (requestRun !== undefined)
16
- return requestRun;
17
- if (descriptorModel === undefined) {
18
- descriptorModel = readDescriptorModel(entry?.event);
19
- }
20
- }
21
- return descriptorModel === undefined ? undefined : { model: descriptorModel };
22
- }
23
- /** Resolve only the model identifier for callers that do not need effort. */
24
- export function resolveSubagentModel(entries) {
25
- return resolveSubagentRun(entries)?.model;
26
- }
27
- /** Format the visible badge as `model` or `model · effort`. */
28
- export function formatSubagentBadgeText(info) {
29
- return info.reasoningEffort === undefined
30
- ? info.model
31
- : `${info.model} · ${info.reasoningEffort}`;
32
- }
33
- function readRequestHeaderRun(event) {
34
- if (!isRecord(event) || event.type !== 'request/header')
35
- return undefined;
36
- const data = event.data;
37
- if (!isRecord(data) || !isRecord(data.header) || !isRecord(data.header.config)) {
38
- return undefined;
39
- }
40
- const model = nonBlankString(data.header.config.model);
41
- if (model === undefined)
42
- return undefined;
43
- const reasoningEffort = nonBlankString(data.header.config.reasoningEffort);
44
- return reasoningEffort === undefined ? { model } : { model, reasoningEffort };
45
- }
46
- function readDescriptorModel(event) {
47
- if (!isRecord(event) || event.type !== 'subagent/descriptor' || !isRecord(event.data)) {
48
- return undefined;
49
- }
50
- return nonBlankString(event.data.agentModel);
51
- }
52
- function isRecord(value) {
53
- return typeof value === 'object' && value !== null;
54
- }
55
- function nonBlankString(value) {
56
- if (typeof value !== 'string')
57
- return undefined;
58
- const trimmed = value.trim();
59
- return trimmed.length === 0 ? undefined : trimmed;
60
- }
@@ -1,114 +0,0 @@
1
- /**
2
- * Anthropic 兼容 Messages 检索后端的契约、设置 schema 与默认值 —— host 半区。
3
- *
4
- * 原生 `dsh-web-search-deepseek` 同样走「Anthropic 兼容 Messages + 原生
5
- * `web_search_20250305` 服务端工具」,但它把响应里**必须出现
6
- * `web_search_tool_result` 块**当成硬前提(缺席即抛 `WEB_PROVIDER_ERROR`)。
7
- * Kaon Router 上的 Claude 系模型(上游 Bedrock)只回 `server_tool_use` 与带
8
- * `citations[]` 的 `text` 块:命中的 URL、标题与摘录全部藏在 citation 里,
9
- * 结果块根本不存在。因此把 deepseek provider 的 `baseURL` 直接指过去,
10
- * 搜索**确实执行了**却必然报「no web_search_tool_result blocks」。
11
- *
12
- * 本域因此注册第二个 search provider(id `kaon-router`),把两种响应形状都
13
- * 归一化成 seam 的 `sources[]`:
14
- *
15
- * 1. 标准 Anthropic 形状:`web_search_tool_result` 条目 + `text.citations[]` 摘录;
16
- * 2. Kaon/Bedrock 形状:没有结果块,只有 `citations[]`(`web_search_result_location`)。
17
- *
18
- * 端点、模型、凭据引用都可在 `web-search-kaon` 命名空间里改,所以任何
19
- * Anthropic 兼容 Messages 端点都能用,Kaon 只是默认值。
20
- *
21
- * @module web-all/host/services/web-search-kaon
22
- */
23
- import z from 'schemastery';
24
- /** 设置命名空间:`web-search-kaon`。 */
25
- export declare const WEB_SEARCH_KAON_SETTINGS_NAMESPACE = "web-search-kaon";
26
- /** 注册到 `ctx.web` 的 search provider id;profile 用它 pin `searchProvider`。 */
27
- export declare const WEB_SEARCH_KAON_PROVIDER_ID = "kaon-router";
28
- /** 默认端点,含 `/v1`;`/messages` 由 provider 追加。 */
29
- export declare const WEB_SEARCH_KAON_DEFAULT_BASE_URL = "https://kaon-router.kaonai.com/v1";
30
- /**
31
- * 默认模型。必须是**端点侧真正执行服务端搜索**的 Anthropic 模型:Kaon 上的
32
- * `kaon/deepseek-*`、`kaon/gemini-*` 会把 `web_search` 当普通客户端工具回一个
33
- * `tool_use`(模型自己申请调用,没有任何搜索结果),provider 会照实报错。
34
- */
35
- export declare const WEB_SEARCH_KAON_DEFAULT_MODEL = "kaon/claude-haiku-4-5-20251001";
36
- /** 默认 `anthropic-version` 头。 */
37
- export declare const WEB_SEARCH_KAON_DEFAULT_API_VERSION = "2023-06-01";
38
- /** 单次 Messages 请求的生成上限(模型侧摘要用)。 */
39
- export declare const WEB_SEARCH_KAON_DEFAULT_MAX_TOKENS = 4096;
40
- /** 单次搜索允许的服务端 `web_search` 调用次数上限。 */
41
- export declare const WEB_SEARCH_KAON_DEFAULT_MAX_USES = 3;
42
- /** 默认凭据引用;与 chat 侧 `llm-pi-ai` 的 kaon-router 路由共用同一把 key。 */
43
- export declare const WEB_SEARCH_KAON_DEFAULT_API_KEY_ENV = "KAON_ROUTER_API_KEY";
44
- /** 用户可配置面(也是设置文档里该命名空间的字段)。 */
45
- export interface WebSearchKaonConfig {
46
- /** Anthropic 兼容端点基址,不含 `/messages`。 */
47
- baseURL?: string;
48
- /** Anthropic 格式模型名。 */
49
- model?: string;
50
- /** 凭据引用名(凭据服务 / 环境变量);优于环境变量,劣于字面 apiKey。 */
51
- apiKeyEnv?: string;
52
- /** 字面 key;仅当没有凭据服务时使用,优先于 apiKeyEnv。 */
53
- apiKey?: string;
54
- /** `anthropic-version` 头。 */
55
- apiVersion?: string;
56
- /** 生成上限。 */
57
- maxTokens?: number;
58
- /** 服务端搜索次数上限。 */
59
- maxUses?: number;
60
- /** 是否把模型生成的摘要并入 seam 的 `content`。 */
61
- includeAnswer?: boolean;
62
- /** 单次请求硬上限(毫秒)。 */
63
- timeoutMs?: number;
64
- }
65
- /** 运行时的权威 schema;导出后该命名空间才会出现在设置文档里。 */
66
- export declare const Config: z<WebSearchKaonConfig>;
67
- /** 解析后的配置:字段全部落地,无 undefined。 */
68
- export type ResolvedWebSearchKaonConfig = Required<WebSearchKaonConfig>;
69
- /** 提取一份可用配置:未配置的字段取 schema 默认值。 */
70
- export declare function effectiveConfig(config: WebSearchKaonConfig | undefined): ResolvedWebSearchKaonConfig;
71
- /** 一次搜索请求;seam 传 query 与可选上限。 */
72
- export interface WebSearchRequest {
73
- readonly query: string;
74
- readonly maxResults?: number;
75
- }
76
- /** 一条可引用来源。 */
77
- export interface WebSearchSource {
78
- readonly url: string;
79
- readonly title?: string;
80
- readonly snippet?: string;
81
- readonly publishedAt?: string;
82
- }
83
- /** 归一化后的搜索产出。 */
84
- export interface WebSearchResult {
85
- readonly content?: string;
86
- readonly sources: readonly WebSearchSource[];
87
- readonly truncated: boolean;
88
- }
89
- /** 一个搜索后端。 */
90
- export interface WebSearchProvider {
91
- readonly id: string;
92
- /** 廉价的本地可用性检查;不得发起网络请求。 */
93
- available(): boolean;
94
- search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
95
- }
96
- /** `ctx.web` 上本域用到的部分。 */
97
- export interface WebSeam {
98
- registerSearchProvider(provider: WebSearchProvider): () => void;
99
- }
100
- /** `ctx.credentials` 上本域用到的部分(可选服务)。 */
101
- export interface CredentialSeam {
102
- resolve(ref: string): Promise<{
103
- value: string;
104
- } | undefined>;
105
- }
106
- /**
107
- * 检索失败。形状对齐 `WebError`(带机器可路由的 `code`),
108
- * 由 `dsh-tool-web` 包装成模型可读的 `Error: <message>`。
109
- */
110
- export declare class WebSearchError extends Error {
111
- readonly code: string;
112
- constructor(message: string, code: string);
113
- }
114
- //# sourceMappingURL=contracts.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../../../../src/host/services/web-search-kaon/contracts.ts"],"names":[],"mappings":"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"}
@@ -1,73 +0,0 @@
1
- /**
2
- * Anthropic 兼容 Messages 检索后端的契约、设置 schema 与默认值 —— host 半区。
3
- *
4
- * 原生 `dsh-web-search-deepseek` 同样走「Anthropic 兼容 Messages + 原生
5
- * `web_search_20250305` 服务端工具」,但它把响应里**必须出现
6
- * `web_search_tool_result` 块**当成硬前提(缺席即抛 `WEB_PROVIDER_ERROR`)。
7
- * Kaon Router 上的 Claude 系模型(上游 Bedrock)只回 `server_tool_use` 与带
8
- * `citations[]` 的 `text` 块:命中的 URL、标题与摘录全部藏在 citation 里,
9
- * 结果块根本不存在。因此把 deepseek provider 的 `baseURL` 直接指过去,
10
- * 搜索**确实执行了**却必然报「no web_search_tool_result blocks」。
11
- *
12
- * 本域因此注册第二个 search provider(id `kaon-router`),把两种响应形状都
13
- * 归一化成 seam 的 `sources[]`:
14
- *
15
- * 1. 标准 Anthropic 形状:`web_search_tool_result` 条目 + `text.citations[]` 摘录;
16
- * 2. Kaon/Bedrock 形状:没有结果块,只有 `citations[]`(`web_search_result_location`)。
17
- *
18
- * 端点、模型、凭据引用都可在 `web-search-kaon` 命名空间里改,所以任何
19
- * Anthropic 兼容 Messages 端点都能用,Kaon 只是默认值。
20
- *
21
- * @module web-all/host/services/web-search-kaon
22
- */
23
- import z from 'schemastery';
24
- import { registerFamilyNamespace } from "../family-config.js";
25
- /** 设置命名空间:`web-search-kaon`。 */
26
- export const WEB_SEARCH_KAON_SETTINGS_NAMESPACE = 'web-search-kaon';
27
- /** 注册到 `ctx.web` 的 search provider id;profile 用它 pin `searchProvider`。 */
28
- export const WEB_SEARCH_KAON_PROVIDER_ID = 'kaon-router';
29
- /** 默认端点,含 `/v1`;`/messages` 由 provider 追加。 */
30
- export const WEB_SEARCH_KAON_DEFAULT_BASE_URL = 'https://kaon-router.kaonai.com/v1';
31
- /**
32
- * 默认模型。必须是**端点侧真正执行服务端搜索**的 Anthropic 模型:Kaon 上的
33
- * `kaon/deepseek-*`、`kaon/gemini-*` 会把 `web_search` 当普通客户端工具回一个
34
- * `tool_use`(模型自己申请调用,没有任何搜索结果),provider 会照实报错。
35
- */
36
- export const WEB_SEARCH_KAON_DEFAULT_MODEL = 'kaon/claude-haiku-4-5-20251001';
37
- /** 默认 `anthropic-version` 头。 */
38
- export const WEB_SEARCH_KAON_DEFAULT_API_VERSION = '2023-06-01';
39
- /** 单次 Messages 请求的生成上限(模型侧摘要用)。 */
40
- export const WEB_SEARCH_KAON_DEFAULT_MAX_TOKENS = 4_096;
41
- /** 单次搜索允许的服务端 `web_search` 调用次数上限。 */
42
- export const WEB_SEARCH_KAON_DEFAULT_MAX_USES = 3;
43
- /** 默认凭据引用;与 chat 侧 `llm-pi-ai` 的 kaon-router 路由共用同一把 key。 */
44
- export const WEB_SEARCH_KAON_DEFAULT_API_KEY_ENV = 'KAON_ROUTER_API_KEY';
45
- /** 运行时的权威 schema;导出后该命名空间才会出现在设置文档里。 */
46
- export const Config = z.object({
47
- baseURL: z.string().default(WEB_SEARCH_KAON_DEFAULT_BASE_URL),
48
- model: z.string().default(WEB_SEARCH_KAON_DEFAULT_MODEL),
49
- apiKeyEnv: z.string().default(WEB_SEARCH_KAON_DEFAULT_API_KEY_ENV),
50
- apiKey: z.string().role('secret').default(''),
51
- apiVersion: z.string().default(WEB_SEARCH_KAON_DEFAULT_API_VERSION),
52
- maxTokens: z.number().step(1).min(1).default(WEB_SEARCH_KAON_DEFAULT_MAX_TOKENS),
53
- maxUses: z.number().step(1).min(1).default(WEB_SEARCH_KAON_DEFAULT_MAX_USES),
54
- includeAnswer: z.boolean().default(true),
55
- timeoutMs: z.number().step(1).min(1).default(120_000),
56
- });
57
- registerFamilyNamespace(WEB_SEARCH_KAON_SETTINGS_NAMESPACE, Config);
58
- /** 提取一份可用配置:未配置的字段取 schema 默认值。 */
59
- export function effectiveConfig(config) {
60
- return Config(config ?? {});
61
- }
62
- /**
63
- * 检索失败。形状对齐 `WebError`(带机器可路由的 `code`),
64
- * 由 `dsh-tool-web` 包装成模型可读的 `Error: <message>`。
65
- */
66
- export class WebSearchError extends Error {
67
- code;
68
- constructor(message, code) {
69
- super(message);
70
- this.name = 'WebError';
71
- this.code = code;
72
- }
73
- }
@@ -1,50 +0,0 @@
1
- /**
2
- * Kaon Router 检索服务域 —— 向 `ctx.web` 注册 `kaon-router` search provider。
3
- *
4
- * ## 为什么注册是「只要 web 在就注册」
5
- *
6
- * profile patch 把 `@deepseek-ai/dsh-web` 的 `searchProvider` pin 成 `kaon-router`。
7
- * seam 在指定了 id 时只用该 id:provider 缺席报
8
- * `WEB_PROVIDER_CONFIGURED_MISSING`,注册了但 `available()` 为假报
9
- * `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`——两者都会让 `web_search` 整体不可用。
10
- * 因此这里选择「只要 web 服务在就注册」:key 缺失属于运行期错误,由 provider
11
- * 按次给出 `WEB_PROVIDER_CREDENTIAL_MISSING`,并点名该配的凭据引用。
12
- *
13
- * 要彻底关掉本域,必须**同时**把 `searchProvider` 改回 `deepseek-official`
14
- * (见 cordis.patch.yml 的注释),只删这一段会留下一个指向不存在 provider 的 pin。
15
- *
16
- * ## 为什么用局部 inject
17
- *
18
- * 0.2 严格注入下,插件只能访问自己 inject 声明过的服务。这里用
19
- * `ctx.inject(['web'], …)` / `ctx.inject(['credentials'], …)` 等待,而不是把
20
- * 它们写进 web-all 的全局 inject:那会让整个全家桶在缺少这些服务的 profile 里挂掉。
21
- *
22
- * ## 配置读取
23
- *
24
- * `web-search-kaon` 是聚合 Config 里的一个 volatile 命名空间;provider 拿到的是
25
- * **读取函数**而非快照,端点/模型/次数上限的热更新立即生效,无需重挂插件。
26
- *
27
- * @module web-all/host/services/web-search-kaon
28
- */
29
- import type { Context } from '@deepseek-ai/cordis';
30
- import { type WebSearchKaonConfig } from './contracts.ts';
31
- export interface WebSearchKaonRegistrationOptions {
32
- /** 结构化告警出口;缺省写 console。 */
33
- logger?: {
34
- warn: (message: string) => void;
35
- };
36
- /** 显式配置(测试用)。缺省时从 `web-search-kaon` 命名空间读取。 */
37
- config?: WebSearchKaonConfig;
38
- /** 环境变量读取面(测试用)。缺省读 `process.env`。 */
39
- env?: (name: string) => string | undefined;
40
- }
41
- /**
42
- * 注册 Anthropic 兼容检索 provider。
43
- * @param ctx - host plugin context。
44
- * @param options - 告警出口与显式配置(测试)。
45
- * @returns 卸载回调:注销 provider。
46
- */
47
- export declare function register(ctx: Context, options?: WebSearchKaonRegistrationOptions): () => void;
48
- export { effectiveConfig, WEB_SEARCH_KAON_DEFAULT_API_KEY_ENV, WEB_SEARCH_KAON_DEFAULT_BASE_URL, WEB_SEARCH_KAON_DEFAULT_MODEL, WEB_SEARCH_KAON_PROVIDER_ID, WEB_SEARCH_KAON_SETTINGS_NAMESPACE, type WebSearchKaonConfig, } from './contracts.ts';
49
- export { citationSnippets, KaonSearchProvider, mapSearchResponse } from './provider.ts';
50
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/host/services/web-search-kaon/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,EAKL,KAAK,mBAAmB,EAEzB,MAAM,gBAAgB,CAAA;AAGvB,MAAM,WAAW,gCAAgC;IAC/C,2BAA2B;IAC3B,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAA;IAC5C,+CAA+C;IAC/C,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B,sCAAsC;IACtC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;CAC3C;AAWD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,gCAAqC,GAAG,MAAM,IAAI,CAmEjG;AAED,OAAO,EACL,eAAe,EACf,mCAAmC,EACnC,gCAAgC,EAChC,6BAA6B,EAC7B,2BAA2B,EAC3B,kCAAkC,EAClC,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA"}
@@ -1,114 +0,0 @@
1
- /**
2
- * Kaon Router 检索服务域 —— 向 `ctx.web` 注册 `kaon-router` search provider。
3
- *
4
- * ## 为什么注册是「只要 web 在就注册」
5
- *
6
- * profile patch 把 `@deepseek-ai/dsh-web` 的 `searchProvider` pin 成 `kaon-router`。
7
- * seam 在指定了 id 时只用该 id:provider 缺席报
8
- * `WEB_PROVIDER_CONFIGURED_MISSING`,注册了但 `available()` 为假报
9
- * `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`——两者都会让 `web_search` 整体不可用。
10
- * 因此这里选择「只要 web 服务在就注册」:key 缺失属于运行期错误,由 provider
11
- * 按次给出 `WEB_PROVIDER_CREDENTIAL_MISSING`,并点名该配的凭据引用。
12
- *
13
- * 要彻底关掉本域,必须**同时**把 `searchProvider` 改回 `deepseek-official`
14
- * (见 cordis.patch.yml 的注释),只删这一段会留下一个指向不存在 provider 的 pin。
15
- *
16
- * ## 为什么用局部 inject
17
- *
18
- * 0.2 严格注入下,插件只能访问自己 inject 声明过的服务。这里用
19
- * `ctx.inject(['web'], …)` / `ctx.inject(['credentials'], …)` 等待,而不是把
20
- * 它们写进 web-all 的全局 inject:那会让整个全家桶在缺少这些服务的 profile 里挂掉。
21
- *
22
- * ## 配置读取
23
- *
24
- * `web-search-kaon` 是聚合 Config 里的一个 volatile 命名空间;provider 拿到的是
25
- * **读取函数**而非快照,端点/模型/次数上限的热更新立即生效,无需重挂插件。
26
- *
27
- * @module web-all/host/services/web-search-kaon
28
- */
29
- import { familyNamespace } from "../family-config.js";
30
- import { effectiveConfig, WEB_SEARCH_KAON_PROVIDER_ID, WEB_SEARCH_KAON_SETTINGS_NAMESPACE, } from "./contracts.js";
31
- import { KaonSearchProvider } from "./provider.js";
32
- /** 可选读取 credentials 缝隙:0.2 严格注入下访问未声明的服务会直接抛错。 */
33
- function credentialSeamOf(ctx) {
34
- try {
35
- return ctx.credentials;
36
- }
37
- catch {
38
- return undefined;
39
- }
40
- }
41
- /**
42
- * 注册 Anthropic 兼容检索 provider。
43
- * @param ctx - host plugin context。
44
- * @param options - 告警出口与显式配置(测试)。
45
- * @returns 卸载回调:注销 provider。
46
- */
47
- export function register(ctx, options = {}) {
48
- const warn = options.logger?.warn ?? ((message) => console.warn(message));
49
- const readConfig = () => effectiveConfig(options.config ?? familyNamespace(WEB_SEARCH_KAON_SETTINGS_NAMESPACE));
50
- const env = options.env ?? ((name) => process.env[name]);
51
- // 凭据服务是可选依赖:没有它时 provider 仍注册,key 回落到环境变量或字面 apiKey。
52
- let credentials;
53
- try {
54
- ctx.inject(['credentials'], (cctx) => { credentials = credentialSeamOf(cctx); });
55
- }
56
- catch {
57
- // 该 profile 没有 credentials 服务:静默缺席,不影响注册。
58
- }
59
- const provider = new KaonSearchProvider(() => {
60
- const config = readConfig();
61
- return {
62
- baseURL: config.baseURL,
63
- model: config.model,
64
- apiVersion: config.apiVersion,
65
- maxTokens: config.maxTokens,
66
- maxUses: config.maxUses,
67
- includeAnswer: config.includeAnswer,
68
- timeoutMs: config.timeoutMs,
69
- apiKeyEnv: config.apiKeyEnv,
70
- ...config.apiKey !== '' ? { apiKey: config.apiKey } : {},
71
- resolveApiKey: async () => {
72
- try {
73
- const stored = await credentials?.resolve(config.apiKeyEnv);
74
- if (stored !== undefined && stored.value.length > 0)
75
- return stored.value;
76
- }
77
- catch {
78
- // 凭据服务拒绝解析(引用不存在/语法非法)时退回环境变量。
79
- }
80
- const ambient = env(config.apiKeyEnv);
81
- return ambient !== undefined && ambient.length > 0 ? ambient : undefined;
82
- },
83
- };
84
- });
85
- const initial = readConfig();
86
- if (!URL.canParse(initial.baseURL)) {
87
- warn(`[web-all:web-search-kaon] baseURL 不是合法 URL(${JSON.stringify(initial.baseURL)}):`
88
- + `web_search 仍被 pin 到 ${WEB_SEARCH_KAON_PROVIDER_ID},调用会以 WEB_PROVIDER_CONFIGURED_UNAVAILABLE 失败;`
89
- + `请在 ${WEB_SEARCH_KAON_SETTINGS_NAMESPACE} 命名空间里改回 Anthropic 兼容端点`);
90
- }
91
- let disposed = false;
92
- let unregister;
93
- try {
94
- ctx.inject(['web'], (cctx) => {
95
- if (disposed)
96
- return;
97
- const seam = cctx.web;
98
- if (seam?.registerSearchProvider === undefined)
99
- return;
100
- unregister = seam.registerSearchProvider(provider);
101
- });
102
- }
103
- catch (error) {
104
- // 组合里没有 web 服务:本域静默缺席,绝不影响全家桶其余能力。
105
- warn(`[web-all:web-search-kaon] 等待 web 服务失败,本域缺席:${error instanceof Error ? error.message : String(error)}`);
106
- return () => undefined;
107
- }
108
- return () => {
109
- disposed = true;
110
- unregister?.();
111
- };
112
- }
113
- export { effectiveConfig, WEB_SEARCH_KAON_DEFAULT_API_KEY_ENV, WEB_SEARCH_KAON_DEFAULT_BASE_URL, WEB_SEARCH_KAON_DEFAULT_MODEL, WEB_SEARCH_KAON_PROVIDER_ID, WEB_SEARCH_KAON_SETTINGS_NAMESPACE, } from "./contracts.js";
114
- export { citationSnippets, KaonSearchProvider, mapSearchResponse } from "./provider.js";
@@ -1,111 +0,0 @@
1
- /**
2
- * Kaon Router(Anthropic 兼容 Messages)搜索 provider。
3
- *
4
- * 一次搜索 = 一次 Messages 请求 + 原生 `web_search_20250305` 服务端工具:代价是
5
- * 一个模型回合,回报是结构化结果块。与原生 deepseek provider 的唯一实质差别是
6
- * **响应归一化**:本 provider 同时接受标准 Anthropic 形状(`web_search_tool_result`
7
- * 条目 + citation 摘录)和 Kaon/Bedrock 形状(没有结果块,只有 citation),
8
- * 并且在「模型只是申请调用客户端工具」时给出可操作的报错,而不是假装搜到了东西。
9
- *
10
- * 配置按**每次搜索**从注入的读取器取,因此 `web-search-kaon` 命名空间的热更新
11
- * (端点/模型/次数上限)立即生效,无需重挂插件。
12
- *
13
- * `available()` 只做本地判断(key 解析器存在、URL 可解析、上限为正整数),
14
- * 不发网络请求(seam 的硬要求)。
15
- *
16
- * @module web-all/host/services/web-search-kaon
17
- */
18
- import { type WebSearchProvider, type WebSearchRequest, type WebSearchResult, type WebSearchSource } from './contracts.ts';
19
- /** 一次引用位置(Kaon 把命中信息全放在这里)。 */
20
- export interface CitationLocation {
21
- type?: string;
22
- url?: string | null;
23
- title?: string | null;
24
- cited_text?: string | null;
25
- }
26
- /** 一个内容块;只消费下方注释里列出的几种。 */
27
- export interface ContentBlock {
28
- type?: string;
29
- text?: string | null;
30
- name?: string | null;
31
- citations?: readonly CitationLocation[] | null;
32
- /** `web_search_tool_result` 的载荷:条目数组,或一个错误对象。 */
33
- content?: unknown;
34
- }
35
- /** `web_search_tool_result` 里的一个可引用条目。 */
36
- export interface SearchResultItem {
37
- type?: string;
38
- url?: string | null;
39
- title?: string | null;
40
- page_age?: string | null;
41
- }
42
- /** Anthropic Messages 响应信封(best-effort;字段随上游而变)。 */
43
- export interface AnthropicResponse {
44
- content?: readonly ContentBlock[] | null;
45
- stop_reason?: string | null;
46
- usage?: {
47
- server_tool_use?: {
48
- web_search_requests?: number | null;
49
- } | null;
50
- } | null;
51
- }
52
- /** 归一化结果:来源、可选的模型摘要,以及用于诊断的检索事实。 */
53
- export interface MappedSearchResponse {
54
- sources: readonly WebSearchSource[];
55
- /** 模型生成的摘要文本(取最长的一段非空 text 块)。 */
56
- content?: string;
57
- /** 端点侧实际执行的服务端搜索次数(0 表示没搜)。 */
58
- searches: number;
59
- /** 响应里有 `tool_use(web_search)`:模型申请调用客户端工具,端点没执行搜索。 */
60
- clientToolUse: boolean;
61
- /** `web_search_tool_result` 自带的错误码(有则整体失败)。 */
62
- resultError?: string;
63
- }
64
- /** 一次搜索的运行期选项快照。 */
65
- export interface KaonSearchProviderOptions {
66
- /** 端点基址,不含 `/messages`。 */
67
- baseURL: string;
68
- model: string;
69
- apiVersion: string;
70
- maxTokens: number;
71
- maxUses: number;
72
- /** 是否把模型摘要并入 `content`。 */
73
- includeAnswer: boolean;
74
- timeoutMs: number;
75
- /** 字面 key;存在时优先于 {@link resolveApiKey}。 */
76
- apiKey?: string;
77
- /** 每次搜索解析一次当前 key(凭据服务 / 环境变量)。 */
78
- resolveApiKey?: () => Promise<string | undefined>;
79
- /** 凭据引用名,只用于报错文案。 */
80
- apiKeyEnv?: string;
81
- /** 注入的 fetch,测试用。 */
82
- fetchImpl?: typeof fetch;
83
- }
84
- /** 收集每个 URL 的 citation 摘录(首次出现优先);标准形状下它才是 snippet 来源。 */
85
- export declare function citationSnippets(blocks: readonly ContentBlock[]): Map<string, string>;
86
- /**
87
- * 把两种 Anthropic 兼容响应形状统一成 seam 的 `sources[]`。
88
- *
89
- * 顺序:先走标准结果块(带 `page_age` 与条目级字段),拿不到任何条目时再回落到
90
- * citation —— Kaon 的 Claude 路由永远走第二条分支;两条分支都用同一张 citation
91
- * 摘录表补 `snippet`,按 URL 去重(`max_uses > 1` 时同一 URL 可能多次出现)。
92
- *
93
- * @param response - 解析后的 Messages 响应体。
94
- * @returns 归一化结果;`searches`/`clientToolUse`/`resultError` 供诊断使用。
95
- */
96
- export declare function mapSearchResponse(response: AnthropicResponse): MappedSearchResponse;
97
- /** 用 Anthropic 兼容 Messages 端点做检索的 provider。 */
98
- export declare class KaonSearchProvider implements WebSearchProvider {
99
- readonly id = "kaon-router";
100
- private readonly resolveOptions;
101
- /**
102
- * @param resolveOptions - 下一次操作的选项;thunk 而非快照,命名空间热更新因此
103
- * 每次搜索都生效,而不必重注册 provider(重注册会让 seam 的选择对用户可见地抖动)。
104
- */
105
- constructor(resolveOptions: () => KaonSearchProviderOptions);
106
- available(): boolean;
107
- search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
108
- /** 解析一次操作的 key:字面 key → 凭据服务 → 环境变量。 */
109
- private apiKey;
110
- }
111
- //# sourceMappingURL=provider.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../../../src/host/services/web-search-kaon/provider.ts"],"names":[],"mappings":"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"}
@@ -1,244 +0,0 @@
1
- /**
2
- * Kaon Router(Anthropic 兼容 Messages)搜索 provider。
3
- *
4
- * 一次搜索 = 一次 Messages 请求 + 原生 `web_search_20250305` 服务端工具:代价是
5
- * 一个模型回合,回报是结构化结果块。与原生 deepseek provider 的唯一实质差别是
6
- * **响应归一化**:本 provider 同时接受标准 Anthropic 形状(`web_search_tool_result`
7
- * 条目 + citation 摘录)和 Kaon/Bedrock 形状(没有结果块,只有 citation),
8
- * 并且在「模型只是申请调用客户端工具」时给出可操作的报错,而不是假装搜到了东西。
9
- *
10
- * 配置按**每次搜索**从注入的读取器取,因此 `web-search-kaon` 命名空间的热更新
11
- * (端点/模型/次数上限)立即生效,无需重挂插件。
12
- *
13
- * `available()` 只做本地判断(key 解析器存在、URL 可解析、上限为正整数),
14
- * 不发网络请求(seam 的硬要求)。
15
- *
16
- * @module web-all/host/services/web-search-kaon
17
- */
18
- import { WebSearchError, WEB_SEARCH_KAON_PROVIDER_ID, } from "./contracts.js";
19
- /** 归因头;与 provider 行为一起演进。 */
20
- const USER_AGENT = 'dsh-web-all/web-search-kaon';
21
- /** 收集每个 URL 的 citation 摘录(首次出现优先);标准形状下它才是 snippet 来源。 */
22
- export function citationSnippets(blocks) {
23
- const map = new Map();
24
- for (const block of blocks) {
25
- if (block.type !== 'text')
26
- continue;
27
- for (const cite of block.citations ?? []) {
28
- const url = cite.url ?? '';
29
- const text = cite.cited_text ?? '';
30
- if (url !== '' && text !== '' && !map.has(url))
31
- map.set(url, text);
32
- }
33
- }
34
- return map;
35
- }
36
- /** 读出 `web_search_tool_result` 自带的错误(载荷是对象而非数组时)。 */
37
- function resultErrorOf(block) {
38
- const payload = block.content;
39
- if (payload === null || typeof payload !== 'object' || Array.isArray(payload))
40
- return undefined;
41
- const record = payload;
42
- if (record.error_code === undefined || record.error_code === null)
43
- return undefined;
44
- return `${String(record.type ?? 'web_search_tool_result_error')}: ${String(record.error_code)}`;
45
- }
46
- /**
47
- * 把两种 Anthropic 兼容响应形状统一成 seam 的 `sources[]`。
48
- *
49
- * 顺序:先走标准结果块(带 `page_age` 与条目级字段),拿不到任何条目时再回落到
50
- * citation —— Kaon 的 Claude 路由永远走第二条分支;两条分支都用同一张 citation
51
- * 摘录表补 `snippet`,按 URL 去重(`max_uses > 1` 时同一 URL 可能多次出现)。
52
- *
53
- * @param response - 解析后的 Messages 响应体。
54
- * @returns 归一化结果;`searches`/`clientToolUse`/`resultError` 供诊断使用。
55
- */
56
- export function mapSearchResponse(response) {
57
- const blocks = response.content ?? [];
58
- const searches = response.usage?.server_tool_use?.web_search_requests
59
- ?? blocks.filter(block => block.type === 'server_tool_use' && block.name === 'web_search').length;
60
- const clientToolUse = blocks.some(block => block.type === 'tool_use' && block.name === 'web_search');
61
- const resultError = blocks.filter(block => block.type === 'web_search_tool_result')
62
- .map(resultErrorOf)
63
- .find(error => error !== undefined);
64
- const snippets = citationSnippets(blocks);
65
- const sources = [];
66
- const seen = new Set();
67
- const add = (url, title, snippet, publishedAt) => {
68
- const value = url ?? '';
69
- if (value === '' || seen.has(value))
70
- return;
71
- seen.add(value);
72
- sources.push({
73
- url: value,
74
- ...title !== null && title !== undefined && title !== '' ? { title } : {},
75
- ...snippet !== null && snippet !== undefined && snippet !== '' ? { snippet } : {},
76
- ...publishedAt !== null && publishedAt !== undefined && publishedAt !== '' ? { publishedAt } : {},
77
- });
78
- };
79
- for (const block of blocks) {
80
- if (block.type !== 'web_search_tool_result' || !Array.isArray(block.content))
81
- continue;
82
- for (const item of block.content) {
83
- if (item?.type !== 'web_search_result')
84
- continue;
85
- const url = item.url ?? '';
86
- add(url, item.title, snippets.get(url), item.page_age);
87
- }
88
- }
89
- if (sources.length === 0) {
90
- for (const block of blocks) {
91
- if (block.type !== 'text')
92
- continue;
93
- for (const cite of block.citations ?? [])
94
- add(cite.url, cite.title, cite.cited_text);
95
- }
96
- }
97
- let content;
98
- for (const block of blocks) {
99
- if (block.type !== 'text')
100
- continue;
101
- const text = block.text ?? '';
102
- if (text.length > (content?.length ?? 0))
103
- content = text;
104
- }
105
- return {
106
- sources,
107
- ...content !== undefined ? { content } : {},
108
- searches,
109
- clientToolUse,
110
- ...resultError !== undefined ? { resultError } : {},
111
- };
112
- }
113
- /** 去掉尾斜杠,避免 `baseURL` 以 `/` 结尾时拼出 `//messages`。 */
114
- function trimTrailingSlash(value) {
115
- return value.endsWith('/') ? value.replace(/\/+$/u, '') : value;
116
- }
117
- /** 端点与配置引导:把「这是配置问题、改哪里」写进错误里。 */
118
- function endpointHint(endpoint) {
119
- return `\n\nThe web search request used endpoint ${JSON.stringify(endpoint)}. Search endpoint configuration is separate from chat: guide the user to the web-search-kaon settings namespace (Settings > Plugins > Plugin configuration, the web-all entry) or to web-search-kaon.baseURL in the profile cordis.patch.yml. Only the user should change the endpoint.`;
120
- }
121
- /** 端点没执行搜索时的可操作报错:区分「模型申请客户端工具」与「搜了但没结果」。 */
122
- function noResultError(mapped, options) {
123
- if (mapped.clientToolUse) {
124
- return new WebSearchError(`The endpoint returned a client-side tool_use(web_search) instead of executing the search itself, so model ${JSON.stringify(options.model)} `
125
- + 'has no web search results. Native server-side web_search_20250305 is an Anthropic-model feature: on Kaon Router pick a kaon/claude-* model '
126
- + '(kaon/deepseek-* and kaon/gemini-* reply with a client tool call). The provider was left pointed at this endpoint on purpose; only the user should change it.', 'WEB_PROVIDER_ERROR');
127
- }
128
- if (mapped.searches === 0) {
129
- return new WebSearchError(`The endpoint executed no server-side web search for model ${JSON.stringify(options.model)} and returned no citeable sources; `
130
- + 'the request may not have triggered native web search.', 'WEB_PROVIDER_ERROR');
131
- }
132
- return new WebSearchError(`The endpoint executed ${mapped.searches} server-side web search(es) but returned no citeable sources.`, 'WEB_PROVIDER_ERROR');
133
- }
134
- /** 用 Anthropic 兼容 Messages 端点做检索的 provider。 */
135
- export class KaonSearchProvider {
136
- id = WEB_SEARCH_KAON_PROVIDER_ID;
137
- resolveOptions;
138
- /**
139
- * @param resolveOptions - 下一次操作的选项;thunk 而非快照,命名空间热更新因此
140
- * 每次搜索都生效,而不必重注册 provider(重注册会让 seam 的选择对用户可见地抖动)。
141
- */
142
- constructor(resolveOptions) {
143
- this.resolveOptions = resolveOptions;
144
- }
145
- available() {
146
- const options = this.resolveOptions();
147
- return ((options.apiKey?.length ?? 0) > 0 || options.resolveApiKey !== undefined)
148
- && URL.canParse(options.baseURL)
149
- && Number.isInteger(options.maxTokens) && options.maxTokens > 0
150
- && Number.isInteger(options.maxUses) && options.maxUses > 0;
151
- }
152
- async search(request, signal) {
153
- const options = this.resolveOptions();
154
- // 读经函数而非直接比属性:TS 会把 `signal?.aborted === true` 早退后的属性
155
- // 窄化成 false,后续再判同一属性就成了「无重叠比较」。
156
- const isAborted = () => signal?.aborted === true;
157
- const endpoint = `${trimTrailingSlash(options.baseURL)}/messages`;
158
- const apiKey = await this.apiKey(options, signal);
159
- if (isAborted())
160
- throw abortedError();
161
- const body = {
162
- model: options.model,
163
- max_tokens: options.maxTokens,
164
- messages: [{
165
- role: 'user',
166
- content: [{ type: 'text', text: `Perform a web search for the query: ${request.query}` }],
167
- }],
168
- tools: [{ type: 'web_search_20250305', name: 'web_search', max_uses: options.maxUses }],
169
- };
170
- const timeout = AbortSignal.timeout(options.timeoutMs);
171
- const composed = signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
172
- const doFetch = options.fetchImpl ?? fetch;
173
- let response;
174
- try {
175
- response = await doFetch(endpoint, {
176
- method: 'POST',
177
- redirect: 'error',
178
- headers: {
179
- 'x-api-key': apiKey,
180
- 'authorization': `Bearer ${apiKey}`,
181
- 'anthropic-version': options.apiVersion,
182
- 'content-type': 'application/json',
183
- 'accept': 'application/json',
184
- 'user-agent': USER_AGENT,
185
- },
186
- body: JSON.stringify(body),
187
- signal: composed,
188
- });
189
- }
190
- catch (error) {
191
- if (isAborted())
192
- throw abortedError();
193
- if (timeout.aborted)
194
- throw new WebSearchError(`Web search timed out after ${options.timeoutMs} ms at ${JSON.stringify(endpoint)}.`, 'WEB_PROVIDER_ERROR');
195
- throw new WebSearchError(`Web search request failed: ${String(error)}.${endpointHint(endpoint)}`, 'WEB_PROVIDER_ERROR');
196
- }
197
- if (!response.ok) {
198
- let detail = '';
199
- try {
200
- const parsed = await response.json();
201
- detail = typeof parsed.error === 'string' ? parsed.error : parsed.error?.message ?? parsed.message ?? '';
202
- }
203
- catch {
204
- // 非 JSON 错误体:状态码已经足够定位。
205
- }
206
- const message = `Web search endpoint error (HTTP ${response.status})${detail === '' ? '' : `: ${detail}`}`;
207
- throw new WebSearchError(`${message}${endpointHint(endpoint)}`, 'WEB_PROVIDER_ERROR');
208
- }
209
- let mapped;
210
- try {
211
- mapped = mapSearchResponse(await response.json());
212
- }
213
- catch (error) {
214
- throw new WebSearchError(`Web search returned an unprocessable response body: ${String(error)}`, 'WEB_PROVIDER_ERROR');
215
- }
216
- if (mapped.resultError !== undefined) {
217
- throw new WebSearchError(`The endpoint's web search failed: ${mapped.resultError}.`, 'WEB_PROVIDER_ERROR');
218
- }
219
- if (mapped.sources.length === 0)
220
- throw noResultError(mapped, options);
221
- return {
222
- ...options.includeAnswer && mapped.content !== undefined && mapped.content !== '' ? { content: mapped.content } : {},
223
- sources: mapped.sources,
224
- truncated: false,
225
- };
226
- }
227
- /** 解析一次操作的 key:字面 key → 凭据服务 → 环境变量。 */
228
- async apiKey(options, signal) {
229
- if (signal?.aborted === true)
230
- throw abortedError();
231
- if (options.apiKey !== undefined && options.apiKey.length > 0)
232
- return options.apiKey;
233
- const resolved = options.resolveApiKey === undefined ? undefined : await options.resolveApiKey();
234
- if (resolved !== undefined && resolved.length > 0)
235
- return resolved;
236
- const ref = options.apiKeyEnv ?? '';
237
- throw new WebSearchError(`Web search has no API key for "${ref === '' ? 'the configured credential reference' : ref}"; store it through the credentials service, `
238
- + 'export it in the launching environment, or set a literal "apiKey" in the web-search-kaon config.', 'WEB_PROVIDER_CREDENTIAL_MISSING');
239
- }
240
- }
241
- /** 调用方取消:与原生 provider 的 `WEB_ABORTED` 对齐。 */
242
- function abortedError() {
243
- return new WebSearchError('Web search aborted', 'WEB_ABORTED');
244
- }