dsh-model-everything 0.1.0 → 0.3.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/README.en-US.md +102 -81
- package/README.md +88 -76
- package/lib/automaton-tool.js +69 -0
- package/lib/automaton.js +327 -0
- package/lib/compose-tool.js +11 -2
- package/lib/concurrency-tool.js +6 -1
- package/lib/data-engine.js +1 -1
- package/lib/data-tool.js +18 -5
- package/lib/design-tool.js +21 -8
- package/lib/edit-tool.js +111 -0
- package/lib/editor.js +293 -0
- package/lib/engine.js +8 -1
- package/lib/export-tool.js +6 -1
- package/lib/index.js +4 -0
- package/lib/model-file.js +48 -0
- package/lib/presentation.js +81 -0
- package/lib/report-diff-tool.js +6 -1
- package/lib/report-view.js +92 -15
- package/lib/routing.js +85 -0
- package/lib/scan.js +547 -0
- package/lib/structure-tool.js +34 -1
- package/lib/structure.js +28 -3
- package/lib/tool.js +19 -4
- package/lib/types/automaton-tool.d.ts +2 -0
- package/lib/types/automaton.d.ts +57 -0
- package/lib/types/edit-tool.d.ts +2 -0
- package/lib/types/editor.d.ts +46 -0
- package/lib/types/engine.d.ts +1 -0
- package/lib/types/model-file.d.ts +7 -0
- package/lib/types/presentation.d.ts +59 -0
- package/lib/types/report-view.d.ts +8 -1
- package/lib/types/routing.d.ts +31 -0
- package/lib/types/scan.d.ts +37 -0
- package/lib/types/structure.d.ts +16 -0
- package/lib/uml-tool.js +16 -6
- package/package.json +8 -3
- package/skills/model-everything/SKILL.md +51 -79
- package/skills/model-everything/references/automata.md +102 -0
- package/skills/model-everything-interleaving/SKILL.zh-CN.md +60 -60
- package/skills/model-everything-report-reading/SKILL.md +137 -137
- package/src/automaton-tool.ts +73 -0
- package/src/automaton.ts +375 -0
- package/src/compose-tool.ts +13 -3
- package/src/concurrency-tool.ts +7 -1
- package/src/data-engine.ts +1 -1
- package/src/data-tool.ts +19 -5
- package/src/design-tool.ts +74 -60
- package/src/edit-tool.ts +119 -0
- package/src/editor.ts +297 -0
- package/src/engine.ts +8 -1
- package/src/export-tool.ts +87 -81
- package/src/index.ts +4 -0
- package/src/model-file.ts +49 -0
- package/src/presentation.ts +125 -0
- package/src/report-diff-tool.ts +7 -1
- package/src/report-view.ts +534 -451
- package/src/routing.ts +96 -0
- package/src/scan.ts +564 -0
- package/src/structure-tool.ts +33 -1
- package/src/structure.ts +37 -3
- package/src/tool.ts +19 -4
- package/src/uml-tool.ts +240 -230
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a JSON model from a file instead of requiring it inline.
|
|
3
|
+
*
|
|
4
|
+
* A model is the largest argument any of these tools takes, and it is the one that arrives as
|
|
5
|
+
* streamed tool-call JSON: the provider rejected one session with `tool input is invalid JSON`
|
|
6
|
+
* after the model spent sixteen steps hand-editing a nested model argument. Reading it from the
|
|
7
|
+
* workspace removes that failure mode and the tokens.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from 'node:fs'
|
|
10
|
+
import { resolve } from 'node:path'
|
|
11
|
+
|
|
12
|
+
import { OptionShapeError } from './option-shape.js'
|
|
13
|
+
|
|
14
|
+
/** Read and parse one JSON file. Formatting is irrelevant: whitespace, BOM and pretty printing all parse. */
|
|
15
|
+
export function readJsonFile(path: unknown, option: string, code: string): unknown {
|
|
16
|
+
if (typeof path !== 'string' || path.trim() === '') {
|
|
17
|
+
throw new OptionShapeError(code, `${option} must be a non-empty path to a JSON file.`)
|
|
18
|
+
}
|
|
19
|
+
const full = resolve(path)
|
|
20
|
+
let text: string
|
|
21
|
+
try {
|
|
22
|
+
text = readFileSync(full, 'utf8')
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
25
|
+
throw new OptionShapeError(code, `${option} could not be read: ${reason}. The path resolves against the process working directory; relative paths in a session resolve against the session's workspace.`)
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(text.replace(/^\uFEFF/, ''))
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
31
|
+
throw new OptionShapeError(code, `${option} is not valid JSON: ${reason}.`)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Exactly one of the inline value and the file path. Both is ambiguous, neither is a missing
|
|
37
|
+
* argument, and each gets its own sentence so the caller knows which one to drop.
|
|
38
|
+
*/
|
|
39
|
+
export function oneOf(value: unknown, path: unknown, inlineName: string, pathName: string, code: string): unknown {
|
|
40
|
+
const hasValue = value !== undefined
|
|
41
|
+
const hasPath = path !== undefined
|
|
42
|
+
if (hasValue && hasPath) {
|
|
43
|
+
throw new OptionShapeError(code, `Pass either ${inlineName} or ${pathName}, not both: the file would be read and then ignored, or the other way round.`)
|
|
44
|
+
}
|
|
45
|
+
if (!hasValue && !hasPath) {
|
|
46
|
+
throw new OptionShapeError(code, `Pass ${inlineName} or ${pathName}: the model has to come from somewhere.`)
|
|
47
|
+
}
|
|
48
|
+
return hasPath ? readJsonFile(path, pathName, code) : value
|
|
49
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a UI shows for a report, separated from what the model reads.
|
|
3
|
+
*
|
|
4
|
+
* A report is written for a model: it names every check, every finding and the next step, because the
|
|
5
|
+
* model has to act on all of it. A reader wants four things instead - did it run, what does it decide,
|
|
6
|
+
* what is the first thing wrong, what to do next - and wants the rest folded away.
|
|
7
|
+
*
|
|
8
|
+
* Two host hooks carry that split:
|
|
9
|
+
*
|
|
10
|
+
* - `output.presentationMeta` projects a small JSON payload the host persists on the result; a client
|
|
11
|
+
* card narrows it without parsing prose.
|
|
12
|
+
* - `presentResult` returns the card body from that payload, so a UI that supports it shows the summary
|
|
13
|
+
* and a UI that does not falls back to the raw text. The model-facing content never changes.
|
|
14
|
+
*
|
|
15
|
+
* Both are pure and replayable: they read the persisted value, never the filesystem.
|
|
16
|
+
*/
|
|
17
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
18
|
+
import { routeFor } from './routing.js'
|
|
19
|
+
import type { ToolResultView } from '@deepseek-ai/dsh-tools'
|
|
20
|
+
|
|
21
|
+
/** One finding, as a card needs it: a code to name it and a sentence to read. */
|
|
22
|
+
export interface CardFinding {
|
|
23
|
+
code: string
|
|
24
|
+
severity: string
|
|
25
|
+
message: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The payload a client card narrows. Small on purpose: a card is a summary, not a second report. */
|
|
29
|
+
export interface ReportCard {
|
|
30
|
+
verdict?: string
|
|
31
|
+
verdictReason?: string
|
|
32
|
+
errors: number
|
|
33
|
+
warnings: number
|
|
34
|
+
checksRun?: number
|
|
35
|
+
first: CardFinding | null
|
|
36
|
+
/** The one step a reader should take, already a sentence. */
|
|
37
|
+
next: string | null
|
|
38
|
+
/** Which family produced this, so a card can pick an icon and a colour without parsing codes. */
|
|
39
|
+
family: string
|
|
40
|
+
/** What this family owns, and where the case just outside it goes. One table, not a second prose copy. */
|
|
41
|
+
route?: { owns: string; instead: string }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const SHORT = 160
|
|
45
|
+
|
|
46
|
+
function shorten(text: string, limit = SHORT): string {
|
|
47
|
+
const flat = text.replace(/\s+/g, ' ').trim()
|
|
48
|
+
return flat.length <= limit ? flat : flat.slice(0, limit - 1).trimEnd() + '…'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
52
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function array(value: unknown): unknown[] {
|
|
56
|
+
return Array.isArray(value) ? value : []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Project a report into the card payload.
|
|
61
|
+
*
|
|
62
|
+
* Read defensively: this runs on replay too, and a payload that once had a shape may come back with a
|
|
63
|
+
* field missing. A card that throws would take the whole session view with it.
|
|
64
|
+
*/
|
|
65
|
+
export function reportCard(value: unknown, family: string): ReportCard {
|
|
66
|
+
const report = record(value) ?? {}
|
|
67
|
+
const findings = array(report.findings)
|
|
68
|
+
.map((entry) => record(entry))
|
|
69
|
+
.filter((entry): entry is Record<string, unknown> => entry !== undefined)
|
|
70
|
+
const severity = (entry: Record<string, unknown>): string => String(entry.severity ?? 'info')
|
|
71
|
+
const errors = findings.filter((entry) => severity(entry) === 'error')
|
|
72
|
+
const warnings = findings.filter((entry) => severity(entry) === 'warning')
|
|
73
|
+
const summary = record(report.summary)
|
|
74
|
+
const checksRun = typeof summary?.checksRun === 'number'
|
|
75
|
+
? summary.checksRun
|
|
76
|
+
: array(report.checks).length > 0
|
|
77
|
+
? array(report.checks).length
|
|
78
|
+
: undefined
|
|
79
|
+
const first = errors[0] ?? warnings[0]
|
|
80
|
+
const next = array(report.nextSteps).map((step) => String(step))[0]
|
|
81
|
+
return {
|
|
82
|
+
...(typeof report.verdict === 'string' ? { verdict: report.verdict } : {}),
|
|
83
|
+
...(typeof report.verdictReason === 'string' ? { verdictReason: shorten(report.verdictReason) } : {}),
|
|
84
|
+
errors: errors.length,
|
|
85
|
+
warnings: warnings.length,
|
|
86
|
+
...(checksRun === undefined ? {} : { checksRun }),
|
|
87
|
+
first: first === undefined
|
|
88
|
+
? null
|
|
89
|
+
: { code: String(first.code ?? 'UNKNOWN'), severity: severity(first), message: shorten(String(first.message ?? '')) },
|
|
90
|
+
next: next === undefined ? null : shorten(next),
|
|
91
|
+
family,
|
|
92
|
+
...(routeFor(family) === undefined
|
|
93
|
+
? {}
|
|
94
|
+
: { route: { owns: routeFor(family)?.owns ?? '', instead: routeFor(family)?.notFor.tool ?? '' } }),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The card body: four lines, in the order a reader asks the questions. */
|
|
99
|
+
export function cardBlocks(card: ReportCard): ContentBlock[] {
|
|
100
|
+
const lines: string[] = []
|
|
101
|
+
const head = card.verdict === undefined ? card.family : card.verdict.toUpperCase()
|
|
102
|
+
const counts = card.checksRun === undefined
|
|
103
|
+
? `${String(card.errors)} error(s), ${String(card.warnings)} warning(s)`
|
|
104
|
+
: `${String(card.checksRun)} checks · ${String(card.errors)} error(s) · ${String(card.warnings)} warning(s)`
|
|
105
|
+
lines.push(head + ' — ' + counts)
|
|
106
|
+
if (card.verdictReason !== undefined) lines.push(card.verdictReason)
|
|
107
|
+
if (card.first !== null) {
|
|
108
|
+
lines.push((card.first.severity === 'error' ? '✗ ' : '⚠ ') + card.first.code + ': ' + card.first.message)
|
|
109
|
+
}
|
|
110
|
+
if (card.next !== null) lines.push('next: ' + card.next)
|
|
111
|
+
return [{ type: 'text', text: lines.join('\n') }]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* `presentResult` for a report tool: render the card from the persisted payload, and fall back to the
|
|
116
|
+
* raw content by returning undefined when there is none.
|
|
117
|
+
*/
|
|
118
|
+
export function reportResultView(family: string) {
|
|
119
|
+
return (_args: unknown, result: { content: ContentBlock[]; meta?: unknown }): ToolResultView | undefined => {
|
|
120
|
+
const card = record(result.meta) as unknown as ReportCard | undefined
|
|
121
|
+
if (card === undefined || card === null) return undefined
|
|
122
|
+
if (card.verdict === undefined && card.next === null && card.first === null) return undefined
|
|
123
|
+
return { card: 'generic', title: family + ' ' + (card.verdict ?? '') , content: cardBlocks(card) }
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/report-diff-tool.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
|
2
2
|
import type { JsonValue } from './json-value.js'
|
|
3
3
|
import { diffReports } from './baseline.js'
|
|
4
4
|
import { refusalVerdict } from './engine.js'
|
|
5
|
+
import { reportCard, reportResultView } from './presentation.js'
|
|
5
6
|
import { renderReportText } from './report-view.js'
|
|
6
7
|
|
|
7
8
|
export const REPORT_DIFF_TOOL_NAME = 'report_diff'
|
|
@@ -54,7 +55,12 @@ export const reportDiffTool = defineTool({
|
|
|
54
55
|
render(args, value) {
|
|
55
56
|
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
56
57
|
},
|
|
57
|
-
|
|
58
|
+
// The card a UI shows, separated from the text the model reads.
|
|
59
|
+
presentationMeta(_args, value) {
|
|
60
|
+
return reportCard(value, REPORT_DIFF_TOOL_NAME) as unknown as JsonValue
|
|
61
|
+
},
|
|
62
|
+
}, presentResult: reportResultView(REPORT_DIFF_TOOL_NAME),
|
|
63
|
+
|
|
58
64
|
timeoutMs: 10_000,
|
|
59
65
|
isConcurrencySafe: () => true,
|
|
60
66
|
async execute(args) {
|