dna-sdk 0.3.1 → 0.4.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 +40 -0
- package/dist/adapters/source-url.js +55 -0
- package/dist/bootstrap.d.ts +15 -0
- package/dist/bootstrap.js +83 -0
- package/dist/config.d.ts +34 -0
- package/dist/config.js +102 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -1
- package/dist/kernel/errors.d.ts +18 -0
- package/dist/kernel/errors.js +23 -0
- package/dist/kernel/prompt-builder.js +9 -2
- package/dist/prompts.d.ts +27 -0
- package/dist/prompts.js +76 -0
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sourceFromUrl` — the URL → SourcePort factory, as a PUBLIC SDK surface.
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with python `dna/adapters/source_url.py`. Promotes URL→adapter
|
|
5
|
+
* resolution out of ad-hoc host code so `fromConfig()` and any host share ONE
|
|
6
|
+
* factory.
|
|
7
|
+
*
|
|
8
|
+
* Scheme map (TS runtime):
|
|
9
|
+
*
|
|
10
|
+
* file:// <path> → FilesystemSource (read/write on disk)
|
|
11
|
+
* fs:// <path> → alias of file://
|
|
12
|
+
* <plain path> → treated as file://<path>
|
|
13
|
+
* postgresql:// … → PostgresSource (node-postgres)
|
|
14
|
+
* postgres:// … → alias of postgresql://
|
|
15
|
+
* sqlite:// <path> → NOT SUPPORTED in the TS runtime (Python-only; the
|
|
16
|
+
* SqlAlchemy adapter is a Python thing) — fails loud.
|
|
17
|
+
*
|
|
18
|
+
* An unknown scheme fails loud with the supported set.
|
|
19
|
+
*/
|
|
20
|
+
import type { SourcePort } from "../kernel/protocols.js";
|
|
21
|
+
export declare class UnsupportedSourceScheme extends Error {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
interface SourceFromUrlOptions {
|
|
25
|
+
/** Postgres schema namespace (postgres sources only). */
|
|
26
|
+
schema?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build a source from a scheme URL (see module docstring).
|
|
30
|
+
*
|
|
31
|
+
* Postgres sources run their migrations on first use; this awaits `init()` so
|
|
32
|
+
* the returned source is ready.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sourceFromUrl(url: string, opts?: SourceFromUrlOptions): Promise<SourcePort>;
|
|
35
|
+
/**
|
|
36
|
+
* The `file://` URL the SDK falls back to with no explicit config.
|
|
37
|
+
* Priority: explicit override > `DNA_BASE_DIR` env > `./.dna`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveDefaultFsUrl(baseDirOverride?: string): string;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export class UnsupportedSourceScheme extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "UnsupportedSourceScheme";
|
|
5
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function urlToFsPath(url) {
|
|
9
|
+
// Mirror the Python rule (join netloc + path so `fs://./x` stays RELATIVE).
|
|
10
|
+
const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/(.*)$/.exec(url);
|
|
11
|
+
if (!m)
|
|
12
|
+
return url; // plain path, no scheme
|
|
13
|
+
return m[2]; // everything after scheme:// (host+path joined)
|
|
14
|
+
}
|
|
15
|
+
function schemeOf(url) {
|
|
16
|
+
const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(url);
|
|
17
|
+
return (m ? m[1] : "file").toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build a source from a scheme URL (see module docstring).
|
|
21
|
+
*
|
|
22
|
+
* Postgres sources run their migrations on first use; this awaits `init()` so
|
|
23
|
+
* the returned source is ready.
|
|
24
|
+
*/
|
|
25
|
+
export async function sourceFromUrl(url, opts = {}) {
|
|
26
|
+
const scheme = schemeOf(url);
|
|
27
|
+
const base = scheme.split("+", 1)[0];
|
|
28
|
+
if (scheme === "file" || scheme === "fs" || scheme === "") {
|
|
29
|
+
const { FilesystemSource } = await import("./filesystem/source.js");
|
|
30
|
+
return new FilesystemSource(urlToFsPath(url) || url);
|
|
31
|
+
}
|
|
32
|
+
if (base === "postgresql" || base === "postgres") {
|
|
33
|
+
const { PostgresSource } = await import("./postgres/source.js");
|
|
34
|
+
const src = new PostgresSource({ connectionString: url, schema: opts.schema });
|
|
35
|
+
await src.init();
|
|
36
|
+
return src;
|
|
37
|
+
}
|
|
38
|
+
if (base === "sqlite") {
|
|
39
|
+
throw new UnsupportedSourceScheme(`sqlite:// sources are Python-only — they ride the Python SqlAlchemy ` +
|
|
40
|
+
`adapter, which the TypeScript runtime does not ship. Use file:// ` +
|
|
41
|
+
`(filesystem) or postgresql:// here. Got: ${url}`);
|
|
42
|
+
}
|
|
43
|
+
throw new UnsupportedSourceScheme(`unsupported source URL scheme '${scheme}://' — the TS runtime ships ` +
|
|
44
|
+
`file:// (filesystem) and postgresql:// adapters. Got: ${url}`);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The `file://` URL the SDK falls back to with no explicit config.
|
|
48
|
+
* Priority: explicit override > `DNA_BASE_DIR` env > `./.dna`.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveDefaultFsUrl(baseDirOverride) {
|
|
51
|
+
const base = baseDirOverride ?? process.env.DNA_BASE_DIR;
|
|
52
|
+
if (base)
|
|
53
|
+
return `file://${base}`;
|
|
54
|
+
return "file://.dna";
|
|
55
|
+
}
|
package/dist/bootstrap.d.ts
CHANGED
|
@@ -43,3 +43,18 @@ export declare function createRuntimeWithBuiltins(): Runtime;
|
|
|
43
43
|
* `ManifestInstance` for the given scope.
|
|
44
44
|
*/
|
|
45
45
|
export declare function quickManifest(scope: string, baseDir?: string): Promise<ManifestInstance>;
|
|
46
|
+
/**
|
|
47
|
+
* Boot a fully-wired Kernel from a `dna.config.yaml` (declarative port wiring —
|
|
48
|
+
* s-dx-kernel-from-config). TS twin of python `Kernel.from_config`.
|
|
49
|
+
*
|
|
50
|
+
* The config selects the `source` (`file://` / `postgresql://`) and,
|
|
51
|
+
* optionally, the `search` + `embedding` providers; every port is resolved to
|
|
52
|
+
* its adapter and wired. With NO config present (and no `path`) the behavior is
|
|
53
|
+
* unchanged — a filesystem `.dna` source, exactly like the bare default.
|
|
54
|
+
*
|
|
55
|
+
* Returns a wired Kernel; call `.instance(scope)` for the ManifestInstance.
|
|
56
|
+
* (Python exposes this as the `Kernel.from_config` static; the TS bootstrap
|
|
57
|
+
* keeps it a free function, mirroring `quickInstance` — same documented
|
|
58
|
+
* asymmetry the SDK already lives with.)
|
|
59
|
+
*/
|
|
60
|
+
export declare function fromConfig(path?: string): Promise<Kernel>;
|
package/dist/bootstrap.js
CHANGED
|
@@ -40,6 +40,8 @@ import { TestkitExtension } from "./extensions/testkit.js";
|
|
|
40
40
|
import { ModelRegExtension } from "./extensions/modelreg.js";
|
|
41
41
|
import { AutomationExtension } from "./extensions/automation.js";
|
|
42
42
|
import { EvalExtension } from "./extensions/eval.js";
|
|
43
|
+
import { loadConfig } from "./config.js";
|
|
44
|
+
import { resolveDefaultFsUrl, sourceFromUrl } from "./adapters/source-url.js";
|
|
43
45
|
/**
|
|
44
46
|
* The canonical built-in extension set. Loaded into BOTH a Kernel and a
|
|
45
47
|
* Runtime through this SINGLE list so the two bootstraps can never drift
|
|
@@ -132,3 +134,84 @@ export async function quickManifest(scope, baseDir = ".dna") {
|
|
|
132
134
|
rt.resolver("github", new GitHubResolver());
|
|
133
135
|
return rt.manifest(scope);
|
|
134
136
|
}
|
|
137
|
+
/** Minimal CachePort for non-filesystem sources (all docs self-contained).
|
|
138
|
+
* TS twin of python `kernel_bootstrap._NoopCache`. */
|
|
139
|
+
class NoopCache {
|
|
140
|
+
async loadAll() {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
async loadKey() {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
async store(_scope, _key, _items) {
|
|
147
|
+
// no-op
|
|
148
|
+
}
|
|
149
|
+
async has() {
|
|
150
|
+
return true; // pretend always hit → skip resolver calls
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Boot a fully-wired Kernel from a `dna.config.yaml` (declarative port wiring —
|
|
155
|
+
* s-dx-kernel-from-config). TS twin of python `Kernel.from_config`.
|
|
156
|
+
*
|
|
157
|
+
* The config selects the `source` (`file://` / `postgresql://`) and,
|
|
158
|
+
* optionally, the `search` + `embedding` providers; every port is resolved to
|
|
159
|
+
* its adapter and wired. With NO config present (and no `path`) the behavior is
|
|
160
|
+
* unchanged — a filesystem `.dna` source, exactly like the bare default.
|
|
161
|
+
*
|
|
162
|
+
* Returns a wired Kernel; call `.instance(scope)` for the ManifestInstance.
|
|
163
|
+
* (Python exposes this as the `Kernel.from_config` static; the TS bootstrap
|
|
164
|
+
* keeps it a free function, mirroring `quickInstance` — same documented
|
|
165
|
+
* asymmetry the SDK already lives with.)
|
|
166
|
+
*/
|
|
167
|
+
export async function fromConfig(path) {
|
|
168
|
+
const cfg = loadConfig(path);
|
|
169
|
+
const sourceUrl = cfg ? cfg.source : resolveDefaultFsUrl();
|
|
170
|
+
const source = await sourceFromUrl(sourceUrl);
|
|
171
|
+
const k = createKernelWithBuiltins();
|
|
172
|
+
k.source(source);
|
|
173
|
+
if (source.supportsReaders) {
|
|
174
|
+
const baseDir = source.baseDir;
|
|
175
|
+
k.cache(new FilesystemCache(baseDir));
|
|
176
|
+
k.resolver("local", new LocalResolver(baseDir));
|
|
177
|
+
k.resolver("http", new HttpResolver());
|
|
178
|
+
k.resolver("https", new HttpResolver());
|
|
179
|
+
k.resolver("github", new GitHubResolver());
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
k.cache(new NoopCache());
|
|
183
|
+
}
|
|
184
|
+
// Uniform writer/reader wiring for any KernelAttachable source.
|
|
185
|
+
const attach = source.attachKernel;
|
|
186
|
+
if (typeof attach === "function")
|
|
187
|
+
attach.call(source, k);
|
|
188
|
+
if (cfg) {
|
|
189
|
+
await wireSearch(k, cfg);
|
|
190
|
+
await wireEmbedding(k, cfg);
|
|
191
|
+
}
|
|
192
|
+
return k;
|
|
193
|
+
}
|
|
194
|
+
async function wireEmbedding(kernel, cfg) {
|
|
195
|
+
if (cfg.embedding === "off" || cfg.embedding === "fake")
|
|
196
|
+
return;
|
|
197
|
+
if (cfg.embedding === "onnx") {
|
|
198
|
+
// Dynamic import: the ONNX adapter (transformers.js) must never enter the
|
|
199
|
+
// default bundle (guard: tests/embedding-import-isolation.test.ts).
|
|
200
|
+
const { OnnxEmbeddingProvider } = await import("./adapters/embedding/onnx.js");
|
|
201
|
+
kernel.embeddingProvider(new OnnxEmbeddingProvider());
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function wireSearch(kernel, cfg) {
|
|
205
|
+
if (cfg.search === "off")
|
|
206
|
+
return;
|
|
207
|
+
if (cfg.search === "sqlite-vec") {
|
|
208
|
+
// Dynamic import: sqlite-vec must never enter the default bundle
|
|
209
|
+
// (guard: tests/search-import-isolation.test.ts).
|
|
210
|
+
const { SqliteVecRecordSearchProvider } = await import("./adapters/search/sqlite-vec.js");
|
|
211
|
+
kernel.recordSearchProvider(new SqliteVecRecordSearchProvider(kernel, {}));
|
|
212
|
+
}
|
|
213
|
+
else if (cfg.search === "pgvector") {
|
|
214
|
+
throw new Error("search: pgvector is Python-only in this build — the TS runtime ships " +
|
|
215
|
+
"the sqlite-vec record-search provider. Use `search: sqlite-vec` here.");
|
|
216
|
+
}
|
|
217
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const CONFIG_FILENAME = "dna.config.yaml";
|
|
2
|
+
declare const VALID_SEARCH: readonly ["off", "pgvector", "sqlite-vec"];
|
|
3
|
+
declare const VALID_EMBEDDING: readonly ["off", "fake", "onnx"];
|
|
4
|
+
export type SearchMode = (typeof VALID_SEARCH)[number];
|
|
5
|
+
export type EmbeddingMode = (typeof VALID_EMBEDDING)[number];
|
|
6
|
+
/** Parsed + validated `dna.config.yaml`. */
|
|
7
|
+
export interface DnaConfig {
|
|
8
|
+
/** Scheme URL: `file://` | `sqlite://` | `postgresql://`. */
|
|
9
|
+
source: string;
|
|
10
|
+
/** Record-search provider selector (default `off`). */
|
|
11
|
+
search: SearchMode;
|
|
12
|
+
/** Embedding provider selector (default `off`). */
|
|
13
|
+
embedding: EmbeddingMode;
|
|
14
|
+
/** Where it was loaded from (`null` for a synthesized default). */
|
|
15
|
+
path: string | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Path to `dna.config.yaml` in `start` (default: cwd), or `null` if absent.
|
|
19
|
+
* Deliberately NOT a walk-up — a config's meaning is tied to the boot dir.
|
|
20
|
+
*/
|
|
21
|
+
export declare function findConfig(start?: string): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Load + validate `dna.config.yaml`.
|
|
24
|
+
*
|
|
25
|
+
* - `path` given → it MUST exist (a typo'd path is an error, not a silent
|
|
26
|
+
* fallback); parsed and validated.
|
|
27
|
+
* - `path` omitted → look for `dna.config.yaml` in cwd. Found → parsed. Absent
|
|
28
|
+
* → `null` (the caller keeps its default: a filesystem `.dna` source).
|
|
29
|
+
*
|
|
30
|
+
* Throws on: not-a-mapping, missing `source`, unknown keys, or an out-of-enum
|
|
31
|
+
* `search` / `embedding` value.
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadConfig(path?: string): DnaConfig | null;
|
|
34
|
+
export {};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dna.config.yaml` — declarative port wiring (s-dx-kernel-from-config).
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with python `dna/config.py`. The SAME file drives both SDKs — the
|
|
5
|
+
* schema is language-agnostic:
|
|
6
|
+
*
|
|
7
|
+
* ```yaml
|
|
8
|
+
* # dna.config.yaml
|
|
9
|
+
* source: postgresql://user:pass@host/db # or sqlite:///./dev.db, file://.dna
|
|
10
|
+
* search: pgvector # pgvector | sqlite-vec | off (default: off)
|
|
11
|
+
* embedding: onnx # onnx | fake | off (default: off / fake floor)
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Only `source` is required. This module parses + VALIDATES the file (unknown
|
|
15
|
+
* keys and bad enum values fail loud); the wiring lives in `fromConfig()`.
|
|
16
|
+
*
|
|
17
|
+
* Runtime asymmetry (documented, not silent): the TS runtime ships the
|
|
18
|
+
* filesystem + postgres source adapters; `sqlite://` is Python-only (it rides
|
|
19
|
+
* the Python-only SqlAlchemy adapter). A `sqlite://` source — or a `pgvector`
|
|
20
|
+
* search — in a TS host fails loud with that explanation at wire time.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import yaml from "js-yaml";
|
|
25
|
+
export const CONFIG_FILENAME = "dna.config.yaml";
|
|
26
|
+
const VALID_SEARCH = ["off", "pgvector", "sqlite-vec"];
|
|
27
|
+
const VALID_EMBEDDING = ["off", "fake", "onnx"];
|
|
28
|
+
const KNOWN_KEYS = new Set(["source", "search", "embedding"]);
|
|
29
|
+
/**
|
|
30
|
+
* Path to `dna.config.yaml` in `start` (default: cwd), or `null` if absent.
|
|
31
|
+
* Deliberately NOT a walk-up — a config's meaning is tied to the boot dir.
|
|
32
|
+
*/
|
|
33
|
+
export function findConfig(start) {
|
|
34
|
+
const candidate = join(start ?? process.cwd(), CONFIG_FILENAME);
|
|
35
|
+
return existsSync(candidate) ? candidate : null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Load + validate `dna.config.yaml`.
|
|
39
|
+
*
|
|
40
|
+
* - `path` given → it MUST exist (a typo'd path is an error, not a silent
|
|
41
|
+
* fallback); parsed and validated.
|
|
42
|
+
* - `path` omitted → look for `dna.config.yaml` in cwd. Found → parsed. Absent
|
|
43
|
+
* → `null` (the caller keeps its default: a filesystem `.dna` source).
|
|
44
|
+
*
|
|
45
|
+
* Throws on: not-a-mapping, missing `source`, unknown keys, or an out-of-enum
|
|
46
|
+
* `search` / `embedding` value.
|
|
47
|
+
*/
|
|
48
|
+
export function loadConfig(path) {
|
|
49
|
+
let resolved;
|
|
50
|
+
if (path !== undefined) {
|
|
51
|
+
if (!existsSync(path)) {
|
|
52
|
+
throw new Error(`dna config not found at ${path} — pass a path to an existing ` +
|
|
53
|
+
`${CONFIG_FILENAME}, or omit it to auto-discover one in the ` +
|
|
54
|
+
`current directory.`);
|
|
55
|
+
}
|
|
56
|
+
resolved = path;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const found = findConfig();
|
|
60
|
+
if (found === null)
|
|
61
|
+
return null;
|
|
62
|
+
resolved = found;
|
|
63
|
+
}
|
|
64
|
+
const raw = yaml.load(readFileSync(resolved, "utf-8"));
|
|
65
|
+
return parse(raw, resolved);
|
|
66
|
+
}
|
|
67
|
+
function parse(raw, path) {
|
|
68
|
+
if (raw === null || raw === undefined) {
|
|
69
|
+
throw new Error(`${path} is empty — it must at least declare a \`source:\` URL ` +
|
|
70
|
+
`(e.g. \`source: file://.dna\`).`);
|
|
71
|
+
}
|
|
72
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
73
|
+
throw new Error(`${path} must be a YAML mapping (key: value).`);
|
|
74
|
+
}
|
|
75
|
+
const obj = raw;
|
|
76
|
+
const unknown = Object.keys(obj).filter((k) => !KNOWN_KEYS.has(k)).sort();
|
|
77
|
+
if (unknown.length > 0) {
|
|
78
|
+
throw new Error(`${path}: unknown key(s) ${JSON.stringify(unknown)} — supported keys ` +
|
|
79
|
+
`are ${JSON.stringify([...KNOWN_KEYS].sort())}.`);
|
|
80
|
+
}
|
|
81
|
+
const source = obj.source;
|
|
82
|
+
if (typeof source !== "string" || source.length === 0) {
|
|
83
|
+
throw new Error(`${path}: \`source:\` is required and must be a URL string ` +
|
|
84
|
+
`(file:// | sqlite:// | postgresql://).`);
|
|
85
|
+
}
|
|
86
|
+
const search = String(obj.search ?? "off").trim() || "off";
|
|
87
|
+
if (!VALID_SEARCH.includes(search)) {
|
|
88
|
+
throw new Error(`${path}: \`search: ${search}\` is not valid — choose one of ` +
|
|
89
|
+
`${JSON.stringify(VALID_SEARCH)}.`);
|
|
90
|
+
}
|
|
91
|
+
const embedding = String(obj.embedding ?? "off").trim() || "off";
|
|
92
|
+
if (!VALID_EMBEDDING.includes(embedding)) {
|
|
93
|
+
throw new Error(`${path}: \`embedding: ${embedding}\` is not valid — choose one of ` +
|
|
94
|
+
`${JSON.stringify(VALID_EMBEDDING)}.`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
source,
|
|
98
|
+
search: search,
|
|
99
|
+
embedding: embedding,
|
|
100
|
+
path,
|
|
101
|
+
};
|
|
102
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,12 @@ export { LockManager } from "./kernel/lock-manager.js";
|
|
|
21
21
|
export { ReportBuilder } from "./kernel/reports.js";
|
|
22
22
|
export { serializeRawToFiles } from "./kernel/serialize-to-files.js";
|
|
23
23
|
export * from "./viz/index.js";
|
|
24
|
-
export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest } from "./bootstrap.js";
|
|
24
|
+
export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest, fromConfig } from "./bootstrap.js";
|
|
25
|
+
export { AgentNotFound } from "./kernel/errors.js";
|
|
26
|
+
export { PromptLibrary, loadPrompts } from "./prompts.js";
|
|
27
|
+
export { loadConfig, findConfig, CONFIG_FILENAME } from "./config.js";
|
|
28
|
+
export type { DnaConfig, SearchMode, EmbeddingMode } from "./config.js";
|
|
29
|
+
export { sourceFromUrl, resolveDefaultFsUrl, UnsupportedSourceScheme } from "./adapters/source-url.js";
|
|
25
30
|
export { HookRegistry, KNOWN_HOOK_NAMES } from "./kernel/hooks.js";
|
|
26
31
|
export type { HookContext, HookName, HookNameArg, Middleware, EventHandler, PreSaveContext, VetoHandler, } from "./kernel/hooks.js";
|
|
27
32
|
export type { SourcePort, CachePort, ResolverPort, ReaderPort, WriterPort, KindPort, KindPresentation, Extension, ExtensionHost, CompositionResult, CacheItem, ResolvedItem, StorageDescriptor, StoragePattern, BodyMode, SerializedFile, SerializedDocument, WritableSourcePort, QueryFilter, QueryOrder, CountResult, SourceQueryOpts, SourceCountOpts, RecordStorePort, RecordSearchProvider, EmbeddingPort, ToolPort, } from "./kernel/protocols.js";
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,13 @@ export { serializeRawToFiles } from "./kernel/serialize-to-files.js";
|
|
|
24
24
|
// Viz — diagram generators, health, matrix, ASCII tree
|
|
25
25
|
export * from "./viz/index.js";
|
|
26
26
|
// Opinionated bootstrap — extension wiring lives here, not in kernel/
|
|
27
|
-
export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest } from "./bootstrap.js";
|
|
27
|
+
export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest, fromConfig } from "./bootstrap.js";
|
|
28
|
+
// DX consumer surface (s-dx-*): fail-loud prompt building + the collapse-the-
|
|
29
|
+
// shim helper + declarative port wiring.
|
|
30
|
+
export { AgentNotFound } from "./kernel/errors.js";
|
|
31
|
+
export { PromptLibrary, loadPrompts } from "./prompts.js";
|
|
32
|
+
export { loadConfig, findConfig, CONFIG_FILENAME } from "./config.js";
|
|
33
|
+
export { sourceFromUrl, resolveDefaultFsUrl, UnsupportedSourceScheme } from "./adapters/source-url.js";
|
|
28
34
|
export { HookRegistry, KNOWN_HOOK_NAMES } from "./kernel/hooks.js";
|
|
29
35
|
// i-005 — types REFERENCED by already-exported public API, previously
|
|
30
36
|
// broken xrefs in TypeDoc. Each is public surface a consumer needs to name:
|
package/dist/kernel/errors.d.ts
CHANGED
|
@@ -13,6 +13,24 @@
|
|
|
13
13
|
* These errors surface those problems at boot time instead of as silent
|
|
14
14
|
* runtime drift.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* `buildPrompt({ agent: X })` was asked for an agent that no prompt-target
|
|
18
|
+
* document in the loaded manifest declares (missing, renamed, or unparseable).
|
|
19
|
+
*
|
|
20
|
+
* Fail-loud contract (s-dx-build-prompt-fail-loud): the builder used to RETURN
|
|
21
|
+
* the string `"Agent 'X' not found"` instead of throwing — which sailed
|
|
22
|
+
* straight through a consumer's `if (!text)` check and became the LITERAL
|
|
23
|
+
* agent instruction. Every consumer therefore wrote the same defensive guard
|
|
24
|
+
* (`mi.findAgent(x) === null`) before every call. Throwing a typed error
|
|
25
|
+
* deletes that guard: the miss is now impossible to ignore.
|
|
26
|
+
*
|
|
27
|
+
* 1:1 parity with python `dna.AgentNotFound` (a `LookupError` subclass).
|
|
28
|
+
* Exported publicly from the package root.
|
|
29
|
+
*/
|
|
30
|
+
export declare class AgentNotFound extends Error {
|
|
31
|
+
readonly agent: string | null;
|
|
32
|
+
constructor(agent: string | null);
|
|
33
|
+
}
|
|
16
34
|
/** Base class for kernel registration validation failures. */
|
|
17
35
|
export declare class KernelRegistrationError extends Error {
|
|
18
36
|
constructor(message?: string);
|
package/dist/kernel/errors.js
CHANGED
|
@@ -13,6 +13,29 @@
|
|
|
13
13
|
* These errors surface those problems at boot time instead of as silent
|
|
14
14
|
* runtime drift.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* `buildPrompt({ agent: X })` was asked for an agent that no prompt-target
|
|
18
|
+
* document in the loaded manifest declares (missing, renamed, or unparseable).
|
|
19
|
+
*
|
|
20
|
+
* Fail-loud contract (s-dx-build-prompt-fail-loud): the builder used to RETURN
|
|
21
|
+
* the string `"Agent 'X' not found"` instead of throwing — which sailed
|
|
22
|
+
* straight through a consumer's `if (!text)` check and became the LITERAL
|
|
23
|
+
* agent instruction. Every consumer therefore wrote the same defensive guard
|
|
24
|
+
* (`mi.findAgent(x) === null`) before every call. Throwing a typed error
|
|
25
|
+
* deletes that guard: the miss is now impossible to ignore.
|
|
26
|
+
*
|
|
27
|
+
* 1:1 parity with python `dna.AgentNotFound` (a `LookupError` subclass).
|
|
28
|
+
* Exported publicly from the package root.
|
|
29
|
+
*/
|
|
30
|
+
export class AgentNotFound extends Error {
|
|
31
|
+
agent;
|
|
32
|
+
constructor(agent) {
|
|
33
|
+
super(`Agent '${agent}' not found`);
|
|
34
|
+
this.name = "AgentNotFound";
|
|
35
|
+
this.agent = agent;
|
|
36
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
16
39
|
/** Base class for kernel registration validation failures. */
|
|
17
40
|
export class KernelRegistrationError extends Error {
|
|
18
41
|
constructor(message) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import Mustache from "mustache";
|
|
12
12
|
import { stripPromptBlock } from "./_text.js";
|
|
13
|
+
import { AgentNotFound } from "./errors.js";
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// PromptBuilder
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
@@ -38,7 +39,10 @@ export class PromptBuilder {
|
|
|
38
39
|
}
|
|
39
40
|
const agentDoc = agentName ? this._findAgent(agentName) : null;
|
|
40
41
|
if (!agentDoc) {
|
|
41
|
-
|
|
42
|
+
// Fail loud (s-dx-build-prompt-fail-loud): throw a typed error instead
|
|
43
|
+
// of returning a placeholder string that would become the literal
|
|
44
|
+
// instruction.
|
|
45
|
+
throw new AgentNotFound(agentName);
|
|
42
46
|
}
|
|
43
47
|
// Build context — merge backwards-compat params into enabledSlots
|
|
44
48
|
const enabledSlots = { ...(opts?.enabledSlots ?? {}) };
|
|
@@ -69,7 +73,10 @@ export class PromptBuilder {
|
|
|
69
73
|
data: {},
|
|
70
74
|
});
|
|
71
75
|
}
|
|
72
|
-
|
|
76
|
+
// Clean output (s-dx-clean-composition-output): template sections can pad
|
|
77
|
+
// the composed prompt with trailing newlines; consumers used to strip them
|
|
78
|
+
// themselves. Return it already clean.
|
|
79
|
+
return prompt.replace(/\n+$/, "");
|
|
73
80
|
}
|
|
74
81
|
// -------------------------------------------------------------------------
|
|
75
82
|
// Private helpers
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ManifestInstance } from "./kernel/instance.js";
|
|
2
|
+
/** Lazy, cached view `agent name -> composed prompt` over one scope. */
|
|
3
|
+
export declare class PromptLibrary {
|
|
4
|
+
/** The underlying ManifestInstance (drop down for the full surface). */
|
|
5
|
+
readonly mi: ManifestInstance;
|
|
6
|
+
private readonly cache;
|
|
7
|
+
constructor(
|
|
8
|
+
/** The underlying ManifestInstance (drop down for the full surface). */
|
|
9
|
+
mi: ManifestInstance);
|
|
10
|
+
/**
|
|
11
|
+
* Compose `name`'s prompt (cached). Throws {@link AgentNotFound} on a miss;
|
|
12
|
+
* the returned text is clean (no trailing newlines).
|
|
13
|
+
*/
|
|
14
|
+
get(name: string): Promise<string>;
|
|
15
|
+
/** True when `name` is a prompt-target document in the scope (no compose). */
|
|
16
|
+
has(name: string): boolean;
|
|
17
|
+
/** Names of every prompt-target document in the scope, sorted. */
|
|
18
|
+
names(): string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compose the prompts of `scope` behind a {@link PromptLibrary}.
|
|
22
|
+
*
|
|
23
|
+
* `baseDir` follows the {@link quickInstance} convention — the directory that
|
|
24
|
+
* holds `<scope>/` (the `.dna` scopes root). Omitted → the `DNA_BASE_DIR` env
|
|
25
|
+
* var, then `.dna` in the cwd.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadPrompts(scope: string, baseDir?: string): Promise<PromptLibrary>;
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loadPrompts` — the one call that collapses the prompt shim (TS twin of
|
|
3
|
+
* python `dna.load_prompts`).
|
|
4
|
+
*
|
|
5
|
+
* With the fail-loud builder (s-dx-build-prompt-fail-loud) and clean output
|
|
6
|
+
* (s-dx-clean-composition-output) in place, a hand-rolled prompt module
|
|
7
|
+
* becomes:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { loadPrompts } from "@ruinosus/dna";
|
|
11
|
+
*
|
|
12
|
+
* const prompts = await loadPrompts("helpdesk");
|
|
13
|
+
* export const TRIAGE = await prompts.get("triage"); // composed, clean, or throws
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* `loadPrompts` boots a filesystem kernel for `scope` and returns a
|
|
17
|
+
* {@link PromptLibrary} — a lazy, cached, read-only view from agent name to
|
|
18
|
+
* composed system prompt. A missing agent throws {@link AgentNotFound} (never a
|
|
19
|
+
* placeholder string); the returned text is already stripped of trailing
|
|
20
|
+
* newlines.
|
|
21
|
+
*
|
|
22
|
+
* TS/Py asymmetry: composition is async in TS, so `get(name)` returns a
|
|
23
|
+
* `Promise<string>` (python's `lib[name]` is sync — same reason `buildPrompt`
|
|
24
|
+
* is `await`ed in TS but not in Python).
|
|
25
|
+
*/
|
|
26
|
+
import { quickInstance } from "./bootstrap.js";
|
|
27
|
+
/** Lazy, cached view `agent name -> composed prompt` over one scope. */
|
|
28
|
+
export class PromptLibrary {
|
|
29
|
+
mi;
|
|
30
|
+
cache = new Map();
|
|
31
|
+
constructor(
|
|
32
|
+
/** The underlying ManifestInstance (drop down for the full surface). */
|
|
33
|
+
mi) {
|
|
34
|
+
this.mi = mi;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compose `name`'s prompt (cached). Throws {@link AgentNotFound} on a miss;
|
|
38
|
+
* the returned text is clean (no trailing newlines).
|
|
39
|
+
*/
|
|
40
|
+
async get(name) {
|
|
41
|
+
const cached = this.cache.get(name);
|
|
42
|
+
if (cached !== undefined)
|
|
43
|
+
return cached;
|
|
44
|
+
// buildPrompt throws AgentNotFound on a miss and returns clean text.
|
|
45
|
+
const text = await this.mi.buildPrompt({ agent: name });
|
|
46
|
+
this.cache.set(name, text);
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
/** True when `name` is a prompt-target document in the scope (no compose). */
|
|
50
|
+
has(name) {
|
|
51
|
+
return this.names().includes(name);
|
|
52
|
+
}
|
|
53
|
+
/** Names of every prompt-target document in the scope, sorted. */
|
|
54
|
+
names() {
|
|
55
|
+
const kinds = this.mi._kinds;
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
for (const doc of this.mi.documents) {
|
|
58
|
+
const kp = kinds.get(`${doc.apiVersion}\0${doc.kind}`);
|
|
59
|
+
if (kp?.isPromptTarget)
|
|
60
|
+
seen.add(doc.name);
|
|
61
|
+
}
|
|
62
|
+
return [...seen].sort();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Compose the prompts of `scope` behind a {@link PromptLibrary}.
|
|
67
|
+
*
|
|
68
|
+
* `baseDir` follows the {@link quickInstance} convention — the directory that
|
|
69
|
+
* holds `<scope>/` (the `.dna` scopes root). Omitted → the `DNA_BASE_DIR` env
|
|
70
|
+
* var, then `.dna` in the cwd.
|
|
71
|
+
*/
|
|
72
|
+
export async function loadPrompts(scope, baseDir) {
|
|
73
|
+
const resolved = baseDir ?? process.env.DNA_BASE_DIR ?? ".dna";
|
|
74
|
+
const mi = await quickInstance(scope, resolved);
|
|
75
|
+
return new PromptLibrary(mi);
|
|
76
|
+
}
|