@caiqy/dsh-plugin-web-search 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,93 @@
1
- # Temporary Holding Version
1
+ # @caiqy/dsh-plugin-web-search
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 替换 DSH 内置 `web_search` 的搜索后端。第一版支持 OpenAI `/v1/alpha/search` 和采用相同协议的兼容接口;不增加模型工具。`web_fetch` 继续使用宿主默认 HTTP 后端。
4
+
5
+ ## 安装
6
+
7
+ 从 npm 安装公开发行版:
8
+
9
+ ```sh
10
+ dsh plugin --profile desktop add @caiqy/dsh-plugin-web-search@0.1.0
11
+ ```
12
+
13
+ 也可以在 DSH 的插件市场添加本仓库索引源:
14
+
15
+ ```text
16
+ https://raw.githubusercontent.com/caiqy/dsh-plugins/main/index.json
17
+ ```
18
+
19
+ 从源码构建时,在仓库根目录安装依赖,再构建与打包:
20
+
21
+ ```sh
22
+ pnpm install
23
+ pnpm --filter @caiqy/dsh-plugin-web-search build
24
+ pnpm -C packages/dsh-plugin-web-search pack
25
+ ```
26
+
27
+ 将生成的 tarball 安装到所需 profile,例如:
28
+
29
+ ```sh
30
+ dsh plugin --profile desktop add ./packages/dsh-plugin-web-search/caiqy-dsh-plugin-web-search-0.1.0.tgz
31
+ ```
32
+
33
+ 在 DSH 的插件详情页打开本插件配置,填写并保存。是否需要重新加载由宿主插件管理器提示决定;本仓库的开发任务不会自动安装或修改你的在用 profile。
34
+
35
+ ## 配置
36
+
37
+ 选择 OpenAI 后显示以下专属字段:
38
+
39
+ | 字段 | 含义 |
40
+ | --- | --- |
41
+ | 接口地址 | 完整操作 URL,默认 `https://api.openai.com/v1/alpha/search`,直接请求,不追加路径。 |
42
+ | 搜索模型 | 该搜索服务支持的模型 ID,默认 `gpt-5.6-luna`;与聊天模型独立,可修改。 |
43
+ | API 密钥 | 单独保存到宿主 credentials,引用名为 `DSH_WEB_SEARCH_OPENAI_API_KEY`;留空保持已有值。 |
44
+
45
+ 非密钥配置位于本插件 `web-search-provider` 条目,模型只属于 OpenAI:
46
+
47
+ ```yaml
48
+ provider: openai
49
+ providers:
50
+ openai:
51
+ endpoint: https://api.openai.com/v1/alpha/search
52
+ model: gpt-5.6-luna
53
+ ```
54
+
55
+ provider 的字段定义位于 `src/providers.ts`。以后新增 provider 时分别增加其配置 schema、字段和执行函数;不需要模型的服务不会继承 OpenAI 的模型字段。第一版下拉框仅提供已经实现的 OpenAI。
56
+
57
+ 密码框不会回填密钥,只报告已配置/未配置状态。环境变量来源不可写时遵循宿主限制。非密钥设置先保存,密钥另行保存;部分失败会分别报告,保留未保存输入,重试不会重复已经接受的设置。
58
+
59
+ ## 接管与恢复
60
+
61
+ bundle patch 选择 `web.searchProvider: web-search-provider`,并保留 `fetchProvider: http`。原 `web_search` 的参数、多查询合并、数量上限和结果卡片均由宿主处理。
62
+
63
+ 用户 profile 中显式写入的 `web.config` 比 bundle patch 优先。如果安装后仍调用默认搜索服务,请检查该覆盖并在需要时将 `searchProvider` 设为 `web-search-provider`,同时保留自己的 fetch 配置。禁用或卸载 bundle 并让宿主重载组合后,会恢复其原 bundle 路由;插件不改宿主源码或工具注册表。
64
+
65
+ ## 协议与验证
66
+
67
+ 发送 POST + Bearer 鉴权,查询位于 `input[].content[].text` 和 `commands.search_query[].q`,附带当前 provider 的 `model`、每次独立请求随机 `id`、`settings.allowed_callers: [direct]`、`external_web_access: true` 和 `max_output_tokens: 2048`。
68
+
69
+ 读取响应的字符串 `output` 和可选 `results[]`。只映射合法 HTTP(S) `url` 与可选 `title`,清理私有引用标记;不假定 snippet 或日期存在,不暴露 `encrypted_output`。HTTP 错误、JSON 错误和取消明确失败;不自动回退或重试,带密钥的请求不跟随重定向,错误不转发可能包含密钥的上游正文。
70
+
71
+ 协议参考:[OpenCode 参考实现](../../../opencode-ide-plugin/packages/core/src/tool/websearch.ts)、[pi-codex-search-remote](https://registry.npmjs.org/pi-codex-search-remote)、[另一公开实现](https://github.com/kk3ya03-star/dsh-lcx-codex/blob/main/src/web-search-alpha.ts)。OpenCode 相对链接仅适用于本机同级 checkout;公开实现是可携带的证据。
72
+
73
+ 可运行校验:
74
+
75
+ ```sh
76
+ pnpm --filter @caiqy/dsh-plugin-web-search typecheck
77
+ pnpm --filter @caiqy/dsh-plugin-web-search build
78
+ pnpm --filter @caiqy/dsh-plugin-web-search test
79
+ ```
80
+
81
+ 真实调用入口从测试进程环境读取 `DSH_SEARCH_TEST_ENDPOINT`(完整 URL)、`DSH_SEARCH_TEST_KEY`、`DSH_SEARCH_TEST_MODEL`:
82
+
83
+ ```sh
84
+ pnpm --filter @caiqy/dsh-plugin-web-search test:live
85
+ ```
86
+
87
+ 本次经用户授权读取全局 OpenCode 配置,使用 `gpt-6-luna` 实测通过:1.225 秒返回 7,991 字符正文和 5 个来源,来源主机为 `docs.python.org`、`www.python.org`、`wiki.python.org`。密钥未打印或复制到仓库;这个模型只用于本次实测,不是插件的通用模型配置。
88
+
89
+ ## 已知限制
90
+
91
+ Alpha search 的公开实现提供协议依据,普通 OpenAI API key 权限、模型支持范围和完整官方契约不能由一次兼容服务实测保证。默认模型来自参考实现,请按实际服务修改。本插件不支持 ChatGPT OAuth、引用续查、其他 provider、缓存或重试。
92
+
93
+ UI 已覆盖状态逻辑、发行 bundle factory 和 React 渲染校验,尚未安装到在用 DSH 完成真实浏览器页面检查。当前 GUI 的认证页面不可直接进入;没有将 SSR 校验当作宿主浏览器实测。
@@ -0,0 +1,8 @@
1
+ # Only the provider changes; the host owns web_search and web_fetch.
2
+ - id: web
3
+ config:
4
+ searchProvider: web-search-provider
5
+ fetchProvider: http
6
+ - insert:
7
+ - id: web-search-provider
8
+ name: "@caiqy/dsh-plugin-web-search"
@@ -0,0 +1,34 @@
1
+ import type { ProviderDefinition, SearchSettings } from './providers.js';
2
+ /** A draft fences all edits with the revision at which editing began. */
3
+ export interface ClientDraft {
4
+ settings: SearchSettings;
5
+ original: SearchSettings;
6
+ revision: number | undefined;
7
+ secrets: Record<string, string>;
8
+ }
9
+ /** Seed only provider-defined fields; credentials never enter settings. */
10
+ export declare function createDraft(settings: SearchSettings | undefined, revision: number | undefined, providers: readonly ProviderDefinition[]): ClientDraft;
11
+ /** Select metadata separately from values so providers can expose different fields. */
12
+ export declare function selectedProvider(draft: ClientDraft, providers: readonly ProviderDefinition[]): ProviderDefinition | undefined;
13
+ /** Keep the other providers' drafts when switching or editing one field. */
14
+ export declare function editDraft(draft: ClientDraft, provider: string, field: string, value: string): ClientDraft;
15
+ /** Path operations touch visible configuration keys without replacing unknown settings. */
16
+ export declare function draftOperations(draft: ClientDraft, providers: readonly ProviderDefinition[]): {
17
+ op: 'set';
18
+ path: string[];
19
+ value: string;
20
+ }[];
21
+ export interface SaveOperations {
22
+ mutate: (ops: ReturnType<typeof draftOperations>, revision: number | undefined) => Promise<boolean>;
23
+ revision: () => number | undefined;
24
+ storeCredential: (ref: string, value: string) => Promise<boolean>;
25
+ }
26
+ export interface SaveOutcome {
27
+ draft: ClientDraft;
28
+ settingsSaved: boolean;
29
+ credentialSaved: boolean;
30
+ failure?: 'settings' | 'credential';
31
+ }
32
+ /** Save settings before credentials; advance the retry baseline only for confirmed writes. */
33
+ export declare function saveDraft(draft: ClientDraft, providers: readonly ProviderDefinition[], operations: SaveOperations): Promise<SaveOutcome>;
34
+ //# sourceMappingURL=client-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-model.d.ts","sourceRoot":"","sources":["../src/client-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAExE,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,cAAc,CAAA;IACxB,QAAQ,EAAE,cAAc,CAAA;IACxB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,SAAS,kBAAkB,EAAE,GAAG,WAAW,CAMrJ;AAED,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,kBAAkB,EAAE,GAAG,kBAAkB,GAAG,SAAS,CAE7H;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAIzG;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,kBAAkB,EAAE;QACzE,KAAK;UAAQ,MAAM,EAAE;WAAS,MAAM;IAWtD;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACnG,QAAQ,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;IAClC,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAClE;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,WAAW,CAAA;IAClB,aAAa,EAAE,OAAO,CAAA;IACtB,eAAe,EAAE,OAAO,CAAA;IACxB,OAAO,CAAC,EAAE,UAAU,GAAG,YAAY,CAAA;CACpC;AAED,8FAA8F;AAC9F,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,kBAAkB,EAAE,EAAE,UAAU,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CA2B9I"}
@@ -0,0 +1,64 @@
1
+ /** Seed only provider-defined fields; credentials never enter settings. */
2
+ export function createDraft(settings, revision, providers) {
3
+ const values = Object.fromEntries(providers.map(provider => [provider.id, Object.fromEntries(provider.fields.map(field => [field.key, settings?.providers[provider.id]?.[field.key] ?? field.defaultValue]))]));
4
+ const resolved = { provider: settings?.provider ?? providers[0].id, providers: values };
5
+ return { settings: resolved, original: structuredClone(resolved), revision, secrets: {} };
6
+ }
7
+ /** Select metadata separately from values so providers can expose different fields. */
8
+ export function selectedProvider(draft, providers) {
9
+ return providers.find(provider => provider.id === draft.settings.provider);
10
+ }
11
+ /** Keep the other providers' drafts when switching or editing one field. */
12
+ export function editDraft(draft, provider, field, value) {
13
+ return { ...draft, settings: { ...draft.settings, providers: {
14
+ ...draft.settings.providers, [provider]: { ...draft.settings.providers[provider], [field]: value },
15
+ } } };
16
+ }
17
+ /** Path operations touch visible configuration keys without replacing unknown settings. */
18
+ export function draftOperations(draft, providers) {
19
+ const ops = [];
20
+ if (draft.settings.provider !== draft.original.provider)
21
+ ops.push({ op: 'set', path: ['provider'], value: draft.settings.provider });
22
+ for (const provider of providers) {
23
+ for (const field of provider.fields) {
24
+ const value = draft.settings.providers[provider.id][field.key];
25
+ if (value !== draft.original.providers[provider.id]?.[field.key]) {
26
+ ops.push({ op: 'set', path: ['providers', provider.id, field.key], value });
27
+ }
28
+ }
29
+ }
30
+ return ops;
31
+ }
32
+ /** Save settings before credentials; advance the retry baseline only for confirmed writes. */
33
+ export async function saveDraft(draft, providers, operations) {
34
+ let next = draft;
35
+ const ops = draftOperations(draft, providers);
36
+ try {
37
+ if (ops.length && !await operations.mutate(ops, draft.revision)) {
38
+ return { draft, settingsSaved: false, credentialSaved: false, failure: 'settings' };
39
+ }
40
+ }
41
+ catch {
42
+ return { draft, settingsSaved: false, credentialSaved: false, failure: 'settings' };
43
+ }
44
+ next = { ...draft, original: structuredClone(draft.settings), revision: operations.revision(), secrets: { ...draft.secrets } };
45
+ let credentialSaved = false;
46
+ for (const provider of providers) {
47
+ const value = next.secrets[provider.id]?.trim();
48
+ if (!value || !provider.credentialRef)
49
+ continue;
50
+ try {
51
+ if (!await operations.storeCredential(provider.credentialRef, value)) {
52
+ return { draft: next, settingsSaved: true, credentialSaved, failure: 'credential' };
53
+ }
54
+ }
55
+ catch {
56
+ // Transport errors can contain request data; never display them beside a secret.
57
+ return { draft: next, settingsSaved: true, credentialSaved, failure: 'credential' };
58
+ }
59
+ next.secrets[provider.id] = '';
60
+ credentialSaved = true;
61
+ }
62
+ return { draft: next, settingsSaved: true, credentialSaved };
63
+ }
64
+ //# sourceMappingURL=client-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-model.js","sourceRoot":"","sources":["../src/client-model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ export declare const name = "web-search-provider-settings";
3
+ export declare const inject: string[];
4
+ /** Register the bundle configuration page with the Host row's reactive form. */
5
+ export declare function apply(ctx: Context): void;
6
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAUlD,eAAO,MAAM,IAAI,iCAAiC,CAAA;AAClD,eAAO,MAAM,MAAM,UAA2D,CAAA;AA4H9E,gFAAgF;AAChF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAiBxC"}
package/lib/client.js ADDED
@@ -0,0 +1,305 @@
1
+ window.__ModuleLoader__.load({id:"@caiqy/dsh-plugin-web-search",factory:(require)=>{var module={exports:{}};var exports=module.exports;
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name2 in all)
9
+ __defProp(target, name2, { get: all[name2], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/client.tsx
22
+ var client_exports = {};
23
+ __export(client_exports, {
24
+ apply: () => apply,
25
+ inject: () => inject,
26
+ name: () => name
27
+ });
28
+ module.exports = __toCommonJS(client_exports);
29
+ var import_react = require("react");
30
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
31
+
32
+ // src/providers.ts
33
+ var PROVIDERS = [{
34
+ id: "openai",
35
+ label: "OpenAI",
36
+ credentialRef: "DSH_WEB_SEARCH_OPENAI_API_KEY",
37
+ fields: [
38
+ { key: "endpoint", label: "\u63A5\u53E3\u5730\u5740", defaultValue: "https://api.openai.com/v1/alpha/search" },
39
+ { key: "model", label: "\u641C\u7D22\u6A21\u578B", defaultValue: "gpt-5.6-luna" }
40
+ ]
41
+ }];
42
+ function validateOpenAIOptions(options) {
43
+ try {
44
+ const url = new URL(options.endpoint ?? "");
45
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash) {
46
+ return "\u8BF7\u586B\u5199\u4E0D\u542B\u767B\u5F55\u4FE1\u606F\u548C\u7247\u6BB5\u7684 HTTP(S) \u63A5\u53E3\u5730\u5740\u3002";
47
+ }
48
+ } catch {
49
+ return "\u8BF7\u586B\u5199\u5B8C\u6574\u7684 HTTP(S) \u63A5\u53E3\u5730\u5740\u3002";
50
+ }
51
+ if (!options.model?.trim()) return "\u8BF7\u586B\u5199\u641C\u7D22\u6A21\u578B\u3002";
52
+ return void 0;
53
+ }
54
+
55
+ // src/client-model.ts
56
+ function createDraft(settings, revision, providers) {
57
+ const values = Object.fromEntries(providers.map((provider) => [provider.id, Object.fromEntries(
58
+ provider.fields.map((field) => [field.key, settings?.providers[provider.id]?.[field.key] ?? field.defaultValue])
59
+ )]));
60
+ const resolved = { provider: settings?.provider ?? providers[0].id, providers: values };
61
+ return { settings: resolved, original: structuredClone(resolved), revision, secrets: {} };
62
+ }
63
+ function selectedProvider(draft, providers) {
64
+ return providers.find((provider) => provider.id === draft.settings.provider);
65
+ }
66
+ function editDraft(draft, provider, field, value) {
67
+ return { ...draft, settings: { ...draft.settings, providers: {
68
+ ...draft.settings.providers,
69
+ [provider]: { ...draft.settings.providers[provider], [field]: value }
70
+ } } };
71
+ }
72
+ function draftOperations(draft, providers) {
73
+ const ops = [];
74
+ if (draft.settings.provider !== draft.original.provider) ops.push({ op: "set", path: ["provider"], value: draft.settings.provider });
75
+ for (const provider of providers) {
76
+ for (const field of provider.fields) {
77
+ const value = draft.settings.providers[provider.id][field.key];
78
+ if (value !== draft.original.providers[provider.id]?.[field.key]) {
79
+ ops.push({ op: "set", path: ["providers", provider.id, field.key], value });
80
+ }
81
+ }
82
+ }
83
+ return ops;
84
+ }
85
+ async function saveDraft(draft, providers, operations) {
86
+ let next = draft;
87
+ const ops = draftOperations(draft, providers);
88
+ try {
89
+ if (ops.length && !await operations.mutate(ops, draft.revision)) {
90
+ return { draft, settingsSaved: false, credentialSaved: false, failure: "settings" };
91
+ }
92
+ } catch {
93
+ return { draft, settingsSaved: false, credentialSaved: false, failure: "settings" };
94
+ }
95
+ next = { ...draft, original: structuredClone(draft.settings), revision: operations.revision(), secrets: { ...draft.secrets } };
96
+ let credentialSaved = false;
97
+ for (const provider of providers) {
98
+ const value = next.secrets[provider.id]?.trim();
99
+ if (!value || !provider.credentialRef) continue;
100
+ try {
101
+ if (!await operations.storeCredential(provider.credentialRef, value)) {
102
+ return { draft: next, settingsSaved: true, credentialSaved, failure: "credential" };
103
+ }
104
+ } catch {
105
+ return { draft: next, settingsSaved: true, credentialSaved, failure: "credential" };
106
+ }
107
+ next.secrets[provider.id] = "";
108
+ credentialSaved = true;
109
+ }
110
+ return { draft: next, settingsSaved: true, credentialSaved };
111
+ }
112
+
113
+ // src/client.tsx
114
+ var import_jsx_runtime = require("react/jsx-runtime");
115
+ var name = "web-search-provider-settings";
116
+ var inject = ["slots", "configForms", "remote", "remote.credentials"];
117
+ var labels = {
118
+ unavailable: "\u641C\u7D22\u914D\u7F6E\u5C1A\u4E0D\u53EF\u7528\uFF0C\u8BF7\u786E\u8BA4\u63D2\u4EF6\u540E\u7AEF\u5DF2\u542F\u7528\u5E76\u4F7F\u7528\u53EF\u6301\u4E45\u5316\u7684\u672C\u673A\u8FDE\u63A5\u3002",
119
+ readOnly: "\u5BBF\u4E3B\u914D\u7F6E\u4E3A\u53EA\u8BFB\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u641C\u7D22\u8BBE\u7F6E\u3002",
120
+ saveFailed: "\u4FDD\u5B58\u672A\u5B8C\u6210\uFF0C\u672A\u4FDD\u5B58\u7684\u8F93\u5165\u5DF2\u4FDD\u7559\u3002",
121
+ save: "\u4FDD\u5B58",
122
+ saving: "\u6B63\u5728\u4FDD\u5B58\u2026"
123
+ };
124
+ function SearchSettingsPage(props) {
125
+ const host = props.useSearchSettings((snapshot) => snapshot);
126
+ const [draft, setDraft] = (0, import_react.useState)();
127
+ const [saving, setSaving] = (0, import_react.useState)(false);
128
+ const [feedback, setFeedback] = (0, import_react.useState)();
129
+ const [menuOpen, setMenuOpen] = (0, import_react.useState)(false);
130
+ const [credentials, setCredentials] = (0, import_react.useState)();
131
+ const [credentialLoading, setCredentialLoading] = (0, import_react.useState)(true);
132
+ const [credentialFailed, setCredentialFailed] = (0, import_react.useState)(false);
133
+ const value = host.value;
134
+ const current = draft ?? createDraft(value, host.revision, PROVIDERS);
135
+ const provider = selectedProvider(current, PROVIDERS);
136
+ const available = host.status === "ready";
137
+ const writable = available && host.writable && host.revision !== void 0;
138
+ const disabled = !writable || saving;
139
+ const changed = draftOperations(current, PROVIDERS).length > 0 || Object.values(current.secrets).some((text) => text.trim());
140
+ const errors = {};
141
+ for (const definition of PROVIDERS) {
142
+ if (definition.id !== "openai") continue;
143
+ const options = current.settings.providers[definition.id];
144
+ errors[definition.id] = {
145
+ endpoint: validateOpenAIOptions({ endpoint: options.endpoint, model: "valid" }),
146
+ model: validateOpenAIOptions({ endpoint: "https://api.openai.com/v1/alpha/search", model: options.model })
147
+ };
148
+ }
149
+ const invalid = !provider || Object.values(errors).some((fields) => Object.values(fields).some(Boolean));
150
+ const credential = provider?.credentialRef ? credentials?.[provider.credentialRef] : void 0;
151
+ const secretBlocked = PROVIDERS.some((definition) => current.secrets[definition.id]?.trim() && (!definition.credentialRef || !credentials?.[definition.credentialRef]?.writable));
152
+ const clearFeedback = () => {
153
+ setFeedback(void 0);
154
+ };
155
+ const discard = () => {
156
+ if (!saving) {
157
+ setDraft(void 0);
158
+ clearFeedback();
159
+ }
160
+ };
161
+ const refreshCredentials = async () => {
162
+ setCredentialLoading(true);
163
+ setCredentialFailed(false);
164
+ try {
165
+ const result = await props.describeCredentials();
166
+ setCredentials(result);
167
+ setCredentialFailed(result === void 0);
168
+ } catch {
169
+ setCredentials(void 0);
170
+ setCredentialFailed(true);
171
+ } finally {
172
+ setCredentialLoading(false);
173
+ }
174
+ };
175
+ (0, import_react.useEffect)(() => {
176
+ void refreshCredentials();
177
+ }, [props.describeCredentials]);
178
+ const save = async () => {
179
+ if (saving || !writable || !changed || invalid || secretBlocked) return;
180
+ setSaving(true);
181
+ clearFeedback();
182
+ const result = await saveDraft(current, PROVIDERS, props.operations);
183
+ setDraft(result.failure ? result.draft : void 0);
184
+ setFeedback(result.failure === "settings" ? { error: true, text: "\u641C\u7D22\u8BBE\u7F6E\u672A\u786E\u8BA4\u4FDD\u5B58\uFF0C\u5BC6\u94A5\u672A\u5199\u5165\u3002\u5BBF\u4E3B\u53EF\u80FD\u5DF2\u4FEE\u6539\u914D\u7F6E\uFF1B\u8F93\u5165\u5DF2\u4FDD\u7559\uFF0C\u653E\u5F03\u4FEE\u6539\u53EF\u8F7D\u5165\u6700\u65B0\u914D\u7F6E\u540E\u91CD\u8BD5\u3002" } : result.failure === "credential" ? { error: true, text: result.credentialSaved ? "\u641C\u7D22\u8BBE\u7F6E\u548C\u90E8\u5206\u5BC6\u94A5\u5DF2\u4FDD\u5B58\uFF1B\u5176\u4F59\u5BC6\u94A5\u672A\u786E\u8BA4\u4FDD\u5B58\uFF0C\u8F93\u5165\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E\u5199\u5165\u6743\u9650\u540E\u91CD\u8BD5\u3002" : "\u641C\u7D22\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u5E76\u4F1A\u7528\u4E8E\u4E0B\u4E00\u6B21\u8BF7\u6C42\uFF1B\u5BC6\u94A5\u672A\u786E\u8BA4\u4FDD\u5B58\uFF0C\u8F93\u5165\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E\u5199\u5165\u6743\u9650\u540E\u91CD\u8BD5\u3002" } : { error: false, text: "\u5DF2\u4FDD\u5B58\uFF0C\u4E0B\u4E00\u6B21\u641C\u7D22\u8BF7\u6C42\u751F\u6548\u3002" });
185
+ setSaving(false);
186
+ if (result.credentialSaved) void refreshCredentials();
187
+ };
188
+ if (props.view === "summary") return "\u901A\u8FC7 OpenAI \u6216\u517C\u5BB9\u63A5\u53E3\u6267\u884C\u7F51\u9875\u641C\u7D22\u3002";
189
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
190
+ import_dsh_client_ui_primitives.SettingsForm,
191
+ {
192
+ labels: { ...labels, unavailable: host.status === "loading" ? "\u6B63\u5728\u8BFB\u53D6\u641C\u7D22\u914D\u7F6E\u2026" : labels.unavailable },
193
+ state: { available, writable, dirty: Boolean(changed) && writable && !secretBlocked, invalid, saving, failed: false },
194
+ onSave: () => {
195
+ void save();
196
+ },
197
+ onDiscard: discard,
198
+ children: [
199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-slot": "provider", children: [
200
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { id: "web-search-provider-label", children: "\u670D\u52A1\u63D0\u4F9B\u5546" }),
201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
202
+ import_dsh_client_ui_primitives.Menu,
203
+ {
204
+ open: menuOpen,
205
+ onClose: () => {
206
+ setMenuOpen(false);
207
+ },
208
+ items: PROVIDERS.map((definition) => ({ id: definition.id, label: definition.label })),
209
+ selectedId: current.settings.provider,
210
+ onSelect: (id) => {
211
+ setDraft({ ...current, settings: { ...current.settings, provider: id } });
212
+ clearFeedback();
213
+ setMenuOpen(false);
214
+ },
215
+ anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
216
+ import_dsh_client_ui_primitives.Button,
217
+ {
218
+ variant: "outline",
219
+ disabled,
220
+ "aria-labelledby": "web-search-provider-label web-search-provider-choice",
221
+ "aria-haspopup": "menu",
222
+ "aria-expanded": menuOpen,
223
+ onClick: () => {
224
+ setMenuOpen(!menuOpen);
225
+ },
226
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: "web-search-provider-choice", children: provider?.label ?? "\u8BF7\u9009\u62E9\u670D\u52A1\u63D0\u4F9B\u5546" })
227
+ }
228
+ )
229
+ }
230
+ )
231
+ ] }),
232
+ !provider ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", children: "\u5F53\u524D\u670D\u52A1\u63D0\u4F9B\u5546\u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u9009\u62E9\u53EF\u7528\u670D\u52A1\u3002" }) : null,
233
+ provider?.fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
234
+ import_dsh_client_ui_primitives.SettingsValueField,
235
+ {
236
+ id: `web-search-${provider.id}-${field.key}`,
237
+ label: field.label,
238
+ text: current.settings.providers[provider.id][field.key],
239
+ placeholder: field.defaultValue,
240
+ hint: field.key === "endpoint" ? "\u586B\u5199\u5B8C\u6574\u64CD\u4F5C URL\uFF0C\u76F4\u63A5\u8BF7\u6C42\u8BE5\u5730\u5740\u3002" : field.key === "model" ? "\u586B\u5199\u641C\u7D22\u670D\u52A1\u652F\u6301\u7684\u6A21\u578B ID\uFF0C\u4E0E\u804A\u5929\u6A21\u578B\u72EC\u7ACB\u3002" : void 0,
241
+ overridden: current.settings.providers[provider.id][field.key] !== field.defaultValue,
242
+ overriddenLabel: "\u81EA\u5B9A\u4E49",
243
+ resetLabel: "\u4F7F\u7528\u9ED8\u8BA4\u503C",
244
+ invalid: Boolean(errors[provider.id]?.[field.key]),
245
+ invalidLabel: errors[provider.id]?.[field.key] ?? "",
246
+ disabled,
247
+ onEdit: (text) => {
248
+ setDraft(editDraft(current, provider.id, field.key, text));
249
+ clearFeedback();
250
+ },
251
+ onReset: () => {
252
+ setDraft(editDraft(current, provider.id, field.key, field.defaultValue));
253
+ clearFeedback();
254
+ }
255
+ },
256
+ `${provider.id}-${field.key}`
257
+ )),
258
+ provider?.credentialRef ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
259
+ import_dsh_client_ui_primitives.SettingsSecretField,
260
+ {
261
+ id: `web-search-${provider.id}-key`,
262
+ label: "API \u5BC6\u94A5",
263
+ text: current.secrets[provider.id] ?? "",
264
+ configured: credential?.configured ?? false,
265
+ stateLabel: credentialLoading ? "\u6B63\u5728\u8BFB\u53D6" : credentialFailed ? "\u72B6\u6001\u672A\u77E5" : credential?.configured ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E",
266
+ hint: credentialLoading ? "\u6B63\u5728\u8BFB\u53D6\u51ED\u636E\u72B6\u6001\u2026" : credentialFailed ? "\u8BFB\u53D6\u51ED\u636E\u72B6\u6001\u5931\u8D25\uFF0C\u53EF\u91CD\u8BD5\u3002\u7559\u7A7A\u4FDD\u6301\u73B0\u6709\u5BC6\u94A5\u3002" : credential && !credential.writable ? "\u6B64\u51ED\u636E\u6765\u6E90\u4E0D\u53EF\u5199\uFF08\u5982\u542F\u52A8\u73AF\u5883\u53D8\u91CF\uFF09\uFF0C\u8BF7\u5728\u8BE5\u6765\u6E90\u4FEE\u6539\u3002" : "\u7559\u7A7A\u4FDD\u6301\u73B0\u6709\u5BC6\u94A5\u3002\u65B0\u5BC6\u94A5\u72EC\u7ACB\u4FDD\u5B58\uFF0C\u5DF2\u4FDD\u5B58\u7684\u5BC6\u94A5\u4E0D\u4F1A\u56DE\u586B\u3002",
267
+ disabled: disabled || !credential?.writable || credentialLoading,
268
+ onEdit: (text) => {
269
+ setDraft({ ...current, secrets: { ...current.secrets, [provider.id]: text } });
270
+ clearFeedback();
271
+ }
272
+ }
273
+ ) : null,
274
+ credentialFailed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "ghost", disabled: credentialLoading || saving, onClick: () => {
275
+ void refreshCredentials();
276
+ }, children: "\u91CD\u8BD5\u8BFB\u53D6\u51ED\u636E\u72B6\u6001" }) : null,
277
+ feedback ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: feedback.error ? "alert" : "status", "data-state": feedback.error ? "error" : "success", children: feedback.text }) : null,
278
+ invalid && draft ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", children: "\u8BF7\u4FEE\u6B63\u5B57\u6BB5\u9519\u8BEF\u540E\u4FDD\u5B58\uFF1B\u5176\u4ED6\u670D\u52A1\u7684\u8349\u7A3F\u4E5F\u4F1A\u4FDD\u7559\u3002" }) : null,
279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "ghost", disabled: !draft || saving, onClick: discard, children: "\u653E\u5F03\u4FEE\u6539" })
280
+ ]
281
+ }
282
+ );
283
+ }
284
+ function apply(ctx) {
285
+ const scope = ctx.configForms.get("web-search-provider");
286
+ const face = {
287
+ hooks: { searchSettings: scope },
288
+ operations: {
289
+ mutate: (ops, revision) => scope.mutate(ops, revision),
290
+ revision: () => scope.getSnapshot().revision,
291
+ storeCredential: async (ref, value) => (await ctx.remote.credentials.set(ref, value)).ok
292
+ },
293
+ describeCredentials: async () => {
294
+ const response = await ctx.remote.credentials.describe(PROVIDERS.flatMap((provider) => provider.credentialRef ? [provider.credentialRef] : []));
295
+ return response.ok ? response.value : void 0;
296
+ }
297
+ };
298
+ ctx.effect(() => ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
299
+ name: "plugins.bundle.config",
300
+ key: "@caiqy/dsh-plugin-web-search",
301
+ inject: () => face
302
+ }, SearchSettingsPage)), "web-search-provider: bundle settings");
303
+ }
304
+ return module.exports;}});
305
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/client.tsx", "../src/providers.ts", "../src/client-model.ts"],
4
+ "sourcesContent": ["import { useEffect, useState } from 'react'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { CredentialInfo } from '@deepseek-ai/dsh-api-remotes/client'\nimport type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'\nimport type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport { Button, Menu, SettingsForm, SettingsSecretField, SettingsValueField } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { PROVIDERS, validateOpenAIOptions, type SearchSettings } from './providers.js'\nimport { createDraft, draftOperations, editDraft, saveDraft, selectedProvider, type ClientDraft, type SaveOperations } from './client-model.js'\n\nexport const name = 'web-search-provider-settings'\nexport const inject = ['slots', 'configForms', 'remote', 'remote.credentials']\n\ninterface ClientFace {\n hooks: { searchSettings: ConfigForm<SearchSettings> }\n operations: SaveOperations\n describeCredentials: () => Promise<Record<string, CredentialInfo> | undefined>\n}\n\ntype PageProps = PropsRuntime<'plugins.bundle.config'> & InjectFace<ClientFace>\n\nconst labels = {\n unavailable: '\u641C\u7D22\u914D\u7F6E\u5C1A\u4E0D\u53EF\u7528\uFF0C\u8BF7\u786E\u8BA4\u63D2\u4EF6\u540E\u7AEF\u5DF2\u542F\u7528\u5E76\u4F7F\u7528\u53EF\u6301\u4E45\u5316\u7684\u672C\u673A\u8FDE\u63A5\u3002',\n readOnly: '\u5BBF\u4E3B\u914D\u7F6E\u4E3A\u53EA\u8BFB\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u641C\u7D22\u8BBE\u7F6E\u3002',\n saveFailed: '\u4FDD\u5B58\u672A\u5B8C\u6210\uFF0C\u672A\u4FDD\u5B58\u7684\u8F93\u5165\u5DF2\u4FDD\u7559\u3002',\n save: '\u4FDD\u5B58', saving: '\u6B63\u5728\u4FDD\u5B58\u2026',\n}\n\n/** The page stages provider-specific values while keeping credentials write-only. */\nfunction SearchSettingsPage(props: PageProps) {\n const host = props.useSearchSettings(snapshot => snapshot)\n const [draft, setDraft] = useState<ClientDraft>()\n const [saving, setSaving] = useState(false)\n const [feedback, setFeedback] = useState<{ error: boolean; text: string }>()\n const [menuOpen, setMenuOpen] = useState(false)\n const [credentials, setCredentials] = useState<Record<string, CredentialInfo>>()\n const [credentialLoading, setCredentialLoading] = useState(true)\n const [credentialFailed, setCredentialFailed] = useState(false)\n const value = host.value\n const current = draft ?? createDraft(value, host.revision, PROVIDERS)\n const provider = selectedProvider(current, PROVIDERS)\n const available = host.status === 'ready'\n const writable = available && host.writable && host.revision !== undefined\n const disabled = !writable || saving\n const changed = draftOperations(current, PROVIDERS).length > 0 || Object.values(current.secrets).some(text => text.trim())\n const errors: Record<string, Record<string, string | undefined>> = {}\n for (const definition of PROVIDERS) {\n if (definition.id !== 'openai') continue\n const options = current.settings.providers[definition.id]!\n errors[definition.id] = {\n endpoint: validateOpenAIOptions({ endpoint: options.endpoint, model: 'valid' }),\n model: validateOpenAIOptions({ endpoint: 'https://api.openai.com/v1/alpha/search', model: options.model }),\n }\n }\n const invalid = !provider || Object.values(errors).some(fields => Object.values(fields).some(Boolean))\n const credential = provider?.credentialRef ? credentials?.[provider.credentialRef] : undefined\n const secretBlocked = PROVIDERS.some(definition => current.secrets[definition.id]?.trim()\n && (!definition.credentialRef || !credentials?.[definition.credentialRef]?.writable))\n\n const clearFeedback = () => { setFeedback(undefined) }\n const discard = () => { if (!saving) { setDraft(undefined); clearFeedback() } }\n const refreshCredentials = async () => {\n setCredentialLoading(true)\n setCredentialFailed(false)\n try {\n const result = await props.describeCredentials()\n setCredentials(result)\n setCredentialFailed(result === undefined)\n } catch {\n setCredentials(undefined)\n setCredentialFailed(true)\n } finally { setCredentialLoading(false) }\n }\n useEffect(() => { void refreshCredentials() }, [props.describeCredentials])\n\n const save = async () => {\n if (saving || !writable || !changed || invalid || secretBlocked) return\n setSaving(true)\n clearFeedback()\n const result = await saveDraft(current, PROVIDERS, props.operations)\n setDraft(result.failure ? result.draft : undefined)\n setFeedback(result.failure === 'settings'\n ? { error: true, text: '\u641C\u7D22\u8BBE\u7F6E\u672A\u786E\u8BA4\u4FDD\u5B58\uFF0C\u5BC6\u94A5\u672A\u5199\u5165\u3002\u5BBF\u4E3B\u53EF\u80FD\u5DF2\u4FEE\u6539\u914D\u7F6E\uFF1B\u8F93\u5165\u5DF2\u4FDD\u7559\uFF0C\u653E\u5F03\u4FEE\u6539\u53EF\u8F7D\u5165\u6700\u65B0\u914D\u7F6E\u540E\u91CD\u8BD5\u3002' }\n : result.failure === 'credential'\n ? { error: true, text: result.credentialSaved\n ? '\u641C\u7D22\u8BBE\u7F6E\u548C\u90E8\u5206\u5BC6\u94A5\u5DF2\u4FDD\u5B58\uFF1B\u5176\u4F59\u5BC6\u94A5\u672A\u786E\u8BA4\u4FDD\u5B58\uFF0C\u8F93\u5165\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E\u5199\u5165\u6743\u9650\u540E\u91CD\u8BD5\u3002'\n : '\u641C\u7D22\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u5E76\u4F1A\u7528\u4E8E\u4E0B\u4E00\u6B21\u8BF7\u6C42\uFF1B\u5BC6\u94A5\u672A\u786E\u8BA4\u4FDD\u5B58\uFF0C\u8F93\u5165\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E\u5199\u5165\u6743\u9650\u540E\u91CD\u8BD5\u3002' }\n : { error: false, text: '\u5DF2\u4FDD\u5B58\uFF0C\u4E0B\u4E00\u6B21\u641C\u7D22\u8BF7\u6C42\u751F\u6548\u3002' })\n setSaving(false)\n if (result.credentialSaved) void refreshCredentials()\n }\n\n if (props.view === 'summary') return '\u901A\u8FC7 OpenAI \u6216\u517C\u5BB9\u63A5\u53E3\u6267\u884C\u7F51\u9875\u641C\u7D22\u3002'\n return (\n <SettingsForm labels={{ ...labels, unavailable: host.status === 'loading' ? '\u6B63\u5728\u8BFB\u53D6\u641C\u7D22\u914D\u7F6E\u2026' : labels.unavailable }}\n state={{ available, writable, dirty: Boolean(changed) && writable && !secretBlocked, invalid, saving, failed: false }}\n onSave={() => { void save() }} onDiscard={discard}>\n <div data-slot=\"provider\">\n <p id=\"web-search-provider-label\">\u670D\u52A1\u63D0\u4F9B\u5546</p>\n <Menu open={menuOpen} onClose={() => { setMenuOpen(false) }}\n items={PROVIDERS.map(definition => ({ id: definition.id, label: definition.label }))}\n selectedId={current.settings.provider}\n onSelect={id => { setDraft({ ...current, settings: { ...current.settings, provider: id } }); clearFeedback(); setMenuOpen(false) }}\n anchor={<Button variant=\"outline\" disabled={disabled} aria-labelledby=\"web-search-provider-label web-search-provider-choice\"\n aria-haspopup=\"menu\" aria-expanded={menuOpen} onClick={() => { setMenuOpen(!menuOpen) }}>\n <span id=\"web-search-provider-choice\">{provider?.label ?? '\u8BF7\u9009\u62E9\u670D\u52A1\u63D0\u4F9B\u5546'}</span>\n </Button>} />\n </div>\n {!provider ? <p role=\"alert\">\u5F53\u524D\u670D\u52A1\u63D0\u4F9B\u5546\u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u9009\u62E9\u53EF\u7528\u670D\u52A1\u3002</p> : null}\n {provider?.fields.map(field => (\n <SettingsValueField key={`${provider.id}-${field.key}`} id={`web-search-${provider.id}-${field.key}`}\n label={field.label} text={current.settings.providers[provider.id]![field.key]!} placeholder={field.defaultValue}\n hint={field.key === 'endpoint' ? '\u586B\u5199\u5B8C\u6574\u64CD\u4F5C URL\uFF0C\u76F4\u63A5\u8BF7\u6C42\u8BE5\u5730\u5740\u3002' : field.key === 'model' ? '\u586B\u5199\u641C\u7D22\u670D\u52A1\u652F\u6301\u7684\u6A21\u578B ID\uFF0C\u4E0E\u804A\u5929\u6A21\u578B\u72EC\u7ACB\u3002' : undefined}\n overridden={current.settings.providers[provider.id]![field.key] !== field.defaultValue}\n overriddenLabel=\"\u81EA\u5B9A\u4E49\" resetLabel=\"\u4F7F\u7528\u9ED8\u8BA4\u503C\" invalid={Boolean(errors[provider.id]?.[field.key])}\n invalidLabel={errors[provider.id]?.[field.key] ?? ''} disabled={disabled}\n onEdit={text => { setDraft(editDraft(current, provider.id, field.key, text)); clearFeedback() }}\n onReset={() => { setDraft(editDraft(current, provider.id, field.key, field.defaultValue)); clearFeedback() }} />\n ))}\n {provider?.credentialRef ? <SettingsSecretField id={`web-search-${provider.id}-key`} label=\"API \u5BC6\u94A5\"\n text={current.secrets[provider.id] ?? ''} configured={credential?.configured ?? false}\n stateLabel={credentialLoading ? '\u6B63\u5728\u8BFB\u53D6' : credentialFailed ? '\u72B6\u6001\u672A\u77E5' : credential?.configured ? '\u5DF2\u914D\u7F6E' : '\u672A\u914D\u7F6E'}\n hint={credentialLoading ? '\u6B63\u5728\u8BFB\u53D6\u51ED\u636E\u72B6\u6001\u2026' : credentialFailed ? '\u8BFB\u53D6\u51ED\u636E\u72B6\u6001\u5931\u8D25\uFF0C\u53EF\u91CD\u8BD5\u3002\u7559\u7A7A\u4FDD\u6301\u73B0\u6709\u5BC6\u94A5\u3002'\n : credential && !credential.writable ? '\u6B64\u51ED\u636E\u6765\u6E90\u4E0D\u53EF\u5199\uFF08\u5982\u542F\u52A8\u73AF\u5883\u53D8\u91CF\uFF09\uFF0C\u8BF7\u5728\u8BE5\u6765\u6E90\u4FEE\u6539\u3002'\n : '\u7559\u7A7A\u4FDD\u6301\u73B0\u6709\u5BC6\u94A5\u3002\u65B0\u5BC6\u94A5\u72EC\u7ACB\u4FDD\u5B58\uFF0C\u5DF2\u4FDD\u5B58\u7684\u5BC6\u94A5\u4E0D\u4F1A\u56DE\u586B\u3002'}\n disabled={disabled || !credential?.writable || credentialLoading}\n onEdit={text => { setDraft({ ...current, secrets: { ...current.secrets, [provider.id]: text } }); clearFeedback() }} /> : null}\n {credentialFailed ? <Button variant=\"ghost\" disabled={credentialLoading || saving} onClick={() => { void refreshCredentials() }}>\u91CD\u8BD5\u8BFB\u53D6\u51ED\u636E\u72B6\u6001</Button> : null}\n {feedback ? <p role={feedback.error ? 'alert' : 'status'} data-state={feedback.error ? 'error' : 'success'}>{feedback.text}</p> : null}\n {invalid && draft ? <p role=\"alert\">\u8BF7\u4FEE\u6B63\u5B57\u6BB5\u9519\u8BEF\u540E\u4FDD\u5B58\uFF1B\u5176\u4ED6\u670D\u52A1\u7684\u8349\u7A3F\u4E5F\u4F1A\u4FDD\u7559\u3002</p> : null}\n <Button variant=\"ghost\" disabled={!draft || saving} onClick={discard}>\u653E\u5F03\u4FEE\u6539</Button>\n </SettingsForm>\n )\n}\n\n/** Register the bundle configuration page with the Host row's reactive form. */\nexport function apply(ctx: Context): void {\n const scope = ctx.configForms.get<SearchSettings>('web-search-provider')\n const face = {\n hooks: { searchSettings: scope },\n operations: {\n mutate: (ops, revision) => scope.mutate(ops, revision),\n revision: () => scope.getSnapshot().revision,\n storeCredential: async (ref, value) => (await ctx.remote.credentials.set(ref, value)).ok,\n } satisfies SaveOperations,\n describeCredentials: async () => {\n const response = await ctx.remote.credentials.describe(PROVIDERS.flatMap(provider => provider.credentialRef ? [provider.credentialRef] : []))\n return response.ok ? response.value : undefined\n },\n }\n ctx.effect(() => ctx.slots.inject('plugins.bundle.config', () => ctx.slots.register({\n name: 'plugins.bundle.config', key: '@caiqy/dsh-plugin-web-search', inject: () => face,\n }, SearchSettingsPage)), 'web-search-provider: bundle settings')\n}\n", "export interface ProviderField {\n readonly key: string\n readonly label: string\n readonly defaultValue: string\n}\n\nexport interface ProviderDefinition {\n readonly id: string\n readonly label: string\n readonly credentialRef?: string\n readonly fields: readonly ProviderField[]\n}\n\nexport const PROVIDERS: readonly ProviderDefinition[] = [{\n id: 'openai',\n label: 'OpenAI',\n credentialRef: 'DSH_WEB_SEARCH_OPENAI_API_KEY',\n fields: [\n { key: 'endpoint', label: '\u63A5\u53E3\u5730\u5740', defaultValue: 'https://api.openai.com/v1/alpha/search' },\n { key: 'model', label: '\u641C\u7D22\u6A21\u578B', defaultValue: 'gpt-5.6-luna' },\n ],\n}]\n\nexport interface SearchSettings {\n provider: string\n providers: Record<string, Record<string, string>>\n}\n\nexport function validateOpenAIOptions(options: { endpoint?: string; model?: string }): string | undefined {\n try {\n const url = new URL(options.endpoint ?? '')\n if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash) {\n return '\u8BF7\u586B\u5199\u4E0D\u542B\u767B\u5F55\u4FE1\u606F\u548C\u7247\u6BB5\u7684 HTTP(S) \u63A5\u53E3\u5730\u5740\u3002'\n }\n } catch {\n return '\u8BF7\u586B\u5199\u5B8C\u6574\u7684 HTTP(S) \u63A5\u53E3\u5730\u5740\u3002'\n }\n if (!options.model?.trim()) return '\u8BF7\u586B\u5199\u641C\u7D22\u6A21\u578B\u3002'\n return undefined\n}\n", "import type { ProviderDefinition, SearchSettings } from './providers.js'\n\n/** A draft fences all edits with the revision at which editing began. */\nexport interface ClientDraft {\n settings: SearchSettings\n original: SearchSettings\n revision: number | undefined\n secrets: Record<string, string>\n}\n\n/** Seed only provider-defined fields; credentials never enter settings. */\nexport function createDraft(settings: SearchSettings | undefined, revision: number | undefined, providers: readonly ProviderDefinition[]): ClientDraft {\n const values = Object.fromEntries(providers.map(provider => [provider.id, Object.fromEntries(\n provider.fields.map(field => [field.key, settings?.providers[provider.id]?.[field.key] ?? field.defaultValue]),\n )]))\n const resolved = { provider: settings?.provider ?? providers[0]!.id, providers: values }\n return { settings: resolved, original: structuredClone(resolved), revision, secrets: {} }\n}\n\n/** Select metadata separately from values so providers can expose different fields. */\nexport function selectedProvider(draft: ClientDraft, providers: readonly ProviderDefinition[]): ProviderDefinition | undefined {\n return providers.find(provider => provider.id === draft.settings.provider)\n}\n\n/** Keep the other providers' drafts when switching or editing one field. */\nexport function editDraft(draft: ClientDraft, provider: string, field: string, value: string): ClientDraft {\n return { ...draft, settings: { ...draft.settings, providers: {\n ...draft.settings.providers, [provider]: { ...draft.settings.providers[provider], [field]: value },\n } } }\n}\n\n/** Path operations touch visible configuration keys without replacing unknown settings. */\nexport function draftOperations(draft: ClientDraft, providers: readonly ProviderDefinition[]) {\n const ops: { op: 'set'; path: string[]; value: string }[] = []\n if (draft.settings.provider !== draft.original.provider) ops.push({ op: 'set', path: ['provider'], value: draft.settings.provider })\n for (const provider of providers) {\n for (const field of provider.fields) {\n const value = draft.settings.providers[provider.id]![field.key]!\n if (value !== draft.original.providers[provider.id]?.[field.key]) {\n ops.push({ op: 'set', path: ['providers', provider.id, field.key], value })\n }\n }\n }\n return ops\n}\n\nexport interface SaveOperations {\n mutate: (ops: ReturnType<typeof draftOperations>, revision: number | undefined) => Promise<boolean>\n revision: () => number | undefined\n storeCredential: (ref: string, value: string) => Promise<boolean>\n}\n\nexport interface SaveOutcome {\n draft: ClientDraft\n settingsSaved: boolean\n credentialSaved: boolean\n failure?: 'settings' | 'credential'\n}\n\n/** Save settings before credentials; advance the retry baseline only for confirmed writes. */\nexport async function saveDraft(draft: ClientDraft, providers: readonly ProviderDefinition[], operations: SaveOperations): Promise<SaveOutcome> {\n let next = draft\n const ops = draftOperations(draft, providers)\n try {\n if (ops.length && !await operations.mutate(ops, draft.revision)) {\n return { draft, settingsSaved: false, credentialSaved: false, failure: 'settings' }\n }\n } catch {\n return { draft, settingsSaved: false, credentialSaved: false, failure: 'settings' }\n }\n next = { ...draft, original: structuredClone(draft.settings), revision: operations.revision(), secrets: { ...draft.secrets } }\n let credentialSaved = false\n for (const provider of providers) {\n const value = next.secrets[provider.id]?.trim()\n if (!value || !provider.credentialRef) continue\n try {\n if (!await operations.storeCredential(provider.credentialRef, value)) {\n return { draft: next, settingsSaved: true, credentialSaved, failure: 'credential' }\n }\n } catch {\n // Transport errors can contain request data; never display them beside a secret.\n return { draft: next, settingsSaved: true, credentialSaved, failure: 'credential' }\n }\n next.secrets[provider.id] = ''\n credentialSaved = true\n }\n return { draft: next, settingsSaved: true, credentialSaved }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import z from '@deepseek-ai/schemastery';
3
+ export declare const name = "web-search-provider";
4
+ export declare const inject: string[];
5
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
6
+ provider: z<"openai", "openai", "volatile-defined">;
7
+ providers: z<Schemastery.ObjectS<NoInfer<{
8
+ openai: z<Schemastery.ObjectS<NoInfer<{
9
+ endpoint: z<string, string, "volatile-defined">;
10
+ model: z<string, string, "volatile-defined">;
11
+ }>>, Schemastery.ObjectT<NoInfer<{
12
+ endpoint: z<string, string, "volatile-defined">;
13
+ model: z<string, string, "volatile-defined">;
14
+ }>>, "plain">;
15
+ }>>, Schemastery.ObjectT<NoInfer<{
16
+ openai: z<Schemastery.ObjectS<NoInfer<{
17
+ endpoint: z<string, string, "volatile-defined">;
18
+ model: z<string, string, "volatile-defined">;
19
+ }>>, Schemastery.ObjectT<NoInfer<{
20
+ endpoint: z<string, string, "volatile-defined">;
21
+ model: z<string, string, "volatile-defined">;
22
+ }>>, "plain">;
23
+ }>>, "plain">;
24
+ }>>, Schemastery.ObjectT<NoInfer<{
25
+ provider: z<"openai", "openai", "volatile-defined">;
26
+ providers: z<Schemastery.ObjectS<NoInfer<{
27
+ openai: z<Schemastery.ObjectS<NoInfer<{
28
+ endpoint: z<string, string, "volatile-defined">;
29
+ model: z<string, string, "volatile-defined">;
30
+ }>>, Schemastery.ObjectT<NoInfer<{
31
+ endpoint: z<string, string, "volatile-defined">;
32
+ model: z<string, string, "volatile-defined">;
33
+ }>>, "plain">;
34
+ }>>, Schemastery.ObjectT<NoInfer<{
35
+ openai: z<Schemastery.ObjectS<NoInfer<{
36
+ endpoint: z<string, string, "volatile-defined">;
37
+ model: z<string, string, "volatile-defined">;
38
+ }>>, Schemastery.ObjectT<NoInfer<{
39
+ endpoint: z<string, string, "volatile-defined">;
40
+ model: z<string, string, "volatile-defined">;
41
+ }>>, "plain">;
42
+ }>>, "plain">;
43
+ }>>, "plain">;
44
+ /** Contribute a provider, never a tool; each call sees current config and credentials. */
45
+ export declare function apply(ctx: Context, config: ReturnType<typeof Config>): void;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAIxC,eAAO,MAAM,IAAI,wBAAwB,CAAA;AACzC,eAAO,MAAM,MAAM,UAAyB,CAAA;AAG5C,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAQjB,CAAA;AAEF,0FAA0F;AAC1F,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,GAAG,IAAI,CAc3E"}
package/lib/index.js ADDED
@@ -0,0 +1,33 @@
1
+ import { credentialRef } from '@deepseek-ai/dsh-credentials';
2
+ import z from '@deepseek-ai/schemastery';
3
+ import { searchOpenAI } from './openai.js';
4
+ import { PROVIDERS, validateOpenAIOptions } from './providers.js';
5
+ export const name = 'web-search-provider';
6
+ export const inject = ['web', 'credentials'];
7
+ const openai = PROVIDERS[0];
8
+ export const Config = z.object({
9
+ provider: z.union(['openai']).default('openai').volatile(),
10
+ providers: z.object({
11
+ openai: z.object({
12
+ endpoint: z.string().default(openai.fields[0].defaultValue).volatile(),
13
+ model: z.string().default(openai.fields[1].defaultValue).volatile(),
14
+ }),
15
+ }),
16
+ });
17
+ /** Contribute a provider, never a tool; each call sees current config and credentials. */
18
+ export function apply(ctx, config) {
19
+ const options = () => ({
20
+ endpoint: config.providers.openai.endpoint.get(),
21
+ model: config.providers.openai.model.get(),
22
+ });
23
+ ctx.web.registerSearchProvider({
24
+ id: name,
25
+ available: () => config.provider.get() === 'openai' && validateOpenAIOptions(options()) === undefined,
26
+ async search(request, signal) {
27
+ const current = options();
28
+ const key = await ctx.credentials.resolve(credentialRef(openai.credentialRef));
29
+ return searchOpenAI(request, { ...current, apiKey: key?.value ?? '' }, signal);
30
+ },
31
+ });
32
+ }
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAE5D,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAEjE,MAAM,CAAC,MAAM,IAAI,GAAG,qBAAqB,CAAA;AACzC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,CAAA;AAE5C,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAE,CAAA;AAC5B,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAU,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE;IACnE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;QAClB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;YACf,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE;YACvE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE;SACrE,CAAC;KACH,CAAC;CACH,CAAC,CAAA;AAEF,0FAA0F;AAC1F,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,MAAiC;IACnE,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;QAChD,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;KAC3C,CAAC,CAAA;IACF,GAAG,CAAC,GAAG,CAAC,sBAAsB,CAAC;QAC7B,EAAE,EAAE,IAAI;QACR,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,QAAQ,IAAI,qBAAqB,CAAC,OAAO,EAAE,CAAC,KAAK,SAAS;QACrG,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM;YAC1B,MAAM,OAAO,GAAG,OAAO,EAAE,CAAA;YACzB,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,aAAc,CAAC,CAAC,CAAA;YAC/E,OAAO,YAAY,CAAC,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;QAChF,CAAC;KACF,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { WebSearchRequest, WebSearchResult } from '@deepseek-ai/dsh-web';
2
+ export interface OpenAIOptions {
3
+ endpoint: string;
4
+ model: string;
5
+ apiKey: string;
6
+ }
7
+ /** Decode the observed alpha-search fields; opaque encrypted output stays private. */
8
+ export declare function mapAlphaSearchResponse(payload: unknown): WebSearchResult;
9
+ /** One independent query; the host owns batching, result caps and the timeout budget. */
10
+ export declare function searchOpenAI(request: WebSearchRequest, options: OpenAIOptions, signal?: AbortSignal): Promise<WebSearchResult>;
11
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAmB,MAAM,sBAAsB,CAAA;AAG9F,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAMD,sFAAsF;AACtF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAkBxE;AAED,yFAAyF;AACzF,wBAAsB,YAAY,CAChC,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,aAAa,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,eAAe,CAAC,CAyC1B"}
package/lib/openai.js ADDED
@@ -0,0 +1,77 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { WebError } from '@deepseek-ai/dsh-web';
3
+ import { validateOpenAIOptions } from './providers.js';
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ /** Decode the observed alpha-search fields; opaque encrypted output stays private. */
8
+ export function mapAlphaSearchResponse(payload) {
9
+ if (!isRecord(payload) || typeof payload.output !== 'string'
10
+ || (payload.results !== undefined && !Array.isArray(payload.results))) {
11
+ throw new WebError('OpenAI search returned an invalid response.', 'WEB_PROVIDER_ERROR');
12
+ }
13
+ const sources = [];
14
+ for (const item of (payload.results ?? [])) {
15
+ if (!isRecord(item) || typeof item.url !== 'string')
16
+ continue;
17
+ try {
18
+ const url = new URL(item.url);
19
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password)
20
+ continue;
21
+ }
22
+ catch {
23
+ continue; // Non-web results are not citeable sources.
24
+ }
25
+ sources.push({ url: item.url, ...(typeof item.title === 'string' ? { title: item.title } : {}) });
26
+ }
27
+ const content = payload.output.replace(/\uE200cite\uE202[^\uE201]*\uE201(?:\s*\[wordlim:\s*\d+\])?/gu, '').trim();
28
+ return { content, sources, truncated: false };
29
+ }
30
+ /** One independent query; the host owns batching, result caps and the timeout budget. */
31
+ export async function searchOpenAI(request, options, signal) {
32
+ const invalid = validateOpenAIOptions(options);
33
+ if (invalid !== undefined)
34
+ throw new WebError(invalid, 'WEB_PROVIDER_ERROR');
35
+ if (!options.apiKey.trim())
36
+ throw new WebError('OpenAI search API key is not configured.', 'WEB_PROVIDER_ERROR');
37
+ try {
38
+ const response = await fetch(options.endpoint, {
39
+ method: 'POST',
40
+ redirect: 'error',
41
+ headers: { authorization: `Bearer ${options.apiKey}`, 'content-type': 'application/json', accept: 'application/json' },
42
+ body: JSON.stringify({
43
+ id: randomUUID(),
44
+ model: options.model,
45
+ input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: request.query }] }],
46
+ commands: { search_query: [{ q: request.query }] },
47
+ settings: { allowed_callers: ['direct'], external_web_access: true },
48
+ max_output_tokens: 2048,
49
+ }),
50
+ ...(signal !== undefined ? { signal } : {}),
51
+ });
52
+ if (!response.ok) {
53
+ await response.body?.cancel();
54
+ // Gateway errors may echo credentials; only the status is safe to forward.
55
+ throw new WebError(`OpenAI search API error (HTTP ${response.status}).`, 'WEB_PROVIDER_ERROR');
56
+ }
57
+ const result = mapAlphaSearchResponse(await response.json());
58
+ const redact = (text) => text.replaceAll(options.apiKey, '[REDACTED]');
59
+ return {
60
+ ...result,
61
+ ...(result.content !== undefined ? { content: redact(result.content) } : {}),
62
+ sources: result.sources.filter(source => !source.url.includes(options.apiKey)).map(source => ({
63
+ ...source, ...(source.title !== undefined ? { title: redact(source.title) } : {}),
64
+ })),
65
+ };
66
+ }
67
+ catch (error) {
68
+ if (signal?.aborted || (error instanceof Error && error.name === 'AbortError')) {
69
+ throw new WebError('OpenAI search aborted.', 'WEB_ABORTED');
70
+ }
71
+ if (error instanceof WebError)
72
+ throw error;
73
+ // No raw cause: fetch and JSON errors can contain the request or response.
74
+ throw new WebError('OpenAI search request failed or returned unreadable JSON.', 'WEB_PROVIDER_ERROR');
75
+ }
76
+ }
77
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface ProviderField {
2
+ readonly key: string;
3
+ readonly label: string;
4
+ readonly defaultValue: string;
5
+ }
6
+ export interface ProviderDefinition {
7
+ readonly id: string;
8
+ readonly label: string;
9
+ readonly credentialRef?: string;
10
+ readonly fields: readonly ProviderField[];
11
+ }
12
+ export declare const PROVIDERS: readonly ProviderDefinition[];
13
+ export interface SearchSettings {
14
+ provider: string;
15
+ providers: Record<string, Record<string, string>>;
16
+ }
17
+ export declare function validateOpenAIOptions(options: {
18
+ endpoint?: string;
19
+ model?: string;
20
+ }): string | undefined;
21
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAA;CAC1C;AAED,eAAO,MAAM,SAAS,EAAE,SAAS,kBAAkB,EAQjD,CAAA;AAEF,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAClD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAWxG"}
@@ -0,0 +1,24 @@
1
+ export const PROVIDERS = [{
2
+ id: 'openai',
3
+ label: 'OpenAI',
4
+ credentialRef: 'DSH_WEB_SEARCH_OPENAI_API_KEY',
5
+ fields: [
6
+ { key: 'endpoint', label: '接口地址', defaultValue: 'https://api.openai.com/v1/alpha/search' },
7
+ { key: 'model', label: '搜索模型', defaultValue: 'gpt-5.6-luna' },
8
+ ],
9
+ }];
10
+ export function validateOpenAIOptions(options) {
11
+ try {
12
+ const url = new URL(options.endpoint ?? '');
13
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash) {
14
+ return '请填写不含登录信息和片段的 HTTP(S) 接口地址。';
15
+ }
16
+ }
17
+ catch {
18
+ return '请填写完整的 HTTP(S) 接口地址。';
19
+ }
20
+ if (!options.model?.trim())
21
+ return '请填写搜索模型。';
22
+ return undefined;
23
+ }
24
+ //# sourceMappingURL=providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,SAAS,GAAkC,CAAC;QACvD,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,QAAQ;QACf,aAAa,EAAE,+BAA+B;QAC9C,MAAM,EAAE;YACN,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,wCAAwC,EAAE;YAC1F,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE;SAC9D;KACF,CAAC,CAAA;AAOF,MAAM,UAAU,qBAAqB,CAAC,OAA8C;IAClF,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAA;QAC3C,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5F,OAAO,6BAA6B,CAAA;QACtC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,sBAAsB,CAAA;IAC/B,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,OAAO,UAAU,CAAA;IAC7C,OAAO,SAAS,CAAA;AAClB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@caiqy/dsh-plugin-web-search",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Replace the built-in DSH web_search backend with configurable search providers; OpenAI alpha search first.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org/"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/caiqy/dsh-plugins.git",
14
+ "directory": "packages/dsh-plugin-web-search"
15
+ },
16
+ "homepage": "https://github.com/caiqy/dsh-plugins/tree/main/packages/dsh-plugin-web-search#readme",
17
+ "main": "lib/index.js",
18
+ "types": "lib/index.d.ts",
19
+ "exports": {
20
+ ".": { "types": "./lib/index.d.ts", "default": "./lib/index.js" },
21
+ "./client": { "types": "./lib/client.d.ts", "default": "./lib/client.js" },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": ["lib", "cordis.patch.yml", "README.md"],
25
+ "dsh": {
26
+ "bundle": { "patch": "./cordis.patch.yml" },
27
+ "client": {
28
+ "platform": "web",
29
+ "inject": ["@deepseek-ai/dsh-client-ui-settings", "@deepseek-ai/dsh-client-ui-plugin-manager", "@deepseek-ai/dsh-api-remotes"]
30
+ }
31
+ },
32
+ "scripts": {
33
+ "build": "tsc -p tsconfig.json && node build.mjs",
34
+ "typecheck": "tsc -p tsconfig.json --noEmit",
35
+ "test": "node --test tests/*.test.mjs",
36
+ "test:live": "node tests/live.mjs"
37
+ },
38
+ "peerDependencies": {
39
+ "@deepseek-ai/cordis": "^4.0.4",
40
+ "@deepseek-ai/dsh-web": ">=0.2.0-rc.2 <0.3.0-0",
41
+ "@deepseek-ai/dsh-credentials": ">=0.2.0-rc.2 <0.3.0-0"
42
+ },
43
+ "dependencies": { "@deepseek-ai/schemastery": "~3.18.4" },
44
+ "devDependencies": {
45
+ "@deepseek-ai/cordis": "catalog:",
46
+ "@deepseek-ai/cordis-plugin-include": "1.0.9",
47
+ "@deepseek-ai/dsh-web": "catalog:",
48
+ "@deepseek-ai/dsh-credentials": "catalog:",
49
+ "@deepseek-ai/dsh-tool-web": "catalog:",
50
+ "@deepseek-ai/dsh-tools": "catalog:",
51
+ "@deepseek-ai/dsh-system-prompt": "catalog:",
52
+ "@deepseek-ai/dsh-client-ui-settings": "catalog:",
53
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "catalog:",
54
+ "@deepseek-ai/dsh-client-ui-slots": "catalog:",
55
+ "@deepseek-ai/dsh-client-ui-renderer": "catalog:",
56
+ "@deepseek-ai/dsh-client-ui-primitives": "catalog:",
57
+ "@deepseek-ai/dsh-client-store": "catalog:",
58
+ "@deepseek-ai/dsh-api-remotes": "catalog:",
59
+ "@types/node": "catalog:",
60
+ "@types/react": "~18.3.1",
61
+ "@types/react-dom": "~18.3.0",
62
+ "typescript": "catalog:",
63
+ "react": "^18.3.1",
64
+ "react-dom": "^18.3.1",
65
+ "esbuild": "^0.25.0",
66
+ "yaml": "^2.8.1"
67
+ }
68
+ }