@agentic-kit/dsh 0.2.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 +23 -0
- package/README.md +132 -0
- package/dsh-tool.d.ts +32 -0
- package/dsh-tool.js +87 -0
- package/dsh-types.d.ts +119 -0
- package/dsh-types.js +19 -0
- package/esm/dsh-tool.d.ts +32 -0
- package/esm/dsh-tool.js +83 -0
- package/esm/dsh-types.d.ts +119 -0
- package/esm/dsh-types.js +18 -0
- package/esm/index.d.ts +27 -0
- package/esm/index.js +26 -0
- package/esm/plugin.d.ts +37 -0
- package/esm/plugin.js +72 -0
- package/esm/schema.d.ts +15 -0
- package/esm/schema.js +148 -0
- package/esm/transcript.d.ts +48 -0
- package/esm/transcript.js +285 -0
- package/index.d.ts +27 -0
- package/index.js +40 -0
- package/package.json +46 -0
- package/plugin.d.ts +37 -0
- package/plugin.js +76 -0
- package/schema.d.ts +15 -0
- package/schema.js +153 -0
- package/transcript.d.ts +48 -0
- package/transcript.js +290 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness's transcript reader: dsh session events → neutral events.
|
|
3
|
+
*
|
|
4
|
+
* The read half of the adapter, and deliberately the only file in this package
|
|
5
|
+
* a renderer imports (`@agentic-kit/dsh/transcript`): it is browser-safe, has
|
|
6
|
+
* no dsh dependency and no db-tools dependency, so a dashboard can project a
|
|
7
|
+
* dsh run without pulling a node graph. `@agentic-kit/run-log` owns the neutral
|
|
8
|
+
* vocabulary and the registry; the format's meaning lives here, beside the
|
|
9
|
+
* adapter that produces it.
|
|
10
|
+
*
|
|
11
|
+
* dsh's log differs from pi's in three ways that matter:
|
|
12
|
+
* - it is an *event* log, not a message log: a tool call and its result are
|
|
13
|
+
* separate events with their own sequence numbers, and a step boundary is an
|
|
14
|
+
* event of its own;
|
|
15
|
+
* - `time` is epoch milliseconds, not an ISO string;
|
|
16
|
+
* - assistant reasoning is a `reasoning` content block, and a tool call's
|
|
17
|
+
* arguments arrive as the raw JSON string the model produced.
|
|
18
|
+
*
|
|
19
|
+
* Register the reader once at host startup, e.g.
|
|
20
|
+
* `transcriptReaders.register(dshTranscriptReader)`.
|
|
21
|
+
*/
|
|
22
|
+
import { APPROVAL_REQUEST_TYPE, APPROVAL_RESOLUTION_TYPE, assertTranscriptEntry } from '@agentic-kit/run-log';
|
|
23
|
+
/** dsh's session-event log (`@deepseek-ai/dsh-session`). */
|
|
24
|
+
export const DSH_TRANSCRIPT_FORMAT = 'dsh';
|
|
25
|
+
/**
|
|
26
|
+
* dsh's `SESSION_FORMAT_VERSION` as of `0.1.0-rc.7`. It bumps only when the
|
|
27
|
+
* event envelope or the surface mechanism changes — a new event *type* does
|
|
28
|
+
* not bump it, which is why an unrecognized type here becomes an `unknown`
|
|
29
|
+
* event rather than a refusal.
|
|
30
|
+
*/
|
|
31
|
+
export const SUPPORTED_DSH_TRANSCRIPT_VERSION = 0;
|
|
32
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
33
|
+
/**
|
|
34
|
+
* Narrow an untrusted dsh event. `seq` and `time` are part of dsh's envelope
|
|
35
|
+
* rather than optional decoration, so an entry missing them is not a dsh event
|
|
36
|
+
* and must not be stored as one.
|
|
37
|
+
*/
|
|
38
|
+
export function assertDshSessionEvent(value) {
|
|
39
|
+
const entry = assertTranscriptEntry(value);
|
|
40
|
+
if (typeof entry.seq !== 'number' || !Number.isFinite(entry.seq)) {
|
|
41
|
+
throw new TypeError('dsh session event must carry a numeric `seq`');
|
|
42
|
+
}
|
|
43
|
+
if (typeof entry.time !== 'number' || !Number.isFinite(entry.time)) {
|
|
44
|
+
throw new TypeError('dsh session event must carry a numeric `time` (epoch ms)');
|
|
45
|
+
}
|
|
46
|
+
if (entry.data !== undefined && !isRecord(entry.data)) {
|
|
47
|
+
throw new TypeError('dsh session event `data` must be an object when present');
|
|
48
|
+
}
|
|
49
|
+
return entry;
|
|
50
|
+
}
|
|
51
|
+
/** What a single dsh event means, in order. */
|
|
52
|
+
export function dshEventToEvents(entry) {
|
|
53
|
+
const event = entry;
|
|
54
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
55
|
+
const base = {
|
|
56
|
+
...(typeof event.seq === 'number' ? { entryId: String(event.seq) } : {}),
|
|
57
|
+
...(typeof event.time === 'number' ? { at: new Date(event.time).toISOString() } : {})
|
|
58
|
+
};
|
|
59
|
+
switch (event.type) {
|
|
60
|
+
case 'user/message': {
|
|
61
|
+
// A user-role event covers a human prompt and dsh's own injected context
|
|
62
|
+
// (file-change notices, skill content); `source.kind` tells them apart,
|
|
63
|
+
// and only a human one belongs in the conversation as a user turn.
|
|
64
|
+
const source = isRecord(data.source) ? data.source : {};
|
|
65
|
+
const text = blockText(data.content);
|
|
66
|
+
if (source.kind === 'user') {
|
|
67
|
+
return [{ kind: 'text', role: 'user', text, ...base }];
|
|
68
|
+
}
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
kind: 'custom',
|
|
72
|
+
customType: `dsh.context.${String(source.kind ?? 'unknown')}`,
|
|
73
|
+
text,
|
|
74
|
+
display: false,
|
|
75
|
+
...(source.plugin === undefined ? {} : { details: { plugin: source.plugin } }),
|
|
76
|
+
...base
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
case 'assistant/message': {
|
|
81
|
+
const message = isRecord(data.message) ? data.message : {};
|
|
82
|
+
const source = isRecord(message.source) ? message.source : {};
|
|
83
|
+
const model = typeof source.model === 'string' ? source.model : undefined;
|
|
84
|
+
const provider = typeof source.provider === 'string' ? source.provider : undefined;
|
|
85
|
+
const usage = tokenUsage(data.usage);
|
|
86
|
+
const events = [
|
|
87
|
+
{
|
|
88
|
+
kind: 'model-response',
|
|
89
|
+
...(model ? { model } : {}),
|
|
90
|
+
...(provider ? { provider } : {}),
|
|
91
|
+
...(usage ? { usage } : {}),
|
|
92
|
+
...base
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
for (const block of Array.isArray(message.content) ? message.content : []) {
|
|
96
|
+
if (!isRecord(block))
|
|
97
|
+
continue;
|
|
98
|
+
if (block.type === 'text' && typeof block.text === 'string' && block.text.length > 0) {
|
|
99
|
+
events.push({
|
|
100
|
+
kind: 'text',
|
|
101
|
+
role: 'assistant',
|
|
102
|
+
text: block.text,
|
|
103
|
+
...(model ? { model } : {}),
|
|
104
|
+
...(provider ? { provider } : {}),
|
|
105
|
+
...base
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else if (block.type === 'reasoning' && typeof block.text === 'string') {
|
|
109
|
+
events.push({ kind: 'thinking', text: block.text, ...base });
|
|
110
|
+
}
|
|
111
|
+
// A `tool-call` block is also logged as its own `tool/call` event, which
|
|
112
|
+
// is the one this reader projects — projecting both would double every
|
|
113
|
+
// call in a trace.
|
|
114
|
+
}
|
|
115
|
+
return events;
|
|
116
|
+
}
|
|
117
|
+
case 'tool/call':
|
|
118
|
+
return [
|
|
119
|
+
{
|
|
120
|
+
kind: 'tool-call',
|
|
121
|
+
toolCallId: String(data.callId ?? ''),
|
|
122
|
+
name: String(data.name ?? ''),
|
|
123
|
+
arguments: parseArguments(data.arguments),
|
|
124
|
+
...base
|
|
125
|
+
}
|
|
126
|
+
];
|
|
127
|
+
case 'tool/result': {
|
|
128
|
+
const message = isRecord(data.message) ? data.message : {};
|
|
129
|
+
const block = (Array.isArray(message.content) ? message.content : []).find((candidate) => isRecord(candidate) && candidate.type === 'tool-result');
|
|
130
|
+
const source = isRecord(message.source) ? message.source : {};
|
|
131
|
+
const error = isRecord(data.error) ? data.error : undefined;
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
kind: 'tool-result',
|
|
135
|
+
toolCallId: String(block?.toolCallId ?? source.callId ?? ''),
|
|
136
|
+
// dsh's result carries the call id, not the tool name; a projector
|
|
137
|
+
// pairs it with the `tool/call` that named it.
|
|
138
|
+
name: '',
|
|
139
|
+
output: blockText(block?.content),
|
|
140
|
+
failed: block?.isError === true || error !== undefined,
|
|
141
|
+
...(data.meta === undefined ? {} : { details: data.meta }),
|
|
142
|
+
...base
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
case 'approval/asked': {
|
|
147
|
+
const callId = typeof data.callId === 'string' ? data.callId : undefined;
|
|
148
|
+
const reason = typeof data.reason === 'string' ? data.reason : '';
|
|
149
|
+
if (!callId)
|
|
150
|
+
break;
|
|
151
|
+
return [
|
|
152
|
+
{
|
|
153
|
+
kind: 'custom',
|
|
154
|
+
customType: APPROVAL_REQUEST_TYPE,
|
|
155
|
+
text: reason || `Approve ${String(data.toolName ?? 'tool call')}?`,
|
|
156
|
+
display: true,
|
|
157
|
+
details: { toolCallId: callId },
|
|
158
|
+
...base
|
|
159
|
+
}
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
case 'approval/decided': {
|
|
163
|
+
const outcome = String(data.outcome ?? '');
|
|
164
|
+
return [
|
|
165
|
+
{
|
|
166
|
+
kind: 'custom',
|
|
167
|
+
customType: APPROVAL_RESOLUTION_TYPE,
|
|
168
|
+
text: outcome,
|
|
169
|
+
display: true,
|
|
170
|
+
details: {
|
|
171
|
+
// dsh pairs a decision with its ask by approval id; the request
|
|
172
|
+
// carried the call id, so a projector joins through the ask.
|
|
173
|
+
approvalId: data.id,
|
|
174
|
+
decision: outcome === 'allowed-once' ? 'approved' : 'rejected',
|
|
175
|
+
reason: outcome
|
|
176
|
+
},
|
|
177
|
+
...base
|
|
178
|
+
}
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
case 'compaction/summary':
|
|
182
|
+
return [
|
|
183
|
+
{
|
|
184
|
+
kind: 'summary',
|
|
185
|
+
reason: 'compaction',
|
|
186
|
+
summary: typeof data.summary === 'string' ? data.summary : blockText(data.content),
|
|
187
|
+
...base
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
case 'command/run':
|
|
191
|
+
return [
|
|
192
|
+
{
|
|
193
|
+
kind: 'bash',
|
|
194
|
+
command: String(data.command ?? ''),
|
|
195
|
+
output: '',
|
|
196
|
+
...base
|
|
197
|
+
}
|
|
198
|
+
];
|
|
199
|
+
case 'command/done':
|
|
200
|
+
return [
|
|
201
|
+
{
|
|
202
|
+
kind: 'bash',
|
|
203
|
+
command: String(data.command ?? ''),
|
|
204
|
+
output: blockText(data.content) || String(data.output ?? ''),
|
|
205
|
+
...(typeof data.exitCode === 'number' ? { exitCode: data.exitCode } : {}),
|
|
206
|
+
...base
|
|
207
|
+
}
|
|
208
|
+
];
|
|
209
|
+
// Token-level replay of an `assistant/message` that is projected in full.
|
|
210
|
+
case 'assistant/chunk':
|
|
211
|
+
return [];
|
|
212
|
+
default:
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
return [{ kind: 'unknown', entryType: event.type, entry, ...base }];
|
|
216
|
+
}
|
|
217
|
+
/** dsh's session-event log, as a registrable reader. */
|
|
218
|
+
export const dshTranscriptReader = {
|
|
219
|
+
format: DSH_TRANSCRIPT_FORMAT,
|
|
220
|
+
version: SUPPORTED_DSH_TRANSCRIPT_VERSION,
|
|
221
|
+
assertEntry: assertDshSessionEvent,
|
|
222
|
+
toEvents: dshEventToEvents
|
|
223
|
+
};
|
|
224
|
+
/** The text of a dsh content-block array. */
|
|
225
|
+
function blockText(content) {
|
|
226
|
+
if (typeof content === 'string')
|
|
227
|
+
return content;
|
|
228
|
+
if (!Array.isArray(content))
|
|
229
|
+
return '';
|
|
230
|
+
return content
|
|
231
|
+
.map((block) => {
|
|
232
|
+
if (!isRecord(block))
|
|
233
|
+
return '';
|
|
234
|
+
if (typeof block.text === 'string')
|
|
235
|
+
return block.text;
|
|
236
|
+
if (Array.isArray(block.content))
|
|
237
|
+
return blockText(block.content);
|
|
238
|
+
return '';
|
|
239
|
+
})
|
|
240
|
+
.filter((text) => text.length > 0)
|
|
241
|
+
.join('\n');
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* A tool call's arguments. dsh logs the raw JSON string the model produced, so
|
|
245
|
+
* a malformed call is *in* the log — it becomes the string it was rather than
|
|
246
|
+
* failing the whole entry.
|
|
247
|
+
*/
|
|
248
|
+
function parseArguments(value) {
|
|
249
|
+
if (isRecord(value))
|
|
250
|
+
return value;
|
|
251
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
252
|
+
return {};
|
|
253
|
+
try {
|
|
254
|
+
const parsed = JSON.parse(value);
|
|
255
|
+
return isRecord(parsed) ? parsed : { value: parsed };
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return { raw: value };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* dsh's `TokenUsage` in the neutral vocabulary. Its input counts are disjoint
|
|
263
|
+
* — cached input is reported apart from `inputTokens` — so a total is the sum
|
|
264
|
+
* rather than the input field.
|
|
265
|
+
*/
|
|
266
|
+
function tokenUsage(value) {
|
|
267
|
+
if (!isRecord(value))
|
|
268
|
+
return undefined;
|
|
269
|
+
const input = numeric(value.inputTokens);
|
|
270
|
+
const output = numeric(value.outputTokens);
|
|
271
|
+
const cacheRead = numeric(value.cacheReadTokens);
|
|
272
|
+
const cacheWrite = numeric(value.cacheWriteTokens);
|
|
273
|
+
const usage = {
|
|
274
|
+
...(input === undefined ? {} : { input }),
|
|
275
|
+
...(output === undefined ? {} : { output }),
|
|
276
|
+
...(cacheRead === undefined ? {} : { cacheRead }),
|
|
277
|
+
...(cacheWrite === undefined ? {} : { cacheWrite })
|
|
278
|
+
};
|
|
279
|
+
if (Object.keys(usage).length === 0)
|
|
280
|
+
return undefined;
|
|
281
|
+
usage.totalTokens =
|
|
282
|
+
(input ?? 0) + (output ?? 0) + (cacheRead ?? 0) + (cacheWrite ?? 0);
|
|
283
|
+
return usage;
|
|
284
|
+
}
|
|
285
|
+
const numeric = (value) => typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agentic-kit/dsh` — the DeepSeek Harness adapter.
|
|
3
|
+
*
|
|
4
|
+
* The sibling of `@agentic-kit/pi`, and the reason the harness contracts are
|
|
5
|
+
* neutral: the same 18 Constructive tools, the same confirm gate and the same
|
|
6
|
+
* run-log vocabulary, bound to a second harness without any of them changing.
|
|
7
|
+
* Everything dsh-specific is here — its tool shape, its JSON Schema subset, its
|
|
8
|
+
* plugin surface, its session-event log — and nothing here reaches back into
|
|
9
|
+
* the neutral packages' internals.
|
|
10
|
+
*
|
|
11
|
+
* dsh is a developer preview whose packages promise breaking changes, so this
|
|
12
|
+
* adapter binds to its *shape* rather than its types (see `./dsh-types`): the
|
|
13
|
+
* package has no `@deepseek-ai/*` dependency, which also keeps dsh's ESM-only
|
|
14
|
+
* graph out of a CJS consumer's way.
|
|
15
|
+
*/
|
|
16
|
+
export { toDshTool, type ToDshToolOptions, toDshTools } from './dsh-tool';
|
|
17
|
+
export { type DshApprovalOutcome, type DshApprovalService, type DshContentBlock, type DshJsonSchema, type DshPlugin, type DshPluginContext, type DshPreToolDecision, type DshToolDefinition, type DshToolExecution, type DshToolOutputDefinition, type DshToolRunContext, type DshToolRuntime } from './dsh-types';
|
|
18
|
+
export { type ConstructivePluginOptions, createConstructivePlugin, DSH_PLUGIN_NAME } from './plugin';
|
|
19
|
+
export { convertDshParameters, type DshSchemaConversion, toDshParameters } from './schema';
|
|
20
|
+
/**
|
|
21
|
+
* The transcript reader, re-exported for a node host. A renderer imports
|
|
22
|
+
* `@agentic-kit/dsh/transcript` instead: that entry point pulls neither the db
|
|
23
|
+
* tools nor anything else a browser cannot load.
|
|
24
|
+
*/
|
|
25
|
+
export { assertDshSessionEvent, DSH_TRANSCRIPT_FORMAT, dshEventToEvents, type DshSessionEvent, dshTranscriptReader, SUPPORTED_DSH_TRANSCRIPT_VERSION } from './transcript';
|
|
26
|
+
/** Stable adapter id, matching the transcript format its runs are logged under. */
|
|
27
|
+
export declare const DSH_HARNESS_ID = "dsh";
|
package/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@agentic-kit/dsh` — the DeepSeek Harness adapter.
|
|
4
|
+
*
|
|
5
|
+
* The sibling of `@agentic-kit/pi`, and the reason the harness contracts are
|
|
6
|
+
* neutral: the same 18 Constructive tools, the same confirm gate and the same
|
|
7
|
+
* run-log vocabulary, bound to a second harness without any of them changing.
|
|
8
|
+
* Everything dsh-specific is here — its tool shape, its JSON Schema subset, its
|
|
9
|
+
* plugin surface, its session-event log — and nothing here reaches back into
|
|
10
|
+
* the neutral packages' internals.
|
|
11
|
+
*
|
|
12
|
+
* dsh is a developer preview whose packages promise breaking changes, so this
|
|
13
|
+
* adapter binds to its *shape* rather than its types (see `./dsh-types`): the
|
|
14
|
+
* package has no `@deepseek-ai/*` dependency, which also keeps dsh's ESM-only
|
|
15
|
+
* graph out of a CJS consumer's way.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.DSH_HARNESS_ID = exports.SUPPORTED_DSH_TRANSCRIPT_VERSION = exports.dshTranscriptReader = exports.dshEventToEvents = exports.DSH_TRANSCRIPT_FORMAT = exports.assertDshSessionEvent = exports.toDshParameters = exports.convertDshParameters = exports.DSH_PLUGIN_NAME = exports.createConstructivePlugin = exports.toDshTools = exports.toDshTool = void 0;
|
|
19
|
+
var dsh_tool_1 = require("./dsh-tool");
|
|
20
|
+
Object.defineProperty(exports, "toDshTool", { enumerable: true, get: function () { return dsh_tool_1.toDshTool; } });
|
|
21
|
+
Object.defineProperty(exports, "toDshTools", { enumerable: true, get: function () { return dsh_tool_1.toDshTools; } });
|
|
22
|
+
var plugin_1 = require("./plugin");
|
|
23
|
+
Object.defineProperty(exports, "createConstructivePlugin", { enumerable: true, get: function () { return plugin_1.createConstructivePlugin; } });
|
|
24
|
+
Object.defineProperty(exports, "DSH_PLUGIN_NAME", { enumerable: true, get: function () { return plugin_1.DSH_PLUGIN_NAME; } });
|
|
25
|
+
var schema_1 = require("./schema");
|
|
26
|
+
Object.defineProperty(exports, "convertDshParameters", { enumerable: true, get: function () { return schema_1.convertDshParameters; } });
|
|
27
|
+
Object.defineProperty(exports, "toDshParameters", { enumerable: true, get: function () { return schema_1.toDshParameters; } });
|
|
28
|
+
/**
|
|
29
|
+
* The transcript reader, re-exported for a node host. A renderer imports
|
|
30
|
+
* `@agentic-kit/dsh/transcript` instead: that entry point pulls neither the db
|
|
31
|
+
* tools nor anything else a browser cannot load.
|
|
32
|
+
*/
|
|
33
|
+
var transcript_1 = require("./transcript");
|
|
34
|
+
Object.defineProperty(exports, "assertDshSessionEvent", { enumerable: true, get: function () { return transcript_1.assertDshSessionEvent; } });
|
|
35
|
+
Object.defineProperty(exports, "DSH_TRANSCRIPT_FORMAT", { enumerable: true, get: function () { return transcript_1.DSH_TRANSCRIPT_FORMAT; } });
|
|
36
|
+
Object.defineProperty(exports, "dshEventToEvents", { enumerable: true, get: function () { return transcript_1.dshEventToEvents; } });
|
|
37
|
+
Object.defineProperty(exports, "dshTranscriptReader", { enumerable: true, get: function () { return transcript_1.dshTranscriptReader; } });
|
|
38
|
+
Object.defineProperty(exports, "SUPPORTED_DSH_TRANSCRIPT_VERSION", { enumerable: true, get: function () { return transcript_1.SUPPORTED_DSH_TRANSCRIPT_VERSION; } });
|
|
39
|
+
/** Stable adapter id, matching the transcript format its runs are logged under. */
|
|
40
|
+
exports.DSH_HARNESS_ID = 'dsh';
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentic-kit/dsh",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
|
+
"description": "the DeepSeek Harness adapter for agentic-kit — Constructive's typed db tools and confirm gate as a dsh plugin, plus a dsh transcript reader for the run log",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "esm/index.js",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"homepage": "https://github.com/constructive-io/constructive",
|
|
10
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"directory": "dist"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/constructive-io/constructive"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/constructive-io/constructive/issues"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"clean": "makage clean",
|
|
24
|
+
"prepack": "npm run build",
|
|
25
|
+
"build": "makage build",
|
|
26
|
+
"build:dev": "makage build --dev",
|
|
27
|
+
"lint": "eslint . --fix",
|
|
28
|
+
"test": "jest",
|
|
29
|
+
"test:watch": "jest --watch"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@agentic-kit/db-tools": "^0.3.0",
|
|
33
|
+
"@agentic-kit/harness": "^0.15.0",
|
|
34
|
+
"@agentic-kit/run-log": "^0.6.0",
|
|
35
|
+
"zod": "^4.4.3"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"agentic-kit",
|
|
39
|
+
"harness",
|
|
40
|
+
"adapter",
|
|
41
|
+
"dsh",
|
|
42
|
+
"deepseek",
|
|
43
|
+
"constructive"
|
|
44
|
+
],
|
|
45
|
+
"gitHead": "e35db12526094b515596ced2dd27a1ec8edd99d5"
|
|
46
|
+
}
|
package/plugin.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ToolsHost } from '@agentic-kit/db-tools';
|
|
2
|
+
import type { AnyHarnessTool, ConfirmGateOptions } from '@agentic-kit/harness';
|
|
3
|
+
import type { DshPlugin } from './dsh-types';
|
|
4
|
+
export interface ConstructivePluginOptions {
|
|
5
|
+
/** Tools to register. Defaults to the whole `constructiveDbTools` set. */
|
|
6
|
+
tools?: readonly AnyHarnessTool[];
|
|
7
|
+
/**
|
|
8
|
+
* The directory the run is rooted at — the project a tool resolves its
|
|
9
|
+
* context and credentials from. Defaults to `process.cwd()`.
|
|
10
|
+
*/
|
|
11
|
+
cwd?: () => string;
|
|
12
|
+
/**
|
|
13
|
+
* The gate in front of a mutating call. Defaults to Constructive's database
|
|
14
|
+
* policy; pass `false` for a host that gates elsewhere (its own
|
|
15
|
+
* `tools/pre-execute` listener, a hook, an approval preset).
|
|
16
|
+
*/
|
|
17
|
+
gate?: ConfirmGateOptions | false;
|
|
18
|
+
/** The db tools' host contract, when it is not configured already. */
|
|
19
|
+
host?: ToolsHost;
|
|
20
|
+
}
|
|
21
|
+
export declare const DSH_PLUGIN_NAME = "constructive-tools";
|
|
22
|
+
/**
|
|
23
|
+
* Constructive's tools as a dsh plugin.
|
|
24
|
+
*
|
|
25
|
+
* The sibling of `@agentic-kit/pi`'s `dbTools` extension: the same neutral
|
|
26
|
+
* tools, registered through dsh's own registry, with the same host-neutral
|
|
27
|
+
* confirm gate wired to dsh's `tools/pre-execute` waterfall instead of pi's
|
|
28
|
+
* `tool_call` event. Nothing Constructive-specific is duplicated — the tools,
|
|
29
|
+
* the gate policy and the decline memory all come from the neutral packages.
|
|
30
|
+
*
|
|
31
|
+
* A `deny` decision is dsh's own vocabulary for "this call does not run, and
|
|
32
|
+
* here is what to tell the model", which is exactly what the gate returns; an
|
|
33
|
+
* approval question goes to dsh's approval service when the host composed one,
|
|
34
|
+
* so a headless dsh run refuses a gated call rather than performing it
|
|
35
|
+
* unasked.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createConstructivePlugin(options?: ConstructivePluginOptions): DshPlugin;
|
package/plugin.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DSH_PLUGIN_NAME = void 0;
|
|
4
|
+
exports.createConstructivePlugin = createConstructivePlugin;
|
|
5
|
+
const db_tools_1 = require("@agentic-kit/db-tools");
|
|
6
|
+
const harness_1 = require("@agentic-kit/harness");
|
|
7
|
+
const dsh_tool_1 = require("./dsh-tool");
|
|
8
|
+
exports.DSH_PLUGIN_NAME = 'constructive-tools';
|
|
9
|
+
/**
|
|
10
|
+
* Constructive's tools as a dsh plugin.
|
|
11
|
+
*
|
|
12
|
+
* The sibling of `@agentic-kit/pi`'s `dbTools` extension: the same neutral
|
|
13
|
+
* tools, registered through dsh's own registry, with the same host-neutral
|
|
14
|
+
* confirm gate wired to dsh's `tools/pre-execute` waterfall instead of pi's
|
|
15
|
+
* `tool_call` event. Nothing Constructive-specific is duplicated — the tools,
|
|
16
|
+
* the gate policy and the decline memory all come from the neutral packages.
|
|
17
|
+
*
|
|
18
|
+
* A `deny` decision is dsh's own vocabulary for "this call does not run, and
|
|
19
|
+
* here is what to tell the model", which is exactly what the gate returns; an
|
|
20
|
+
* approval question goes to dsh's approval service when the host composed one,
|
|
21
|
+
* so a headless dsh run refuses a gated call rather than performing it
|
|
22
|
+
* unasked.
|
|
23
|
+
*/
|
|
24
|
+
function createConstructivePlugin(options = {}) {
|
|
25
|
+
const cwd = options.cwd ?? (() => process.cwd());
|
|
26
|
+
const tools = options.tools ?? db_tools_1.constructiveDbTools;
|
|
27
|
+
if (options.host)
|
|
28
|
+
(0, db_tools_1.configureHost)(options.host);
|
|
29
|
+
return {
|
|
30
|
+
name: exports.DSH_PLUGIN_NAME,
|
|
31
|
+
inject: ['tools'],
|
|
32
|
+
apply(ctx) {
|
|
33
|
+
for (const definition of (0, dsh_tool_1.toDshTools)(tools, { cwd })) {
|
|
34
|
+
ctx.tools.register(definition);
|
|
35
|
+
}
|
|
36
|
+
if (options.gate === false)
|
|
37
|
+
return;
|
|
38
|
+
const gate = (0, harness_1.createConfirmGate)(options.gate ?? (0, db_tools_1.constructiveGateDeps)());
|
|
39
|
+
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
40
|
+
const result = await gate.onToolCall({
|
|
41
|
+
toolName: exec.name,
|
|
42
|
+
toolCallId: exec.callId,
|
|
43
|
+
input: (exec.arguments ?? undefined)
|
|
44
|
+
}, approvalHost(ctx.approval, exec), cwd());
|
|
45
|
+
if (result?.block)
|
|
46
|
+
return { kind: 'deny', reason: result.reason };
|
|
47
|
+
return next();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* dsh's approval service as the gate's host. `allowed-once` is the only
|
|
54
|
+
* outcome that approves — `rejected`, `cancelled` and the fail-closed
|
|
55
|
+
* `unavailable` all decline — and a host with no approval service composed has
|
|
56
|
+
* no confirm surface at all, which the gate answers by blocking.
|
|
57
|
+
*/
|
|
58
|
+
function approvalHost(approval, exec) {
|
|
59
|
+
return {
|
|
60
|
+
hasUI: approval !== undefined,
|
|
61
|
+
confirmTool: async (_toolCallId, title, message) => {
|
|
62
|
+
if (!approval)
|
|
63
|
+
return false;
|
|
64
|
+
const outcome = await approval.request({
|
|
65
|
+
toolName: exec.name,
|
|
66
|
+
callId: exec.callId,
|
|
67
|
+
reason: `${title}\n\n${message}`,
|
|
68
|
+
...(exec.agent === undefined ? {} : { agent: exec.agent })
|
|
69
|
+
});
|
|
70
|
+
return outcome === 'allowed-once';
|
|
71
|
+
},
|
|
72
|
+
// dsh records the ask and its outcome itself (`approval/asked`,
|
|
73
|
+
// `approval/decided`), so an auto-skipped retry needs no separate notice.
|
|
74
|
+
notifyToolSkipped: () => undefined
|
|
75
|
+
};
|
|
76
|
+
}
|
package/schema.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { DshJsonSchema } from './dsh-types';
|
|
3
|
+
/** What a conversion dropped, so a caller can log it rather than wonder. */
|
|
4
|
+
export interface DshSchemaConversion {
|
|
5
|
+
parameters: DshJsonSchema;
|
|
6
|
+
/**
|
|
7
|
+
* Keywords dropped from the model-facing schema, as JSON-pointer-ish paths
|
|
8
|
+
* (`properties.rows.minItems`). Still enforced by zod at execute time.
|
|
9
|
+
*/
|
|
10
|
+
dropped: string[];
|
|
11
|
+
}
|
|
12
|
+
/** Convert and report, for a host that wants to see what degraded. */
|
|
13
|
+
export declare function convertDshParameters(schema: z.ZodType): DshSchemaConversion;
|
|
14
|
+
/** The dsh-subset JSON Schema for a tool's parameters. */
|
|
15
|
+
export declare const toDshParameters: (schema: z.ZodType) => DshJsonSchema;
|