@dcrays/web-search 0.1.2 → 0.1.3

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
@@ -2,4 +2,4 @@
2
2
 
3
3
  书灵墨宝的 DSH 联网搜索插件。加载后注册 `web_search` 工具,把模型搜索请求映射到搜索网关。
4
4
 
5
- 单一发布包同时适用于生产与测试桌面:运行时根据 `mobook.json` 中记录的环境选择搜索网关与 MBHChat 凭据通道;`endpointUrl` 可在 Cordis 插件配置中覆盖。
5
+ 单一发布包同时适用于生产与测试桌面:运行时从 `mobook.json` 根级 `servers.apiGatewayBaseUrl` `account` 读取搜索网关与签名身份。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/web-search",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "DeepSeek Harness web search plugin for Mobook",
5
5
  "type": "module",
6
6
  "main": "plugin/index.js",
package/plugin/index.d.ts CHANGED
@@ -6,8 +6,6 @@ export declare const name = "mbh-web-search";
6
6
  export declare const inject: string[];
7
7
  export interface Config {
8
8
  enabled: boolean;
9
- endpointUrl: string;
10
- metadataPath: string;
11
9
  timeoutMs: number;
12
10
  maxResponseBytes: number;
13
11
  defaultCount: number;
@@ -26,5 +24,5 @@ export declare class MbhWebSearchService extends Service {
26
24
  isEnabled(): boolean;
27
25
  search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
28
26
  }
29
- export declare function resolveMetadataPath(configuredPath: string): string;
27
+ export declare function resolveMetadataPath(): string;
30
28
  export declare function apply(ctx: Context, config: Config): void;
package/plugin/index.js CHANGED
@@ -9,25 +9,6 @@ import z from "@deepseek-ai/schemastery";
9
9
  // dist-npm/src/api-client.js
10
10
  import { createHmac, randomUUID } from "node:crypto";
11
11
  import { readFile } from "node:fs/promises";
12
-
13
- // dist-npm/src/config/env-config.js
14
- var ENV_CONFIG = {
15
- production: {
16
- channelId: "mbh-chat",
17
- legacyChannelId: "mbhchat",
18
- endpointUrl: "https://api-gateway.shuwenda.com/apikey-manage/api/search"
19
- },
20
- test: {
21
- channelId: "mbh-chat-test",
22
- legacyChannelId: "mbhchat-test",
23
- endpointUrl: "https://api-gateway.shuwenda.icu/apikey-manage/api/search"
24
- }
25
- };
26
- function resolveWebSearchEnvironment(value) {
27
- return value === "production" ? "production" : "test";
28
- }
29
-
30
- // dist-npm/src/api-client.js
31
12
  var WebSearchApiError = class extends Error {
32
13
  code;
33
14
  constructor(code, message, options) {
@@ -43,23 +24,45 @@ function searchServiceLabel(service) {
43
24
  function record(value) {
44
25
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
45
26
  }
27
+ var LEGACY_ACCOUNT_PLUGIN_KEYS = [
28
+ "mbh-chat",
29
+ "mbh-chat-test",
30
+ "mbhchat",
31
+ "mbhchat-test",
32
+ "mbh-chat-uat",
33
+ "mbhchat-uat"
34
+ ];
46
35
  function nonEmptyString(value) {
47
36
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
48
37
  }
49
- function resolveSearchCredentials(metadata, channelId, legacyChannelId) {
38
+ function credentialUserId(value) {
39
+ return typeof value === "number" && Number.isSafeInteger(value) ? String(value) : nonEmptyString(value);
40
+ }
41
+ function credentialsFrom(source, deviceId) {
42
+ const userId = credentialUserId(source?.userId);
43
+ const botToken = nonEmptyString(source?.botToken);
44
+ if (userId && botToken && deviceId)
45
+ return { userId, botToken, deviceId };
46
+ return void 0;
47
+ }
48
+ function resolveSearchCredentials(metadata) {
50
49
  const root = record(metadata);
51
- const plugins = record(root?.plugins);
52
50
  const hardware = record(root?.hardware);
51
+ const plugins = record(root?.plugins);
53
52
  const deviceId = nonEmptyString(hardware?.fingerprint);
54
- for (const plugin of [record(plugins?.[channelId]), record(plugins?.[legacyChannelId])]) {
55
- const userIdValue = plugin?.userId;
56
- const userId = typeof userIdValue === "number" && Number.isSafeInteger(userIdValue) ? String(userIdValue) : nonEmptyString(userIdValue);
57
- const botToken = nonEmptyString(plugin?.botToken);
58
- if (userId && botToken && deviceId)
59
- return { userId, botToken, deviceId };
60
- }
53
+ const credentials = credentialsFrom(record(root?.account), deviceId) ?? LEGACY_ACCOUNT_PLUGIN_KEYS.map((key) => credentialsFrom(record(plugins?.[key]), deviceId)).find(Boolean);
54
+ if (credentials)
55
+ return credentials;
61
56
  throw new WebSearchApiError("MISSING_CREDENTIALS", "\u641C\u7D22\u670D\u52A1\u5C1A\u672A\u51C6\u5907\u597D\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u767B\u5F55\u58A8\u5B9D\u5E76\u5B8C\u6210\u5BA2\u6237\u7AEF\u521D\u59CB\u5316\u540E\u91CD\u8BD5\u3002");
62
57
  }
58
+ function resolveSearchEndpoint(metadata) {
59
+ const baseUrl = nonEmptyString(record(record(metadata)?.servers)?.apiGatewayBaseUrl);
60
+ if (!baseUrl) {
61
+ throw new WebSearchApiError("INVALID_CONFIG", "\u641C\u7D22\u670D\u52A1\u5730\u5740\u5C1A\u672A\u914D\u7F6E\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u5BA2\u6237\u7AEF\u914D\u7F6E\u3002");
62
+ }
63
+ const base = toEndpoint(baseUrl);
64
+ return new URL("/apikey-manage/api/search", base);
65
+ }
63
66
  function requestSignal(signal, timeoutMs) {
64
67
  const timeout = AbortSignal.timeout(timeoutMs);
65
68
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
@@ -158,27 +161,12 @@ function toEndpoint(endpointUrl) {
158
161
  }
159
162
  var WebSearchApiClient = class {
160
163
  config;
161
- environments;
162
164
  fetchImpl;
163
165
  readFileImpl;
164
166
  nowSeconds;
165
167
  nonce;
166
168
  constructor(config, dependencies = {}) {
167
169
  this.config = config;
168
- const environments = config.environments ?? ENV_CONFIG;
169
- const override = config.endpointOverride?.trim();
170
- this.environments = {
171
- production: {
172
- endpoint: toEndpoint(override || environments.production.endpointUrl),
173
- channelId: environments.production.channelId,
174
- legacyChannelId: environments.production.legacyChannelId
175
- },
176
- test: {
177
- endpoint: toEndpoint(override || environments.test.endpointUrl),
178
- channelId: environments.test.channelId,
179
- legacyChannelId: environments.test.legacyChannelId
180
- }
181
- };
182
170
  for (const [name2, value] of [
183
171
  ["timeoutMs", config.timeoutMs],
184
172
  ["maxResponseBytes", config.maxResponseBytes],
@@ -213,11 +201,12 @@ var WebSearchApiClient = class {
213
201
  try {
214
202
  metadata = JSON.parse(await this.readFileImpl(this.config.metadataPath, "utf8"));
215
203
  } catch (error) {
216
- throw new WebSearchApiError("MISSING_CREDENTIALS", "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u58A8\u5B9D\u8FD0\u884C\u914D\u7F6E\uFF0C\u8BF7\u786E\u8BA4\u5BA2\u6237\u7AEF\u5DF2\u6B63\u5E38\u521D\u59CB\u5316\u540E\u91CD\u8BD5\u3002", { cause: error });
204
+ throw new WebSearchApiError("MISSING_CREDENTIALS", "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u58A8\u5B9D\u8FD0\u884C\u914D\u7F6E\uFF0C\u8BF7\u786E\u8BA4\u5BA2\u6237\u7AEF\u5DF2\u6B63\u5E38\u521D\u59CB\u5316\u540E\u91CD\u8BD5\u3002", {
205
+ cause: error
206
+ });
217
207
  }
218
- const environment = resolveWebSearchEnvironment(record(metadata)?.environment);
219
- const resolved = this.environments[environment];
220
- const credentials = resolveSearchCredentials(metadata, resolved.channelId, resolved.legacyChannelId);
208
+ const endpoint = resolveSearchEndpoint(metadata);
209
+ const credentials = resolveSearchCredentials(metadata);
221
210
  const call = async (service) => {
222
211
  const timestamp = String(this.nowSeconds());
223
212
  const nonce = this.nonce();
@@ -246,7 +235,7 @@ ${nonce}`;
246
235
  const effectiveSignal = requestSignal(signal, this.config.timeoutMs);
247
236
  let response;
248
237
  try {
249
- response = await this.fetchImpl(resolved.endpoint, {
238
+ response = await this.fetchImpl(endpoint, {
250
239
  method: "POST",
251
240
  headers: { accept: "application/json", "content-type": "application/json" },
252
241
  body: JSON.stringify(body),
@@ -260,7 +249,9 @@ ${nonce}`;
260
249
  cause: error
261
250
  });
262
251
  }
263
- throw new WebSearchApiError("NETWORK_ERROR", `\u6682\u65F6\u65E0\u6CD5\u8FDE\u63A5${searchServiceLabel(service)}\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002`, { cause: error });
252
+ throw new WebSearchApiError("NETWORK_ERROR", `\u6682\u65F6\u65E0\u6CD5\u8FDE\u63A5${searchServiceLabel(service)}\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002`, {
253
+ cause: error
254
+ });
264
255
  }
265
256
  const text = await responseText(response, this.config.maxResponseBytes);
266
257
  if (!response.ok) {
@@ -321,8 +312,6 @@ var name = "@dcrays/web-search";
321
312
  var inject = ["tools"];
322
313
  var Config = z.object({
323
314
  enabled: z.boolean().default(true),
324
- endpointUrl: z.string().default(""),
325
- metadataPath: z.string().default(""),
326
315
  timeoutMs: z.natural().min(1).default(3e4),
327
316
  maxResponseBytes: z.natural().min(1).default(1024 * 1024),
328
317
  defaultCount: z.natural().min(1).default(5),
@@ -337,8 +326,7 @@ var MbhWebSearchService = class extends Service {
337
326
  if (!config.enabled)
338
327
  return;
339
328
  this.operations = new WebSearchApiClient({
340
- metadataPath: resolveMetadataPath(config.metadataPath),
341
- ...config.endpointUrl.trim() ? { endpointOverride: config.endpointUrl.trim() } : {},
329
+ metadataPath: resolveMetadataPath(),
342
330
  timeoutMs: config.timeoutMs,
343
331
  maxResponseBytes: config.maxResponseBytes,
344
332
  defaultCount: config.defaultCount,
@@ -355,8 +343,8 @@ var MbhWebSearchService = class extends Service {
355
343
  return this.operations.search(request, signal);
356
344
  }
357
345
  };
358
- function resolveMetadataPath(configuredPath) {
359
- return configuredPath.trim() || resolve(resolveDshHome(), "mobook.json");
346
+ function resolveMetadataPath() {
347
+ return resolve(resolveDshHome(), "mobook.json");
360
348
  }
361
349
  function apply(ctx, config) {
362
350
  const service = new MbhWebSearchService(ctx, config);
@@ -1,4 +1,3 @@
1
- import { type EnvConfig, type WebSearchEnvironment } from './config/env-config.js';
2
1
  export interface WebSearchRequest {
3
2
  query: string;
4
3
  count?: number;
@@ -26,8 +25,6 @@ export interface WebSearchOperations {
26
25
  }
27
26
  export interface WebSearchApiClientConfig {
28
27
  metadataPath: string;
29
- endpointOverride?: string;
30
- environments?: Record<WebSearchEnvironment, EnvConfig>;
31
28
  timeoutMs: number;
32
29
  maxResponseBytes: number;
33
30
  defaultCount: number;
@@ -50,10 +47,10 @@ interface SearchCredentials {
50
47
  botToken: string;
51
48
  deviceId: string;
52
49
  }
53
- export declare function resolveSearchCredentials(metadata: unknown, channelId: string, legacyChannelId: string): SearchCredentials;
50
+ export declare function resolveSearchCredentials(metadata: unknown): SearchCredentials;
51
+ export declare function resolveSearchEndpoint(metadata: unknown): URL;
54
52
  export declare class WebSearchApiClient implements WebSearchOperations {
55
53
  private readonly config;
56
- private readonly environments;
57
54
  private readonly fetchImpl;
58
55
  private readonly readFileImpl;
59
56
  private readonly nowSeconds;
package/src/api-client.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { createHmac, randomUUID } from 'node:crypto';
2
2
  import { readFile } from 'node:fs/promises';
3
- import { ENV_CONFIG, resolveWebSearchEnvironment } from './config/env-config.js';
4
3
  export class WebSearchApiError extends Error {
5
4
  code;
6
5
  constructor(code, message, options) {
@@ -16,23 +15,46 @@ function searchServiceLabel(service) {
16
15
  function record(value) {
17
16
  return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
18
17
  }
18
+ const LEGACY_ACCOUNT_PLUGIN_KEYS = [
19
+ 'mbh-chat',
20
+ 'mbh-chat-test',
21
+ 'mbhchat',
22
+ 'mbhchat-test',
23
+ 'mbh-chat-uat',
24
+ 'mbhchat-uat'
25
+ ];
19
26
  function nonEmptyString(value) {
20
27
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
21
28
  }
22
- export function resolveSearchCredentials(metadata, channelId, legacyChannelId) {
29
+ function credentialUserId(value) {
30
+ return typeof value === 'number' && Number.isSafeInteger(value) ? String(value) : nonEmptyString(value);
31
+ }
32
+ function credentialsFrom(source, deviceId) {
33
+ const userId = credentialUserId(source?.userId);
34
+ const botToken = nonEmptyString(source?.botToken);
35
+ if (userId && botToken && deviceId)
36
+ return { userId, botToken, deviceId };
37
+ return undefined;
38
+ }
39
+ export function resolveSearchCredentials(metadata) {
23
40
  const root = record(metadata);
24
- const plugins = record(root?.plugins);
25
41
  const hardware = record(root?.hardware);
42
+ const plugins = record(root?.plugins);
26
43
  const deviceId = nonEmptyString(hardware?.fingerprint);
27
- for (const plugin of [record(plugins?.[channelId]), record(plugins?.[legacyChannelId])]) {
28
- const userIdValue = plugin?.userId;
29
- const userId = typeof userIdValue === 'number' && Number.isSafeInteger(userIdValue) ? String(userIdValue) : nonEmptyString(userIdValue);
30
- const botToken = nonEmptyString(plugin?.botToken);
31
- if (userId && botToken && deviceId)
32
- return { userId, botToken, deviceId };
33
- }
44
+ const credentials = credentialsFrom(record(root?.account), deviceId) ??
45
+ LEGACY_ACCOUNT_PLUGIN_KEYS.map((key) => credentialsFrom(record(plugins?.[key]), deviceId)).find(Boolean);
46
+ if (credentials)
47
+ return credentials;
34
48
  throw new WebSearchApiError('MISSING_CREDENTIALS', '搜索服务尚未准备好,请确认已登录墨宝并完成客户端初始化后重试。');
35
49
  }
50
+ export function resolveSearchEndpoint(metadata) {
51
+ const baseUrl = nonEmptyString(record(record(metadata)?.servers)?.apiGatewayBaseUrl);
52
+ if (!baseUrl) {
53
+ throw new WebSearchApiError('INVALID_CONFIG', '搜索服务地址尚未配置,请联系管理员检查客户端配置。');
54
+ }
55
+ const base = toEndpoint(baseUrl);
56
+ return new URL('/apikey-manage/api/search', base);
57
+ }
36
58
  function requestSignal(signal, timeoutMs) {
37
59
  const timeout = AbortSignal.timeout(timeoutMs);
38
60
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
@@ -134,27 +156,12 @@ function toEndpoint(endpointUrl) {
134
156
  }
135
157
  export class WebSearchApiClient {
136
158
  config;
137
- environments;
138
159
  fetchImpl;
139
160
  readFileImpl;
140
161
  nowSeconds;
141
162
  nonce;
142
163
  constructor(config, dependencies = {}) {
143
164
  this.config = config;
144
- const environments = config.environments ?? ENV_CONFIG;
145
- const override = config.endpointOverride?.trim();
146
- this.environments = {
147
- production: {
148
- endpoint: toEndpoint(override || environments.production.endpointUrl),
149
- channelId: environments.production.channelId,
150
- legacyChannelId: environments.production.legacyChannelId
151
- },
152
- test: {
153
- endpoint: toEndpoint(override || environments.test.endpointUrl),
154
- channelId: environments.test.channelId,
155
- legacyChannelId: environments.test.legacyChannelId
156
- }
157
- };
158
165
  for (const [name, value] of [
159
166
  ['timeoutMs', config.timeoutMs],
160
167
  ['maxResponseBytes', config.maxResponseBytes],
@@ -190,11 +197,12 @@ export class WebSearchApiClient {
190
197
  metadata = JSON.parse(await this.readFileImpl(this.config.metadataPath, 'utf8'));
191
198
  }
192
199
  catch (error) {
193
- throw new WebSearchApiError('MISSING_CREDENTIALS', '暂时无法读取墨宝运行配置,请确认客户端已正常初始化后重试。', { cause: error });
200
+ throw new WebSearchApiError('MISSING_CREDENTIALS', '暂时无法读取墨宝运行配置,请确认客户端已正常初始化后重试。', {
201
+ cause: error
202
+ });
194
203
  }
195
- const environment = resolveWebSearchEnvironment(record(metadata)?.environment);
196
- const resolved = this.environments[environment];
197
- const credentials = resolveSearchCredentials(metadata, resolved.channelId, resolved.legacyChannelId);
204
+ const endpoint = resolveSearchEndpoint(metadata);
205
+ const credentials = resolveSearchCredentials(metadata);
198
206
  const call = async (service) => {
199
207
  const timestamp = String(this.nowSeconds());
200
208
  const nonce = this.nonce();
@@ -220,7 +228,7 @@ export class WebSearchApiClient {
220
228
  const effectiveSignal = requestSignal(signal, this.config.timeoutMs);
221
229
  let response;
222
230
  try {
223
- response = await this.fetchImpl(resolved.endpoint, {
231
+ response = await this.fetchImpl(endpoint, {
224
232
  method: 'POST',
225
233
  headers: { accept: 'application/json', 'content-type': 'application/json' },
226
234
  body: JSON.stringify(body),
@@ -235,7 +243,9 @@ export class WebSearchApiClient {
235
243
  cause: error
236
244
  });
237
245
  }
238
- throw new WebSearchApiError('NETWORK_ERROR', `暂时无法连接${searchServiceLabel(service)},请检查网络后重试。`, { cause: error });
246
+ throw new WebSearchApiError('NETWORK_ERROR', `暂时无法连接${searchServiceLabel(service)},请检查网络后重试。`, {
247
+ cause: error
248
+ });
239
249
  }
240
250
  const text = await responseText(response, this.config.maxResponseBytes);
241
251
  if (!response.ok) {
@@ -1,15 +1 @@
1
- export type WebSearchEnvironment = 'production' | 'test';
2
- export interface EnvConfig {
3
- channelId: string;
4
- legacyChannelId: string;
5
- endpointUrl: string;
6
- }
7
1
  export declare const PACKAGE_NAME = "@dcrays/web-search";
8
- export declare const ENV_CONFIG: Record<WebSearchEnvironment, EnvConfig>;
9
- /**
10
- * Map the desktop runtime environment recorded in mobook.json to the search
11
- * environment. Development desktops share the test gateway and MBHChat channel.
12
- * Missing/unknown values fall back to test so a misconfigured desktop never
13
- * silently reaches the production gateway with test credentials.
14
- */
15
- export declare function resolveWebSearchEnvironment(value: unknown): WebSearchEnvironment;
@@ -1,22 +1 @@
1
1
  export const PACKAGE_NAME = '@dcrays/web-search';
2
- export const ENV_CONFIG = {
3
- production: {
4
- channelId: 'mbh-chat',
5
- legacyChannelId: 'mbhchat',
6
- endpointUrl: 'https://api-gateway.shuwenda.com/apikey-manage/api/search'
7
- },
8
- test: {
9
- channelId: 'mbh-chat-test',
10
- legacyChannelId: 'mbhchat-test',
11
- endpointUrl: 'https://api-gateway.shuwenda.icu/apikey-manage/api/search'
12
- }
13
- };
14
- /**
15
- * Map the desktop runtime environment recorded in mobook.json to the search
16
- * environment. Development desktops share the test gateway and MBHChat channel.
17
- * Missing/unknown values fall back to test so a misconfigured desktop never
18
- * silently reaches the production gateway with test credentials.
19
- */
20
- export function resolveWebSearchEnvironment(value) {
21
- return value === 'production' ? 'production' : 'test';
22
- }