@dcrays/web-search 0.1.1 → 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
@@ -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",
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
@@ -24,23 +24,45 @@ function searchServiceLabel(service) {
24
24
  function record(value) {
25
25
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
26
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
+ ];
27
35
  function nonEmptyString(value) {
28
36
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
29
37
  }
30
- 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) {
31
49
  const root = record(metadata);
32
- const plugins = record(root?.plugins);
33
50
  const hardware = record(root?.hardware);
51
+ const plugins = record(root?.plugins);
34
52
  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
- }
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;
42
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");
43
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
+ }
44
66
  function requestSignal(signal, timeoutMs) {
45
67
  const timeout = AbortSignal.timeout(timeoutMs);
46
68
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
@@ -125,23 +147,26 @@ function parseResponse(text, fallbackQuery, service) {
125
147
  }
126
148
  return result;
127
149
  }
150
+ function toEndpoint(endpointUrl) {
151
+ let endpoint;
152
+ try {
153
+ endpoint = new URL(endpointUrl);
154
+ } catch (error) {
155
+ 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 });
156
+ }
157
+ if (!["http:", "https:"].includes(endpoint.protocol)) {
158
+ 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");
159
+ }
160
+ return endpoint;
161
+ }
128
162
  var WebSearchApiClient = class {
129
163
  config;
130
- endpoint;
131
164
  fetchImpl;
132
165
  readFileImpl;
133
166
  nowSeconds;
134
167
  nonce;
135
168
  constructor(config, dependencies = {}) {
136
169
  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
170
  for (const [name2, value] of [
146
171
  ["timeoutMs", config.timeoutMs],
147
172
  ["maxResponseBytes", config.maxResponseBytes],
@@ -176,9 +201,12 @@ var WebSearchApiClient = class {
176
201
  try {
177
202
  metadata = JSON.parse(await this.readFileImpl(this.config.metadataPath, "utf8"));
178
203
  } 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 });
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
+ });
180
207
  }
181
- const credentials = resolveSearchCredentials(metadata, this.config.channelId, this.config.legacyChannelId);
208
+ const endpoint = resolveSearchEndpoint(metadata);
209
+ const credentials = resolveSearchCredentials(metadata);
182
210
  const call = async (service) => {
183
211
  const timestamp = String(this.nowSeconds());
184
212
  const nonce = this.nonce();
@@ -207,7 +235,7 @@ ${nonce}`;
207
235
  const effectiveSignal = requestSignal(signal, this.config.timeoutMs);
208
236
  let response;
209
237
  try {
210
- response = await this.fetchImpl(this.endpoint, {
238
+ response = await this.fetchImpl(endpoint, {
211
239
  method: "POST",
212
240
  headers: { accept: "application/json", "content-type": "application/json" },
213
241
  body: JSON.stringify(body),
@@ -221,7 +249,9 @@ ${nonce}`;
221
249
  cause: error
222
250
  });
223
251
  }
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 });
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
+ });
225
255
  }
226
256
  const text = await responseText(response, this.config.maxResponseBytes);
227
257
  if (!response.ok) {
@@ -277,31 +307,11 @@ function createWebSearchTool(search) {
277
307
  // dist-npm/src/index.js
278
308
  var MBH_WEB_SEARCH_SERVICE_KEY = "mbhWebSearch";
279
309
 
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
310
  // dist-npm/plugin/index.js
299
311
  var name = "@dcrays/web-search";
300
312
  var inject = ["tools"];
301
313
  var Config = z.object({
302
314
  enabled: z.boolean().default(true),
303
- endpointUrl: z.string().default(""),
304
- metadataPath: z.string().default(""),
305
315
  timeoutMs: z.natural().min(1).default(3e4),
306
316
  maxResponseBytes: z.natural().min(1).default(1024 * 1024),
307
317
  defaultCount: z.natural().min(1).default(5),
@@ -316,10 +326,7 @@ var MbhWebSearchService = class extends Service {
316
326
  if (!config.enabled)
317
327
  return;
318
328
  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,
329
+ metadataPath: resolveMetadataPath(),
323
330
  timeoutMs: config.timeoutMs,
324
331
  maxResponseBytes: config.maxResponseBytes,
325
332
  defaultCount: config.defaultCount,
@@ -336,8 +343,8 @@ var MbhWebSearchService = class extends Service {
336
343
  return this.operations.search(request, signal);
337
344
  }
338
345
  };
339
- function resolveMetadataPath(configuredPath) {
340
- return configuredPath.trim() || resolve(resolveDshHome(), "mobook.json");
346
+ function resolveMetadataPath() {
347
+ return resolve(resolveDshHome(), "mobook.json");
341
348
  }
342
349
  function apply(ctx, config) {
343
350
  const service = new MbhWebSearchService(ctx, config);
@@ -345,13 +352,12 @@ function apply(ctx, config) {
345
352
  if (!service.isEnabled())
346
353
  return () => void 0;
347
354
  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);
355
+ ctx.logger(name).info("%s plugin loaded with web_search tool", name);
349
356
  return unregister;
350
357
  }, `${name} lifecycle`);
351
358
  }
352
359
  export {
353
360
  Config,
354
- DEFAULT_BUILD_ENV,
355
361
  MbhWebSearchService,
356
362
  apply,
357
363
  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
@@ -15,23 +15,46 @@ function searchServiceLabel(service) {
15
15
  function record(value) {
16
16
  return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
17
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
+ ];
18
26
  function nonEmptyString(value) {
19
27
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
20
28
  }
21
- 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) {
22
40
  const root = record(metadata);
23
- const plugins = record(root?.plugins);
24
41
  const hardware = record(root?.hardware);
42
+ const plugins = record(root?.plugins);
25
43
  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
- }
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;
33
48
  throw new WebSearchApiError('MISSING_CREDENTIALS', '搜索服务尚未准备好,请确认已登录墨宝并完成客户端初始化后重试。');
34
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
+ }
35
58
  function requestSignal(signal, timeoutMs) {
36
59
  const timeout = AbortSignal.timeout(timeoutMs);
37
60
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
@@ -118,24 +141,27 @@ function parseResponse(text, fallbackQuery, service) {
118
141
  }
119
142
  return result;
120
143
  }
144
+ function toEndpoint(endpointUrl) {
145
+ let endpoint;
146
+ try {
147
+ endpoint = new URL(endpointUrl);
148
+ }
149
+ catch (error) {
150
+ throw new WebSearchApiError('INVALID_CONFIG', '搜索服务地址配置不正确,请联系管理员检查插件配置。', { cause: error });
151
+ }
152
+ if (!['http:', 'https:'].includes(endpoint.protocol)) {
153
+ throw new WebSearchApiError('INVALID_CONFIG', '搜索服务地址仅支持 HTTP 或 HTTPS,请联系管理员检查插件配置。');
154
+ }
155
+ return endpoint;
156
+ }
121
157
  export class WebSearchApiClient {
122
158
  config;
123
- endpoint;
124
159
  fetchImpl;
125
160
  readFileImpl;
126
161
  nowSeconds;
127
162
  nonce;
128
163
  constructor(config, dependencies = {}) {
129
164
  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
165
  for (const [name, value] of [
140
166
  ['timeoutMs', config.timeoutMs],
141
167
  ['maxResponseBytes', config.maxResponseBytes],
@@ -171,9 +197,12 @@ export class WebSearchApiClient {
171
197
  metadata = JSON.parse(await this.readFileImpl(this.config.metadataPath, 'utf8'));
172
198
  }
173
199
  catch (error) {
174
- throw new WebSearchApiError('MISSING_CREDENTIALS', '暂时无法读取墨宝运行配置,请确认客户端已正常初始化后重试。', { cause: error });
200
+ throw new WebSearchApiError('MISSING_CREDENTIALS', '暂时无法读取墨宝运行配置,请确认客户端已正常初始化后重试。', {
201
+ cause: error
202
+ });
175
203
  }
176
- const credentials = resolveSearchCredentials(metadata, this.config.channelId, this.config.legacyChannelId);
204
+ const endpoint = resolveSearchEndpoint(metadata);
205
+ const credentials = resolveSearchCredentials(metadata);
177
206
  const call = async (service) => {
178
207
  const timestamp = String(this.nowSeconds());
179
208
  const nonce = this.nonce();
@@ -199,7 +228,7 @@ export class WebSearchApiClient {
199
228
  const effectiveSignal = requestSignal(signal, this.config.timeoutMs);
200
229
  let response;
201
230
  try {
202
- response = await this.fetchImpl(this.endpoint, {
231
+ response = await this.fetchImpl(endpoint, {
203
232
  method: 'POST',
204
233
  headers: { accept: 'application/json', 'content-type': 'application/json' },
205
234
  body: JSON.stringify(body),
@@ -214,7 +243,9 @@ export class WebSearchApiClient {
214
243
  cause: error
215
244
  });
216
245
  }
217
- throw new WebSearchApiError('NETWORK_ERROR', `暂时无法连接${searchServiceLabel(service)},请检查网络后重试。`, { cause: error });
246
+ throw new WebSearchApiError('NETWORK_ERROR', `暂时无法连接${searchServiceLabel(service)},请检查网络后重试。`, {
247
+ cause: error
248
+ });
218
249
  }
219
250
  const text = await responseText(response, this.config.maxResponseBytes);
220
251
  if (!response.ok) {
@@ -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';