dsh-jev-decide 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/lib/index.js +262 -0
  4. package/package.json +36 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nanami-0713
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # dsh-jev-decide
2
+
3
+ 把 [TypeSafe Jev](https://docs.typesafe.ai/)("System One" 决策模型)接入 DSH:注册一个 agent 工具 **`jev_decide`**,让 agent 在需要"快而准的判断"时调用 Jev,而不是让对话模型凭感觉猜。
4
+
5
+ ## 为什么不是"再加一个对话模型"
6
+
7
+ Jev 不生成文本。它对输入 `state` 回答类型化问题并返回**校准过的概率**:
8
+
9
+ | type | 问法 | 返回 |
10
+ | --- | --- | --- |
11
+ | `noul` | 是/否问题 | `answer` = 是的概率 0..1 |
12
+ | `choice` | 从选项里挑一个(需 `options` ≥2) | `answer` = 选中项 + `probabilities` 全分布 + `confidence` |
13
+ | `score` | 按有序等级打分(需 `levels` ≥2) | `answer` = 概率加权分值 + 分布 + `confidence` |
14
+
15
+ 适合:消息紧急度分级、意图路由、guardrail 检查、二选一决策、按 rubric 打分。不适合:写代码、写文案、任何需要生成文本的活。
16
+
17
+ ## 工具签名
18
+
19
+ ```
20
+ jev_decide({
21
+ state: string, // 必填,要评估的内容(纯文本)
22
+ question: string, // 必填,要做的判断
23
+ type?: 'noul' | 'choice' | 'score', // 默认 noul
24
+ options?: string[], // type=choice 时必填(≥2)
25
+ levels?: string[], // type=score 时必填(≥2,从低到高)
26
+ model?: string, // 默认 jev-latest(当前 jev-1.13.0)
27
+ }) → { model, type, answer, confidence?, probabilities?, usage }
28
+ ```
29
+
30
+ ## 凭证解析顺序
31
+
32
+ 1. 插件配置的 `apiKey`(cordis patch config 或数据目录 config.json)
33
+ 2. 环境变量 `TYPESAFE_API_KEY`
34
+ 3. `~/.dsh/.credentials.yaml` 的 `refs.TYPESAFE_AI_API_KEY`(与 DSH 凭证缝同源——本机已配置,无需重复)
35
+
36
+ ## 配置
37
+
38
+ 数据目录 `$DSH_HOME/plugins/dsh-jev-decide/config.json`(或 cordis patch 的 `config:`):
39
+
40
+ ```json
41
+ { "model": "jev-latest", "timeoutMs": 15000, "baseUrl": "https://api.typesafe.ai/v1" }
42
+ ```
43
+
44
+ ## 安装/接线
45
+
46
+ **插件市场(npm 上架后)**:
47
+
48
+ ```sh
49
+ dsh plugin --profile web add dsh-jev-decide
50
+ ```
51
+
52
+ **手动接线(本机已完成的步骤)**:
53
+
54
+ 1. 源码:`~/dsh/plugins/dsh-jev-decide/`
55
+ 2. `~/.dsh/profiles/web/package.json` 依赖:`"dsh-jev-decide": "link:~/dsh/plugins/dsh-jev-decide"`
56
+ 3. `~/.dsh/profiles/web/cordis.patch.yml`:`- insert: [- id: dsh-jev-decide, name: dsh-jev-decide]`
57
+ 4. 因无 shell 无法建 pnpm symlink,`profiles/web/node_modules/dsh-jev-decide/` 放的是实体副本;下次 `pnpm install` 会把它规范成 link,无副作用。
58
+ 5. **生效需重启 DSH**(host 插件在进程启动时装配)。
59
+
60
+ ## 生态扫描:同类项目(2026-09-19)
61
+
62
+ Jev 发布三天内 GitHub 上已出现多个 DSH 接入实现,各有侧重:
63
+
64
+ | 项目 | 特点 |
65
+ | --- | --- |
66
+ | [noetion/dsh-jev](https://github.com/noetion/dsh-jev) | `jev_ask` 工具,**一次 state 可混搭多问题**;bundle + 内置 skill;配套 mock 传输测试,工程最完整 |
67
+ | [kaijia323/dsh-plugin-jev](https://github.com/kaijia323/dsh-plugin-jev) | 同名 `jev_decide`;**双传输**(官方 API + Vercel AI Gateway);指数退避重试、state/问题数上限守卫、置信度阈值路由示例 |
68
+ | [buberlo/dsh-jev](https://github.com/buberlo/dsh-jev) | 决策层封装 |
69
+ | [zhangxaochen/dsh-jev](https://github.com/zhangxaochen/dsh-jev) | 插件套件 |
70
+
71
+ 本插件的差异点:
72
+
73
+ - **单文件、零构建**(纯 ESM JS),copy 即用,没有 TypeScript 工具链依赖
74
+ - **凭证零配置**:直接复用 DSH 凭证缝(`~/.dsh/.credentials.yaml` 的 `TYPESAFE_AI_API_KEY`),不要求 env 也不在 patch 里落明文
75
+ - 内建 429/529 官方建议的短退避重试
76
+ - 只做一件事:把 `jev_decide` 注册进工具集;多问题、双网关等进阶需求请用上面的项目
77
+
78
+ ## 定价参考
79
+
80
+ Jev 1.13:$0.042/百万输入 token,输出免费;官方限流 250k tok/s、1200 req/min([模型页](https://docs.typesafe.ai/models))。
package/lib/index.js ADDED
@@ -0,0 +1,262 @@
1
+ /**
2
+ * dsh-jev-decide — 把 TypeSafe Jev(System One 决策模型)接入 DSH 的 agent 工具集。
3
+ *
4
+ * Jev 不生成文本:它对 `state` 回答类型化问题并返回校准过的概率
5
+ * (noul=是/否概率,choice=选项+分布,score=有序评分+分布)。
6
+ * 本插件把它注册为一个 agent 工具 `jev_decide`,适用于:
7
+ * 路由、分类、紧急度分级、guardrail 检查等"快而准的判断"场景,
8
+ * 而不是让对话模型凭感觉猜。
9
+ *
10
+ * API: POST {baseUrl}/systemone(官方文档 https://docs.typesafe.ai/api)
11
+ * - 401/422/429/529 语义见文档;429/529 做一次短退避重试。
12
+ *
13
+ * 凭证解析顺序(第一个非空者生效):
14
+ * 1. cordis patch / 插件 config 的 `apiKey` 字段
15
+ * 2. 环境变量 TYPESAFE_API_KEY
16
+ * 3. ~/.dsh/.credentials.yaml refs.TYPESAFE_AI_API_KEY(与 DSH 凭证缝同源,避免明文双份)
17
+ *
18
+ * 配置(cordis patch 的 config 或 $DSH_HOME/plugins/dsh-jev-decide/config.json):
19
+ * baseUrl 默认 https://api.typesafe.ai/v1
20
+ * model 默认 jev-latest
21
+ * timeoutMs 默认 15000
22
+ * apiKey 可选,见上方解析顺序
23
+ */
24
+ import { readFile } from 'node:fs/promises';
25
+ import { homedir } from 'node:os';
26
+ import { join } from 'node:path';
27
+ import { defineTool } from '@deepseek-ai/dsh-tools';
28
+
29
+ export const name = 'dsh-jev-decide';
30
+ export const inject = ['tools'];
31
+
32
+ const DEFAULTS = Object.freeze({
33
+ baseUrl: 'https://api.typesafe.ai/v1',
34
+ model: 'jev-latest',
35
+ timeoutMs: 15000,
36
+ });
37
+
38
+ //#region ── 纯函数(导出仅为可测性)────────────────────────────────
39
+
40
+ /** 依据工具入参构造一个 TypeSafe question 对象。校验失败抛 Error。 */
41
+ export function buildQuestion(args) {
42
+ const type = args.type ?? 'noul';
43
+ switch (type) {
44
+ case 'noul':
45
+ return { type, instructions: args.question };
46
+ case 'choice': {
47
+ if (!Array.isArray(args.options) || args.options.length < 2) {
48
+ throw new Error('jev_decide: type=choice 需要 options(≥2 个选项字符串)');
49
+ }
50
+ const criteria = {};
51
+ for (const option of args.options) criteria[option] = null;
52
+ return { type, instructions: args.question, criteria };
53
+ }
54
+ case 'score': {
55
+ if (!Array.isArray(args.levels) || args.levels.length < 2) {
56
+ throw new Error('jev_decide: type=score 需要 levels(≥2 个有序等级描述)');
57
+ }
58
+ return { type, instructions: args.question, criteria: [...args.levels] };
59
+ }
60
+ default:
61
+ throw new Error(`jev_decide: 未知 question 类型 ${JSON.stringify(type)}(应为 noul/choice/score)`);
62
+ }
63
+ }
64
+
65
+ /** 构造 /v1/systemone 请求体。 */
66
+ export function buildPayload(args, model) {
67
+ if (typeof args.state !== 'string' || args.state.length === 0) {
68
+ throw new Error('jev_decide: state 不能为空');
69
+ }
70
+ if (typeof args.question !== 'string' || args.question.length === 0) {
71
+ throw new Error('jev_decide: question 不能为空');
72
+ }
73
+ return {
74
+ model,
75
+ state: args.state,
76
+ questions: { decision: buildQuestion(args) },
77
+ };
78
+ }
79
+
80
+ /** 把 /v1/systemone 响应压缩成工具输出(answers.decision + usage)。 */
81
+ export function summarize(response) {
82
+ const answer = response?.answers?.decision;
83
+ if (answer === undefined || answer === null) {
84
+ throw new Error(`jev_decide: 响应缺少 answers.decision:${JSON.stringify(response).slice(0, 300)}`);
85
+ }
86
+ const type = answer.type;
87
+ const output = {
88
+ model: typeof response.model === 'string' ? response.model : 'unknown',
89
+ type,
90
+ answer: type === 'noul' ? answer.noul
91
+ : type === 'choice' ? answer.choice
92
+ : type === 'score' ? answer.score
93
+ : answer,
94
+ };
95
+ if (typeof answer.confidence === 'number') output.confidence = answer.confidence;
96
+ if (answer.probabilities !== undefined && answer.probabilities !== null) {
97
+ output.probabilities = { ...answer.probabilities };
98
+ }
99
+ if (response.usage !== undefined && response.usage !== null) {
100
+ output.usage = {
101
+ input_tokens: response.usage.input_tokens ?? 0,
102
+ output_tokens: response.usage.output_tokens ?? 0,
103
+ };
104
+ }
105
+ return output;
106
+ }
107
+
108
+ //#endregion
109
+
110
+ //#region ── 凭证与配置 ────────────────────────────────────────────
111
+
112
+ function dshHome() {
113
+ return process.env.DSH_HOME || join(homedir(), '.dsh');
114
+ }
115
+
116
+ /** 极简读取 ~/.dsh/.credentials.yaml refs 里的一行引用(避免引入 YAML 依赖)。 */
117
+ async function readCredentialRef(ref) {
118
+ try {
119
+ const raw = await readFile(join(dshHome(), '.credentials.yaml'), 'utf8');
120
+ const inRefs = /^refs:\s*$/m.test(raw);
121
+ if (!inRefs) return undefined;
122
+ for (const line of raw.split('\n')) {
123
+ const match = new RegExp(`^\\s{2}${ref}:\\s*(.+?)\\s*$`).exec(line);
124
+ if (match) return match[1];
125
+ }
126
+ } catch {
127
+ /* 文件不存在/不可读 → 视为未配置 */
128
+ }
129
+ return undefined;
130
+ }
131
+
132
+ async function loadPluginFileConfig() {
133
+ try {
134
+ const raw = await readFile(join(dshHome(), 'plugins', name, 'config.json'), 'utf8');
135
+ return JSON.parse(raw);
136
+ } catch {
137
+ return {};
138
+ }
139
+ }
140
+
141
+ async function resolveKey(config) {
142
+ if (typeof config.apiKey === 'string' && config.apiKey.length > 0) return config.apiKey;
143
+ if (typeof process.env.TYPESAFE_API_KEY === 'string' && process.env.TYPESAFE_API_KEY.length > 0) {
144
+ return process.env.TYPESAFE_API_KEY;
145
+ }
146
+ return readCredentialRef('TYPESAFE_AI_API_KEY');
147
+ }
148
+
149
+ //#endregion
150
+
151
+ export async function apply(ctx, config = {}) {
152
+ const fileConfig = await loadPluginFileConfig();
153
+ const cfg = {
154
+ ...DEFAULTS,
155
+ ...fileConfig,
156
+ ...config,
157
+ };
158
+
159
+ ctx.tools.register(defineTool({
160
+ name: 'jev_decide',
161
+ description: [
162
+ 'Ask the TypeSafe Jev "System One" decision model a typed question about a state and get a calibrated, structured answer (NOT a chat/text model).',
163
+ 'Use for fast decisions: urgency classification, intent routing, guardrails, ranking between options, scoring against a rubric.',
164
+ 'type=noul → yes/no probability; type=choice → pick one option (requires options) and returns the full distribution; type=score → rating on ordered levels (requires levels, ≥2).',
165
+ ].join(' '),
166
+ parameters: {
167
+ state: {
168
+ type: 'string',
169
+ required: true,
170
+ description: 'The content to evaluate: a message, an email, a log line, a short document… (text only)',
171
+ },
172
+ question: {
173
+ type: 'string',
174
+ required: true,
175
+ description: 'The decision to make, e.g. "Does this message convey urgency?" or "Which team should handle this?"',
176
+ },
177
+ type: {
178
+ type: 'string',
179
+ enum: ['noul', 'choice', 'score'],
180
+ description: 'Question type. Defaults to noul (yes/no probability).',
181
+ },
182
+ options: {
183
+ type: 'array',
184
+ description: 'For type=choice: the option labels (≥2), e.g. ["billing", "technical", "sales"].',
185
+ items: { type: 'string' },
186
+ },
187
+ levels: {
188
+ type: 'array',
189
+ description: 'For type=score: ordered level descriptions from low to high (≥2), e.g. ["Calm", "Frustrated", "Very angry"].',
190
+ items: { type: 'string' },
191
+ },
192
+ model: {
193
+ type: 'string',
194
+ description: 'TypeSafe model id. Defaults to jev-latest (currently jev-1.13.0).',
195
+ },
196
+ },
197
+ output: {
198
+ schema: {
199
+ type: 'object',
200
+ additionalProperties: false,
201
+ properties: {
202
+ model: { type: 'string', required: true },
203
+ type: { type: 'string', required: true },
204
+ answer: {
205
+ required: true,
206
+ oneOf: [
207
+ { type: 'number', description: 'noul → yes-probability 0..1; score → probability-weighted level value' },
208
+ { type: 'string', description: 'choice → the chosen option label' },
209
+ ],
210
+ },
211
+ confidence: { type: 'number' },
212
+ probabilities: { type: 'object', additionalProperties: true },
213
+ usage: {
214
+ type: 'object',
215
+ additionalProperties: false,
216
+ properties: {
217
+ input_tokens: { type: 'integer' },
218
+ output_tokens: { type: 'integer' },
219
+ },
220
+ },
221
+ },
222
+ },
223
+ render: (_args, value) => [{
224
+ type: 'text',
225
+ text: JSON.stringify(value),
226
+ }],
227
+ },
228
+ timeoutMs: cfg.timeoutMs + 5000,
229
+ async execute(args, exec) {
230
+ const apiKey = await resolveKey(cfg);
231
+ if (!apiKey) {
232
+ throw new Error('jev_decide: 未找到 Typesafe API key(按序检查:插件 config.apiKey → 环境变量 TYPESAFE_API_KEY → ~/.dsh/.credentials.yaml refs.TYPESAFE_AI_API_KEY)');
233
+ }
234
+ const payload = buildPayload(args, typeof args.model === 'string' && args.model.length > 0 ? args.model : cfg.model);
235
+ const url = `${cfg.baseUrl.replace(/\/+$/, '')}/systemone`;
236
+
237
+ // 429/529 官方建议退避重试:这里做一次 1s 短退避,共最多 2 次尝试。
238
+ let response;
239
+ for (let attempt = 0; ; attempt++) {
240
+ response = await fetch(url, {
241
+ method: 'POST',
242
+ headers: {
243
+ authorization: `Bearer ${apiKey}`,
244
+ 'content-type': 'application/json',
245
+ },
246
+ body: JSON.stringify(payload),
247
+ signal: exec.signal,
248
+ });
249
+ if ((response.status === 429 || response.status === 529) && attempt === 0) {
250
+ await new Promise((resolve) => setTimeout(resolve, 1000));
251
+ continue;
252
+ }
253
+ break;
254
+ }
255
+ if (!response.ok) {
256
+ const body = (await response.text()).slice(0, 300);
257
+ throw new Error(`jev_decide: Typesafe API HTTP ${response.status}: ${body}`);
258
+ }
259
+ return summarize(await response.json());
260
+ },
261
+ }));
262
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "dsh-jev-decide",
3
+ "version": "0.1.0",
4
+ "description": "DSH 插件:把 TypeSafe Jev(System One 决策模型)注册为 agent 工具 jev_decide。调用 https://api.typesafe.ai/v1/systemone,让 agent 在路由/分级/guardrail 等场景拿到带校准概率的结构化判定(noul/choice/score),而不是让大模型瞎猜。",
5
+ "private": false,
6
+ "type": "module",
7
+ "main": "./lib/index.js",
8
+ "files": [
9
+ "lib",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "exports": {
17
+ ".": "./lib/index.js"
18
+ },
19
+ "keywords": [
20
+ "dsh-plugin",
21
+ "deepseek-harness",
22
+ "typesafe",
23
+ "jev",
24
+ "system-one",
25
+ "decision"
26
+ ],
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/nanami-0713/dsh-jev-decide.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/nanami-0713/dsh-jev-decide/issues"
34
+ },
35
+ "homepage": "https://github.com/nanami-0713/dsh-jev-decide#readme"
36
+ }