@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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The DeepSeek Harness surface this adapter binds to, declared structurally.
3
+ *
4
+ * Nothing here imports `@deepseek-ai/dsh-*`, and the package has no dependency
5
+ * on it. That is deliberate rather than lazy: dsh is a developer preview whose
6
+ * packages promise breaking changes, and its published rc's trail its own
7
+ * source. A structural declaration of the four things we actually touch — a
8
+ * tool definition, a tool's run context, a content block, a plugin's `apply` —
9
+ * binds to the *shape* dsh asks for, so a host on any rc can register our tools
10
+ * without this package tracking their release train. It also keeps the adapter
11
+ * free of dsh's ESM-only graph: a CJS consumer imports it like any other
12
+ * agentic-kit package.
13
+ *
14
+ * Mirrored from dsh `0.1.0-rc.7` (`packages/core/tools`, `packages/core/session`,
15
+ * `packages/llm/llm`). Where dsh brands a string (`CallId`, `SessionId`) this
16
+ * uses `string` — a brand is theirs to enforce, and ours to carry.
17
+ */
18
+ export {};
package/esm/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/esm/index.js ADDED
@@ -0,0 +1,26 @@
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, toDshTools } from './dsh-tool';
17
+ export { createConstructivePlugin, DSH_PLUGIN_NAME } from './plugin';
18
+ export { convertDshParameters, toDshParameters } from './schema';
19
+ /**
20
+ * The transcript reader, re-exported for a node host. A renderer imports
21
+ * `@agentic-kit/dsh/transcript` instead: that entry point pulls neither the db
22
+ * tools nor anything else a browser cannot load.
23
+ */
24
+ export { assertDshSessionEvent, DSH_TRANSCRIPT_FORMAT, dshEventToEvents, dshTranscriptReader, SUPPORTED_DSH_TRANSCRIPT_VERSION } from './transcript';
25
+ /** Stable adapter id, matching the transcript format its runs are logged under. */
26
+ export const DSH_HARNESS_ID = 'dsh';
@@ -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/esm/plugin.js ADDED
@@ -0,0 +1,72 @@
1
+ import { configureHost, constructiveDbTools, constructiveGateDeps } from '@agentic-kit/db-tools';
2
+ import { createConfirmGate } from '@agentic-kit/harness';
3
+ import { toDshTools } from './dsh-tool';
4
+ export const DSH_PLUGIN_NAME = 'constructive-tools';
5
+ /**
6
+ * Constructive's tools as a dsh plugin.
7
+ *
8
+ * The sibling of `@agentic-kit/pi`'s `dbTools` extension: the same neutral
9
+ * tools, registered through dsh's own registry, with the same host-neutral
10
+ * confirm gate wired to dsh's `tools/pre-execute` waterfall instead of pi's
11
+ * `tool_call` event. Nothing Constructive-specific is duplicated — the tools,
12
+ * the gate policy and the decline memory all come from the neutral packages.
13
+ *
14
+ * A `deny` decision is dsh's own vocabulary for "this call does not run, and
15
+ * here is what to tell the model", which is exactly what the gate returns; an
16
+ * approval question goes to dsh's approval service when the host composed one,
17
+ * so a headless dsh run refuses a gated call rather than performing it
18
+ * unasked.
19
+ */
20
+ export function createConstructivePlugin(options = {}) {
21
+ const cwd = options.cwd ?? (() => process.cwd());
22
+ const tools = options.tools ?? constructiveDbTools;
23
+ if (options.host)
24
+ configureHost(options.host);
25
+ return {
26
+ name: DSH_PLUGIN_NAME,
27
+ inject: ['tools'],
28
+ apply(ctx) {
29
+ for (const definition of toDshTools(tools, { cwd })) {
30
+ ctx.tools.register(definition);
31
+ }
32
+ if (options.gate === false)
33
+ return;
34
+ const gate = createConfirmGate(options.gate ?? constructiveGateDeps());
35
+ ctx.on('tools/pre-execute', async (exec, next) => {
36
+ const result = await gate.onToolCall({
37
+ toolName: exec.name,
38
+ toolCallId: exec.callId,
39
+ input: (exec.arguments ?? undefined)
40
+ }, approvalHost(ctx.approval, exec), cwd());
41
+ if (result?.block)
42
+ return { kind: 'deny', reason: result.reason };
43
+ return next();
44
+ });
45
+ }
46
+ };
47
+ }
48
+ /**
49
+ * dsh's approval service as the gate's host. `allowed-once` is the only
50
+ * outcome that approves — `rejected`, `cancelled` and the fail-closed
51
+ * `unavailable` all decline — and a host with no approval service composed has
52
+ * no confirm surface at all, which the gate answers by blocking.
53
+ */
54
+ function approvalHost(approval, exec) {
55
+ return {
56
+ hasUI: approval !== undefined,
57
+ confirmTool: async (_toolCallId, title, message) => {
58
+ if (!approval)
59
+ return false;
60
+ const outcome = await approval.request({
61
+ toolName: exec.name,
62
+ callId: exec.callId,
63
+ reason: `${title}\n\n${message}`,
64
+ ...(exec.agent === undefined ? {} : { agent: exec.agent })
65
+ });
66
+ return outcome === 'allowed-once';
67
+ },
68
+ // dsh records the ask and its outcome itself (`approval/asked`,
69
+ // `approval/decided`), so an auto-skipped retry needs no separate notice.
70
+ notifyToolSkipped: () => undefined
71
+ };
72
+ }
@@ -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;
package/esm/schema.js ADDED
@@ -0,0 +1,148 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * A neutral tool's zod parameters, as dsh's JSON Schema subset.
4
+ *
5
+ * dsh enforces a deliberately small schema vocabulary on every registered tool:
6
+ * `type`, `properties`, `required`, `items`, `oneOf`, `enum`, `const`, a boolean
7
+ * `additionalProperties`, and the annotations. A zod schema routinely produces
8
+ * more than that — `format` from `.uuid()`, `minimum`, `minItems`, `anyOf` from
9
+ * a union, an object-valued `additionalProperties` from a record, `$defs`/`$ref`
10
+ * from a reused sub-schema — and dsh rejects a tool carrying any of them.
11
+ *
12
+ * So this narrows: unsupported *constraints* are dropped, and unsupported
13
+ * *structure* throws. Dropping a constraint is safe here and only here, because
14
+ * the schema dsh receives is a hint to the model, not the enforcement:
15
+ * `toDshTool` parses the model's arguments with the tool's own zod schema
16
+ * before the body runs, so every constraint this drops is still applied — by
17
+ * the party that owns it. What cannot degrade is the shape a caller has to
18
+ * satisfy, which is why a non-object root or an unresolvable `$ref` is an error
19
+ * rather than an open schema.
20
+ */
21
+ const CONSTRAINTS = new Set([
22
+ 'type',
23
+ 'properties',
24
+ 'required',
25
+ 'items',
26
+ 'oneOf',
27
+ 'enum',
28
+ 'const',
29
+ 'additionalProperties'
30
+ ]);
31
+ const ANNOTATIONS = new Set(['description', 'title', 'default', 'examples']);
32
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
33
+ function narrow(node, path, dropped) {
34
+ if (!isRecord(node))
35
+ return {};
36
+ if (typeof node.$ref === 'string') {
37
+ throw new Error(`tool parameters use a JSON Schema $ref ("${node.$ref}") at ${path || 'the root'}; ` +
38
+ 'dsh reads no references, so the schema must be inlined');
39
+ }
40
+ const out = {};
41
+ const source = widenUnions(node, path, dropped);
42
+ for (const [key, value] of Object.entries(source)) {
43
+ const at = path === '' ? key : `${path}.${key}`;
44
+ if (key === '$schema' || key === '$defs' || key === 'definitions')
45
+ continue;
46
+ if (ANNOTATIONS.has(key)) {
47
+ out[key] = value;
48
+ continue;
49
+ }
50
+ if (!CONSTRAINTS.has(key)) {
51
+ dropped.push(at);
52
+ continue;
53
+ }
54
+ switch (key) {
55
+ case 'properties': {
56
+ const properties = {};
57
+ for (const [name, sub] of Object.entries(isRecord(value) ? value : {})) {
58
+ properties[name] = narrow(sub, `${at}.${name}`, dropped);
59
+ }
60
+ out.properties = properties;
61
+ break;
62
+ }
63
+ case 'items':
64
+ out.items = narrow(value, at, dropped);
65
+ break;
66
+ case 'oneOf':
67
+ out.oneOf = (Array.isArray(value) ? value : []).map((sub, index) => narrow(sub, `${at}[${index}]`, dropped));
68
+ break;
69
+ case 'additionalProperties':
70
+ // Only the boolean form exists in dsh's subset; a zod record's
71
+ // object-valued form degrades to the open default.
72
+ if (typeof value === 'boolean')
73
+ out.additionalProperties = value;
74
+ else
75
+ dropped.push(at);
76
+ break;
77
+ default:
78
+ out[key] = value;
79
+ }
80
+ }
81
+ // `required` may not name a property the narrowed schema no longer declares.
82
+ if (Array.isArray(out.required)) {
83
+ const declared = new Set(Object.keys(out.properties ?? {}));
84
+ const kept = out.required.filter((name) => typeof name === 'string' && declared.has(name));
85
+ if (kept.length === 0)
86
+ delete out.required;
87
+ else
88
+ out.required = kept;
89
+ }
90
+ return out;
91
+ }
92
+ /**
93
+ * `anyOf` and a type array as dsh's `oneOf`, where that is sound.
94
+ *
95
+ * zod emits `anyOf` for a union and `type: ['string', 'null']` for a nullable;
96
+ * dsh has neither, only `oneOf` — which validates *exactly one* branch. That is
97
+ * the same thing as `anyOf` only when the branches are disjoint, which is true
98
+ * of the shapes zod actually produces here (a nullable, a union of distinct
99
+ * scalar types) and not true in general. So a disjoint union converts, and an
100
+ * overlapping one degrades to unconstrained rather than becoming a schema that
101
+ * rejects a legitimate argument.
102
+ */
103
+ function widenUnions(node, path, dropped) {
104
+ const rest = { ...node };
105
+ let branches;
106
+ if (Array.isArray(rest.anyOf)) {
107
+ branches = rest.anyOf;
108
+ delete rest.anyOf;
109
+ }
110
+ else if (Array.isArray(rest.type)) {
111
+ branches = rest.type.map((type) => ({ type }));
112
+ delete rest.type;
113
+ }
114
+ if (!branches || rest.oneOf !== undefined)
115
+ return node;
116
+ const types = branches.map((branch) => isRecord(branch) && typeof branch.type === 'string' ? branch.type : undefined);
117
+ const disjoint = branches.length >= 2 &&
118
+ types.every((type) => type !== undefined) &&
119
+ new Set(types).size === types.length;
120
+ if (!disjoint) {
121
+ dropped.push(path === '' ? 'anyOf' : `${path}.anyOf`);
122
+ return rest;
123
+ }
124
+ return { ...rest, oneOf: branches };
125
+ }
126
+ /** Convert and report, for a host that wants to see what degraded. */
127
+ export function convertDshParameters(schema) {
128
+ const dropped = [];
129
+ // zod's own emitter, in input mode (what a *caller* must send) against
130
+ // draft-7 — the dialect dsh's subset is carved out of.
131
+ const jsonSchema = z.toJSONSchema(schema, { target: 'draft-7', io: 'input' });
132
+ const narrowed = narrow(jsonSchema, '', dropped);
133
+ if (narrowed.type !== 'object') {
134
+ throw new Error(`tool parameters must be an object schema; received ${String(narrowed.type ?? 'no type')}. ` +
135
+ 'dsh names every argument, so a tool cannot take a bare value or a top-level union');
136
+ }
137
+ return {
138
+ parameters: {
139
+ type: 'object',
140
+ properties: narrowed.properties ?? {},
141
+ ...(narrowed.required ? { required: narrowed.required } : {}),
142
+ ...(narrowed.description ? { description: narrowed.description } : {})
143
+ },
144
+ dropped
145
+ };
146
+ }
147
+ /** The dsh-subset JSON Schema for a tool's parameters. */
148
+ export const toDshParameters = (schema) => convertDshParameters(schema).parameters;
@@ -0,0 +1,48 @@
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 { type TranscriptEntry, type TranscriptEvent, type TranscriptReader } from '@agentic-kit/run-log';
23
+ /** dsh's session-event log (`@deepseek-ai/dsh-session`). */
24
+ export declare 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 declare const SUPPORTED_DSH_TRANSCRIPT_VERSION = 0;
32
+ /** One entry of a dsh session log, structurally. */
33
+ export interface DshSessionEvent extends TranscriptEntry {
34
+ type: string;
35
+ seq?: number;
36
+ time?: number;
37
+ data?: Record<string, unknown>;
38
+ }
39
+ /**
40
+ * Narrow an untrusted dsh event. `seq` and `time` are part of dsh's envelope
41
+ * rather than optional decoration, so an entry missing them is not a dsh event
42
+ * and must not be stored as one.
43
+ */
44
+ export declare function assertDshSessionEvent(value: unknown): DshSessionEvent;
45
+ /** What a single dsh event means, in order. */
46
+ export declare function dshEventToEvents(entry: TranscriptEntry): TranscriptEvent[];
47
+ /** dsh's session-event log, as a registrable reader. */
48
+ export declare const dshTranscriptReader: TranscriptReader;