dsh-cite 0.3.1 → 0.3.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.en.md CHANGED
@@ -18,7 +18,7 @@ A DeepSeek Harness plugin for bibliographic references: query Crossref and forma
18
18
 
19
19
  ## Compatibility
20
20
 
21
- Verified against `@deepseek-ai/dsh@0.1.2-alpha.2` on 2026-08-31. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
21
+ Verified with official `@deepseek-ai/dsh@0.1.5-rc.1` and Node `24.16.0` on 2026-09-11: all 18 components load alongside Modlens, with passing tool-schema, skill-registration and offline read-only invocation checks. Uses the `cordis.patch.yml` + `dsh.bundle.patch` bundle model. Node requirements match this Harness release: 22.19 or later within 22.x, or 24 or later. Live external-service workflows require separate configuration and validation.
22
22
 
23
23
  ## Install
24
24
 
package/README.md CHANGED
@@ -18,7 +18,7 @@ DeepSeek Harness 参考文献工具插件:通过 Crossref API 查询文献元
18
18
 
19
19
  ## 兼容性
20
20
 
21
- 在 `@deepseek-ai/dsh@0.1.2-alpha.2` 上验证(2026-08-31)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
21
+ 已在官方 `@deepseek-ai/dsh@0.1.5-rc.1`、Node `24.16.0` 上验证(2026-09-11):18 个组件与 Modlens 同载,工具 schema、技能注册及离线只读调用检查通过。采用 `cordis.patch.yml` + `dsh.bundle.patch` 组合包模型。Node 要求与该版本 Harness 一致:22.19 及以上的 22.x,或 24 及以上。外部服务的实际业务操作需按各组件配置单独验证。
22
22
 
23
23
  ## 安装
24
24
 
package/lib/config.d.ts CHANGED
@@ -1,8 +1,3 @@
1
- /**
2
- * dsh-cite 配置解析:Crossref 请求超时与 User-Agent。
3
- *
4
- * @module dsh-cite/config
5
- */
6
1
  export interface CiteConfig {
7
2
  timeoutMs?: number;
8
3
  userAgent?: string;
@@ -11,7 +6,7 @@ export interface ResolvedCiteConfig {
11
6
  timeoutMs: number;
12
7
  userAgent: string;
13
8
  }
14
- export declare const DEFAULT_USER_AGENT = "dsh-cite/0.3.1 (DeepSeek Harness citation plugin; mailto:STARDUSTLC666@users.noreply.github.com)";
9
+ export declare const DEFAULT_USER_AGENT: string;
15
10
  export declare const CITE_TIMEOUT_ENV = "DSH_CITE_TIMEOUT_MS";
16
11
  export declare const CITE_USER_AGENT_ENV = "DSH_CITE_USER_AGENT";
17
12
  export declare function resolveConfig(config: CiteConfig | undefined | null, env?: NodeJS.ProcessEnv): ResolvedCiteConfig;
package/lib/config.js CHANGED
@@ -3,8 +3,10 @@
3
3
  *
4
4
  * @module dsh-cite/config
5
5
  */
6
+ import { createRequire } from 'node:module';
6
7
  const DEFAULT_TIMEOUT_MS = 15000;
7
- export const DEFAULT_USER_AGENT = 'dsh-cite/0.3.1 (DeepSeek Harness citation plugin; mailto:STARDUSTLC666@users.noreply.github.com)';
8
+ const pkg = createRequire(import.meta.url)('../package.json');
9
+ export const DEFAULT_USER_AGENT = `dsh-cite/${pkg.version} (DeepSeek Harness citation plugin; mailto:STARDUSTLC666@users.noreply.github.com)`;
8
10
  export const CITE_TIMEOUT_ENV = 'DSH_CITE_TIMEOUT_MS';
9
11
  export const CITE_USER_AGENT_ENV = 'DSH_CITE_USER_AGENT';
10
12
  export function resolveConfig(config, env = process.env) {
package/lib/crossref.d.ts CHANGED
@@ -30,8 +30,8 @@ export type FetchLike = (url: string, init?: {
30
30
  }) => Promise<Response>;
31
31
  export declare function assertDoi(value: string): string;
32
32
  /** 按 DOI 查询 Crossref 并归一化。 */
33
- export declare function lookupDoi(doi: string, cfg: ResolvedCiteConfig, fetchImpl?: FetchLike): Promise<Work>;
33
+ export declare function lookupDoi(doi: string, cfg: ResolvedCiteConfig, fetchImpl?: FetchLike, signal?: AbortSignal): Promise<Work>;
34
34
  /** 按题录文本检索 Crossref,返回归一化结果。 */
35
- export declare function searchWorks(query: string, limit: number, cfg: ResolvedCiteConfig, fetchImpl?: FetchLike): Promise<Work[]>;
35
+ export declare function searchWorks(query: string, limit: number, cfg: ResolvedCiteConfig, fetchImpl?: FetchLike, signal?: AbortSignal): Promise<Work[]>;
36
36
  /** 从任意文本里提取 DOI。 */
37
37
  export declare function extractDois(text: string): string[];
package/lib/crossref.js CHANGED
@@ -63,18 +63,23 @@ function normalizeWork(raw) {
63
63
  function makeHeaders(cfg) {
64
64
  return { 'user-agent': cfg.userAgent, accept: 'application/json' };
65
65
  }
66
+ function requestSignal(timeoutMs, signal) {
67
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
68
+ return signal === undefined ? timeoutSignal : AbortSignal.any([signal, timeoutSignal]);
69
+ }
66
70
  /** 按 DOI 查询 Crossref 并归一化。 */
67
- export async function lookupDoi(doi, cfg, fetchImpl) {
71
+ export async function lookupDoi(doi, cfg, fetchImpl, signal) {
68
72
  const clean = assertDoi(doi);
69
73
  const fetcher = fetchImpl ?? globalThis.fetch;
70
74
  let response;
71
75
  try {
72
76
  response = await fetcher(CROSSREF_API + encodeURIComponent(clean), {
73
77
  headers: makeHeaders(cfg),
74
- signal: AbortSignal.timeout(cfg.timeoutMs),
78
+ signal: requestSignal(cfg.timeoutMs, signal),
75
79
  });
76
80
  }
77
81
  catch (error) {
82
+ signal?.throwIfAborted();
78
83
  throw new Error('Crossref 请求失败:' + (error instanceof Error ? error.message : String(error)));
79
84
  }
80
85
  if (!response.ok) {
@@ -98,7 +103,7 @@ export async function lookupDoi(doi, cfg, fetchImpl) {
98
103
  return work;
99
104
  }
100
105
  /** 按题录文本检索 Crossref,返回归一化结果。 */
101
- export async function searchWorks(query, limit, cfg, fetchImpl) {
106
+ export async function searchWorks(query, limit, cfg, fetchImpl, signal) {
102
107
  const q = query.trim();
103
108
  if (q === '')
104
109
  throw new Error('检索词不能为空。');
@@ -108,10 +113,11 @@ export async function searchWorks(query, limit, cfg, fetchImpl) {
108
113
  try {
109
114
  response = await fetcher(url, {
110
115
  headers: makeHeaders(cfg),
111
- signal: AbortSignal.timeout(cfg.timeoutMs),
116
+ signal: requestSignal(cfg.timeoutMs, signal),
112
117
  });
113
118
  }
114
119
  catch (error) {
120
+ signal?.throwIfAborted();
115
121
  throw new Error('Crossref 检索失败:' + (error instanceof Error ? error.message : String(error)));
116
122
  }
117
123
  if (!response.ok)
package/lib/index.d.ts CHANGED
@@ -14,9 +14,7 @@ export declare const name = "cite";
14
14
  export declare const inject: string[];
15
15
  export interface CitePluginContext {
16
16
  tools: {
17
- register(definition: CiteToolDefinition, options?: {
18
- prepend?: boolean;
19
- }): () => void;
17
+ register(definition: CiteToolDefinition): () => void;
20
18
  };
21
19
  on?(event: string, listener: () => void): () => void;
22
20
  }
package/lib/index.js CHANGED
@@ -23,7 +23,7 @@ export function apply(ctx, config) {
23
23
  }
24
24
  const disposers = [];
25
25
  for (const definition of buildCiteTools(cfg)) {
26
- disposers.push(ctx.tools.register(definition, { prepend: true }));
26
+ disposers.push(ctx.tools.register(definition));
27
27
  }
28
28
  if (typeof ctx.on === 'function') {
29
29
  ctx.on('dispose', () => {
package/lib/tools.js CHANGED
@@ -74,11 +74,12 @@ const checkSchema = {
74
74
  additionalProperties: true,
75
75
  };
76
76
  /** 以固定并发度执行异步任务并保持原顺序。 */
77
- async function mapWithConcurrency(items, concurrency, fn) {
77
+ async function mapWithConcurrency(items, concurrency, fn, signal) {
78
78
  const results = new Array(items.length);
79
79
  let next = 0;
80
80
  const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
81
81
  while (true) {
82
+ signal?.throwIfAborted();
82
83
  const index = next;
83
84
  next += 1;
84
85
  if (index >= items.length)
@@ -89,6 +90,14 @@ async function mapWithConcurrency(items, concurrency, fn) {
89
90
  await Promise.all(workers);
90
91
  return results;
91
92
  }
93
+ function executionSignal(exec) {
94
+ if (typeof exec !== 'object' || exec === null)
95
+ return undefined;
96
+ const signal = exec.signal;
97
+ return signal instanceof AbortSignal ? signal : undefined;
98
+ }
99
+ const CHECK_CONCURRENCY = 3;
100
+ const MAX_CHECKS = 50;
92
101
  function workLabel(work) {
93
102
  const title = work.title !== '' ? work.title : '(无标题)';
94
103
  return title + '(' + (work.year > 0 ? work.year + ', ' : '') + (work.containerTitle !== '' ? work.containerTitle : work.publisher) + ')';
@@ -107,23 +116,23 @@ export function buildCiteTools(cfg, fetchImpl) {
107
116
  const works = Array.isArray(rec.works) ? rec.works : [];
108
117
  return [{ type: 'text', text: works.map((work, index) => (index + 1) + '. ' + workLabel(work)).join('\n') }];
109
118
  } },
110
- async execute(rawArgs) {
119
+ async execute(rawArgs, exec) {
111
120
  const args = (rawArgs ?? {});
112
121
  const doi = optionalString(args, 'doi');
113
122
  const query = optionalString(args, 'query');
114
123
  if (doi === undefined && query === undefined)
115
124
  throw new Error('cite_lookup 需要 doi 或 query 参数(至少一个)。');
116
125
  if (doi !== undefined) {
117
- const work = await lookupDoi(doi, cfg, fetchImpl);
126
+ const work = await lookupDoi(doi, cfg, fetchImpl, executionSignal(exec));
118
127
  return { count: 1, works: [work] };
119
128
  }
120
129
  const queryDoi = extractDois(query)[0];
121
130
  if (queryDoi !== undefined) {
122
- const work = await lookupDoi(queryDoi, cfg, fetchImpl);
131
+ const work = await lookupDoi(queryDoi, cfg, fetchImpl, executionSignal(exec));
123
132
  return { count: 1, works: [work], matchedDoi: queryDoi };
124
133
  }
125
134
  const limit = optionalInteger(args, 'limit', '返回条数', 1, 10, 5);
126
- const works = await searchWorks(query, limit, cfg, fetchImpl);
135
+ const works = await searchWorks(query, limit, cfg, fetchImpl, executionSignal(exec));
127
136
  if (works.length === 0)
128
137
  throw new Error('Crossref 没有找到匹配文献,请尝试更精确的标题/作者。');
129
138
  return { count: works.length, works };
@@ -142,12 +151,12 @@ export function buildCiteTools(cfg, fetchImpl) {
142
151
  const rec = (value ?? {});
143
152
  return [{ type: 'text', text: typeof rec.citation === 'string' ? rec.citation : '' }];
144
153
  } },
145
- async execute(rawArgs) {
154
+ async execute(rawArgs, exec) {
146
155
  const args = (rawArgs ?? {});
147
156
  const doi = requiredString(args, 'doi', 'DOI');
148
157
  const style = readStyle(args.style);
149
158
  const lang = args.lang === 'en' ? 'en' : 'zh';
150
- const work = await lookupDoi(doi, cfg, fetchImpl);
159
+ const work = await lookupDoi(doi, cfg, fetchImpl, executionSignal(exec));
151
160
  const citation = buildCitation(work, style, lang);
152
161
  return { doi: work.doi || doi, style, citation, work };
153
162
  },
@@ -164,13 +173,13 @@ export function buildCiteTools(cfg, fetchImpl) {
164
173
  const rec = (value ?? {});
165
174
  return [{ type: 'text', text: typeof rec.bibtex === 'string' ? rec.bibtex : '' }];
166
175
  } },
167
- async execute(rawArgs) {
176
+ async execute(rawArgs, exec) {
168
177
  const args = (rawArgs ?? {});
169
178
  const doi = requiredString(args, 'doi', 'DOI');
170
179
  const key = optionalString(args, 'key');
171
- const work = await lookupDoi(doi, cfg, fetchImpl);
180
+ const work = await lookupDoi(doi, cfg, fetchImpl, executionSignal(exec));
172
181
  const bibtex = buildBibtex(work, key);
173
- return { doi: work.doi || doi, key, bibtex };
182
+ return { doi: work.doi || doi, ...(key !== undefined ? { key } : {}), bibtex };
174
183
  },
175
184
  timeoutMs: cfg.timeoutMs + 2000,
176
185
  };
@@ -189,23 +198,25 @@ export function buildCiteTools(cfg, fetchImpl) {
189
198
  return (row.ok === true ? '✅' : '❌') + ' ' + row.doi + (typeof row.title === 'string' && row.title !== '' ? ' — ' + row.title : '');
190
199
  }).join('\n') }];
191
200
  } },
192
- async execute(rawArgs) {
201
+ async execute(rawArgs, exec) {
193
202
  const args = (rawArgs ?? {});
194
203
  const text = requiredString(args, 'text', '待检查文本');
195
204
  const maxChecks = optionalInteger(args, 'maxChecks', '校验数量', 1, 50, 10);
196
205
  const dois = extractDois(text).slice(0, maxChecks);
197
- const results = await mapWithConcurrency(dois, 3, async (doi) => {
206
+ const signal = executionSignal(exec);
207
+ const results = await mapWithConcurrency(dois, CHECK_CONCURRENCY, async (doi) => {
198
208
  try {
199
- const work = await lookupDoi(doi, cfg, fetchImpl);
209
+ const work = await lookupDoi(doi, cfg, fetchImpl, signal);
200
210
  return { doi, ok: true, title: work.title, url: work.url };
201
211
  }
202
212
  catch (error) {
213
+ signal?.throwIfAborted();
203
214
  return { doi, ok: false, title: '', url: '', error: error instanceof Error ? error.message : String(error) };
204
215
  }
205
- });
216
+ }, signal);
206
217
  return { count: results.length, results };
207
218
  },
208
- timeoutMs: cfg.timeoutMs * 3 + 5000,
219
+ timeoutMs: cfg.timeoutMs * Math.ceil(MAX_CHECKS / CHECK_CONCURRENCY) + 5000,
209
220
  };
210
221
  const citeHealth = {
211
222
  name: 'cite_health',
@@ -224,16 +235,20 @@ export function buildCiteTools(cfg, fetchImpl) {
224
235
  return [{ type: 'text', text: lines.join('\n') }];
225
236
  },
226
237
  },
227
- async execute() {
238
+ async execute(_rawArgs, exec) {
228
239
  const checks = [];
229
240
  let ok = true;
230
241
  const startedAt = Date.now();
242
+ const signal = executionSignal(exec);
231
243
  try {
232
244
  const fetcher = fetchImpl ?? globalThis.fetch;
233
245
  const response = await fetcher('https://api.crossref.org/works?rows=1&select=DOI', {
234
246
  headers: { 'user-agent': cfg.userAgent },
235
- signal: AbortSignal.timeout(Math.min(cfg.timeoutMs, 15000)),
247
+ signal: signal === undefined
248
+ ? AbortSignal.timeout(Math.min(cfg.timeoutMs, 15000))
249
+ : AbortSignal.any([signal, AbortSignal.timeout(Math.min(cfg.timeoutMs, 15000))]),
236
250
  });
251
+ signal?.throwIfAborted();
237
252
  if (response.ok) {
238
253
  checks.push({ name: 'Crossref API', ok: true, detail: 'HTTP ' + response.status + ',' + (Date.now() - startedAt) + 'ms' });
239
254
  }
@@ -243,6 +258,7 @@ export function buildCiteTools(cfg, fetchImpl) {
243
258
  }
244
259
  }
245
260
  catch (error) {
261
+ signal?.throwIfAborted();
246
262
  ok = false;
247
263
  checks.push({ name: 'Crossref API', ok: false, detail: (error instanceof Error ? error.message : String(error)) + '(网络需要特殊代理时请配置系统代理后重启)' });
248
264
  }
package/package.json CHANGED
@@ -1,63 +1,63 @@
1
- {
2
- "name": "dsh-cite",
3
- "version": "0.3.1",
4
- "description": "DSH 参考文献工具插件:cite_lookup/cite_format/cite_bibtex/cite_check/cite_health 五工具(含自检),Crossref 元数据查询(含 DOI 精确解析与并发校验),GB/T 7714 / APA / MLA / Chicago 格式化,零运行时依赖。",
5
- "type": "module",
6
- "main": "lib/index.js",
7
- "types": "lib/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./lib/index.d.ts",
11
- "default": "./lib/index.js"
12
- },
13
- "./cordis.patch.yml": "./cordis.patch.yml",
14
- "./package.json": "./package.json"
15
- },
16
- "files": [
17
- "lib",
18
- "cordis.patch.yml",
19
- "README.md",
20
- "README.en.md"
21
- ],
22
- "scripts": {
23
- "build": "tsc -p tsconfig.json",
24
- "typecheck": "tsc -p tsconfig.json --noEmit",
25
- "test": "pnpm run build && node --test \"test/*.test.mjs\"",
26
- "prepublishOnly": "pnpm run build"
27
- },
28
- "dsh": {
29
- "bundle": {
30
- "patch": "./cordis.patch.yml"
31
- }
32
- },
33
- "keywords": [
34
- "dsh",
35
- "deepseek-harness",
36
- "plugin",
37
- "citation",
38
- "crossref",
39
- "gb-t-7714",
40
- "apa",
41
- "mla",
42
- "chicago",
43
- "dsh-plugin"
44
- ],
45
- "license": "MIT",
46
- "engines": {
47
- "node": ">=22"
48
- },
49
- "devDependencies": {
50
- "@types/node": "^24.0.0",
51
- "typescript": "^5.6.0"
52
- },
53
- "repository": {
54
- "type": "git",
55
- "url": "git+https://github.com/STARDUSTLC666/dsh-cite.git"
56
- },
57
- "bugs": {
58
- "url": "https://github.com/STARDUSTLC666/dsh-cite/issues"
59
- },
60
- "homepage": "https://github.com/STARDUSTLC666/dsh-cite#readme",
61
- "author": "stardustlc",
62
- "packageManager": "pnpm@11.7.0"
63
- }
1
+ {
2
+ "name": "dsh-cite",
3
+ "version": "0.3.3",
4
+ "description": "DSH 参考文献工具插件:cite_lookup/cite_format/cite_bibtex/cite_check/cite_health 五工具(含自检),Crossref 元数据查询(含 DOI 精确解析与并发校验),GB/T 7714 / APA / MLA / Chicago 格式化,零运行时依赖。",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./cordis.patch.yml": "./cordis.patch.yml",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "lib",
18
+ "cordis.patch.yml",
19
+ "README.md",
20
+ "README.en.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "typecheck": "tsc -p tsconfig.json --noEmit",
25
+ "test": "pnpm run build && node --test \"test/*.test.mjs\"",
26
+ "prepublishOnly": "pnpm run build"
27
+ },
28
+ "dsh": {
29
+ "bundle": {
30
+ "patch": "./cordis.patch.yml"
31
+ }
32
+ },
33
+ "keywords": [
34
+ "dsh",
35
+ "deepseek-harness",
36
+ "plugin",
37
+ "citation",
38
+ "crossref",
39
+ "gb-t-7714",
40
+ "apa",
41
+ "mla",
42
+ "chicago",
43
+ "dsh-plugin"
44
+ ],
45
+ "license": "MIT",
46
+ "engines": {
47
+ "node": "^22.19.0 || >=24.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^24.0.0",
51
+ "typescript": "^5.6.0"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/STARDUSTLC666/dsh-cite.git"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/STARDUSTLC666/dsh-cite/issues"
59
+ },
60
+ "homepage": "https://github.com/STARDUSTLC666/dsh-cite#readme",
61
+ "author": "stardustlc",
62
+ "packageManager": "pnpm@11.7.0"
63
+ }