@dcrays/web-search 0.1.1 → 0.1.3-beta.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,5 +1,5 @@
1
1
  # @dcrays/web-search
2
2
 
3
- 书灵墨宝的 DSH 联网搜索插件。加载后注册 `web_search` 工具,把模型搜索请求映射到配置的 HTTP API。
3
+ 书灵墨宝的 DSH 联网搜索插件。加载后注册 `web_search` 工具,把模型搜索请求映射到搜索网关。
4
4
 
5
- 本包对应 `mbh-chat` 环境,端点地址从 Cordis 插件配置读取。
5
+ 单一发布包同时适用于生产与测试桌面:运行时从 `mobook.json` 根级 `servers.apiGatewayBaseUrl` 和 `account` 读取搜索网关与签名身份。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dcrays/web-search",
3
- "version": "0.1.1",
4
- "description": "DeepSeek Harness web search plugin for Mobook (production)",
3
+ "version": "0.1.3-beta.0",
4
+ "description": "DeepSeek Harness web search plugin for Mobook",
5
5
  "type": "module",
6
6
  "main": "plugin/index.js",
7
7
  "types": "plugin/index.d.ts",
package/plugin/index.d.ts CHANGED
@@ -2,13 +2,10 @@ import type { Context } from '@deepseek-ai/cordis';
2
2
  import { Service } from '@deepseek-ai/cordis';
3
3
  import z from '@deepseek-ai/schemastery';
4
4
  import { type WebSearchRequest, type WebSearchResult } from '../src/index.js';
5
- export { DEFAULT_BUILD_ENV } from '../src/config/env-config.js';
6
5
  export declare const name = "mbh-web-search";
7
6
  export declare const inject: string[];
8
7
  export interface Config {
9
8
  enabled: boolean;
10
- endpointUrl: string;
11
- metadataPath: string;
12
9
  timeoutMs: number;
13
10
  maxResponseBytes: number;
14
11
  defaultCount: number;
@@ -27,5 +24,5 @@ export declare class MbhWebSearchService extends Service {
27
24
  isEnabled(): boolean;
28
25
  search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
29
26
  }
30
- export declare function resolveMetadataPath(configuredPath: string): string;
27
+ export declare function resolveMetadataPath(): string;
31
28
  export declare function apply(ctx: Context, config: Config): void;
package/plugin/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { createRequire as __mobookCreateRequire } from 'node:module'; const require = __mobookCreateRequire(import.meta.url);
2
2
 
3
- // dist-npm/plugin/index.js
3
+ // dist/plugin/index.js
4
4
  import { resolve } from "node:path";
5
5
  import { Service } from "@deepseek-ai/cordis";
6
6
  import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
7
7
  import z from "@deepseek-ai/schemastery";
8
8
 
9
- // dist-npm/src/api-client.js
9
+ // dist/src/api-client.js
10
10
  import { createHmac, randomUUID } from "node:crypto";
11
11
  import { readFile } from "node:fs/promises";
12
12
  var WebSearchApiError = class extends Error {
@@ -27,20 +27,26 @@ function record(value) {
27
27
  function nonEmptyString(value) {
28
28
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
29
29
  }
30
- function resolveSearchCredentials(metadata, channelId, legacyChannelId) {
30
+ function resolveSearchCredentials(metadata) {
31
31
  const root = record(metadata);
32
- const plugins = record(root?.plugins);
33
32
  const hardware = record(root?.hardware);
33
+ const account = record(root?.account);
34
34
  const deviceId = nonEmptyString(hardware?.fingerprint);
35
- for (const plugin of [record(plugins?.[channelId]), record(plugins?.[legacyChannelId])]) {
36
- const userIdValue = plugin?.userId;
37
- const userId = typeof userIdValue === "number" && Number.isSafeInteger(userIdValue) ? String(userIdValue) : nonEmptyString(userIdValue);
38
- const botToken = nonEmptyString(plugin?.botToken);
39
- if (userId && botToken && deviceId)
40
- return { userId, botToken, deviceId };
41
- }
35
+ const userIdValue = account?.userId;
36
+ const userId = typeof userIdValue === "number" && Number.isSafeInteger(userIdValue) ? String(userIdValue) : nonEmptyString(userIdValue);
37
+ const botToken = nonEmptyString(account?.botToken);
38
+ if (userId && botToken && deviceId)
39
+ return { userId, botToken, deviceId };
42
40
  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");
43
41
  }
42
+ function resolveSearchEndpoint(metadata) {
43
+ const baseUrl = nonEmptyString(record(record(metadata)?.servers)?.apiGatewayBaseUrl);
44
+ if (!baseUrl) {
45
+ 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");
46
+ }
47
+ const base = toEndpoint(baseUrl);
48
+ return new URL("/apikey-manage/api/search", base);
49
+ }
44
50
  function requestSignal(signal, timeoutMs) {
45
51
  const timeout = AbortSignal.timeout(timeoutMs);
46
52
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
@@ -125,23 +131,26 @@ function parseResponse(text, fallbackQuery, service) {
125
131
  }
126
132
  return result;
127
133
  }
134
+ function toEndpoint(endpointUrl) {
135
+ let endpoint;
136
+ try {
137
+ endpoint = new URL(endpointUrl);
138
+ } catch (error) {
139
+ throw new WebSearchApiError("INVALID_CONFIG", "\u641C\u7D22\u670D\u52A1\u5730\u5740\u914D\u7F6E\u4E0D\u6B63\u786E\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u63D2\u4EF6\u914D\u7F6E\u3002", { cause: error });
140
+ }
141
+ if (!["http:", "https:"].includes(endpoint.protocol)) {
142
+ throw new WebSearchApiError("INVALID_CONFIG", "\u641C\u7D22\u670D\u52A1\u5730\u5740\u4EC5\u652F\u6301 HTTP \u6216 HTTPS\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u63D2\u4EF6\u914D\u7F6E\u3002");
143
+ }
144
+ return endpoint;
145
+ }
128
146
  var WebSearchApiClient = class {
129
147
  config;
130
- endpoint;
131
148
  fetchImpl;
132
149
  readFileImpl;
133
150
  nowSeconds;
134
151
  nonce;
135
152
  constructor(config, dependencies = {}) {
136
153
  this.config = config;
137
- try {
138
- this.endpoint = new URL(config.endpointUrl);
139
- } catch (error) {
140
- throw new WebSearchApiError("INVALID_CONFIG", "\u641C\u7D22\u670D\u52A1\u5730\u5740\u914D\u7F6E\u4E0D\u6B63\u786E\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u63D2\u4EF6\u914D\u7F6E\u3002", { cause: error });
141
- }
142
- if (!["http:", "https:"].includes(this.endpoint.protocol)) {
143
- throw new WebSearchApiError("INVALID_CONFIG", "\u641C\u7D22\u670D\u52A1\u5730\u5740\u4EC5\u652F\u6301 HTTP \u6216 HTTPS\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u63D2\u4EF6\u914D\u7F6E\u3002");
144
- }
145
154
  for (const [name2, value] of [
146
155
  ["timeoutMs", config.timeoutMs],
147
156
  ["maxResponseBytes", config.maxResponseBytes],
@@ -176,9 +185,12 @@ var WebSearchApiClient = class {
176
185
  try {
177
186
  metadata = JSON.parse(await this.readFileImpl(this.config.metadataPath, "utf8"));
178
187
  } catch (error) {
179
- 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 });
188
+ 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", {
189
+ cause: error
190
+ });
180
191
  }
181
- const credentials = resolveSearchCredentials(metadata, this.config.channelId, this.config.legacyChannelId);
192
+ const endpoint = resolveSearchEndpoint(metadata);
193
+ const credentials = resolveSearchCredentials(metadata);
182
194
  const call = async (service) => {
183
195
  const timestamp = String(this.nowSeconds());
184
196
  const nonce = this.nonce();
@@ -207,7 +219,7 @@ ${nonce}`;
207
219
  const effectiveSignal = requestSignal(signal, this.config.timeoutMs);
208
220
  let response;
209
221
  try {
210
- response = await this.fetchImpl(this.endpoint, {
222
+ response = await this.fetchImpl(endpoint, {
211
223
  method: "POST",
212
224
  headers: { accept: "application/json", "content-type": "application/json" },
213
225
  body: JSON.stringify(body),
@@ -221,7 +233,9 @@ ${nonce}`;
221
233
  cause: error
222
234
  });
223
235
  }
224
- 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 });
236
+ throw new WebSearchApiError("NETWORK_ERROR", `\u6682\u65F6\u65E0\u6CD5\u8FDE\u63A5 ${searchServiceLabel(service)}\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002`, {
237
+ cause: error
238
+ });
225
239
  }
226
240
  const text = await responseText(response, this.config.maxResponseBytes);
227
241
  if (!response.ok) {
@@ -230,23 +244,23 @@ ${nonce}`;
230
244
  return parseResponse(text, query, service);
231
245
  };
232
246
  try {
233
- const tavily = await call("tavily_search");
234
- if (tavily.results.length > 0)
235
- return tavily;
247
+ const bocha = await call("bocha_search");
248
+ if (bocha.results.length > 0)
249
+ return bocha;
236
250
  } catch (error) {
237
251
  if (signal?.aborted)
238
252
  throw error;
239
253
  }
240
- return call("bocha_search");
254
+ return call("tavily_search");
241
255
  }
242
256
  };
243
257
 
244
- // dist-npm/src/tools.js
258
+ // dist/src/tools.js
245
259
  import { defineTool } from "@deepseek-ai/dsh-tools";
246
260
  function createWebSearchTool(search) {
247
261
  return defineTool({
248
262
  name: "web_search",
249
- description: "Search the public web with the configured Mobook Bocha search service. Use this for current information, source discovery, fact verification, news, weather, prices, schedules, or any question that requires internet access. Cite result URLs when answering.",
263
+ description: "Search the public web with Mobook search services. Use Bocha first and fall back to Tavily when Bocha fails or returns no results. Use this for current information, source discovery, fact verification, news, weather, prices, schedules, or any question that requires internet access. In the final answer, summarize first, then present useful sources as a numbered list with Markdown links in the form [title | siteName](url), followed by a concise snippet-based explanation. Never expose the raw JSON or internal provider fields.",
250
264
  parameters: {
251
265
  query: { type: "string", required: true, description: "A focused web search query." },
252
266
  count: { type: "integer", description: "Number of results to return. Defaults to 5; maximum 20." },
@@ -274,34 +288,14 @@ function createWebSearchTool(search) {
274
288
  });
275
289
  }
276
290
 
277
- // dist-npm/src/index.js
291
+ // dist/src/index.js
278
292
  var MBH_WEB_SEARCH_SERVICE_KEY = "mbhWebSearch";
279
293
 
280
- // dist-npm/src/config/env-config.js
281
- var ENV_CONFIG = {
282
- production: {
283
- packageName: "@dcrays/web-search",
284
- channelId: "mbh-chat",
285
- legacyChannelId: "mbhchat",
286
- endpointUrl: "https://api-gateway.shuwenda.com/apikey-manage/api/search"
287
- },
288
- test: {
289
- packageName: "@dcrays/web-search-test",
290
- channelId: "mbh-chat-test",
291
- legacyChannelId: "mbhchat-test",
292
- endpointUrl: "https://api-gateway.shuwenda.icu/apikey-manage/api/search"
293
- }
294
- };
295
- var DEFAULT_BUILD_ENV = "production";
296
- var ACTIVE_ENV_CONFIG = ENV_CONFIG[DEFAULT_BUILD_ENV];
297
-
298
- // dist-npm/plugin/index.js
294
+ // dist/plugin/index.js
299
295
  var name = "@dcrays/web-search";
300
296
  var inject = ["tools"];
301
297
  var Config = z.object({
302
298
  enabled: z.boolean().default(true),
303
- endpointUrl: z.string().default(""),
304
- metadataPath: z.string().default(""),
305
299
  timeoutMs: z.natural().min(1).default(3e4),
306
300
  maxResponseBytes: z.natural().min(1).default(1024 * 1024),
307
301
  defaultCount: z.natural().min(1).default(5),
@@ -316,10 +310,7 @@ var MbhWebSearchService = class extends Service {
316
310
  if (!config.enabled)
317
311
  return;
318
312
  this.operations = new WebSearchApiClient({
319
- endpointUrl: config.endpointUrl.trim() || ACTIVE_ENV_CONFIG.endpointUrl,
320
- metadataPath: resolveMetadataPath(config.metadataPath),
321
- channelId: ACTIVE_ENV_CONFIG.channelId,
322
- legacyChannelId: ACTIVE_ENV_CONFIG.legacyChannelId,
313
+ metadataPath: resolveMetadataPath(),
323
314
  timeoutMs: config.timeoutMs,
324
315
  maxResponseBytes: config.maxResponseBytes,
325
316
  defaultCount: config.defaultCount,
@@ -336,8 +327,8 @@ var MbhWebSearchService = class extends Service {
336
327
  return this.operations.search(request, signal);
337
328
  }
338
329
  };
339
- function resolveMetadataPath(configuredPath) {
340
- return configuredPath.trim() || resolve(resolveDshHome(), "mobook.json");
330
+ function resolveMetadataPath() {
331
+ return resolve(resolveDshHome(), "mobook.json");
341
332
  }
342
333
  function apply(ctx, config) {
343
334
  const service = new MbhWebSearchService(ctx, config);
@@ -345,13 +336,12 @@ function apply(ctx, config) {
345
336
  if (!service.isEnabled())
346
337
  return () => void 0;
347
338
  const unregister = ctx.tools.register(createWebSearchTool(service));
348
- ctx.logger(name).info("%s plugin loaded with web_search tool for %s", name, DEFAULT_BUILD_ENV);
339
+ ctx.logger(name).info("%s plugin loaded with web_search tool", name);
349
340
  return unregister;
350
341
  }, `${name} lifecycle`);
351
342
  }
352
343
  export {
353
344
  Config,
354
- DEFAULT_BUILD_ENV,
355
345
  MbhWebSearchService,
356
346
  apply,
357
347
  inject,
@@ -24,10 +24,7 @@ export interface WebSearchOperations {
24
24
  search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
25
25
  }
26
26
  export interface WebSearchApiClientConfig {
27
- endpointUrl: string;
28
27
  metadataPath: string;
29
- channelId: string;
30
- legacyChannelId: string;
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 endpoint;
57
54
  private readonly fetchImpl;
58
55
  private readonly readFileImpl;
59
56
  private readonly nowSeconds;
package/src/api-client.js CHANGED
@@ -18,20 +18,26 @@ function record(value) {
18
18
  function nonEmptyString(value) {
19
19
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
20
20
  }
21
- export function resolveSearchCredentials(metadata, channelId, legacyChannelId) {
21
+ export function resolveSearchCredentials(metadata) {
22
22
  const root = record(metadata);
23
- const plugins = record(root?.plugins);
24
23
  const hardware = record(root?.hardware);
24
+ const account = record(root?.account);
25
25
  const deviceId = nonEmptyString(hardware?.fingerprint);
26
- for (const plugin of [record(plugins?.[channelId]), record(plugins?.[legacyChannelId])]) {
27
- const userIdValue = plugin?.userId;
28
- const userId = typeof userIdValue === 'number' && Number.isSafeInteger(userIdValue) ? String(userIdValue) : nonEmptyString(userIdValue);
29
- const botToken = nonEmptyString(plugin?.botToken);
30
- if (userId && botToken && deviceId)
31
- return { userId, botToken, deviceId };
32
- }
26
+ const userIdValue = account?.userId;
27
+ const userId = typeof userIdValue === 'number' && Number.isSafeInteger(userIdValue) ? String(userIdValue) : nonEmptyString(userIdValue);
28
+ const botToken = nonEmptyString(account?.botToken);
29
+ if (userId && botToken && deviceId)
30
+ return { userId, botToken, deviceId };
33
31
  throw new WebSearchApiError('MISSING_CREDENTIALS', '搜索服务尚未准备好,请确认已登录墨宝并完成客户端初始化后重试。');
34
32
  }
33
+ export function resolveSearchEndpoint(metadata) {
34
+ const baseUrl = nonEmptyString(record(record(metadata)?.servers)?.apiGatewayBaseUrl);
35
+ if (!baseUrl) {
36
+ throw new WebSearchApiError('INVALID_CONFIG', '搜索服务地址尚未配置,请联系管理员检查客户端配置。');
37
+ }
38
+ const base = toEndpoint(baseUrl);
39
+ return new URL('/apikey-manage/api/search', base);
40
+ }
35
41
  function requestSignal(signal, timeoutMs) {
36
42
  const timeout = AbortSignal.timeout(timeoutMs);
37
43
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
@@ -118,24 +124,27 @@ function parseResponse(text, fallbackQuery, service) {
118
124
  }
119
125
  return result;
120
126
  }
127
+ function toEndpoint(endpointUrl) {
128
+ let endpoint;
129
+ try {
130
+ endpoint = new URL(endpointUrl);
131
+ }
132
+ catch (error) {
133
+ throw new WebSearchApiError('INVALID_CONFIG', '搜索服务地址配置不正确,请联系管理员检查插件配置。', { cause: error });
134
+ }
135
+ if (!['http:', 'https:'].includes(endpoint.protocol)) {
136
+ throw new WebSearchApiError('INVALID_CONFIG', '搜索服务地址仅支持 HTTP 或 HTTPS,请联系管理员检查插件配置。');
137
+ }
138
+ return endpoint;
139
+ }
121
140
  export class WebSearchApiClient {
122
141
  config;
123
- endpoint;
124
142
  fetchImpl;
125
143
  readFileImpl;
126
144
  nowSeconds;
127
145
  nonce;
128
146
  constructor(config, dependencies = {}) {
129
147
  this.config = config;
130
- try {
131
- this.endpoint = new URL(config.endpointUrl);
132
- }
133
- catch (error) {
134
- throw new WebSearchApiError('INVALID_CONFIG', '搜索服务地址配置不正确,请联系管理员检查插件配置。', { cause: error });
135
- }
136
- if (!['http:', 'https:'].includes(this.endpoint.protocol)) {
137
- throw new WebSearchApiError('INVALID_CONFIG', '搜索服务地址仅支持 HTTP 或 HTTPS,请联系管理员检查插件配置。');
138
- }
139
148
  for (const [name, value] of [
140
149
  ['timeoutMs', config.timeoutMs],
141
150
  ['maxResponseBytes', config.maxResponseBytes],
@@ -171,9 +180,12 @@ export class WebSearchApiClient {
171
180
  metadata = JSON.parse(await this.readFileImpl(this.config.metadataPath, 'utf8'));
172
181
  }
173
182
  catch (error) {
174
- throw new WebSearchApiError('MISSING_CREDENTIALS', '暂时无法读取墨宝运行配置,请确认客户端已正常初始化后重试。', { cause: error });
183
+ throw new WebSearchApiError('MISSING_CREDENTIALS', '暂时无法读取墨宝运行配置,请确认客户端已正常初始化后重试。', {
184
+ cause: error
185
+ });
175
186
  }
176
- const credentials = resolveSearchCredentials(metadata, this.config.channelId, this.config.legacyChannelId);
187
+ const endpoint = resolveSearchEndpoint(metadata);
188
+ const credentials = resolveSearchCredentials(metadata);
177
189
  const call = async (service) => {
178
190
  const timestamp = String(this.nowSeconds());
179
191
  const nonce = this.nonce();
@@ -199,7 +211,7 @@ export class WebSearchApiClient {
199
211
  const effectiveSignal = requestSignal(signal, this.config.timeoutMs);
200
212
  let response;
201
213
  try {
202
- response = await this.fetchImpl(this.endpoint, {
214
+ response = await this.fetchImpl(endpoint, {
203
215
  method: 'POST',
204
216
  headers: { accept: 'application/json', 'content-type': 'application/json' },
205
217
  body: JSON.stringify(body),
@@ -214,7 +226,9 @@ export class WebSearchApiClient {
214
226
  cause: error
215
227
  });
216
228
  }
217
- throw new WebSearchApiError('NETWORK_ERROR', `暂时无法连接${searchServiceLabel(service)},请检查网络后重试。`, { cause: error });
229
+ throw new WebSearchApiError('NETWORK_ERROR', `暂时无法连接 ${searchServiceLabel(service)},请检查网络后重试。`, {
230
+ cause: error
231
+ });
218
232
  }
219
233
  const text = await responseText(response, this.config.maxResponseBytes);
220
234
  if (!response.ok) {
@@ -223,14 +237,14 @@ export class WebSearchApiClient {
223
237
  return parseResponse(text, query, service);
224
238
  };
225
239
  try {
226
- const tavily = await call('tavily_search');
227
- if (tavily.results.length > 0)
228
- return tavily;
240
+ const bocha = await call('bocha_search');
241
+ if (bocha.results.length > 0)
242
+ return bocha;
229
243
  }
230
244
  catch (error) {
231
245
  if (signal?.aborted)
232
246
  throw error;
233
247
  }
234
- return call('bocha_search');
248
+ return call('tavily_search');
235
249
  }
236
250
  }
@@ -1,10 +1 @@
1
- export type BuildEnv = 'production' | 'test';
2
- export interface EnvConfig {
3
- packageName: string;
4
- channelId: string;
5
- legacyChannelId: string;
6
- endpointUrl: string;
7
- }
8
- export declare const ENV_CONFIG: Record<BuildEnv, EnvConfig>;
9
- export declare const DEFAULT_BUILD_ENV: BuildEnv;
10
- export declare const ACTIVE_ENV_CONFIG: EnvConfig;
1
+ export declare const PACKAGE_NAME = "@dcrays/web-search";
@@ -1,16 +1 @@
1
- export const ENV_CONFIG = {
2
- production: {
3
- packageName: '@dcrays/web-search',
4
- channelId: 'mbh-chat',
5
- legacyChannelId: 'mbhchat',
6
- endpointUrl: 'https://api-gateway.shuwenda.com/apikey-manage/api/search'
7
- },
8
- test: {
9
- packageName: '@dcrays/web-search-test',
10
- channelId: 'mbh-chat-test',
11
- legacyChannelId: 'mbhchat-test',
12
- endpointUrl: 'https://api-gateway.shuwenda.icu/apikey-manage/api/search'
13
- }
14
- };
15
- export const DEFAULT_BUILD_ENV = 'production';
16
- export const ACTIVE_ENV_CONFIG = ENV_CONFIG[DEFAULT_BUILD_ENV];
1
+ export const PACKAGE_NAME = '@dcrays/web-search';
package/src/tools.js CHANGED
@@ -2,7 +2,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools';
2
2
  export function createWebSearchTool(search) {
3
3
  return defineTool({
4
4
  name: 'web_search',
5
- description: 'Search the public web with the configured Mobook Bocha search service. Use this for current information, source discovery, fact verification, news, weather, prices, schedules, or any question that requires internet access. Cite result URLs when answering.',
5
+ description: 'Search the public web with Mobook search services. Use Bocha first and fall back to Tavily when Bocha fails or returns no results. Use this for current information, source discovery, fact verification, news, weather, prices, schedules, or any question that requires internet access. In the final answer, summarize first, then present useful sources as a numbered list with Markdown links in the form [title | siteName](url), followed by a concise snippet-based explanation. Never expose the raw JSON or internal provider fields.',
6
6
  parameters: {
7
7
  query: { type: 'string', required: true, description: 'A focused web search query.' },
8
8
  count: { type: 'integer', description: 'Number of results to return. Defaults to 5; maximum 20.' },