Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ jobs:
- batch: graphile-unit
packages: 'graphile/graphile-plugin-utils graphile/graphile-realtime-subscriptions graphile/graphile-sql-expression-validator graphile/graphile-upload-plugin graphile/graphile-storage-registry'
- batch: agentic
packages: 'agentic/protocol agentic/agentic-kit agentic/agent agentic/harness agentic/chat agentic/cli agentic/db-tools agentic/pi agentic/react agentic/agentic-server agentic/anthropic agentic/openai agentic/ollama agentic/run-log agentic/metering'
packages: 'agentic/protocol agentic/agentic-kit agentic/agent agentic/harness agentic/chat agentic/cli agentic/db-tools agentic/pi agentic/dsh agentic/react agentic/agentic-server agentic/anthropic agentic/openai agentic/ollama agentic/run-log agentic/metering'
- batch: pgpm-unit
packages: 'pgpm/types pgpm/naming-spec pgpm/diff pgpm/import pgpm/slice pgpm/transform'
- batch: pglite
Expand Down
1 change: 1 addition & 0 deletions agentic/db-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ npm install @agentic-kit/db-tools
- **Host injection** — credentials, backend endpoints and data-plane tokens come from the host application, not from the package. Call `configureHost()` once at startup; the tools read it lazily per call.
- **Project context** — `resolveProjectContext` / `resolveDataToken` resolve the database a tool acts on from the cwd's `.env` plus the host's session, and `deriveSubdomainEndpoint` derives its per-database endpoints.
- **Provisioning model** — the pinned `node-type-registry` presets, the provision manifest, and overlay resolution (`resolveProvisionModules`).
- **`constructiveGateDeps`** — the confirm gate's host capabilities (is the project runnable, is there a data token, what tables would a template copy) answered by these resolvers, so every adapter gates the same tools against the same project state instead of restating it.
- **`toolSchema`** — a tool's zod parameters as plain JSON Schema, for adapters whose harness wants JSON Schema rather than zod.

## Usage
Expand Down
62 changes: 62 additions & 0 deletions agentic/db-tools/__tests__/gate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { ConfirmPreviewTable } from '@agentic-kit/harness';

import type { ProjectContext } from '../src/context';
import { constructiveGateDeps, type ConstructiveGateResolvers } from '../src/gate';

const context = { databaseId: 'db-1' } as ProjectContext;
const table: ConfirmPreviewTable = {
name: 'contact',
fields: [],
policies: [],
relationCount: 0,
};

const resolvers = (
overrides: Partial<ConstructiveGateResolvers> = {}
): Partial<ConstructiveGateResolvers> => ({
resolveProjectContext: async () => ({ context, reason: '' }),
resolveDataToken: async () => ({ token: 'tok' }),
createTemplatePreviewTables: async () => ({ blueprintName: 'crm', tables: [table] }),
...overrides,
});

describe('constructiveGateDeps', () => {
it('is runnable only with a resolved project', async () => {
await expect(constructiveGateDeps(resolvers()).isProjectRunnable('/p')).resolves.toBe(true);
await expect(
constructiveGateDeps(
resolvers({ resolveProjectContext: async () => ({ context: null, reason: 'no project' }) })
).isProjectRunnable('/p')
).resolves.toBe(false);
});

it('has a data token only when one resolves for that project', async () => {
await expect(constructiveGateDeps(resolvers()).hasDataToken('/p')).resolves.toBe(true);
await expect(
constructiveGateDeps(
resolvers({ resolveDataToken: async () => ({ reason: 'signed out' }) })
).hasDataToken('/p')
).resolves.toBe(false);
});

it('previews the tables a template would copy', async () => {
await expect(
constructiveGateDeps(resolvers()).resolveTemplatePreview('/p', 'crm', 'CRM')
).resolves.toEqual({
kind: 'template',
displayName: 'CRM',
blueprintName: 'crm',
tables: [table],
});
});

it('has no preview when the blueprint contributes no table', async () => {
await expect(
constructiveGateDeps(
resolvers({
createTemplatePreviewTables: async () => ({ blueprintName: '', tables: [] }),
})
).resolveTemplatePreview('/p', undefined, 'CRM')
).resolves.toBeUndefined();
});
});
58 changes: 58 additions & 0 deletions agentic/db-tools/src/gate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { ConstructiveGateDeps } from '@agentic-kit/harness';

import {
type ProjectContext,
resolveDataToken as defaultResolveDataToken,
resolveProjectContext as defaultResolveProjectContext,
} from './context';
import { createTemplatePreviewTables as defaultCreateTemplatePreviewTables } from './tools/templates';

/** The resolvers the deps are built from, so a test can substitute fakes. */
export type ConstructiveGateResolvers = {
resolveProjectContext: typeof defaultResolveProjectContext;
resolveDataToken: typeof defaultResolveDataToken;
createTemplatePreviewTables: typeof defaultCreateTemplatePreviewTables;
};

/**
* The Constructive gate's host capabilities, answered by this package's own
* project/token/template resolvers.
*
* Every adapter gates the same tools against the same project state, so the
* mapping lives here rather than once per harness — an adapter's job is only
* to hand its harness's confirm surface to the gate.
*/
export function constructiveGateDeps(
resolvers: Partial<ConstructiveGateResolvers> = {}
): ConstructiveGateDeps {
const resolveProjectContext = resolvers.resolveProjectContext ?? defaultResolveProjectContext;
const resolveDataToken = resolvers.resolveDataToken ?? defaultResolveDataToken;
const createTemplatePreviewTables =
resolvers.createTemplatePreviewTables ?? defaultCreateTemplatePreviewTables;

const context = async (cwd: string): Promise<ProjectContext | null> =>
(await resolveProjectContext(cwd)).context;

return {
isProjectRunnable: async (cwd) => (await context(cwd)) !== null,

hasDataToken: async (cwd) => {
const resolved = await context(cwd);
if (!resolved) return false;
return Boolean((await resolveDataToken(resolved)).token);
},

resolveTemplatePreview: async (cwd, blueprintName, displayName) => {
const resolved = await context(cwd);
if (!resolved) return undefined;
const result = await createTemplatePreviewTables(resolved, blueprintName);
if (result.tables.length === 0) return undefined;
return {
kind: 'template',
displayName,
blueprintName: result.blueprintName || undefined,
tables: result.tables,
};
},
};
}
1 change: 1 addition & 0 deletions agentic/db-tools/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export {
resolveDataToken,
resolveProjectContext,
} from './context';
export { constructiveGateDeps, type ConstructiveGateResolvers } from './gate';
export {
type ActiveDataToken,
configureHost,
Expand Down
58 changes: 58 additions & 0 deletions agentic/dsh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# @agentic-kit/dsh

<p align="center" width="100%">
<img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
</p>

<p align="center" width="100%">
<a href="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml">
<img height="20" src="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml/badge.svg" />
</a>
<a href="https://github.com/constructive-io/constructive/blob/main/LICENSE"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
<a href="https://www.npmjs.com/package/@agentic-kit/dsh"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/constructive?filename=agentic%2Fdsh%2Fpackage.json"/></a>
</p>

The **DeepSeek Harness (dsh)** adapter — the sibling of [`@agentic-kit/pi`](https://www.npmjs.com/package/@agentic-kit/pi), and the reason the harness contracts are neutral. The same 18 [`@agentic-kit/db-tools`](https://www.npmjs.com/package/@agentic-kit/db-tools), the same confirm gate and the same run-log vocabulary, bound to a second harness without any of them changing.

```
neutral contracts: HarnessTool ConfirmGate TranscriptEvent
│ │ ▲
@agentic-kit/pi ────┼───────────────┼─────────────────┤ pi's ToolDefinition, tool_call, pi session
@agentic-kit/dsh ───┴───────────────┴─────────────────┘ dsh's ToolDefinition, tools/pre-execute, dsh events
```

```bash
npm install @agentic-kit/dsh
```

## What's inside

- **`toDshTool` / `toDshTools`** — a neutral `HarnessTool` as dsh's `ToolDefinition`: parameters in dsh's JSON Schema subset, a declared canonical output whose value *is* the neutral `HarnessToolResult` (so dsh's durable log keeps the tool's structured `details`), and the caller's `AbortSignal` threaded to the tool.
- **`createConstructivePlugin`** — the tools as a dsh plugin, with Constructive's confirm gate on dsh's `tools/pre-execute` waterfall. A gated call asks dsh's approval service; a host with no approval service composed gets a `deny`, never a silent mutation.
- **`toDshParameters` / `convertDshParameters`** — zod → dsh's subset (`type`, `properties`, `required`, `items`, `oneOf`, `enum`, `const`, boolean `additionalProperties`). Constraints outside it are dropped from the *model-facing* schema and reported in `dropped`; they still hold, because a bound tool parses arguments with its own zod schema before the body runs. Structure that cannot degrade safely — a non-object root, a `$ref` — throws.
- **`dshTranscriptReader`** — dsh's session-event log as neutral `TranscriptEvent`s, so a Constructive surface renders a dsh run through the same projectors as a pi one. Import it from `@agentic-kit/dsh/transcript` in a browser: that entry point has no node dependency.

## Usage

```ts
import { configureHost } from '@agentic-kit/db-tools';
import { createConstructivePlugin } from '@agentic-kit/dsh';

configureHost(host);

export default createConstructivePlugin({ cwd: () => projectDir });
```

Reading a dsh run back:

```ts
import { dshTranscriptReader } from '@agentic-kit/dsh/transcript';
import { TranscriptReaderRegistry } from '@agentic-kit/run-log';

const readers = new TranscriptReaderRegistry([piTranscriptReader, dshTranscriptReader]);
const events = readers.require(record.transcriptFormat).toEvents(record.entry);
```

## No dsh dependency

dsh is a developer preview whose packages promise breaking changes, and whose published rc's trail its own source. So this adapter binds to the *shape* dsh asks for — a tool definition, a tool run context, a content block, a plugin's `apply` — declared structurally in `dsh-types.ts`, and has no `@deepseek-ai/*` dependency. A host on any rc registers the plugin; dsh's ESM-only graph never reaches a CJS consumer of this package.
Loading
Loading