@klarkxy/dsh-web-search-manager 0.1.11 → 0.2.1

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.
@@ -1,4 +1,5 @@
1
1
  import { WebFetchProvider, WebSearchProvider } from "@deepseek-ai/dsh-web";
2
+ import { DshWebManagementDescriptor } from "@klarkxy/dsh-plugin-kit/contracts";
2
3
  //#region src/contracts.d.ts
3
4
  declare const WEB_SEARCH_RPC_CHANNEL = "/web-search-manager";
4
5
  type RpcResult<T = unknown> = {
@@ -13,25 +14,16 @@ type RpcResult<T = unknown> = {
13
14
  };
14
15
  };
15
16
  type ProviderKind = 'search' | 'fetch';
16
- /** Management metadata only. Execution and source vocabulary belong to ctx.web. */
17
- interface ProviderDescriptor {
18
- readonly id: string;
19
- readonly label: string;
20
- readonly description: string;
21
- readonly defaultBaseURL?: string;
22
- readonly credentialRef?: string;
23
- /** True when the key is shared with another surface. The page must not delete it. */
24
- readonly credentialShared?: boolean;
25
- /** Optional notice naming the settings surface that owns a shared credential. */
26
- readonly credentialHint?: string;
27
- readonly billing: 'request' | 'model-and-tools' | 'none';
28
- /** Optional provider-supplied billing summary. */
29
- readonly pricing?: string;
30
- /** Public HTTPS page explaining provider charges. */
31
- readonly pricingUrl?: string;
32
- /** Public HTTPS page where the user can create an API key. */
33
- readonly signupUrl?: string;
34
- }
17
+ /** Management metadata only. Field contract lives in plugin-kit; execution stays with ctx.web. */
18
+ interface ProviderDescriptor extends DshWebManagementDescriptor {}
19
+ declare const WEB_MANAGER_OWNER = "@klarkxy/dsh-web-search-manager";
20
+ /** Optional display metadata on a provider registered directly with ctx.web. */
21
+ type DiscoverableSearchProvider = WebSearchProvider & {
22
+ readonly dshWebManagement?: ProviderDescriptor;
23
+ };
24
+ type DiscoverableFetchProvider = WebFetchProvider & {
25
+ readonly dshWebManagement?: ProviderDescriptor;
26
+ };
35
27
  interface ProviderOptions {
36
28
  readonly apiKey?: string;
37
29
  readonly baseURL?: string;
@@ -67,6 +59,8 @@ declare function pickActiveSearch(order: readonly string[], configured: (id: str
67
59
  interface ProviderView extends ProviderDescriptor {
68
60
  kind: ProviderKind;
69
61
  configured: boolean;
62
+ /** True only for configuration explicitly owned by this manager bundle. */
63
+ configurationOwned?: boolean;
70
64
  baseURL?: string;
71
65
  calls: number;
72
66
  failures: number;
@@ -79,7 +73,8 @@ interface WebStatus {
79
73
  storageFailed: boolean;
80
74
  }
81
75
  declare function providerKey(kind: ProviderKind, id: string): string;
76
+ declare function safeConfigurationUrl(value: unknown): string | undefined;
82
77
  declare function validateBaseURL(value: string): string;
83
78
  //#endregion
84
- export { DEFAULT_SEARCH_ORDER, FetchProviderFactory, ProviderDescriptor, ProviderKind, ProviderOptions, ProviderView, RpcResult, SearchProviderFactory, WEB_SEARCH_RPC_CHANNEL, WebSettings, WebStatus, defaultSearchOrder, defaultSettings, migrateSearchOrder, pickActiveSearch, providerKey, resolveSearchOrder, validateBaseURL };
79
+ export { DEFAULT_SEARCH_ORDER, DiscoverableFetchProvider, DiscoverableSearchProvider, FetchProviderFactory, ProviderDescriptor, ProviderKind, ProviderOptions, ProviderView, RpcResult, SearchProviderFactory, WEB_MANAGER_OWNER, WEB_SEARCH_RPC_CHANNEL, WebSettings, WebStatus, defaultSearchOrder, defaultSettings, migrateSearchOrder, pickActiveSearch, providerKey, resolveSearchOrder, safeConfigurationUrl, validateBaseURL };
85
80
  //# sourceMappingURL=contracts.d.ts.map
package/lib/contracts.js CHANGED
@@ -1,5 +1,6 @@
1
1
  //#region src/contracts.ts
2
2
  const WEB_SEARCH_RPC_CHANNEL = "/web-search-manager";
3
+ const WEB_MANAGER_OWNER = "@klarkxy/dsh-web-search-manager";
3
4
  const DEFAULT_SEARCH_ORDER = ["ddg"];
4
5
  const defaultSettings = () => ({
5
6
  revision: 0,
@@ -30,12 +31,23 @@ function pickActiveSearch(order, configured) {
30
31
  function providerKey(kind, id) {
31
32
  return `${kind}:${id}`;
32
33
  }
34
+ function safeConfigurationUrl(value) {
35
+ if (typeof value !== "string" || !value || /[\u0000-\u0020\\]/.test(value)) return;
36
+ try {
37
+ const url = new URL(value, "https://dsh.invalid");
38
+ if (url.username || url.password) return;
39
+ if (value.startsWith("/") && !value.startsWith("//") || value.startsWith("#")) return url.origin === "https://dsh.invalid" ? value : void 0;
40
+ return /^https:\/\//i.test(value) && url.protocol === "https:" ? value : void 0;
41
+ } catch {
42
+ return;
43
+ }
44
+ }
33
45
  function validateBaseURL(value) {
34
46
  const url = new URL(value);
35
47
  if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error("供应商地址必须是 HTTPS,且不能包含用户名、密码、查询参数或片段。");
36
48
  return url.href.replace(/\/+$/, "");
37
49
  }
38
50
  //#endregion
39
- export { DEFAULT_SEARCH_ORDER, WEB_SEARCH_RPC_CHANNEL, defaultSearchOrder, defaultSettings, migrateSearchOrder, pickActiveSearch, providerKey, resolveSearchOrder, validateBaseURL };
51
+ export { DEFAULT_SEARCH_ORDER, WEB_MANAGER_OWNER, WEB_SEARCH_RPC_CHANNEL, defaultSearchOrder, defaultSettings, migrateSearchOrder, pickActiveSearch, providerKey, resolveSearchOrder, safeConfigurationUrl, validateBaseURL };
40
52
 
41
53
  //# sourceMappingURL=contracts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { WebFetchProvider, WebSearchProvider } from '@deepseek-ai/dsh-web'\n\nexport const WEB_SEARCH_RPC_CHANNEL = '/web-search-manager'\nexport type RpcResult<T = unknown> = { ok: true; value: T } | {\n ok: false; error: { code: string; message: string; details: Record<string, unknown> }\n}\nexport type ProviderKind = 'search' | 'fetch'\n/** Management metadata only. Execution and source vocabulary belong to ctx.web. */\nexport interface ProviderDescriptor {\n readonly id: string\n readonly label: string\n readonly description: string\n readonly defaultBaseURL?: string\n readonly credentialRef?: string\n /** True when the key is shared with another surface. The page must not delete it. */\n readonly credentialShared?: boolean\n /** Optional notice naming the settings surface that owns a shared credential. */\n readonly credentialHint?: string\n readonly billing: 'request' | 'model-and-tools' | 'none'\n /** Optional provider-supplied billing summary. */\n readonly pricing?: string\n /** Public HTTPS page explaining provider charges. */\n readonly pricingUrl?: string\n /** Public HTTPS page where the user can create an API key. */\n readonly signupUrl?: string\n}\nexport interface ProviderOptions {\n readonly apiKey?: string\n readonly baseURL?: string\n readonly timeoutMs: number\n readonly maxFetchChars: number\n}\nexport type SearchProviderFactory = (options: ProviderOptions) => WebSearchProvider\nexport type FetchProviderFactory = (options: ProviderOptions) => WebFetchProvider\n/** Never store credential values here. Keys are resolved by reference per call. */\nexport interface WebSettings {\n revision: number\n searchEnabled: boolean\n fetchEnabled: boolean\n searchProvider: string\n fetchProvider: string\n searchOrder: string[]\n maxResults: number\n maxQueries: number\n timeoutMs: number\n maxFetchChars: number\n endpoints: Record<string, string>\n}\nexport const DEFAULT_SEARCH_ORDER = ['ddg'] as const\n\nexport const defaultSettings = (): WebSettings => ({\n revision: 0, searchEnabled: true, fetchEnabled: true,\n searchProvider: 'ddg', fetchProvider: 'http', searchOrder: [...DEFAULT_SEARCH_ORDER],\n maxResults: 5, maxQueries: 1,\n timeoutMs: 30_000, maxFetchChars: 100_000, endpoints: {},\n})\n\n/** Keyed backends first, keyless last — same idea as OpenClaw auto-detect. */\nexport function defaultSearchOrder(ids: readonly string[], keyed: (id: string) => boolean): string[] {\n return [...ids.filter(keyed), ...ids.filter(id => !keyed(id))]\n}\n\nexport function migrateSearchOrder(settings: { searchOrder?: string[]; searchProvider?: string }): string[] {\n return settings.searchOrder ?? (settings.searchProvider ? [settings.searchProvider] : [...DEFAULT_SEARCH_ORDER])\n}\n\nexport function resolveSearchOrder(\n knownIds: readonly string[],\n settings: Pick<WebSettings, 'searchOrder' | 'searchProvider'>,\n _keyed: (id: string) => boolean,\n): string[] {\n return [...new Set(settings.searchOrder.filter(id => knownIds.includes(id)))]\n}\n\nexport function pickActiveSearch(order: readonly string[], configured: (id: string) => boolean): string {\n return order.find(configured) ?? ''\n}\nexport interface ProviderView extends ProviderDescriptor {\n kind: ProviderKind\n configured: boolean\n baseURL?: string\n calls: number\n failures: number\n}\nexport interface WebStatus {\n settings: WebSettings\n providers: ProviderView[]\n searchActive: boolean\n fetchActive: boolean\n storageFailed: boolean\n}\nexport function providerKey(kind: ProviderKind, id: string): string { return `${kind}:${id}` }\nexport function validateBaseURL(value: string): string {\n const url = new URL(value)\n if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) {\n throw new Error('供应商地址必须是 HTTPS,且不能包含用户名、密码、查询参数或片段。')\n }\n return url.href.replace(/\\/+$/, '')\n}\n"],"mappings":";AAEA,MAAa,yBAAyB;AA8CtC,MAAa,uBAAuB,CAAC,KAAK;AAE1C,MAAa,yBAAsC;CACjD,UAAU;CAAG,eAAe;CAAM,cAAc;CAChD,gBAAgB;CAAO,eAAe;CAAQ,aAAa,CAAC,GAAG,oBAAoB;CACnF,YAAY;CAAG,YAAY;CAC3B,WAAW;CAAQ,eAAe;CAAS,WAAW,CAAC;AACzD;;AAGA,SAAgB,mBAAmB,KAAwB,OAA0C;CACnG,OAAO,CAAC,GAAG,IAAI,OAAO,KAAK,GAAG,GAAG,IAAI,QAAO,OAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/D;AAEA,SAAgB,mBAAmB,UAAyE;CAC1G,OAAO,SAAS,gBAAgB,SAAS,iBAAiB,CAAC,SAAS,cAAc,IAAI,CAAC,GAAG,oBAAoB;AAChH;AAEA,SAAgB,mBACd,UACA,UACA,QACU;CACV,OAAO,CAAC,GAAG,IAAI,IAAI,SAAS,YAAY,QAAO,OAAM,SAAS,SAAS,EAAE,CAAC,CAAC,CAAC;AAC9E;AAEA,SAAgB,iBAAiB,OAA0B,YAA6C;CACtG,OAAO,MAAM,KAAK,UAAU,KAAK;AACnC;AAeA,SAAgB,YAAY,MAAoB,IAAoB;CAAE,OAAO,GAAG,KAAK,GAAG;AAAK;AAC7F,SAAgB,gBAAgB,OAAuB;CACrD,MAAM,MAAM,IAAI,IAAI,KAAK;CACzB,IAAI,IAAI,aAAa,YAAY,IAAI,YAAY,IAAI,YAAY,IAAI,UAAU,IAAI,MACjF,MAAM,IAAI,MAAM,qCAAqC;CAEvD,OAAO,IAAI,KAAK,QAAQ,QAAQ,EAAE;AACpC"}
1
+ {"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { WebFetchProvider, WebSearchProvider } from '@deepseek-ai/dsh-web'\nimport type { DshWebManagementDescriptor } from '@klarkxy/dsh-plugin-kit/contracts'\n\nexport const WEB_SEARCH_RPC_CHANNEL = '/web-search-manager'\nexport type RpcResult<T = unknown> = { ok: true; value: T } | {\n ok: false; error: { code: string; message: string; details: Record<string, unknown> }\n}\nexport type ProviderKind = 'search' | 'fetch'\n/** Management metadata only. Field contract lives in plugin-kit; execution stays with ctx.web. */\nexport interface ProviderDescriptor extends DshWebManagementDescriptor {}\nexport const WEB_MANAGER_OWNER = '@klarkxy/dsh-web-search-manager'\n/** Optional display metadata on a provider registered directly with ctx.web. */\nexport type DiscoverableSearchProvider = WebSearchProvider & { readonly dshWebManagement?: ProviderDescriptor }\nexport type DiscoverableFetchProvider = WebFetchProvider & { readonly dshWebManagement?: ProviderDescriptor }\nexport interface ProviderOptions {\n readonly apiKey?: string\n readonly baseURL?: string\n readonly timeoutMs: number\n readonly maxFetchChars: number\n}\nexport type SearchProviderFactory = (options: ProviderOptions) => WebSearchProvider\nexport type FetchProviderFactory = (options: ProviderOptions) => WebFetchProvider\n/** Never store credential values here. Keys are resolved by reference per call. */\nexport interface WebSettings {\n revision: number\n searchEnabled: boolean\n fetchEnabled: boolean\n searchProvider: string\n fetchProvider: string\n searchOrder: string[]\n maxResults: number\n maxQueries: number\n timeoutMs: number\n maxFetchChars: number\n endpoints: Record<string, string>\n}\nexport const DEFAULT_SEARCH_ORDER = ['ddg'] as const\n\nexport const defaultSettings = (): WebSettings => ({\n revision: 0, searchEnabled: true, fetchEnabled: true,\n searchProvider: 'ddg', fetchProvider: 'http', searchOrder: [...DEFAULT_SEARCH_ORDER],\n maxResults: 5, maxQueries: 1,\n timeoutMs: 30_000, maxFetchChars: 100_000, endpoints: {},\n})\n\n/** Keyed backends first, keyless last — same idea as OpenClaw auto-detect. */\nexport function defaultSearchOrder(ids: readonly string[], keyed: (id: string) => boolean): string[] {\n return [...ids.filter(keyed), ...ids.filter(id => !keyed(id))]\n}\n\nexport function migrateSearchOrder(settings: { searchOrder?: string[]; searchProvider?: string }): string[] {\n return settings.searchOrder ?? (settings.searchProvider ? [settings.searchProvider] : [...DEFAULT_SEARCH_ORDER])\n}\n\nexport function resolveSearchOrder(\n knownIds: readonly string[],\n settings: Pick<WebSettings, 'searchOrder' | 'searchProvider'>,\n _keyed: (id: string) => boolean,\n): string[] {\n return [...new Set(settings.searchOrder.filter(id => knownIds.includes(id)))]\n}\n\nexport function pickActiveSearch(order: readonly string[], configured: (id: string) => boolean): string {\n return order.find(configured) ?? ''\n}\nexport interface ProviderView extends ProviderDescriptor {\n kind: ProviderKind\n configured: boolean\n /** True only for configuration explicitly owned by this manager bundle. */\n configurationOwned?: boolean\n baseURL?: string\n calls: number\n failures: number\n}\nexport interface WebStatus {\n settings: WebSettings\n providers: ProviderView[]\n searchActive: boolean\n fetchActive: boolean\n storageFailed: boolean\n}\nexport function providerKey(kind: ProviderKind, id: string): string { return `${kind}:${id}` }\nexport function safeConfigurationUrl(value: unknown): string | undefined {\n if (typeof value !== 'string' || !value || /[\\u0000-\\u0020\\\\]/.test(value)) return\n try {\n const url = new URL(value, 'https://dsh.invalid')\n if (url.username || url.password) return\n if ((value.startsWith('/') && !value.startsWith('//')) || value.startsWith('#')) {\n return url.origin === 'https://dsh.invalid' ? value : undefined\n }\n return /^https:\\/\\//i.test(value) && url.protocol === 'https:' ? value : undefined\n } catch { return }\n}\nexport function validateBaseURL(value: string): string {\n const url = new URL(value)\n if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) {\n throw new Error('供应商地址必须是 HTTPS,且不能包含用户名、密码、查询参数或片段。')\n }\n return url.href.replace(/\\/+$/, '')\n}\n"],"mappings":";AAGA,MAAa,yBAAyB;AAOtC,MAAa,oBAAoB;AA0BjC,MAAa,uBAAuB,CAAC,KAAK;AAE1C,MAAa,yBAAsC;CACjD,UAAU;CAAG,eAAe;CAAM,cAAc;CAChD,gBAAgB;CAAO,eAAe;CAAQ,aAAa,CAAC,GAAG,oBAAoB;CACnF,YAAY;CAAG,YAAY;CAC3B,WAAW;CAAQ,eAAe;CAAS,WAAW,CAAC;AACzD;;AAGA,SAAgB,mBAAmB,KAAwB,OAA0C;CACnG,OAAO,CAAC,GAAG,IAAI,OAAO,KAAK,GAAG,GAAG,IAAI,QAAO,OAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/D;AAEA,SAAgB,mBAAmB,UAAyE;CAC1G,OAAO,SAAS,gBAAgB,SAAS,iBAAiB,CAAC,SAAS,cAAc,IAAI,CAAC,GAAG,oBAAoB;AAChH;AAEA,SAAgB,mBACd,UACA,UACA,QACU;CACV,OAAO,CAAC,GAAG,IAAI,IAAI,SAAS,YAAY,QAAO,OAAM,SAAS,SAAS,EAAE,CAAC,CAAC,CAAC;AAC9E;AAEA,SAAgB,iBAAiB,OAA0B,YAA6C;CACtG,OAAO,MAAM,KAAK,UAAU,KAAK;AACnC;AAiBA,SAAgB,YAAY,MAAoB,IAAoB;CAAE,OAAO,GAAG,KAAK,GAAG;AAAK;AAC7F,SAAgB,qBAAqB,OAAoC;CACvE,IAAI,OAAO,UAAU,YAAY,CAAC,SAAS,oBAAoB,KAAK,KAAK,GAAG;CAC5E,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,OAAO,qBAAqB;EAChD,IAAI,IAAI,YAAY,IAAI,UAAU;EAClC,IAAK,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,WAAW,IAAI,KAAM,MAAM,WAAW,GAAG,GAC5E,OAAO,IAAI,WAAW,wBAAwB,QAAQ,KAAA;EAExD,OAAO,eAAe,KAAK,KAAK,KAAK,IAAI,aAAa,WAAW,QAAQ,KAAA;CAC3E,QAAQ;EAAE;CAAO;AACnB;AACA,SAAgB,gBAAgB,OAAuB;CACrD,MAAM,MAAM,IAAI,IAAI,KAAK;CACzB,IAAI,IAAI,aAAa,YAAY,IAAI,YAAY,IAAI,YAAY,IAAI,UAAU,IAAI,MACjF,MAAM,IAAI,MAAM,qCAAqC;CAEvD,OAAO,IAAI,KAAK,QAAQ,QAAQ,EAAE;AACpC"}
package/lib/index.d.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import { FetchProviderFactory, ProviderDescriptor, ProviderOptions, SearchProviderFactory, WebSettings, WebStatus } from "./contracts.js";
2
2
  import { WebFetchProvider, WebSearchProvider } from "@deepseek-ai/dsh-web";
3
+ import { WEB_PROVIDER_AVAILABILITY_EVENT } from "@klarkxy/dsh-plugin-kit/contracts";
3
4
  import { Context } from "@deepseek-ai/cordis";
4
- //#region src/manager.d.ts
5
+ //#region src/web-registry.d.ts
5
6
  interface WebRegistry {
6
7
  registerSearchProvider(provider: WebSearchProvider): () => void;
7
8
  registerFetchProvider(provider: WebFetchProvider): () => void;
8
9
  }
10
+ //#endregion
11
+ //#region src/manager.d.ts
9
12
  interface ManagerOptions {
10
13
  web: WebRegistry;
11
14
  initial?: Omit<WebSettings, 'searchOrder'> & {
@@ -25,22 +28,29 @@ declare class WebSearchManager {
25
28
  private suspended;
26
29
  private disposed;
27
30
  private storageFailed;
31
+ private refreshRevision;
32
+ private detach;
33
+ private legacyRegistrations;
28
34
  constructor(options: ManagerOptions);
29
35
  subscribe(listener: () => void): () => void;
30
36
  private notify;
31
37
  private knownSearchIds;
32
38
  private keyed;
33
39
  private configured;
40
+ private ready;
34
41
  private searchOrder;
35
42
  private activeSearchId;
36
43
  private selected;
37
44
  status(): WebStatus;
38
45
  refresh(): Promise<WebStatus>;
39
46
  private abort;
40
- private add;
41
- private registration;
47
+ private discover;
48
+ /** @deprecated Provider plugins should register directly with ctx.web. */
42
49
  registerSearchProvider(descriptor: ProviderDescriptor, factory: SearchProviderFactory): () => void;
50
+ /** @deprecated Provider plugins should register directly with ctx.web. */
43
51
  registerFetchProvider(descriptor: ProviderDescriptor, factory: FetchProviderFactory): () => void;
52
+ private providerConfiguration;
53
+ private legacyRegistration;
44
54
  private execute;
45
55
  update(next: Omit<WebSettings, 'revision'>, expectedRevision: number): Promise<WebStatus>;
46
56
  dispose(): Promise<void>;
@@ -55,6 +65,12 @@ declare module '@deepseek-ai/cordis' {
55
65
  }
56
66
  }
57
67
  declare function apply(ctx: Context): Promise<void>;
68
+ declare module '@deepseek-ai/cordis' {
69
+ interface Events {
70
+ /** Repository extension: native providers announce a local availability change. */
71
+ [WEB_PROVIDER_AVAILABILITY_EVENT](kind: 'search' | 'fetch', id: string): void;
72
+ }
73
+ }
58
74
  //#endregion
59
75
  export { type FetchProviderFactory, type ProviderDescriptor, type ProviderOptions, type SearchProviderFactory, WebSearchManager, apply, inject, name };
60
76
  //# sourceMappingURL=index.d.ts.map