deepseek-coordination 0.1.9 → 0.10.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.
- package/LICENSE +21 -21
- package/README.md +349 -1083
- package/README.zh-CN.md +349 -697
- package/cordis.patch.yml +5 -74
- package/lib/client.js +33772 -0
- package/lib/index.d.mts +252 -0
- package/lib/index.mjs +27374 -0
- package/package.json +98 -114
- package/client.js +0 -338
- package/src/host/bin/dsh-os.bat +0 -19
- package/src/host/bin/dsh-os.ps1 +0 -5039
- package/src/host/bin/dsh-os.sh +0 -3385
- package/src/host/bin/dsh-os7.ps1 +0 -5518
- package/src/host/command.txt +0 -136
- package/src/host/docs/command-original.txt +0 -26
- package/src/host/docs/os-spec.md +0 -528
- package/src/host/docs/permission-model.md +0 -206
- package/src/host/spec/charsets.tsv +0 -28
- package/src/host/spec/verbs.tsv +0 -120
- package/src/permission/index.js +0 -546
- package/src/plugin/index.js +0 -226
- package/src/plugin/invoke.js +0 -306
- package/src/plugin/settings.js +0 -349
- package/src/plugin/takeover.js +0 -436
- package/src/plugin/workflow.js +0 -1494
- package/tools/measure-cache.mjs +0 -223
package/tools/measure-cache.mjs
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cache-measure.mjs — measure the prefix-cache effect of this plugin's design decisions, on the real endpoint.
|
|
3
|
-
*
|
|
4
|
-
* THE MISTAKE THIS VERSION FIXES
|
|
5
|
-
*
|
|
6
|
-
* The first version salted each variant's SYSTEM PROMPT to give it its own cache lineage, and then mutated
|
|
7
|
-
* the system prompt. That compares a mutated prompt against a baseline that was never the same bytes, so
|
|
8
|
-
* every reading came back at ~96% and the measurement was worthless. The lineage identity and the thing
|
|
9
|
-
* being mutated have to be independent, and here they are: one shared, fixed system prompt and tool
|
|
10
|
-
* catalog, and the lineage distinguished by a long conversation prefix.
|
|
11
|
-
*
|
|
12
|
-
* METHOD
|
|
13
|
-
*
|
|
14
|
-
* Four users are separated by >1800 tokens of distinct filler, which exceeds the interval at which the
|
|
15
|
-
* provider carves cache prefix units, so user B's requests cannot match user A's units or each other's
|
|
16
|
-
* variants. Each reading is ONE request after a warm request on the same lineage — never two identical
|
|
17
|
-
* requests, which would persist a longer unit and hide the variant's true cost.
|
|
18
|
-
*
|
|
19
|
-
* `max_tokens: 1` keeps requests cheap; the cache prefix is settled before decoding, so the cap does not
|
|
20
|
-
* affect what is being measured.
|
|
21
|
-
*
|
|
22
|
-
* Usage: node .temporary/cache-measure.mjs
|
|
23
|
-
* The key is read from $DSH_HOME/.credentials.yaml and never printed.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { readFileSync } from 'node:fs'
|
|
27
|
-
import { join } from 'node:path'
|
|
28
|
-
|
|
29
|
-
const DSH_HOME = process.env.DSH_HOME ?? 'C:/Users/Administrator/.dsh'
|
|
30
|
-
const MODEL = 'deepseek-flash'
|
|
31
|
-
const ENDPOINT = 'https://api.deepseek.com/anthropic/v1/messages'
|
|
32
|
-
|
|
33
|
-
function apiKey() {
|
|
34
|
-
const text = readFileSync(join(DSH_HOME, '.credentials.yaml'), 'utf8')
|
|
35
|
-
const line = text.split(/\r?\n/).find((l) => l.trim().startsWith('DEEPSEEK_API_KEY:'))
|
|
36
|
-
if (line === undefined) throw new Error('DEEPSEEK_API_KEY is not present in .credentials.yaml')
|
|
37
|
-
const value = line.split(':').slice(1).join(':').trim()
|
|
38
|
-
if (value === '') throw new Error('DEEPSEEK_API_KEY is empty')
|
|
39
|
-
return value
|
|
40
|
-
}
|
|
41
|
-
const KEY = apiKey()
|
|
42
|
-
|
|
43
|
-
const FILLER = 'The quick brown fox jumps over the lazy dog and then documents the boundary conditions. '
|
|
44
|
-
/** Filler whose tokens differ per word, so two users' prefixes cannot coincide. */
|
|
45
|
-
function filler(word, blocks = 260) {
|
|
46
|
-
return Array.from({ length: blocks }, (_, i) => `${i + 1}. ${word} ${FILLER}`).join('\n')
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const BASE_SYSTEM = 'You are an AI agent powered by DeepSeek Harness. Follow the user instruction exactly.'
|
|
50
|
-
const TOOLS = [
|
|
51
|
-
{ name: 'read', description: 'Read a UTF-8 text file and return line-numbered content.', input_schema: { type: 'object', properties: { file_path: { type: 'string' } }, required: ['file_path'] } },
|
|
52
|
-
{ name: 'write', description: 'Create or fully replace a UTF-8 text file.', input_schema: { type: 'object', properties: { file_path: { type: 'string' }, content: { type: 'string' } }, required: ['file_path', 'content'] } },
|
|
53
|
-
{ name: 'pwsh', description: 'Execute a PowerShell command and return its stdout/stderr.', input_schema: { type: 'object', properties: { command: { type: 'string' }, description: { type: 'string' } }, required: ['command', 'description'] } },
|
|
54
|
-
{ name: 'todo_write', description: 'Record and update a task list to plan multi-step work and show progress.', input_schema: { type: 'object', properties: { todos: { type: 'array', items: { type: 'object' } } }, required: ['todos'] } },
|
|
55
|
-
{ name: 'workflow_plan', description: 'Intent-first workflow: agree on what is wanted and how it will be done, before doing it.', input_schema: { type: 'object', properties: { action: { type: 'string' } }, required: ['action'] } },
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
async function call({ system = BASE_SYSTEM, messages, tools = TOOLS }) {
|
|
59
|
-
const res = await fetch(ENDPOINT, {
|
|
60
|
-
method: 'POST',
|
|
61
|
-
headers: { 'content-type': 'application/json', 'x-api-key': KEY, 'anthropic-version': '2023-06-01' },
|
|
62
|
-
body: JSON.stringify({ model: MODEL, max_tokens: 1, system, messages, tools }),
|
|
63
|
-
})
|
|
64
|
-
const text = await res.text()
|
|
65
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`)
|
|
66
|
-
const json = JSON.parse(text)
|
|
67
|
-
const u = json.usage ?? {}
|
|
68
|
-
const input = u.input_tokens ?? 0
|
|
69
|
-
const cacheRead = u.cache_read_input_tokens ?? 0
|
|
70
|
-
const cacheWrite = u.cache_creation_input_tokens ?? 0
|
|
71
|
-
const total = input + cacheRead + cacheWrite
|
|
72
|
-
return { input, cacheRead, cacheWrite, total, rate: total === 0 ? 0 : cacheRead / total, content: json.content ?? [] }
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* A tool-call turn, built by hand.
|
|
77
|
-
*
|
|
78
|
-
* Thinking mode rejects `tool_choice` outright ("Thinking mode does not support this tool_choice") and
|
|
79
|
-
* requires every assistant turn it reads back to carry a `thinking` block, so neither a plain assistant
|
|
80
|
-
* message nor a forced tool call works here. An empty thinking block beside the `tool_use` is accepted,
|
|
81
|
-
* which keeps this measurement to ONE request per variant instead of paying for a model call to author
|
|
82
|
-
* each turn. `max_tokens: 1` is correct for the same reason: the cache prefix is settled before decoding,
|
|
83
|
-
* and the reply is not read.
|
|
84
|
-
*/
|
|
85
|
-
function toolTurn(index, action, result) {
|
|
86
|
-
return [
|
|
87
|
-
{
|
|
88
|
-
role: 'assistant',
|
|
89
|
-
content: [
|
|
90
|
-
{ type: 'thinking', thinking: '', signature: '' },
|
|
91
|
-
{ type: 'tool_use', id: `t${index}`, name: 'workflow_plan', input: { action } },
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
{ role: 'user', content: [{ type: 'tool_result', tool_use_id: `t${index}`, content: result }] },
|
|
95
|
-
]
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const rows = []
|
|
99
|
-
function report(name, usage) {
|
|
100
|
-
rows.push({ name, ...usage })
|
|
101
|
-
console.log(` ${name.padEnd(42)} read=${String(usage.cacheRead).padStart(6)} write=${String(usage.cacheWrite).padStart(6)} miss=${String(usage.input).padStart(5)} total=${String(usage.total).padStart(6)} hit=${(usage.rate * 100).toFixed(1)}%`)
|
|
102
|
-
return usage
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** Warm one lineage once, then send the variant once. */
|
|
106
|
-
async function variant(user, name, mutate) {
|
|
107
|
-
const base = { messages: [{ role: 'user', content: `${filler(user)}\n\nSummarise line 1 in one word.` }] }
|
|
108
|
-
await call(base)
|
|
109
|
-
return report(name, await call(mutate(base)))
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
console.log(`prefix-cache measurement — ${ENDPOINT} — model ${MODEL}`)
|
|
113
|
-
console.log('one request per variant, after one warm request on the same lineage\n')
|
|
114
|
-
|
|
115
|
-
console.log('A user: the design this plugin uses — nothing touches the system prompt or the tool catalog')
|
|
116
|
-
await variant('alpha', 'A1 unchanged (control)', (r) => r)
|
|
117
|
-
await variant('alpha', 'A2 append a user message', (r) => ({
|
|
118
|
-
messages: [...r.messages, { role: 'user', content: 'Now say OK.' }],
|
|
119
|
-
}))
|
|
120
|
-
await variant('alpha', 'A3 append a tool result', (r) => ({
|
|
121
|
-
messages: [...r.messages, ...toolTurn('a3', 'start', '{"ok":true,"phase":"plan","next":"call workflow_plan with action \\"plan\\""}')],
|
|
122
|
-
}))
|
|
123
|
-
|
|
124
|
-
console.log('\nB user: the rejected design — the phase rendered into the system prompt')
|
|
125
|
-
await variant('bravo', 'B1 unchanged (control)', (r) => r)
|
|
126
|
-
await variant('bravo', 'B2 rewrite the LEADING system prompt', () => ({
|
|
127
|
-
messages: [{ role: 'user', content: `${filler('bravo')}\n\nSummarise line 1 in one word.` }],
|
|
128
|
-
system: `${BASE_SYSTEM}\n## Workflow: plan. Call workflow_plan with action "plan".`,
|
|
129
|
-
}))
|
|
130
|
-
await variant('bravo', 'B3 append an in-history system message', () => ({
|
|
131
|
-
messages: [{ role: 'user', content: `${filler('bravo')}\n\nSummarise line 1 in one word.` }],
|
|
132
|
-
system: [
|
|
133
|
-
{ type: 'text', text: BASE_SYSTEM },
|
|
134
|
-
{ type: 'text', text: '## Workflow: plan. Call workflow_plan with action "plan".' },
|
|
135
|
-
],
|
|
136
|
-
}))
|
|
137
|
-
|
|
138
|
-
console.log('\nC user: the tool catalog')
|
|
139
|
-
await variant('charlie', 'C1 unchanged (control)', (r) => r)
|
|
140
|
-
await variant('charlie', 'C2 change one tool description', (r) => ({
|
|
141
|
-
messages: r.messages,
|
|
142
|
-
tools: TOOLS.map((t) => (t.name === 'workflow_plan' ? { ...t, description: `${t.description} (edited)` } : t)),
|
|
143
|
-
}))
|
|
144
|
-
await variant('charlie', 'C3 swap two tools', (r) => ({
|
|
145
|
-
messages: r.messages,
|
|
146
|
-
tools: [TOOLS[1], TOOLS[0], ...TOOLS.slice(2)],
|
|
147
|
-
}))
|
|
148
|
-
|
|
149
|
-
console.log('\nD user: one whole workflow, four calls, system prompt and catalog never moving')
|
|
150
|
-
const workflow = await (async () => {
|
|
151
|
-
const messages = [{ role: 'user', content: `${filler('delta')}\n\nSummarise line 1 in one word.` }]
|
|
152
|
-
await call({ messages })
|
|
153
|
-
const steps = []
|
|
154
|
-
const phases = [
|
|
155
|
-
['start', '{"ok":true,"phase":"plan","next":"call workflow_plan with action \\"plan\\""}'],
|
|
156
|
-
['plan', '{"ok":true,"phase":"choose","next":"call workflow_plan with action \\"choose\\""}'],
|
|
157
|
-
['choose', '{"ok":true,"phase":"run","next":"call workflow_plan with action \\"run\\""}'],
|
|
158
|
-
['run', '{"ok":true,"phase":"done","next":null}'],
|
|
159
|
-
]
|
|
160
|
-
for (const [index, [action, result]] of phases.entries()) {
|
|
161
|
-
messages.push(...toolTurn(index, action, result))
|
|
162
|
-
const usage = await call({ messages })
|
|
163
|
-
steps.push(usage)
|
|
164
|
-
console.log(` ${String(index + 1)} ${action.padEnd(38)} read=${String(usage.cacheRead).padStart(6)} write=${String(usage.cacheWrite).padStart(6)} miss=${String(usage.input).padStart(5)} total=${String(usage.total).padStart(6)} hit=${(usage.rate * 100).toFixed(1)}%`)
|
|
165
|
-
}
|
|
166
|
-
return steps
|
|
167
|
-
})()
|
|
168
|
-
|
|
169
|
-
console.log('\nE user: the same four phases with the prompt rewritten each time')
|
|
170
|
-
const rewritten = await (async () => {
|
|
171
|
-
const messages = [{ role: 'user', content: `${filler('echo')}\n\nSummarise line 1 in one word.` }]
|
|
172
|
-
await call({ messages })
|
|
173
|
-
const out = []
|
|
174
|
-
for (const phase of ['plan', 'choose', 'run']) {
|
|
175
|
-
const usage = await call({ messages, system: `${BASE_SYSTEM}\n## Workflow: ${phase}.` })
|
|
176
|
-
out.push(usage)
|
|
177
|
-
console.log(` phase -> ${phase.padEnd(34)} read=${String(usage.cacheRead).padStart(6)} write=${String(usage.cacheWrite).padStart(6)} miss=${String(usage.input).padStart(5)} total=${String(usage.total).padStart(6)} hit=${(usage.rate * 100).toFixed(1)}%`)
|
|
178
|
-
}
|
|
179
|
-
return out
|
|
180
|
-
})()
|
|
181
|
-
|
|
182
|
-
// --- verdict -----------------------------------------------------------------------------------
|
|
183
|
-
const mean = (list) => list.reduce((a, u) => a + u.rate, 0) / list.length
|
|
184
|
-
const find = (prefix) => rows.find((r) => r.name.startsWith(prefix))
|
|
185
|
-
const workflowRate = mean(workflow)
|
|
186
|
-
const rewriteRate = mean(rewritten)
|
|
187
|
-
const appendRate = (find('A2').rate + find('A3').rate) / 2
|
|
188
|
-
const inHistory = find('B3').rate
|
|
189
|
-
const leading = find('B2').rate
|
|
190
|
-
const missCost = rewritten.reduce((a, u) => a + u.input, 0)
|
|
191
|
-
|
|
192
|
-
console.log('\n=== results ===')
|
|
193
|
-
console.log(`appending history (the plugin's shape) : ${(appendRate * 100).toFixed(1)}% mean hit`)
|
|
194
|
-
console.log(`one workflow, four calls, appending only : ${(workflowRate * 100).toFixed(1)}% mean hit`)
|
|
195
|
-
console.log(`rewriting the leading system prompt : ${(leading * 100).toFixed(1)}% hit, and ${(rewriteRate * 100).toFixed(1)}% across three phases`)
|
|
196
|
-
console.log(`appending an in-history system message : ${(inHistory * 100).toFixed(1)}% hit`)
|
|
197
|
-
console.log(`changing a tool description : ${(find('C2').rate * 100).toFixed(1)}% hit`)
|
|
198
|
-
console.log(`swapping two tools : ${(find('C3').rate * 100).toFixed(1)}% hit`)
|
|
199
|
-
console.log(`cache reads the four appended calls kept : ${workflow.reduce((a, u) => a + u.cacheRead, 0)} tokens`)
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* The claims this experiment is entitled to make.
|
|
203
|
-
*
|
|
204
|
-
* Only the append side. The mutation readings (rewriting the prompt, editing or reordering tools) are NOT
|
|
205
|
-
* asserted here, and that is a deliberate retreat after measuring them twice and getting opposite answers:
|
|
206
|
-
* the first run read 0.0% for all of them, the second read 97%. The provider documents why — it persists a
|
|
207
|
-
* common prefix once it detects one ACROSS requests, so by the second run my own repeated identical
|
|
208
|
-
* prefixes had been learned, and a mutation that changes them still matched. Per-variant A/B isolation on
|
|
209
|
-
* a shared account therefore cannot settle the mutation side; the append side is stable across both runs
|
|
210
|
-
* and is what this plugin's design actually depends on.
|
|
211
|
-
*/
|
|
212
|
-
const claims = {
|
|
213
|
-
'appending history keeps the cache (>= 90%)': appendRate >= 0.9,
|
|
214
|
-
'a whole workflow of appended calls stays warm (>= 90%)': workflowRate >= 0.9,
|
|
215
|
-
}
|
|
216
|
-
console.log('\n=== claims ===')
|
|
217
|
-
let failed = 0
|
|
218
|
-
for (const [claim, ok] of Object.entries(claims)) {
|
|
219
|
-
console.log(` ${ok ? 'HOLDS ' : 'FAILED '} ${claim}`)
|
|
220
|
-
if (!ok) failed += 1
|
|
221
|
-
}
|
|
222
|
-
console.log(failed === 0 ? '\nRESULT: PASS' : `\nRESULT: FAIL (${failed} claim(s) did not hold)`)
|
|
223
|
-
process.exit(failed === 0 ? 0 : 1)
|