dsh-zhihu-search 2.0.0 → 2.0.2

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,7 @@
1
+ <p align="center">
2
+ <img src="icon.svg" alt="dsh-zhihu-search" width="80" height="80">
3
+ </p>
4
+
1
5
  <p align="center">
2
6
  <h1 align="center">dsh-zhihu-search</h1>
3
7
  </p>
@@ -101,16 +105,16 @@
101
105
 
102
106
  ### 前置
103
107
 
104
- - **DSH `0.1.7-alpha.1` 或更高,且低于 `0.2.0`** —— 即 [package.json](package.json) 里 `engines.dsh` 与全部 `@deepseek-ai/dsh-*` 共同声明的那条区间(两处**同形状**是硬要求:接缝缺席时配置界面会静默不出现)。
108
+ - **DSH `0.1.7-rc.2` 或更高,且低于 `0.2.0`** —— 即 [package.json](package.json) 里 `engines.dsh` 与全部 `@deepseek-ai/dsh-*` 共同声明的那条区间(两处**同形状**是硬要求:接缝缺席时配置界面会静默不出现)。
105
109
  - Node `>= 20`
106
110
 
107
111
  装宿主时**要显式指定版本线**:本家族的 `latest` 标签不可靠(多数 `@deepseek-ai/dsh-*` 包上它指向很早的版本),按默认方式装可能落在声明范围之外 —— 现查 `npm view @deepseek-ai/dsh dist-tags`。
108
112
 
109
113
  ```bash
110
- npm install -g @deepseek-ai/dsh@alpha # 本插件承诺支持的线
114
+ npm install -g @deepseek-ai/dsh@next # 本插件承诺支持的线
111
115
  ```
112
116
 
113
- 兼容性不是推断出来的:每周由 [compat.yml](.github/workflows/compat.yml) 对 `alpha`(承诺线)与 `next`(**已低于我们声明的下限,只作记录**)两条线换包实跑一遍现有测试。当前结论与红了怎么办见 [docs/PUBLISHING.md](docs/PUBLISHING.md) 的「兼容性」。
117
+ 兼容性不是推断出来的:每周由 [compat.yml](.github/workflows/compat.yml) 对 `next`(承诺线)与 `alpha`(**已低于我们声明的下限,只作记录**)两条线换包实跑一遍现有测试。当前结论与红了怎么办见 [docs/PUBLISHING.md](docs/PUBLISHING.md) 的「兼容性」。
114
118
 
115
119
  ### 从源码安装
116
120
 
package/README_en.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="icon.svg" alt="dsh-zhihu-search" width="80" height="80">
3
+ </p>
4
+
1
5
  <p align="center">
2
6
  <h1 align="center">dsh-zhihu-search</h1>
3
7
  </p>
@@ -101,16 +105,16 @@ The three sections below are each tool's full parameter set and limits. The mode
101
105
 
102
106
  ### Requirements
103
107
 
104
- - **DSH `0.1.7-alpha.1` or newer, below `0.2.0`** — the single range that [package.json](package.json) declares in both `engines.dsh` and every `@deepseek-ai/dsh-*` entry (the two must stay the same shape: a missing host seam makes the configuration UI disappear silently).
108
+ - **DSH `0.1.7-rc.2` or newer, below `0.2.0`** — the single range that [package.json](package.json) declares in both `engines.dsh` and every `@deepseek-ai/dsh-*` entry (the two must stay the same shape: a missing host seam makes the configuration UI disappear silently).
105
109
  - Node `>= 20`
106
110
 
107
111
  Install the Host from an **explicit dist-tag**: `latest` is not trustworthy across this family (on most `@deepseek-ai/dsh-*` packages it points at a much older version), so a default install can land outside the declared range — check it live with `npm view @deepseek-ai/dsh dist-tags`.
108
112
 
109
113
  ```bash
110
- npm install -g @deepseek-ai/dsh@alpha # the line this plugin commits to
114
+ npm install -g @deepseek-ai/dsh@next # the line this plugin commits to
111
115
  ```
112
116
 
113
- Compatibility is measured, not inferred: [compat.yml](.github/workflows/compat.yml) swaps the DSH packages onto the `alpha` line (the committed one) and the `next` line (now **below our declared floor**, kept as a record only) every week, and runs the existing suite. Current results and what to do when a line breaks: [docs/PUBLISHING.md](docs/PUBLISHING.md).
117
+ Compatibility is measured, not inferred: [compat.yml](.github/workflows/compat.yml) swaps the DSH packages onto the `next` line (the committed one) and the `alpha` line (now **below our declared floor**, kept as a record only) every week, and runs the existing suite. Current results and what to do when a line breaks: [docs/PUBLISHING.md](docs/PUBLISHING.md).
114
118
 
115
119
  ### From source
116
120
 
package/lib/client.js CHANGED
@@ -145,35 +145,30 @@ function asRecord(value) {
145
145
  }
146
146
  var S = {
147
147
  form: { display: "flex", flexDirection: "column" },
148
- readOnly: { margin: "12px 0 0", fontSize: 12, lineHeight: 1.5, color: "var(--dsw-alias-label-tertiary)" },
149
- field: { display: "flex", flexDirection: "column", gap: 6, padding: "12px 0" },
150
- fieldDivider: { display: "flex", flexDirection: "column", gap: 6, padding: "12px 0", borderTop: "0.5px solid var(--dsw-alias-border-l2)" },
151
- head: { display: "flex", alignItems: "center", gap: 8 },
152
- label: { flex: 1, minWidth: 0, fontSize: 13, fontWeight: 500, lineHeight: 1.5, color: "var(--dsw-alias-label-primary)" },
153
- badges: { display: "inline-flex", alignItems: "center", gap: 8 },
154
- reset: {
155
- border: "none",
156
- background: "none",
157
- padding: 0,
158
- font: "inherit",
148
+ readOnly: {
149
+ margin: "12px 0 0",
159
150
  fontSize: 12,
160
151
  lineHeight: 1.5,
161
- color: "var(--dsw-alias-label-secondary)",
162
- cursor: "pointer"
152
+ color: "var(--dsw-alias-label-tertiary)"
153
+ },
154
+ field: { display: "flex", flexDirection: "column", gap: 6, padding: "12px 0" },
155
+ fieldDivider: {
156
+ display: "flex",
157
+ flexDirection: "column",
158
+ gap: 6,
159
+ padding: "12px 0",
160
+ borderTop: "0.5px solid var(--dsw-alias-border-l2)"
163
161
  },
164
- input: {
165
- height: 34,
166
- padding: "0 12px",
167
- border: "0.5px solid var(--dsw-alias-border-l4)",
168
- borderRadius: 8,
169
- background: "var(--dsw-alias-bg-layer-3)",
170
- font: "inherit",
162
+ head: { display: "flex", alignItems: "center", gap: 8 },
163
+ label: {
164
+ flex: 1,
165
+ minWidth: 0,
171
166
  fontSize: 13,
167
+ fontWeight: 500,
172
168
  lineHeight: 1.5,
173
- color: "var(--dsw-alias-label-primary)",
174
- boxSizing: "border-box",
175
- width: "100%"
169
+ color: "var(--dsw-alias-label-primary)"
176
170
  },
171
+ badges: { display: "inline-flex", alignItems: "center", gap: 8 },
177
172
  hint: { margin: 0, fontSize: 12, lineHeight: 1.5, color: "var(--dsw-alias-label-tertiary)" },
178
173
  // 开关行:逐条对齐官方 SubagentModelSelectionCard.module.css 的 .toggleRow / .toggleLabel,
179
174
  // 右侧放原生 `Switch`(同一个 ui-primitives 包),因此外观与官方卡片一致。
@@ -188,7 +183,12 @@ var S = {
188
183
  },
189
184
  toggleLabel: { flex: 1, minWidth: 0, fontSize: 13, lineHeight: 1.5 },
190
185
  // 状态行:比说明行重一档(secondary 对 tertiary),一行说清开关当前的含义。
191
- stateNote: { margin: 0, fontSize: 12, lineHeight: 1.5, color: "var(--dsw-alias-label-secondary)" },
186
+ stateNote: {
187
+ margin: 0,
188
+ fontSize: 12,
189
+ lineHeight: 1.5,
190
+ color: "var(--dsw-alias-label-secondary)"
191
+ },
192
192
  // 说明句里的外链:只用卡片既有的语义令牌,颜色取自 ui-theme 的 --dsw-alias-link。
193
193
  // 卡片是纯内联样式、无法写 :hover,所以常驻下划线作为静态可点提示。
194
194
  link: {
@@ -204,23 +204,15 @@ var S = {
204
204
  paddingTop: 16
205
205
  },
206
206
  // 错误色用有定义的那个令牌:原生照抄来的那一个在 harness 里从未定义,写了不会生效(见 [AGENTS.md](./AGENTS.md))。
207
- failed: { flex: 1, minWidth: 0, margin: 0, fontSize: 12, lineHeight: 1.5, color: "var(--dsw-alias-state-error-primary)" },
208
- save: {
209
- appearance: "none",
210
- border: "1px solid transparent",
211
- borderRadius: 8,
212
- padding: "5px 14px",
213
- font: "inherit",
214
- fontSize: 13,
207
+ failed: {
208
+ flex: 1,
209
+ minWidth: 0,
210
+ margin: 0,
211
+ fontSize: 12,
215
212
  lineHeight: 1.5,
216
- cursor: "pointer",
217
- background: "var(--dsw-alias-label-primary)",
218
- color: "var(--dsw-alias-bg-layer-3)"
213
+ color: "var(--dsw-alias-state-error-primary)"
219
214
  }
220
215
  };
221
- function dimStyle(base, disabled) {
222
- return disabled ? { ...base, opacity: 0.4, cursor: "default" } : base;
223
- }
224
216
  function unrefTimer(timer) {
225
217
  const candidate = timer;
226
218
  if (typeof candidate === "object" && candidate !== null && "unref" in candidate && typeof candidate.unref === "function") {
@@ -268,7 +260,9 @@ function ZhihuCard({ form, store, trackSavedRef, t }) {
268
260
  try {
269
261
  const ops = [];
270
262
  if (refDirty) {
271
- ops.push(refText === "" ? { op: "unset", path: [REF_FIELD] } : { op: "set", path: [REF_FIELD], value: refText });
263
+ ops.push(
264
+ refText === "" ? { op: "unset", path: [REF_FIELD] } : { op: "set", path: [REF_FIELD], value: refText }
265
+ );
272
266
  }
273
267
  if (hideDirty) ops.push({ op: "set", path: [HIDE_FIELD], value: hideText });
274
268
  if (ops.length > 0) {
@@ -292,10 +286,9 @@ function ZhihuCard({ form, store, trackSavedRef, t }) {
292
286
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: S.badges, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: credential.configured ? "neutral" : "quiet", children: credential.configured ? t("secretConfigured") : t("secretMissing") }) })
293
287
  ] }),
294
288
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
295
- "input",
289
+ import_dsh_client_ui_primitives.Input,
296
290
  {
297
291
  id: "zhihu-access-secret",
298
- style: S.input,
299
292
  type: "password",
300
293
  autoComplete: "off",
301
294
  value: secret,
@@ -307,16 +300,7 @@ function ZhihuCard({ form, store, trackSavedRef, t }) {
307
300
  ),
308
301
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: S.hint, children: [
309
302
  t("secretHintBefore"),
310
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
311
- "a",
312
- {
313
- style: S.link,
314
- href: PROFILE_URL,
315
- target: "_blank",
316
- rel: "noopener noreferrer",
317
- children: t("secretHintLink")
318
- }
319
- ),
303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { style: S.link, href: PROFILE_URL, target: "_blank", rel: "noopener noreferrer", children: t("secretHintLink") }),
320
304
  t("secretHintAfter")
321
305
  ] }),
322
306
  credential.writable ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: S.hint, children: t("secretShadowed") })
@@ -327,10 +311,11 @@ function ZhihuCard({ form, store, trackSavedRef, t }) {
327
311
  refOverridden ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: S.badges, children: [
328
312
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "neutral", children: t("refOverridden") }),
329
313
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
330
- "button",
314
+ import_dsh_client_ui_primitives.Button,
331
315
  {
332
316
  type: "button",
333
- style: dimStyle(S.reset, disabled),
317
+ variant: "ghost",
318
+ size: "sm",
334
319
  disabled,
335
320
  onClick: () => {
336
321
  setRefDraft("");
@@ -341,10 +326,9 @@ function ZhihuCard({ form, store, trackSavedRef, t }) {
341
326
  ] }) : null
342
327
  ] }),
343
328
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
344
- "input",
329
+ import_dsh_client_ui_primitives.Input,
345
330
  {
346
331
  id: "zhihu-access-secret-ref",
347
- style: S.input,
348
332
  type: "text",
349
333
  spellCheck: false,
350
334
  value: refText,
@@ -377,10 +361,10 @@ function ZhihuCard({ form, store, trackSavedRef, t }) {
377
361
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: S.footer, children: [
378
362
  failed !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: S.failed, role: "status", children: failed }) : null,
379
363
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
380
- "button",
364
+ import_dsh_client_ui_primitives.Button,
381
365
  {
382
366
  type: "button",
383
- style: dimStyle(S.save, blocked),
367
+ variant: "primary",
384
368
  disabled: blocked,
385
369
  onClick: () => {
386
370
  void save();
@@ -397,7 +381,10 @@ function apply(ctx) {
397
381
  const trackSavedRef = (ref) => {
398
382
  savedRef = ref;
399
383
  };
400
- const store = createCredentialStore(() => remote.credentials, () => savedRef);
384
+ const store = createCredentialStore(
385
+ () => remote.credentials,
386
+ () => savedRef
387
+ );
401
388
  ctx.effect(
402
389
  () => ctx.locale.register(LOCALE_NS, ZHIHU_LOCALES),
403
390
  "zhihu-search: card dictionaries"
@@ -438,7 +425,12 @@ function apply(ctx) {
438
425
  }, "zhihu-search: configuration service probe");
439
426
  let slotProbe;
440
427
  ctx.effect(() => {
441
- slotProbe = armProbe(SLOT_MISSING_WARNING, SLOT_LATE_INFO, () => slotDeclared, () => serviceDeclared);
428
+ slotProbe = armProbe(
429
+ SLOT_MISSING_WARNING,
430
+ SLOT_LATE_INFO,
431
+ () => slotDeclared,
432
+ () => serviceDeclared
433
+ );
442
434
  return () => {
443
435
  if (slotProbe?.timer !== void 0) clearTimeout(slotProbe.timer);
444
436
  slotProbe = void 0;
package/lib/index.js CHANGED
@@ -9,9 +9,9 @@
9
9
  * 由返回的 disposer 释放。模块顶层**不持有任何可变状态** ——
10
10
  * 否则 HMR 重载后会留下两代插件共用缓存与限流桶的幽灵故障。
11
11
  */
12
- import { credentialRef, isCredentialRefName } from '@deepseek-ai/dsh-credentials';
12
+ import { credentialRef, isCredentialRefName, } from '@deepseek-ai/dsh-credentials';
13
13
  import z from '@deepseek-ai/schemastery';
14
- import { DEFAULT_STREAM_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, ZHIHU_BASE_URL, ZhihuClient } from './transport.js';
14
+ import { DEFAULT_STREAM_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, ZHIHU_BASE_URL, ZhihuClient, } from './transport.js';
15
15
  import { resolveAccessSecret as resolveAccessSecretFrom } from './credentials.js';
16
16
  import { migrateLegacySecret } from './migrate.js';
17
17
  import { createState } from './state.js';
@@ -55,7 +55,10 @@ function formatDate(seconds) {
55
55
  * 不转义会把 `### [标题](url)` 撕成非法 Markdown,模型读到的结构就错位了。
56
56
  */
57
57
  function escapeLinkText(input) {
58
- return input.replace(/[\r\n]+/g, ' ').replace(/([\[\]])/g, '\\$1').trim();
58
+ return input
59
+ .replace(/[\r\n]+/g, ' ')
60
+ .replace(/([[\]])/g, '\\$1')
61
+ .trim();
59
62
  }
60
63
  /**
61
64
  * 渲染模型可见的 Markdown。
@@ -140,7 +143,11 @@ export function renderSearch(value, context = {}) {
140
143
  */
141
144
  export function searchMetaFromValue(value) {
142
145
  return {
143
- sources: value.items.map((item) => ({ url: item.url, title: item.title, snippet: item.snippet })),
146
+ sources: value.items.map((item) => ({
147
+ url: item.url,
148
+ title: item.title,
149
+ snippet: item.snippet,
150
+ })),
144
151
  truncated: value.hasMore,
145
152
  };
146
153
  }
@@ -167,8 +174,8 @@ export function searchMetaFromResult(meta) {
167
174
  continue;
168
175
  results.push({
169
176
  url: raw.url,
170
- ...(('title' in raw && typeof raw.title === 'string') ? { title: raw.title } : {}),
171
- ...(('snippet' in raw && typeof raw.snippet === 'string') ? { snippet: raw.snippet } : {}),
177
+ ...('title' in raw && typeof raw.title === 'string' ? { title: raw.title } : {}),
178
+ ...('snippet' in raw && typeof raw.snippet === 'string' ? { snippet: raw.snippet } : {}),
172
179
  });
173
180
  }
174
181
  return { sources: results, truncated: 'truncated' in meta && meta.truncated === true };
@@ -11,9 +11,9 @@
11
11
  */
12
12
  import { defineTool } from '@deepseek-ai/dsh-tools';
13
13
  import { compileFilter } from '../utils/compiler.js';
14
- import { presentSearchCall, presentSearchResult, renderSearch, searchMetaFromValue } from '../present/search.js';
14
+ import { presentSearchCall, presentSearchResult, renderSearch, searchMetaFromValue, } from '../present/search.js';
15
15
  import { GLOBAL_SEARCH_MAX_COUNT } from '../transport.js';
16
- import { executeSearch, rawRequestedCount, resolveRequestedCount, SEARCH_OUTPUT_SCHEMA } from './search-shared.js';
16
+ import { executeSearch, rawRequestedCount, resolveRequestedCount, SEARCH_OUTPUT_SCHEMA, } from './search-shared.js';
17
17
  /** 工具名。 */
18
18
  export const ZHIHU_GLOBAL_SEARCH_TOOL = 'zhihu_global_search';
19
19
  /**
@@ -31,7 +31,14 @@ const DEFAULT_COUNT = 8;
31
31
  * 未知键会被静默丢弃 —— 模型传 `page=2` 会拿到第一页却以为翻页成功。
32
32
  * 与参数定义的一致性由 `test/tool.test.ts` 断言守着。
33
33
  */
34
- const PARAM_NAMES = ['query', 'count', 'site', 'publishedAfter', 'publishedBefore', 'searchDb'];
34
+ const PARAM_NAMES = [
35
+ 'query',
36
+ 'count',
37
+ 'site',
38
+ 'publishedAfter',
39
+ 'publishedBefore',
40
+ 'searchDb',
41
+ ];
35
42
  /** 协作式超时预算。 */
36
43
  const TIMEOUT_MS = 20_000;
37
44
  /**
@@ -50,14 +57,29 @@ export function createZhihuGlobalSearchTool(deps) {
50
57
  '结果含标题、链接、摘要、作者与时间,知乎来源另带点赞数与评论数,不含图片;没有翻页参数。',
51
58
  parameters: {
52
59
  query: { type: 'string', required: true, description: '搜索关键词。' },
53
- count: { type: 'integer', description: `返回条数,1–${String(MAX_COUNT)},默认 ${String(DEFAULT_COUNT)}。`, default: DEFAULT_COUNT },
60
+ count: {
61
+ type: 'integer',
62
+ description: `返回条数,1–${String(MAX_COUNT)},默认 ${String(DEFAULT_COUNT)}。`,
63
+ default: DEFAULT_COUNT,
64
+ },
54
65
  site: {
55
66
  type: 'string',
56
67
  description: '只搜索该域名,例如 github.com;传完整 URL 会被剥成主机名并去掉开头的 www.。域名是精确匹配(子站要单独写),不支持知乎域名。',
57
68
  },
58
- publishedAfter: { type: 'string', description: '只要该日期之后发布的内容,格式 YYYY-MM-DD。' },
59
- publishedBefore: { type: 'string', description: '只要该日期之前发布的内容,格式 YYYY-MM-DD。' },
60
- searchDb: { type: 'string', enum: ['all', 'realtime', 'static'], description: '索引库,默认 all。realtime 偏最新,static 偏长期收录。', default: 'all' },
69
+ publishedAfter: {
70
+ type: 'string',
71
+ description: '只要该日期之后发布的内容,格式 YYYY-MM-DD。',
72
+ },
73
+ publishedBefore: {
74
+ type: 'string',
75
+ description: '只要该日期之前发布的内容,格式 YYYY-MM-DD。',
76
+ },
77
+ searchDb: {
78
+ type: 'string',
79
+ enum: ['all', 'realtime', 'static'],
80
+ description: '索引库,默认 all。realtime 偏最新,static 偏长期收录。',
81
+ default: 'all',
82
+ },
61
83
  },
62
84
  output: {
63
85
  schema: SEARCH_OUTPUT_SCHEMA,
@@ -85,12 +107,17 @@ export function createZhihuGlobalSearchTool(deps) {
85
107
  toolName: ZHIHU_GLOBAL_SEARCH_TOOL,
86
108
  signal: exec.signal,
87
109
  plan: (query) => {
88
- const count = resolveRequestedCount(args.count, { max: MAX_COUNT, fallback: DEFAULT_COUNT });
110
+ const count = resolveRequestedCount(args.count, {
111
+ max: MAX_COUNT,
112
+ fallback: DEFAULT_COUNT,
113
+ });
89
114
  // 全网作用域:允许 host;编译器会拦下知乎域名并给出「请用站内搜索」的提示。
90
115
  const filter = compileFilter({
91
116
  ...(args.site === undefined ? {} : { site: args.site }),
92
117
  ...(args.publishedAfter === undefined ? {} : { publishedAfter: args.publishedAfter }),
93
- ...(args.publishedBefore === undefined ? {} : { publishedBefore: args.publishedBefore }),
118
+ ...(args.publishedBefore === undefined
119
+ ? {}
120
+ : { publishedBefore: args.publishedBefore }),
94
121
  }, 'global');
95
122
  const searchDb = args.searchDb ?? 'all';
96
123
  return {
@@ -95,10 +95,16 @@ export function projectItem(item) {
95
95
  // 上游没报点赞数时整个键省略,不兜底成 0 —— 写 0 等于告诉模型「没人赞」。
96
96
  // 注意实测结论:上游**从未省略**该字段,外站网页也有这个键、值是占位的 0。
97
97
  // 省略分支是防伪造的兜底;外站那个 0 由渲染层决定不展示(present/search.ts)。
98
- ...(typeof item.VoteUpCount === 'number' && Number.isFinite(item.VoteUpCount) ? { voteUpCount: item.VoteUpCount } : {}),
98
+ ...(typeof item.VoteUpCount === 'number' && Number.isFinite(item.VoteUpCount)
99
+ ? { voteUpCount: item.VoteUpCount }
100
+ : {}),
99
101
  // 投影规则同上:上游没报就省略,绝不兜底成 0(外站那个 0 是占位值,由渲染层决定不展示)。
100
- ...(typeof item.CommentCount === 'number' && Number.isFinite(item.CommentCount) ? { commentCount: item.CommentCount } : {}),
101
- ...(typeof item.EditTime === 'number' && Number.isFinite(item.EditTime) ? { editTime: item.EditTime } : {}),
102
+ ...(typeof item.CommentCount === 'number' && Number.isFinite(item.CommentCount)
103
+ ? { commentCount: item.CommentCount }
104
+ : {}),
105
+ ...(typeof item.EditTime === 'number' && Number.isFinite(item.EditTime)
106
+ ? { editTime: item.EditTime }
107
+ : {}),
102
108
  // ContentType 缺失时留空,不编造标签:实测站内与网页链接的平台各自返回**空串**(字段在、值为空),
103
109
  // 兜底成 'Answer'(站内)或 'Article'(全网)都会让模型把陌生网页当成知乎内容。
104
110
  contentType: typeof item.ContentType === 'string' ? item.ContentType : '',
@@ -10,9 +10,9 @@
10
10
  */
11
11
  import { defineTool } from '@deepseek-ai/dsh-tools';
12
12
  import { compileFilter, compileSortBy, SORT_FIELDS } from '../utils/compiler.js';
13
- import { presentSearchCall, presentSearchResult, renderSearch, searchMetaFromValue } from '../present/search.js';
13
+ import { presentSearchCall, presentSearchResult, renderSearch, searchMetaFromValue, } from '../present/search.js';
14
14
  import { ZHIHU_SEARCH_MAX_COUNT } from '../transport.js';
15
- import { executeSearch, rawRequestedCount, resolveRequestedCount, SEARCH_OUTPUT_SCHEMA } from './search-shared.js';
15
+ import { executeSearch, rawRequestedCount, resolveRequestedCount, SEARCH_OUTPUT_SCHEMA, } from './search-shared.js';
16
16
  /** 工具名。 */
17
17
  export const ZHIHU_SEARCH_TOOL = 'zhihu_search';
18
18
  /**
@@ -45,7 +45,9 @@ const FILTERED_CANDIDATE_COUNT = MAX_COUNT;
45
45
  * @returns 条目数不超过 `requestedCount` 的结果。
46
46
  */
47
47
  function sliceItems(value, requestedCount) {
48
- return value.items.length <= requestedCount ? value : { ...value, items: value.items.slice(0, requestedCount) };
48
+ return value.items.length <= requestedCount
49
+ ? value
50
+ : { ...value, items: value.items.slice(0, requestedCount) };
49
51
  }
50
52
  /**
51
53
  * 参数白名单。
@@ -55,7 +57,15 @@ function sliceItems(value, requestedCount) {
55
57
  * 会拿到第一页却以为翻页成功。校验放本地,schema 里不加散文。
56
58
  * 与参数定义的一致性由 `test/tool.test.ts` 断言守着,避免两处漂移。
57
59
  */
58
- const PARAM_NAMES = ['query', 'count', 'sortField', 'order', 'minValue', 'publishedAfter', 'publishedBefore'];
60
+ const PARAM_NAMES = [
61
+ 'query',
62
+ 'count',
63
+ 'sortField',
64
+ 'order',
65
+ 'minValue',
66
+ 'publishedAfter',
67
+ 'publishedBefore',
68
+ ];
59
69
  /** 协作式超时预算;超时必须早于 DSH 的外层截断,才能返回结构化错误。 */
60
70
  const TIMEOUT_MS = 15_000;
61
71
  /**
@@ -76,7 +86,11 @@ export function createZhihuSearchTool(deps) {
76
86
  // 语义化参数:模型永远不会看到 SortBy / Filter 的字符串语法。
77
87
  parameters: {
78
88
  query: { type: 'string', required: true, description: '搜索关键词,中文效果最好。' },
79
- count: { type: 'integer', description: `返回条数,1–${String(MAX_COUNT)},默认 ${String(DEFAULT_COUNT)}。`, default: DEFAULT_COUNT },
89
+ count: {
90
+ type: 'integer',
91
+ description: `返回条数,1–${String(MAX_COUNT)},默认 ${String(DEFAULT_COUNT)}。`,
92
+ default: DEFAULT_COUNT,
93
+ },
80
94
  sortField: {
81
95
  type: 'string',
82
96
  enum: SORT_FIELDS,
@@ -96,8 +110,14 @@ export function createZhihuSearchTool(deps) {
96
110
  type: 'number',
97
111
  description: '[依赖 sortField] 排序字段的下限(含)。它只筛本次检索到的候选,不是全库过滤:达标项不足时返回条数会少于 count —— 放宽下限或换关键词,不要据此断定知乎没有高赞内容。',
98
112
  },
99
- publishedAfter: { type: 'string', description: '只要该日期之后发布的内容,格式 YYYY-MM-DD。' },
100
- publishedBefore: { type: 'string', description: '只要该日期之前发布的内容,格式 YYYY-MM-DD。' },
113
+ publishedAfter: {
114
+ type: 'string',
115
+ description: '只要该日期之后发布的内容,格式 YYYY-MM-DD。',
116
+ },
117
+ publishedBefore: {
118
+ type: 'string',
119
+ description: '只要该日期之前发布的内容,格式 YYYY-MM-DD。',
120
+ },
101
121
  },
102
122
  output: {
103
123
  schema: SEARCH_OUTPUT_SCHEMA,
@@ -106,7 +126,9 @@ export function createZhihuSearchTool(deps) {
106
126
  requestedCount: rawRequestedCount(args.count, DEFAULT_COUNT),
107
127
  minValue: args.minValue,
108
128
  maxCount: MAX_COUNT,
109
- filtered: args.minValue !== undefined || args.publishedAfter !== undefined || args.publishedBefore !== undefined,
129
+ filtered: args.minValue !== undefined ||
130
+ args.publishedAfter !== undefined ||
131
+ args.publishedBefore !== undefined,
110
132
  scope: 'zhihu',
111
133
  }),
112
134
  presentationMeta: (_args, value) => searchMetaFromValue(value),
@@ -124,7 +146,10 @@ export function createZhihuSearchTool(deps) {
124
146
  toolName: ZHIHU_SEARCH_TOOL,
125
147
  signal: exec.signal,
126
148
  plan: (query) => {
127
- const requestedCount = resolveRequestedCount(args.count, { max: MAX_COUNT, fallback: DEFAULT_COUNT });
149
+ const requestedCount = resolveRequestedCount(args.count, {
150
+ max: MAX_COUNT,
151
+ fallback: DEFAULT_COUNT,
152
+ });
128
153
  const sortField = args.sortField ?? 'default';
129
154
  const order = args.order ?? 'desc';
130
155
  const minValue = args.minValue;
@@ -136,15 +161,24 @@ export function createZhihuSearchTool(deps) {
136
161
  // 站内搜索作用域:只允许 publish_time,site 会被编译器拒绝。
137
162
  const filter = compileFilter({
138
163
  ...(args.publishedAfter === undefined ? {} : { publishedAfter: args.publishedAfter }),
139
- ...(args.publishedBefore === undefined ? {} : { publishedBefore: args.publishedBefore }),
164
+ ...(args.publishedBefore === undefined
165
+ ? {}
166
+ : { publishedBefore: args.publishedBefore }),
140
167
  }, 'zhihu');
141
168
  // 有下限时把候选池取满(理由见 FILTERED_CANDIDATE_COUNT)。
142
- const poolCount = minValue === undefined ? requestedCount : Math.max(requestedCount, FILTERED_CANDIDATE_COUNT);
169
+ const poolCount = minValue === undefined
170
+ ? requestedCount
171
+ : Math.max(requestedCount, FILTERED_CANDIDATE_COUNT);
143
172
  // 缓存键必须用**归一化后**的参数,且必须用候选池大小:
144
173
  // 否则 count=3 与 count=10 会各占一个键,却发出两个内容相同的请求。
145
174
  return {
146
175
  cacheArgs: { query, count: poolCount, sortBy, filter },
147
- fetch: (signal) => deps.client.searchZhihu({ query, count: poolCount, ...(sortBy === undefined ? {} : { sortBy }), ...(filter === undefined ? {} : { filter }) }, signal),
176
+ fetch: (signal) => deps.client.searchZhihu({
177
+ query,
178
+ count: poolCount,
179
+ ...(sortBy === undefined ? {} : { sortBy }),
180
+ ...(filter === undefined ? {} : { filter }),
181
+ }, signal),
148
182
  // 缓存完整池子,返回按本次条数截断 —— 顺序不可颠倒。
149
183
  onReturn: (value) => sliceItems(value, requestedCount),
150
184
  };
@@ -45,7 +45,11 @@ export function createZhihuZhidaTool(deps) {
45
45
  '与两个搜索工具的区别:搜索返回可点开的来源列表,直答返回一段成体系的回答——要来源用搜索,要解释用直答。' +
46
46
  '答案由知乎生成,可能有误,重要结论请自行核对。',
47
47
  parameters: {
48
- question: { type: 'string', required: true, description: '要提问的问题,中文描述越具体越好。' },
48
+ question: {
49
+ type: 'string',
50
+ required: true,
51
+ description: '要提问的问题,中文描述越具体越好。',
52
+ },
49
53
  mode: {
50
54
  type: 'string',
51
55
  enum: ZHIDA_MODELS,
@@ -125,7 +129,14 @@ export function createZhihuZhidaTool(deps) {
125
129
  return { ...stored, reasoning: includeReasoning ? stored.reasoning : '' };
126
130
  }
127
131
  catch (error) {
128
- return { ok: false, question, model: model ?? '', answer: '', reasoning: '', error: mapError(error) };
132
+ return {
133
+ ok: false,
134
+ question,
135
+ model: model ?? '',
136
+ answer: '',
137
+ reasoning: '',
138
+ error: mapError(error),
139
+ };
129
140
  }
130
141
  },
131
142
  });
package/lib/transport.js CHANGED
@@ -98,10 +98,7 @@ function mapPlatformError(code, message) {
98
98
  }
99
99
  /** 判断异常是否为 AbortSignal 触发的中断。 */
100
100
  function isAbortError(error) {
101
- return (typeof error === 'object' &&
102
- error !== null &&
103
- 'name' in error &&
104
- error.name === 'AbortError');
101
+ return (typeof error === 'object' && error !== null && 'name' in error && error.name === 'AbortError');
105
102
  }
106
103
  /** 把任意异常归一化为 {@link ZhihuClientError},保留取消语义。 */
107
104
  function toClientError(error, signal) {
@@ -161,17 +158,15 @@ function sleep(ms, signal) {
161
158
  reject(new ZhihuClientError('aborted', '请求已被取消。'));
162
159
  return;
163
160
  }
164
- let timer;
161
+ const timer = setTimeout(() => {
162
+ signal?.removeEventListener('abort', onAbort);
163
+ resolve();
164
+ }, ms);
165
165
  const onAbort = () => {
166
- if (timer !== undefined)
167
- clearTimeout(timer);
166
+ clearTimeout(timer);
168
167
  signal?.removeEventListener('abort', onAbort);
169
168
  reject(new ZhihuClientError('aborted', '请求已被取消。'));
170
169
  };
171
- timer = setTimeout(() => {
172
- signal?.removeEventListener('abort', onAbort);
173
- resolve();
174
- }, ms);
175
170
  signal?.addEventListener('abort', onAbort, { once: true });
176
171
  });
177
172
  }
@@ -305,7 +300,9 @@ export function classifyChatFailure(failure, status) {
305
300
  hint: '知乎直答触发频率限制。稍等片刻再试,或降低调用频率。',
306
301
  });
307
302
  }
308
- if (status === 401 || status === 403 || /unauthor|authentication|forbidden|invalid.?api.?key|20001/.test(hay)) {
303
+ if (status === 401 ||
304
+ status === 403 ||
305
+ /unauthor|authentication|forbidden|invalid.?api.?key|20001/.test(hay)) {
309
306
  return new ZhihuClientError('auth', message, {
310
307
  ...codeField,
311
308
  hint: '鉴权失败。若 Secret 正确,请检查本机系统时间(知乎要求时间差 < 10 分钟)。',
@@ -319,7 +316,8 @@ export function classifyChatFailure(failure, status) {
319
316
  hint: '请求不被知乎直答接受。若与档位有关,换成 fast 或 thinking 再试;档位是否可用取决于账号授权。',
320
317
  });
321
318
  }
322
- if ((status ?? 0) >= 500 || /server_error|internal|overload|unavailable|temporar|90001/.test(hay)) {
319
+ if ((status ?? 0) >= 500 ||
320
+ /server_error|internal|overload|unavailable|temporar|90001/.test(hay)) {
323
321
  return new ZhihuClientError('server', message, {
324
322
  ...codeField,
325
323
  hint: '知乎服务端错误,请稍后重试。',
@@ -372,10 +370,14 @@ export function deltaFromPayload(payload) {
372
370
  : undefined);
373
371
  const chunk = {};
374
372
  if (typeof delta === 'object' && delta !== null) {
375
- const content = 'content' in delta && typeof delta.content === 'string' && delta.content !== '' ? delta.content : undefined;
373
+ const content = 'content' in delta && typeof delta.content === 'string' && delta.content !== ''
374
+ ? delta.content
375
+ : undefined;
376
376
  if (content !== undefined)
377
377
  chunk.content = content;
378
- const reasoning = 'reasoning_content' in delta && typeof delta.reasoning_content === 'string' && delta.reasoning_content !== ''
378
+ const reasoning = 'reasoning_content' in delta &&
379
+ typeof delta.reasoning_content === 'string' &&
380
+ delta.reasoning_content !== ''
379
381
  ? delta.reasoning_content
380
382
  : undefined;
381
383
  if (reasoning !== undefined)
@@ -392,7 +394,7 @@ function isEnvelope(value) {
392
394
  if (typeof value !== 'object' || value === null)
393
395
  return false;
394
396
  const record = value;
395
- return typeof record['Code'] === 'number' && typeof record['Message'] === 'string' && 'Data' in record;
397
+ return (typeof record['Code'] === 'number' && typeof record['Message'] === 'string' && 'Data' in record);
396
398
  }
397
399
  /**
398
400
  * 知乎开放平台客户端。
@@ -541,7 +543,11 @@ export class ZhihuClient {
541
543
  */
542
544
  async searchZhihu(request, signal) {
543
545
  const data = await this.#requestJson(this.#searchPath(ENDPOINT_ZHIHU_SEARCH, request, ZHIHU_SEARCH_MAX_COUNT), { method: 'GET' }, signal, 1);
544
- return { HasMore: data?.HasMore ?? false, Items: data?.Items ?? [], SearchHashId: data?.SearchHashId };
546
+ return {
547
+ HasMore: data?.HasMore ?? false,
548
+ Items: data?.Items ?? [],
549
+ SearchHashId: data?.SearchHashId,
550
+ };
545
551
  }
546
552
  /**
547
553
  * 知乎全网搜索。
@@ -551,7 +557,11 @@ export class ZhihuClient {
551
557
  */
552
558
  async searchGlobal(request, signal) {
553
559
  const data = await this.#requestJson(this.#searchPath(ENDPOINT_GLOBAL_SEARCH, request, GLOBAL_SEARCH_MAX_COUNT), { method: 'GET' }, signal, 1);
554
- return { HasMore: data?.HasMore ?? false, Items: data?.Items ?? [], SearchHashId: data?.SearchHashId };
560
+ return {
561
+ HasMore: data?.HasMore ?? false,
562
+ Items: data?.Items ?? [],
563
+ SearchHashId: data?.SearchHashId,
564
+ };
555
565
  }
556
566
  /** 查询每日额度;实测不消耗业务额度,可用于自检。 */
557
567
  async quota(signal) {
@@ -632,7 +642,9 @@ export class ZhihuClient {
632
642
  catch {
633
643
  // 落到下面的通用分支。
634
644
  }
635
- return new ZhihuClientError('parse', `知乎直答未返回事件流(HTTP ${status})。`, { hint: text.slice(0, 200) });
645
+ return new ZhihuClientError('parse', `知乎直答未返回事件流(HTTP ${status})。`, {
646
+ hint: text.slice(0, 200),
647
+ });
636
648
  }
637
649
  /**
638
650
  * 执行一次直答并拼接完整回答。
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * 挂载点是插件管理页声明的 `plugins.bundle.config` 槽,key 是**包名**。
6
6
  * 这个槽**只渲染 `view: 'page'`,且座位里没有任何 `form`**(DSH `slot-contract.ts:12-16`
7
- * 「Bundle configuration renders only `page`」;`PluginManagerPage.tsx:584` 只递 `view` 与
7
+ * 「Bundle configuration renders only `page`」;`PluginManagerPage.tsx:579` 只递 `view` 与
8
8
  * `entryKey`)—— 表单因此由卡片自己向 `ctx.configForms.get(<loader entry id>)` 取,
9
9
  * 这也是该槽唯一的官方取表单路径。
10
10
  *
@@ -28,9 +28,9 @@
28
28
  * 与官方 web 搜索卡片同一套做法。配置里只留引用名,因此活动 profile 的 Cordis patch
29
29
  * 被截图或上传时不泄任何凭据。
30
30
  *
31
- * 复用 `@deepseek-ai/dsh-client-ui-primitives` 的 `Tag` 与 `Switch`:那是公共基础库,
32
- * 不是别的插件 —— 被 bundle-purity gate 禁止的是跨插件值导入。
33
- * 其余控件按官方 CSS 自带样式,取值只用 `--dsw-alias-*` 语义令牌。
31
+ * 复用 `@deepseek-ai/dsh-client-ui-primitives` 的 `Input`、`Button`、`Switch` 与 `Tag`:
32
+ * 那是公共基础库,不是别的插件 —— 被 bundle-purity gate 禁止的是跨插件值导入。
33
+ * 行布局(label / badge / hint 的排布)仍是内联样式,取值只用 `--dsw-alias-*` 语义令牌。
34
34
  */
35
35
  import type { Context } from '@deepseek-ai/cordis';
36
36
  /**
package/lib/utils/text.js CHANGED
@@ -68,6 +68,8 @@ export function stripTrackingParams(input) {
68
68
  for (const key of keys)
69
69
  parsed.searchParams.delete(key);
70
70
  // 参数被清空后会留下一个孤立的 '?',Markdown 链接里很难看。
71
- return parsed.search === '' && input.includes('?') ? `${parsed.origin}${parsed.pathname}${parsed.hash}` : parsed.toString();
71
+ return parsed.search === '' && input.includes('?')
72
+ ? `${parsed.origin}${parsed.pathname}${parsed.hash}`
73
+ : parsed.toString();
72
74
  }
73
75
  //# sourceMappingURL=text.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-zhihu-search",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Zhihu in-site search, global web search, and Zhida answers for the model, over the official Zhihu Open Platform API.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -48,7 +48,7 @@
48
48
  ],
49
49
  "engines": {
50
50
  "node": ">=20",
51
- "dsh": ">=0.1.7-alpha.1 <0.2.0"
51
+ "dsh": ">=0.1.7-alpha.1 <0.3.0"
52
52
  },
53
53
  "dsh": {
54
54
  "bundle": {
@@ -67,20 +67,23 @@
67
67
  "test": "npm run build && vitest run",
68
68
  "test:watch": "vitest",
69
69
  "test:contract": "vitest run --config vitest.contract.config.ts",
70
+ "lint": "eslint .",
71
+ "format": "prettier --write .",
72
+ "format:check": "prettier --check .",
70
73
  "check:release": "node scripts/check-release.mjs",
71
74
  "prepare": "npm run build"
72
75
  },
73
76
  "peerDependencies": {
74
77
  "@deepseek-ai/cordis": "^4.0.2",
75
- "@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.1 <0.2.0",
76
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-alpha.1 <0.2.0",
77
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-alpha.1 <0.2.0",
78
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-alpha.1 <0.2.0",
79
- "@deepseek-ai/dsh-credentials": ">=0.1.7-alpha.1 <0.2.0",
80
- "@deepseek-ai/dsh-llm": ">=0.1.7-alpha.1 <0.2.0",
81
- "@deepseek-ai/dsh-settings": ">=0.1.7-alpha.1 <0.2.0",
82
- "@deepseek-ai/dsh-tools": ">=0.1.7-alpha.1 <0.2.0",
83
- "@deepseek-ai/dsh-util-values": ">=0.1.7-alpha.1 <0.2.0",
78
+ "@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.1 <0.3.0",
79
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-alpha.1 <0.3.0",
80
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-alpha.1 <0.3.0",
81
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-alpha.1 <0.3.0",
82
+ "@deepseek-ai/dsh-credentials": ">=0.1.7-alpha.1 <0.3.0",
83
+ "@deepseek-ai/dsh-llm": ">=0.1.7-alpha.1 <0.3.0",
84
+ "@deepseek-ai/dsh-settings": ">=0.1.7-alpha.1 <0.3.0",
85
+ "@deepseek-ai/dsh-tools": ">=0.1.7-alpha.1 <0.3.0",
86
+ "@deepseek-ai/dsh-util-values": ">=0.1.7-alpha.1 <0.3.0",
84
87
  "@deepseek-ai/schemastery": "^3.18.2",
85
88
  "react": "^18.3.1",
86
89
  "react-dom": "^18.3.1"
@@ -88,32 +91,38 @@
88
91
  "devDependencies": {
89
92
  "@deepseek-ai/cordis": "^4.0.2",
90
93
  "@deepseek-ai/cordis-plugin-loader": "^1.0.4",
91
- "@deepseek-ai/dsh-agent": ">=0.1.7-alpha.1 <0.2.0",
92
- "@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.1 <0.2.0",
93
- "@deepseek-ai/dsh-client-ui-plugin-manager": ">=0.1.7-alpha.1 <0.2.0",
94
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-alpha.1 <0.2.0",
95
- "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.7-alpha.1 <0.2.0",
96
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-alpha.1 <0.2.0",
97
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-alpha.1 <0.2.0",
98
- "@deepseek-ai/dsh-credentials": ">=0.1.7-alpha.1 <0.2.0",
99
- "@deepseek-ai/dsh-invariants": ">=0.1.7-alpha.1 <0.2.0",
100
- "@deepseek-ai/dsh-llm": ">=0.1.7-alpha.1 <0.2.0",
101
- "@deepseek-ai/dsh-scope": ">=0.1.7-alpha.1 <0.2.0",
102
- "@deepseek-ai/dsh-session": ">=0.1.7-alpha.1 <0.2.0",
103
- "@deepseek-ai/dsh-session-projection": ">=0.1.7-alpha.1 <0.2.0",
104
- "@deepseek-ai/dsh-settings": ">=0.1.7-alpha.1 <0.2.0",
105
- "@deepseek-ai/dsh-system-prompt": ">=0.1.7-alpha.1 <0.2.0",
106
- "@deepseek-ai/dsh-tools": ">=0.1.7-alpha.1 <0.2.0",
107
- "@deepseek-ai/dsh-user-approval": ">=0.1.7-alpha.1 <0.2.0",
108
- "@deepseek-ai/dsh-util-values": ">=0.1.7-alpha.1 <0.2.0",
94
+ "@deepseek-ai/dsh-agent": ">=0.1.7-alpha.1 <0.3.0",
95
+ "@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.1 <0.3.0",
96
+ "@deepseek-ai/dsh-client-ui-plugin-manager": ">=0.1.7-alpha.1 <0.3.0",
97
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-alpha.1 <0.3.0",
98
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.7-alpha.1 <0.3.0",
99
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-alpha.1 <0.3.0",
100
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-alpha.1 <0.3.0",
101
+ "@deepseek-ai/dsh-credentials": ">=0.1.7-alpha.1 <0.3.0",
102
+ "@deepseek-ai/dsh-invariants": ">=0.1.7-alpha.1 <0.3.0",
103
+ "@deepseek-ai/dsh-llm": ">=0.1.7-alpha.1 <0.3.0",
104
+ "@deepseek-ai/dsh-scope": ">=0.1.7-alpha.1 <0.3.0",
105
+ "@deepseek-ai/dsh-session": ">=0.1.7-alpha.1 <0.3.0",
106
+ "@deepseek-ai/dsh-session-projection": ">=0.1.7-alpha.1 <0.3.0",
107
+ "@deepseek-ai/dsh-settings": ">=0.1.7-alpha.1 <0.3.0",
108
+ "@deepseek-ai/dsh-system-prompt": ">=0.1.7-alpha.1 <0.3.0",
109
+ "@deepseek-ai/dsh-tools": ">=0.1.7-alpha.1 <0.3.0",
110
+ "@deepseek-ai/dsh-user-approval": ">=0.1.7-alpha.1 <0.3.0",
111
+ "@deepseek-ai/dsh-util-values": ">=0.1.7-alpha.1 <0.3.0",
109
112
  "@deepseek-ai/schemastery": "^3.18.2",
113
+ "@eslint/js": "^10.0.1",
110
114
  "@types/node": "^20.0.0",
111
115
  "@types/react": "~18.3.1",
112
116
  "@types/react-dom": "~18.3.1",
113
117
  "esbuild": "^0.21.5",
118
+ "eslint": "^10.11.0",
119
+ "eslint-config-prettier": "^10.1.8",
120
+ "globals": "^17.12.0",
121
+ "prettier": "^3.9.9",
114
122
  "react": "^18.3.1",
115
123
  "react-dom": "^18.3.1",
116
124
  "typescript": "^5.4.0",
125
+ "typescript-eslint": "^8.70.1",
117
126
  "vitest": "^2.0.0"
118
127
  }
119
128
  }