@master0071/dsh-systemone 0.5.0 → 0.6.1
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/LICENSE +21 -0
- package/README.md +124 -22
- package/lib/auto.js +218 -40
- package/lib/client.js +21 -1
- package/lib/format.js +106 -9
- package/lib/index.d.ts +54 -2
- package/lib/index.js +121 -7
- package/lib/provider.js +107 -6
- package/lib/scenarios.js +15 -4
- package/lib/tools.js +93 -7
- package/lib/usage.js +167 -0
- package/package.json +10 -4
package/lib/tools.js
CHANGED
|
@@ -8,14 +8,15 @@
|
|
|
8
8
|
* 场景全部来自 scenarios.js 的场景库(内置 11 个 + 配置追加),新增场景无需改代码。
|
|
9
9
|
*/
|
|
10
10
|
import {
|
|
11
|
-
computeNeedsHumanReview,
|
|
12
11
|
deriveFields,
|
|
13
12
|
formatAnswerLines,
|
|
14
13
|
formatSummary,
|
|
15
14
|
normalizeAnswers,
|
|
16
15
|
renderTemplate,
|
|
16
|
+
reviewAssessment,
|
|
17
17
|
} from './format.js'
|
|
18
18
|
import { findScenario } from './scenarios.js'
|
|
19
|
+
import { recordUsage } from './usage.js'
|
|
19
20
|
import { readNumber, readString } from './volatile.js'
|
|
20
21
|
|
|
21
22
|
const DEFAULT_MIN_CONFIDENCE = 0.6
|
|
@@ -25,7 +26,7 @@ const DEFAULT_MIN_CONFIDENCE = 0.6
|
|
|
25
26
|
* MAX_CURRENT_CHARS / 每条历史 2000 字),这里只防工具路径被塞进超大输入
|
|
26
27
|
* 直接打到上游 API;超长内容对决策质量也没有增量。
|
|
27
28
|
*/
|
|
28
|
-
const MAX_TOOL_STATE_CHARS = 16000
|
|
29
|
+
export const MAX_TOOL_STATE_CHARS = 16000
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* 给 state 套硬上限:超长字符串截断;对象/数组按序列化长度衡量,
|
|
@@ -59,11 +60,12 @@ function capState(state) {
|
|
|
59
60
|
* @param {object} [hooks] - 可选运行时钩子
|
|
60
61
|
* @param {() => string[]} [hooks.warnings] - 读取当前非致命提示(如别名遮蔽),
|
|
61
62
|
* 用于让 `action=list` 把「哪个别名被抢走了」告诉模型,而不只是写进宿主日志。
|
|
63
|
+
* @param {object} [hooks.usage] - 用量台账(createUsageLedger 的返回值)。有则记录每次上游调用。
|
|
62
64
|
*/
|
|
63
65
|
export function createTools(provider, config, scenarios, hooks = {}) {
|
|
64
66
|
return [
|
|
65
67
|
scenarioTool(provider, config, scenarios, hooks),
|
|
66
|
-
decideTool(provider, config),
|
|
68
|
+
decideTool(provider, config, hooks),
|
|
67
69
|
]
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -113,7 +115,7 @@ function scenarioTool(provider, config, scenarios, hooks = {}) {
|
|
|
113
115
|
const action = args.action || 'run'
|
|
114
116
|
|
|
115
117
|
if (action === 'list') {
|
|
116
|
-
return listScenarios(scenarios, args.keyword, readWarnings(hooks))
|
|
118
|
+
return listScenarios(scenarios, args.keyword, readWarnings(hooks), hooks?.usage)
|
|
117
119
|
}
|
|
118
120
|
if (action === 'describe') {
|
|
119
121
|
return describeScenario(scenarios, args.scenario)
|
|
@@ -126,6 +128,7 @@ function scenarioTool(provider, config, scenarios, hooks = {}) {
|
|
|
126
128
|
state: capState(args.state),
|
|
127
129
|
params: args.params,
|
|
128
130
|
exec,
|
|
131
|
+
usage: hooks?.usage,
|
|
129
132
|
})
|
|
130
133
|
},
|
|
131
134
|
presentCall: (args) => ({
|
|
@@ -147,7 +150,7 @@ function readWarnings(hooks) {
|
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
function listScenarios(scenarios, keyword, warnings = []) {
|
|
153
|
+
function listScenarios(scenarios, keyword, warnings = [], usage) {
|
|
151
154
|
const needle = typeof keyword === 'string' ? keyword.trim().toLowerCase() : ''
|
|
152
155
|
const items = scenarios
|
|
153
156
|
.filter((s) => {
|
|
@@ -166,6 +169,19 @@ function listScenarios(scenarios, keyword, warnings = []) {
|
|
|
166
169
|
questions: Object.entries(s.questions).map(([qid, q]) => `${qid}:${q.type}`),
|
|
167
170
|
}))
|
|
168
171
|
|
|
172
|
+
// 用量台账:list 是调用方最容易顺手调用的入口,把今日花销挂在这里,
|
|
173
|
+
// 让「花了多少」不需要另开一个工具就能看到。
|
|
174
|
+
let usageLine = null
|
|
175
|
+
let usageSnapshot = null
|
|
176
|
+
try {
|
|
177
|
+
if (typeof usage?.today === 'function') {
|
|
178
|
+
usageSnapshot = usage.today()
|
|
179
|
+
usageLine = typeof usage.formatToday === 'function' ? usage.formatToday() : null
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
// 台账读取失败不影响 list
|
|
183
|
+
}
|
|
184
|
+
|
|
169
185
|
const summary = [
|
|
170
186
|
`## SystemOne 场景库(${items.length} 个)`,
|
|
171
187
|
...items.map((s) => `- **${s.id}**:${s.title} — ${s.question_count} 个问题(${s.questions.join(',')})`),
|
|
@@ -174,6 +190,7 @@ function listScenarios(scenarios, keyword, warnings = []) {
|
|
|
174
190
|
...(warnings.length > 0
|
|
175
191
|
? ['', '### 配置提示', ...warnings.map((w) => `- ${w}`)]
|
|
176
192
|
: []),
|
|
193
|
+
...(usageLine ? ['', '### 用量', `- ${usageLine}`] : []),
|
|
177
194
|
'',
|
|
178
195
|
'用 action=describe 查看某场景的问题定义,用 action=run 执行决策。',
|
|
179
196
|
].join('\n')
|
|
@@ -184,6 +201,7 @@ function listScenarios(scenarios, keyword, warnings = []) {
|
|
|
184
201
|
count: items.length,
|
|
185
202
|
scenarios: items,
|
|
186
203
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
204
|
+
...(usageSnapshot ? { usage_today: usageSnapshot } : {}),
|
|
187
205
|
summary,
|
|
188
206
|
}
|
|
189
207
|
}
|
|
@@ -237,6 +255,7 @@ function describeScenario(scenarios, id) {
|
|
|
237
255
|
* @param {object} [options.params] - 按问题 id 覆盖问题定义
|
|
238
256
|
* @param {object} [options.exec] - 工具执行上下文(取 signal)
|
|
239
257
|
* @param {number} [options.minConfidence] - 覆盖置信度阈值
|
|
258
|
+
* @param {object} [options.usage] - 用量台账;有则记录本次调用
|
|
240
259
|
* @returns {Promise<object>} 与工具输出同构的决策结果
|
|
241
260
|
*/
|
|
242
261
|
export async function runScenarioDecision({
|
|
@@ -248,6 +267,7 @@ export async function runScenarioDecision({
|
|
|
248
267
|
params,
|
|
249
268
|
exec,
|
|
250
269
|
minConfidence,
|
|
270
|
+
usage,
|
|
251
271
|
}) {
|
|
252
272
|
const scenario = findScenario(scenarios, scenarioRef)
|
|
253
273
|
if (!scenario) {
|
|
@@ -286,6 +306,7 @@ export async function runScenarioDecision({
|
|
|
286
306
|
config,
|
|
287
307
|
minConfidence: threshold,
|
|
288
308
|
exec,
|
|
309
|
+
usage,
|
|
289
310
|
warnings: ignored.length > 0
|
|
290
311
|
? [`以下 params 覆盖无效,已忽略:${ignored.join(';')}`]
|
|
291
312
|
: [],
|
|
@@ -355,7 +376,7 @@ function applyParams(questions, params) {
|
|
|
355
376
|
|
|
356
377
|
/* ─── 2. 通用决策工具 ─────────────────────────────────────────────────────── */
|
|
357
378
|
|
|
358
|
-
function decideTool(provider, config) {
|
|
379
|
+
function decideTool(provider, config, hooks = {}) {
|
|
359
380
|
return {
|
|
360
381
|
name: 'systemone_decide',
|
|
361
382
|
description: [
|
|
@@ -404,6 +425,7 @@ function decideTool(provider, config) {
|
|
|
404
425
|
minConfidence,
|
|
405
426
|
exec,
|
|
406
427
|
model: args.model,
|
|
428
|
+
usage: hooks?.usage,
|
|
407
429
|
finalize(answers) {
|
|
408
430
|
const { decision, labels, confidences } = normalizeAnswers(answers, args.questions || {})
|
|
409
431
|
return { decision, labels, derived: {}, confidences, recommendation: '' }
|
|
@@ -433,6 +455,7 @@ async function executeDecision({
|
|
|
433
455
|
model,
|
|
434
456
|
finalize,
|
|
435
457
|
warnings = [],
|
|
458
|
+
usage,
|
|
436
459
|
}) {
|
|
437
460
|
try {
|
|
438
461
|
const started = Date.now()
|
|
@@ -443,9 +466,69 @@ async function executeDecision({
|
|
|
443
466
|
model: model || configuredModel,
|
|
444
467
|
signal: exec?.signal,
|
|
445
468
|
})
|
|
469
|
+
// 记账:成功与失败都记(上游可能已计费)。记账失败绝不影响决策。
|
|
470
|
+
recordUsage(usage, {
|
|
471
|
+
source: 'tool',
|
|
472
|
+
ok: true,
|
|
473
|
+
model: response.model || model || configuredModel,
|
|
474
|
+
provider: provider.name,
|
|
475
|
+
scenario: scenarioId || '',
|
|
476
|
+
usage: response.usage,
|
|
477
|
+
latencyMs: response.latency_ms,
|
|
478
|
+
})
|
|
446
479
|
const answers = response.answers || {}
|
|
447
480
|
const { decision, labels, derived, confidences, recommendation } = finalize(answers)
|
|
448
|
-
|
|
481
|
+
|
|
482
|
+
// ── 空决策守卫 ────────────────────────────────────────────────────────────
|
|
483
|
+
// 必须同时覆盖两种"沉默"形态:
|
|
484
|
+
// a) answers 为空 / 全是无法识别的 type → decision 里一个键都没有;
|
|
485
|
+
// b) 有 answers 但关键字段缺失(如 noul 答案没有 noul 数值)→
|
|
486
|
+
// normalizeAnswers 会把该问题写成 null("无法判断")。
|
|
487
|
+
// 注意 type 的取值是 `question.type || raw.type`:上游给个无法识别的 type、
|
|
488
|
+
// 而问题定义是合法 type 时,会走 (b) 这条路——所以只看 decision 是否为空
|
|
489
|
+
// 是不够的,必须看**有没有任何一个问题真正产出了判断**。
|
|
490
|
+
// 若此时仍报 ok:true,调用方(模型)会以为"判断已完成",实际拿到的是空判断;
|
|
491
|
+
// 对决策工具来说,这种静默空成功比显式失败更危险。
|
|
492
|
+
const asked = Object.keys(questions || {})
|
|
493
|
+
const usable = asked.filter((qid) => {
|
|
494
|
+
const value = decision[qid]
|
|
495
|
+
return value !== null && value !== undefined
|
|
496
|
+
})
|
|
497
|
+
if (usable.length === 0) {
|
|
498
|
+
const returned = Object.keys(answers)
|
|
499
|
+
const unusable = asked.filter((qid) => qid in decision)
|
|
500
|
+
let detail
|
|
501
|
+
if (asked.length === 0) {
|
|
502
|
+
detail = '本次没有提交任何问题'
|
|
503
|
+
} else if (returned.length === 0) {
|
|
504
|
+
detail = `提交了 ${asked.length} 个问题(${asked.join('、')});上游未返回任何 answers`
|
|
505
|
+
} else if (unusable.length > 0) {
|
|
506
|
+
detail = `提交了 ${asked.length} 个问题(${asked.join('、')});上游返回的 answers 里没有可用取值`
|
|
507
|
+
+ `(无法判断:${unusable.join('、')})`
|
|
508
|
+
} else {
|
|
509
|
+
detail = `提交了 ${asked.length} 个问题(${asked.join('、')});`
|
|
510
|
+
+ `上游仅返回了 ${returned.join('、')},与所问的问题对不上`
|
|
511
|
+
}
|
|
512
|
+
const error = `SystemOne 未产出任何可用的决策:${detail}`
|
|
513
|
+
return {
|
|
514
|
+
ok: false,
|
|
515
|
+
scenario: scenarioId,
|
|
516
|
+
provider: provider.name,
|
|
517
|
+
error,
|
|
518
|
+
answers,
|
|
519
|
+
decision,
|
|
520
|
+
labels,
|
|
521
|
+
derived: {},
|
|
522
|
+
confidences: {},
|
|
523
|
+
recommendation: '',
|
|
524
|
+
needs_human_review: true,
|
|
525
|
+
warnings,
|
|
526
|
+
summary: `## ${title}\n\n**未产出任何可用的决策**:${detail}\n\n无法给出置信度与概率分布。请核对问题定义(type 必须是 choice / noul / score;choice 的 criteria 需 2~26 个选项;score 的 criteria 需 ≥2 级标签)后重试。`,
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const assessment = reviewAssessment(answers, minConfidence)
|
|
531
|
+
const needsHumanReview = assessment.needed
|
|
449
532
|
const lines = formatAnswerLines(answers, questions)
|
|
450
533
|
const latencyMs = Number.isFinite(Number(response.latency_ms))
|
|
451
534
|
? Number(response.latency_ms)
|
|
@@ -461,6 +544,7 @@ async function executeDecision({
|
|
|
461
544
|
recommendation,
|
|
462
545
|
notes: warnings,
|
|
463
546
|
needsHumanReview,
|
|
547
|
+
reviewReasons: assessment.reasons,
|
|
464
548
|
})
|
|
465
549
|
return {
|
|
466
550
|
ok: true,
|
|
@@ -475,12 +559,14 @@ async function executeDecision({
|
|
|
475
559
|
derived,
|
|
476
560
|
confidences,
|
|
477
561
|
needs_human_review: needsHumanReview,
|
|
562
|
+
review_reasons: assessment.reasons,
|
|
478
563
|
recommendation,
|
|
479
564
|
warnings,
|
|
480
565
|
summary,
|
|
481
566
|
}
|
|
482
567
|
} catch (err) {
|
|
483
568
|
const msg = messageOf(err)
|
|
569
|
+
recordUsage(usage, { source: 'tool', ok: false, model, provider: provider?.name, scenario: scenarioId || '' })
|
|
484
570
|
return {
|
|
485
571
|
ok: false,
|
|
486
572
|
scenario: scenarioId,
|
package/lib/usage.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usage.js — 调用与 token 用量台账。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它:SystemOne 调用是**按量计费**的,而本插件默认关闭不了的两条路径
|
|
5
|
+
* (自动路由 + 场景决策)会随会话推进持续发请求。上游返回的 `usage.input_tokens`
|
|
6
|
+
* 原本只是塞进工具返回值里,既不累计也不设上限——用户看不到花销,也就没有刹车。
|
|
7
|
+
*
|
|
8
|
+
* 设计取舍(对齐生态里同类做法,如 dsh-jev-plugin 的 ledger + 双日限):
|
|
9
|
+
* - 每次调用**追加**一行 JSONL,包括失败/超时的调用(失败也可能已计费);
|
|
10
|
+
* - 日限在**发请求之前**判定并拒绝,附上该改哪个配置项;
|
|
11
|
+
* - 台账不可写时降级为「仅内存」,绝不因为记账失败而让决策失败(fail-open 记账);
|
|
12
|
+
* - 计数按**本地日**切分,跨日自动归零,不需要定时任务。
|
|
13
|
+
*
|
|
14
|
+
* 模块无第三方依赖,使用 node:fs / node:os / node:path。
|
|
15
|
+
*/
|
|
16
|
+
import { appendFileSync, mkdirSync } from 'node:fs'
|
|
17
|
+
import { homedir } from 'node:os'
|
|
18
|
+
import { join } from 'node:path'
|
|
19
|
+
|
|
20
|
+
/** token 数缺失时的兜底(上游不保证返回 usage)。 */
|
|
21
|
+
const UNKNOWN_TOKENS = 0
|
|
22
|
+
|
|
23
|
+
/** 本地日键,形如 2026-10-05。 */
|
|
24
|
+
function localDayKey(now = Date.now()) {
|
|
25
|
+
const d = new Date(now)
|
|
26
|
+
const pad = (n) => String(n).padStart(2, '0')
|
|
27
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 解析 DSH 主目录(尊重 DSH_HOME);provider.js 的凭证读取与此同源。 */
|
|
31
|
+
export function dshHome() {
|
|
32
|
+
const fromEnv = typeof process.env.DSH_HOME === 'string' ? process.env.DSH_HOME.trim() : ''
|
|
33
|
+
return fromEnv || join(homedir(), '.dsh')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 安全记账:台账是**旁路**设施,任何异常都不得影响决策或注入。
|
|
38
|
+
*
|
|
39
|
+
* 台账自身已经吞掉了写盘失败,但调用方可能传入别的实现(测试桩、将来的远端
|
|
40
|
+
* 上报),所以这里再兜一层——「记账失败绝不影响决策」是写在文档里的承诺,
|
|
41
|
+
* 不能只靠台账实现自觉。auto.js 与 tools.js 共用本函数。
|
|
42
|
+
*/
|
|
43
|
+
export function recordUsage(usage, entry) {
|
|
44
|
+
try {
|
|
45
|
+
usage?.record?.(entry)
|
|
46
|
+
} catch {
|
|
47
|
+
// 静默忽略
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 台账文件绝对路径。 */
|
|
52
|
+
export function usageLogPath(dir) {
|
|
53
|
+
const base = typeof dir === 'string' && dir.trim() ? dir.trim() : join(dshHome(), 'dsh-systemone')
|
|
54
|
+
return join(base, 'usage.jsonl')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 从任意形态的 usage 字段里取 input_tokens(容忍缺字段与字符串)。 */
|
|
58
|
+
function inputTokensOf(usage) {
|
|
59
|
+
const value = Number(usage?.input_tokens)
|
|
60
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : UNKNOWN_TOKENS
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 创建用量台账。
|
|
65
|
+
*
|
|
66
|
+
* @param {object} [options]
|
|
67
|
+
* @param {string} [options.path] - 台账文件路径;留空则用 `$DSH_HOME/dsh-systemone/usage.jsonl`
|
|
68
|
+
* @param {object} [options.logger] - 可选 logger,用于告警(写盘失败等)
|
|
69
|
+
* @returns {{ record: Function, today: Function, check: Function, formatToday: Function }}
|
|
70
|
+
*/
|
|
71
|
+
export function createUsageLedger(options = {}) {
|
|
72
|
+
const file = typeof options.path === 'string' && options.path.trim()
|
|
73
|
+
? options.path.trim()
|
|
74
|
+
: usageLogPath('')
|
|
75
|
+
const logger = options.logger
|
|
76
|
+
|
|
77
|
+
/** 内存累计:{ day, calls, tokens, failures, refused } —— 读取时先按天校验再复用 */
|
|
78
|
+
let totals = { day: localDayKey(), calls: 0, tokens: 0, failures: 0 }
|
|
79
|
+
/** 写盘是否失败过(只告警一次,避免每一步刷日志) */
|
|
80
|
+
let writeFailed = false
|
|
81
|
+
|
|
82
|
+
function rollIfNeeded(now = Date.now()) {
|
|
83
|
+
const day = localDayKey(now)
|
|
84
|
+
if (totals.day !== day) {
|
|
85
|
+
totals = { day, calls: 0, tokens: 0, failures: 0 }
|
|
86
|
+
}
|
|
87
|
+
return totals
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 记录一次调用。永不抛错——记账失败不影响决策。 */
|
|
91
|
+
function record(entry = {}) {
|
|
92
|
+
const now = Number.isFinite(entry.at) ? entry.at : Date.now()
|
|
93
|
+
const tokens = inputTokensOf(entry.usage)
|
|
94
|
+
const ok = entry.ok !== false
|
|
95
|
+
const current = rollIfNeeded(now)
|
|
96
|
+
current.calls += 1
|
|
97
|
+
current.tokens += tokens
|
|
98
|
+
if (!ok) current.failures += 1
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
mkdirSync(join(file, '..'), { recursive: true })
|
|
102
|
+
appendFileSync(file, `${JSON.stringify({
|
|
103
|
+
ts: now,
|
|
104
|
+
day: current.day,
|
|
105
|
+
source: String(entry.source || 'unknown'),
|
|
106
|
+
model: entry.model ? String(entry.model) : '',
|
|
107
|
+
provider: entry.provider ? String(entry.provider) : '',
|
|
108
|
+
scenario: entry.scenario ? String(entry.scenario) : '',
|
|
109
|
+
ok,
|
|
110
|
+
input_tokens: tokens,
|
|
111
|
+
latency_ms: Number.isFinite(Number(entry.latencyMs)) ? Number(entry.latencyMs) : null,
|
|
112
|
+
})}\n`, 'utf8')
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (!writeFailed) {
|
|
115
|
+
writeFailed = true
|
|
116
|
+
try {
|
|
117
|
+
logger?.warn?.(`@master0071/dsh-systemone[usage]: 台账写入失败,本次起降级为仅内存统计:${error?.message || error}`)
|
|
118
|
+
} catch {
|
|
119
|
+
// 日志失败不影响决策
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { calls: current.calls, tokens: current.tokens, failures: current.failures, day: current.day }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 今日累计(只读快照)。 */
|
|
127
|
+
function today(now = Date.now()) {
|
|
128
|
+
const current = rollIfNeeded(now)
|
|
129
|
+
return { day: current.day, calls: current.calls, tokens: current.tokens, failures: current.failures }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 发请求前的限额判定。
|
|
134
|
+
* @param {{dailyCallLimit:number, dailyTokenLimit:number}} limits - 0 表示不限
|
|
135
|
+
* @returns {{allowed:boolean, reason?:string, snapshot:object}}
|
|
136
|
+
*/
|
|
137
|
+
function check(limits = {}) {
|
|
138
|
+
const snapshot = today()
|
|
139
|
+
const callLimit = Number(limits.dailyCallLimit)
|
|
140
|
+
const tokenLimit = Number(limits.dailyTokenLimit)
|
|
141
|
+
if (Number.isFinite(callLimit) && callLimit > 0 && snapshot.calls >= callLimit) {
|
|
142
|
+
return {
|
|
143
|
+
allowed: false,
|
|
144
|
+
snapshot,
|
|
145
|
+
reason: `今日调用数已达上限 ${callLimit}(当前 ${snapshot.calls})。调大 dailyCallLimit,或设为 0 关闭上限。`,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (Number.isFinite(tokenLimit) && tokenLimit > 0 && snapshot.tokens >= tokenLimit) {
|
|
149
|
+
return {
|
|
150
|
+
allowed: false,
|
|
151
|
+
snapshot,
|
|
152
|
+
reason: `今日 input token 已达上限 ${tokenLimit}(当前 ${snapshot.tokens})。调大 dailyTokenLimit,或设为 0 关闭上限。`,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { allowed: true, snapshot }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 一行人类可读的今日用量(供工具输出与日志)。 */
|
|
159
|
+
function formatToday(now = Date.now()) {
|
|
160
|
+
const s = today(now)
|
|
161
|
+
const tokens = s.tokens >= 1000 ? `${(s.tokens / 1000).toFixed(1)}k` : String(s.tokens)
|
|
162
|
+
const fail = s.failures > 0 ? `,其中失败 ${s.failures} 次` : ''
|
|
163
|
+
return `今日:${s.calls} 次判断 · ${tokens} input tokens(${s.day})${fail}`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { record, today, check, formatToday, get path() { return file } }
|
|
167
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@master0071/dsh-systemone",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "DeepSeek Harness 插件:将 SystemOne 决策模型(默认云知声 u2-decision,可切换提供商)接入软件开发、需求预检、客服、内容审核、路由、销售线索、风控、招聘、数据治理、教育、需求变更等 11 大业务场景,为 Agent 返回带概率分布的决策与处置建议。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"lib/",
|
|
20
20
|
"cordis.patch.yml",
|
|
21
|
-
"README.md"
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
22
23
|
],
|
|
23
24
|
"main": "./lib/index.js",
|
|
24
25
|
"types": "./lib/index.d.ts",
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
"./package.json": "./package.json"
|
|
32
33
|
},
|
|
33
34
|
"dsh": {
|
|
35
|
+
"manifestVersion": 1,
|
|
34
36
|
"bundle": {
|
|
35
37
|
"patch": "cordis.patch.yml"
|
|
36
38
|
},
|
|
@@ -46,14 +48,18 @@
|
|
|
46
48
|
"node": ">=22.19"
|
|
47
49
|
},
|
|
48
50
|
"scripts": {
|
|
49
|
-
"check": "node
|
|
50
|
-
"test": "node --test \"test/**/*.test.mjs\""
|
|
51
|
+
"check": "node -e \"const{readdirSync}=require('node:fs'),{spawnSync}=require('node:child_process');const files=readdirSync('lib').filter((f)=>f.endsWith('.js')).sort();if(files.length===0){console.error('check: lib/ 下没有 .js 文件,检查失败');process.exit(1)}const bad=files.filter((f)=>spawnSync(process.execPath,['--check','lib/'+f],{stdio:'inherit'}).status!==0);if(bad.length>0){console.error('check: 语法检查失败 -> '+bad.join(', '));process.exit(1)}console.log('check: '+files.length+' 个 lib/*.js 语法检查通过 -> '+files.join(', '))\"",
|
|
52
|
+
"test": "node --test \"test/**/*.test.mjs\"",
|
|
53
|
+
"probe": "node scripts/probe-host.mjs",
|
|
54
|
+
"verify:docs": "node scripts/verify-doc-claims.mjs",
|
|
55
|
+
"fix:docs": "node scripts/verify-doc-claims.mjs --update"
|
|
51
56
|
},
|
|
52
57
|
"dependencies": {
|
|
53
58
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
54
59
|
},
|
|
55
60
|
"peerDependencies": {
|
|
56
61
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
62
|
+
"@deepseek-ai/dsh": ">=0.1.7-rc.1 <0.3.0",
|
|
57
63
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
58
64
|
}
|
|
59
65
|
}
|