dna-sdk 0.4.0 → 0.6.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/dist/adapters/source-url.d.ts +3 -0
- package/dist/adapters/source-url.js +28 -1
- package/dist/extensions/helix/kinds/tool.kind.yaml +240 -0
- package/dist/extensions/helix.js +54 -99
- package/dist/extensions/sdlc.js +162 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/kernel/errors.d.ts +35 -0
- package/dist/kernel/errors.js +53 -0
- package/dist/kernel/kind-registry.js +3 -1
- package/dist/kernel/kind_base.d.ts +13 -0
- package/dist/kernel/kind_base.js +17 -0
- package/dist/kernel/models.d.ts +85 -191
- package/dist/kernel/models.js +19 -32
- package/dist/kernel/prompt-builder.js +15 -4
- package/dist/package-scope.d.ts +18 -0
- package/dist/package-scope.js +87 -0
- package/dist/prompts.d.ts +27 -4
- package/dist/prompts.js +21 -6
- package/dist/tools.d.ts +41 -0
- package/dist/tools.js +88 -0
- package/package.json +1 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a DNA scope embedded as PACKAGE DATA (`s-scope-as-package-data`).
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with python `dna/package_scope.py`. A consumer that deploys an
|
|
5
|
+
* app used to make the scope travel by hand — a brittle
|
|
6
|
+
* `path.resolve(__dirname, "../../.dna")` plus a manual `COPY .dna` in the
|
|
7
|
+
* Dockerfile. The image is the *app*, not the repo; forget the COPY and the
|
|
8
|
+
* app boots with no scope. This module owns the deploy-safe alternative:
|
|
9
|
+
* resolve the scope from INSIDE the installed package.
|
|
10
|
+
*
|
|
11
|
+
* The TS mechanism mirrors how the SDK finds its OWN bundled `*.kind.yaml`
|
|
12
|
+
* (see `kernel/descriptor-loader.ts`): resolve relative to a module. Here the
|
|
13
|
+
* anchor is a package NAME, so we resolve its `package.json` via
|
|
14
|
+
* `createRequire(import.meta.url)` — the package root is that file's dir —
|
|
15
|
+
* then join the scopes-root subpath (`.dna` by default). `npm`/`bun`/`pnpm`
|
|
16
|
+
* install the package UNPACKED into `node_modules`, and the package's `files`
|
|
17
|
+
* field carries the scope into the published tarball and into a Docker image,
|
|
18
|
+
* so resolution works from a source checkout, an installed dependency, and a
|
|
19
|
+
* container whose CWD is not the repo — zero path navigation, zero manual copy.
|
|
20
|
+
*
|
|
21
|
+
* Read-only by nature: package data is composition input, never a write
|
|
22
|
+
* target. To WRITE a scope, use a filesystem or postgres source.
|
|
23
|
+
*/
|
|
24
|
+
import { createRequire } from "node:module";
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
27
|
+
/** The conventional scopes-root sub-directory inside a package (matches the
|
|
28
|
+
* repo's own `.dna/<scope>/` layout). */
|
|
29
|
+
export const DEFAULT_SUBPATH = ".dna";
|
|
30
|
+
/** Raised when an `anchor` package / subpath can't be resolved to a real
|
|
31
|
+
* on-disk scopes-root directory. */
|
|
32
|
+
export class PackageScopeNotFound extends Error {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "PackageScopeNotFound";
|
|
36
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the `.dna` scopes-root embedded in package `anchor`.
|
|
42
|
+
*
|
|
43
|
+
* `anchor` is a resolvable package specifier (e.g. `"app"`); `subpath` is the
|
|
44
|
+
* scopes-root dir inside it (default `.dna`). Returns the concrete filesystem
|
|
45
|
+
* path of `<anchor-package>/<subpath>` — the `baseDir` a FilesystemSource
|
|
46
|
+
* consumes. Fails loud (`PackageScopeNotFound`) when the package cannot be
|
|
47
|
+
* resolved or the subpath does not exist in the installed package.
|
|
48
|
+
*/
|
|
49
|
+
export function anchorScopesRoot(anchor, subpath = DEFAULT_SUBPATH) {
|
|
50
|
+
const pkgRoot = resolvePackageRoot(anchor);
|
|
51
|
+
const base = subpath ? join(pkgRoot, subpath) : pkgRoot;
|
|
52
|
+
if (!existsSync(base)) {
|
|
53
|
+
throw new PackageScopeNotFound(`anchor '${anchor}' is installed, but its scopes-root '${subpath}' was ` +
|
|
54
|
+
`not found at ${base}. Declare the scope files as package data so ` +
|
|
55
|
+
`they ship in the tarball/image: add the scopes dir to the package's ` +
|
|
56
|
+
`"files" array in package.json (e.g. "files": ["dist", "${subpath}"]). ` +
|
|
57
|
+
`See the guide "Shipping a scope with your app".`);
|
|
58
|
+
}
|
|
59
|
+
return base;
|
|
60
|
+
}
|
|
61
|
+
/** Locate a package's root dir from its name, via its `package.json`. */
|
|
62
|
+
function resolvePackageRoot(anchor) {
|
|
63
|
+
// Primary: resolve the package's package.json — its dir IS the package root.
|
|
64
|
+
// (A package can gate this behind "exports"; the example adds the standard
|
|
65
|
+
// `"./package.json": "./package.json"` so it always resolves.)
|
|
66
|
+
try {
|
|
67
|
+
return dirname(require.resolve(join(anchor, "package.json")));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Fallback: an absolute/relative path anchor (a directory, not a package
|
|
71
|
+
// name) — treat it as the package root directly.
|
|
72
|
+
if (isAbsolute(anchor) && existsSync(anchor))
|
|
73
|
+
return anchor;
|
|
74
|
+
try {
|
|
75
|
+
// Last resort: resolve the package entry point and use its dir. Only
|
|
76
|
+
// reliable for a flat single-file package, but better than nothing.
|
|
77
|
+
return dirname(require.resolve(anchor));
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
throw new PackageScopeNotFound(`anchor '${anchor}' is not resolvable — it must be an INSTALLED ` +
|
|
81
|
+
`package that embeds the scope as package data (npm/bun install it, ` +
|
|
82
|
+
`and list the scopes dir in the package's "files" so it ships). ` +
|
|
83
|
+
`If it uses "exports", add "./package.json": "./package.json". ` +
|
|
84
|
+
`Original resolve error: ${err.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
package/dist/prompts.d.ts
CHANGED
|
@@ -17,11 +17,34 @@ export declare class PromptLibrary {
|
|
|
17
17
|
/** Names of every prompt-target document in the scope, sorted. */
|
|
18
18
|
names(): string[];
|
|
19
19
|
}
|
|
20
|
+
/** Options for {@link loadPrompts}. */
|
|
21
|
+
export interface LoadPromptsOptions {
|
|
22
|
+
/**
|
|
23
|
+
* The directory that holds `<scope>/` (the `.dna` scopes root), following
|
|
24
|
+
* the {@link quickInstance} convention.
|
|
25
|
+
*/
|
|
26
|
+
baseDir?: string;
|
|
27
|
+
/**
|
|
28
|
+
* A package specifier whose package data embeds the scope. When given, the
|
|
29
|
+
* scope is resolved from INSIDE the installed package (via its
|
|
30
|
+
* `package.json`), so it TRAVELS with the app — an `npm`/`bun install`
|
|
31
|
+
* carries the package data into the published tarball and into a Docker
|
|
32
|
+
* image, and resolution works identically from a source checkout, an
|
|
33
|
+
* installed dependency, or a container whose CWD is not the repo (no
|
|
34
|
+
* `path.resolve(__dirname, "../..")` navigation, no manual `COPY .dna`).
|
|
35
|
+
* A scope embedded via `anchor` is READ-ONLY. See the guide "Shipping a
|
|
36
|
+
* scope with your app".
|
|
37
|
+
*/
|
|
38
|
+
anchor?: string;
|
|
39
|
+
}
|
|
20
40
|
/**
|
|
21
41
|
* Compose the prompts of `scope` behind a {@link PromptLibrary}.
|
|
22
42
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
43
|
+
* Precedence for the scopes-root (first one set wins):
|
|
44
|
+
*
|
|
45
|
+
* `opts.baseDir` > `$DNA_BASE_DIR` > `opts.anchor` (package data) > `.dna`
|
|
46
|
+
*
|
|
47
|
+
* The legacy positional-string form (`loadPrompts(scope, "/path/.dna")`) is
|
|
48
|
+
* still accepted for back-compat and is treated as `baseDir`.
|
|
26
49
|
*/
|
|
27
|
-
export declare function loadPrompts(scope: string,
|
|
50
|
+
export declare function loadPrompts(scope: string, opts?: string | LoadPromptsOptions): Promise<PromptLibrary>;
|
package/dist/prompts.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* is `await`ed in TS but not in Python).
|
|
25
25
|
*/
|
|
26
26
|
import { quickInstance } from "./bootstrap.js";
|
|
27
|
+
import { anchorScopesRoot } from "./package-scope.js";
|
|
27
28
|
/** Lazy, cached view `agent name -> composed prompt` over one scope. */
|
|
28
29
|
export class PromptLibrary {
|
|
29
30
|
mi;
|
|
@@ -65,12 +66,26 @@ export class PromptLibrary {
|
|
|
65
66
|
/**
|
|
66
67
|
* Compose the prompts of `scope` behind a {@link PromptLibrary}.
|
|
67
68
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
69
|
+
* Precedence for the scopes-root (first one set wins):
|
|
70
|
+
*
|
|
71
|
+
* `opts.baseDir` > `$DNA_BASE_DIR` > `opts.anchor` (package data) > `.dna`
|
|
72
|
+
*
|
|
73
|
+
* The legacy positional-string form (`loadPrompts(scope, "/path/.dna")`) is
|
|
74
|
+
* still accepted for back-compat and is treated as `baseDir`.
|
|
71
75
|
*/
|
|
72
|
-
export async function loadPrompts(scope,
|
|
73
|
-
const
|
|
74
|
-
const mi = await quickInstance(scope,
|
|
76
|
+
export async function loadPrompts(scope, opts) {
|
|
77
|
+
const o = typeof opts === "string" ? { baseDir: opts } : opts ?? {};
|
|
78
|
+
const mi = await quickInstance(scope, resolveScopeBaseDir(o));
|
|
75
79
|
return new PromptLibrary(mi);
|
|
76
80
|
}
|
|
81
|
+
/** Pick the `.dna` scopes-root by the documented precedence. */
|
|
82
|
+
function resolveScopeBaseDir(o) {
|
|
83
|
+
if (o.baseDir !== undefined)
|
|
84
|
+
return o.baseDir;
|
|
85
|
+
const env = process.env.DNA_BASE_DIR;
|
|
86
|
+
if (env)
|
|
87
|
+
return env;
|
|
88
|
+
if (o.anchor !== undefined)
|
|
89
|
+
return anchorScopesRoot(o.anchor);
|
|
90
|
+
return ".dna";
|
|
91
|
+
}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ToolNotFound } from "./kernel/errors.js";
|
|
2
|
+
import type { ManifestInstance } from "./kernel/instance.js";
|
|
3
|
+
export { ToolNotFound };
|
|
4
|
+
/**
|
|
5
|
+
* The agent-facing surface of a Tool — exactly what a tool-calling model is
|
|
6
|
+
* shown to decide whether, and how, to call it.
|
|
7
|
+
*/
|
|
8
|
+
export interface ToolSurface {
|
|
9
|
+
/** Natural-language description (`metadata.description`) — the text the
|
|
10
|
+
* model reads. */
|
|
11
|
+
readonly description: string;
|
|
12
|
+
/** JSON Schema of the arguments (`spec.input_schema`) — what the model
|
|
13
|
+
* fills in. Empty `{}` when the tool takes no args. */
|
|
14
|
+
readonly parameters: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
/** Lazy, cached view `tool name -> ToolSurface` over one scope. */
|
|
17
|
+
export declare class ToolLibrary {
|
|
18
|
+
/** The underlying ManifestInstance (drop down for the full surface). */
|
|
19
|
+
readonly mi: ManifestInstance;
|
|
20
|
+
private readonly cache;
|
|
21
|
+
constructor(
|
|
22
|
+
/** The underlying ManifestInstance (drop down for the full surface). */
|
|
23
|
+
mi: ManifestInstance);
|
|
24
|
+
/**
|
|
25
|
+
* Project `name`'s agent-facing surface (cached). Throws {@link ToolNotFound}
|
|
26
|
+
* on a miss.
|
|
27
|
+
*/
|
|
28
|
+
get(name: string): ToolSurface;
|
|
29
|
+
/** True when `name` is a Tool document in the scope (no projection). */
|
|
30
|
+
has(name: string): boolean;
|
|
31
|
+
/** Names of every Tool document in the scope, sorted. */
|
|
32
|
+
names(): string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Load the Tool surfaces of `scope` behind a {@link ToolLibrary}.
|
|
36
|
+
*
|
|
37
|
+
* `baseDir` follows the {@link quickInstance} convention — the directory that
|
|
38
|
+
* holds `<scope>/` (the `.dna` scopes root). Omitted → the `DNA_BASE_DIR` env
|
|
39
|
+
* var, then `.dna` in the cwd.
|
|
40
|
+
*/
|
|
41
|
+
export declare function loadTools(scope: string, baseDir?: string): Promise<ToolLibrary>;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loadTools` — the agent-facing tool surface, as data (TS twin of python
|
|
3
|
+
* `dna.load_tools`).
|
|
4
|
+
*
|
|
5
|
+
* The DNA already governs persona, instruction and guardrails declaratively.
|
|
6
|
+
* A **Tool** (record-plane Kind, `helix/kinds/tool.kind.yaml`) moves the last
|
|
7
|
+
* hard-coded piece into the same plane: the `description` the model reads to
|
|
8
|
+
* decide whether to call a tool, and the JSON Schema of its `parameters`.
|
|
9
|
+
* `loadTools` is the consumer one-liner — the twin of `loadPrompts`:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { loadTools } from "@ruinosus/dna";
|
|
13
|
+
*
|
|
14
|
+
* const tools = await loadTools("open-swe");
|
|
15
|
+
* const surface = tools.get("github-search"); // or throws ToolNotFound
|
|
16
|
+
* surface.description; // the text the model reads
|
|
17
|
+
* surface.parameters; // the args JSON Schema
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Because a Tool is ONE declarative document, the SAME surface is served to a
|
|
21
|
+
* Python backend (a `@tool` function's `description=`) and this TypeScript
|
|
22
|
+
* frontend (CopilotKit `useCopilotAction`) from one source of truth — the
|
|
23
|
+
* first place the Py↔TS descriptor parity pays off in a real consumer (see
|
|
24
|
+
* `examples/tools_as_data`).
|
|
25
|
+
*
|
|
26
|
+
* TS/Py asymmetry: `loadTools` is async (booting the kernel is async, exactly
|
|
27
|
+
* like `loadPrompts`), but `get(name)` is SYNC — reading a record document
|
|
28
|
+
* needs no async work (unlike `loadPrompts.get`, which awaits composition).
|
|
29
|
+
* A missing tool throws {@link ToolNotFound} (never an empty surface).
|
|
30
|
+
* Overlay-aware: a tenant overlay that overrides a tool's
|
|
31
|
+
* `metadata.description` / `spec.input_schema` is reflected here.
|
|
32
|
+
*/
|
|
33
|
+
import { quickInstance } from "./bootstrap.js";
|
|
34
|
+
import { ToolNotFound } from "./kernel/errors.js";
|
|
35
|
+
export { ToolNotFound };
|
|
36
|
+
/** Lazy, cached view `tool name -> ToolSurface` over one scope. */
|
|
37
|
+
export class ToolLibrary {
|
|
38
|
+
mi;
|
|
39
|
+
cache = new Map();
|
|
40
|
+
constructor(
|
|
41
|
+
/** The underlying ManifestInstance (drop down for the full surface). */
|
|
42
|
+
mi) {
|
|
43
|
+
this.mi = mi;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Project `name`'s agent-facing surface (cached). Throws {@link ToolNotFound}
|
|
47
|
+
* on a miss.
|
|
48
|
+
*/
|
|
49
|
+
get(name) {
|
|
50
|
+
const cached = this.cache.get(name);
|
|
51
|
+
if (cached !== undefined)
|
|
52
|
+
return cached;
|
|
53
|
+
const doc = this.mi._one("Tool", name);
|
|
54
|
+
if (doc === null) {
|
|
55
|
+
throw new ToolNotFound(name, this.mi.scope ?? null, this.names());
|
|
56
|
+
}
|
|
57
|
+
const meta = doc.metadata;
|
|
58
|
+
const spec = doc.spec;
|
|
59
|
+
const description = typeof meta.description === "string" ? meta.description : "";
|
|
60
|
+
const rawParams = spec.input_schema;
|
|
61
|
+
const parameters = rawParams && typeof rawParams === "object" && !Array.isArray(rawParams)
|
|
62
|
+
? { ...rawParams }
|
|
63
|
+
: {};
|
|
64
|
+
const surface = { description, parameters };
|
|
65
|
+
this.cache.set(name, surface);
|
|
66
|
+
return surface;
|
|
67
|
+
}
|
|
68
|
+
/** True when `name` is a Tool document in the scope (no projection). */
|
|
69
|
+
has(name) {
|
|
70
|
+
return this.names().includes(name);
|
|
71
|
+
}
|
|
72
|
+
/** Names of every Tool document in the scope, sorted. */
|
|
73
|
+
names() {
|
|
74
|
+
return this.mi._all("Tool").map((d) => d.name).sort();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Load the Tool surfaces of `scope` behind a {@link ToolLibrary}.
|
|
79
|
+
*
|
|
80
|
+
* `baseDir` follows the {@link quickInstance} convention — the directory that
|
|
81
|
+
* holds `<scope>/` (the `.dna` scopes root). Omitted → the `DNA_BASE_DIR` env
|
|
82
|
+
* var, then `.dna` in the cwd.
|
|
83
|
+
*/
|
|
84
|
+
export async function loadTools(scope, baseDir) {
|
|
85
|
+
const resolved = baseDir ?? process.env.DNA_BASE_DIR ?? ".dna";
|
|
86
|
+
const mi = await quickInstance(scope, resolved);
|
|
87
|
+
return new ToolLibrary(mi);
|
|
88
|
+
}
|