dna-sdk 0.5.0 → 0.7.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.
@@ -10,6 +10,9 @@
10
10
  * file:// <path> → FilesystemSource (read/write on disk)
11
11
  * fs:// <path> → alias of file://
12
12
  * <plain path> → treated as file://<path>
13
+ * pkg://<pkg>[/sub] → FilesystemSource (READ-ONLY) over a scope embedded
14
+ * as PACKAGE DATA of <pkg> (sub defaults to .dna);
15
+ * travels with the app (tarball / Docker)
13
16
  * postgresql:// … → PostgresSource (node-postgres)
14
17
  * postgres:// … → alias of postgresql://
15
18
  * sqlite:// <path> → NOT SUPPORTED in the TS runtime (Python-only; the
@@ -16,6 +16,22 @@ function schemeOf(url) {
16
16
  const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(url);
17
17
  return (m ? m[1] : "file").toLowerCase();
18
18
  }
19
+ /**
20
+ * Split `pkg://<package>[/<subpath>]` into `{ pkg, subpath }`. Parity with the
21
+ * python `_parse_pkg_url`: `pkg://app` → `{pkg:"app", subpath:""}`;
22
+ * `pkg://app/.dna` → `{pkg:"app", subpath:".dna"}`. The package is the netloc
23
+ * (a dotted `pkg://my.app` stays `my.app`).
24
+ */
25
+ function parsePkgUrl(url) {
26
+ const m = /^pkg:\/\/([^/]*)(?:\/(.*))?$/.exec(url);
27
+ const pkg = m?.[1] ?? "";
28
+ if (!pkg) {
29
+ throw new UnsupportedSourceScheme(`pkg:// source URL is missing a package name — use ` +
30
+ `pkg://<package>[/<subpath>] (e.g. pkg://app or pkg://app/.dna). ` +
31
+ `Got: ${url}`);
32
+ }
33
+ return { pkg, subpath: m?.[2] ?? "" };
34
+ }
19
35
  /**
20
36
  * Build a source from a scheme URL (see module docstring).
21
37
  *
@@ -29,6 +45,16 @@ export async function sourceFromUrl(url, opts = {}) {
29
45
  const { FilesystemSource } = await import("./filesystem/source.js");
30
46
  return new FilesystemSource(urlToFsPath(url) || url);
31
47
  }
48
+ if (scheme === "pkg") {
49
+ // A scope embedded as PACKAGE DATA — resolve it from inside the installed
50
+ // package so it travels with the app (tarball / Docker), no path
51
+ // navigation and no manual copy. READ-ONLY: FilesystemSource has no write
52
+ // surface here (to write, use file:// or postgresql://).
53
+ const { FilesystemSource } = await import("./filesystem/source.js");
54
+ const { anchorScopesRoot, DEFAULT_SUBPATH } = await import("../package-scope.js");
55
+ const { pkg, subpath } = parsePkgUrl(url);
56
+ return new FilesystemSource(anchorScopesRoot(pkg, subpath || DEFAULT_SUBPATH));
57
+ }
32
58
  if (base === "postgresql" || base === "postgres") {
33
59
  const { PostgresSource } = await import("./postgres/source.js");
34
60
  const src = new PostgresSource({ connectionString: url, schema: opts.schema });
@@ -41,7 +67,8 @@ export async function sourceFromUrl(url, opts = {}) {
41
67
  `(filesystem) or postgresql:// here. Got: ${url}`);
42
68
  }
43
69
  throw new UnsupportedSourceScheme(`unsupported source URL scheme '${scheme}://' — the TS runtime ships ` +
44
- `file:// (filesystem) and postgresql:// adapters. Got: ${url}`);
70
+ `file:// (filesystem), pkg:// (read-only package-data scope) and ` +
71
+ `postgresql:// adapters. Got: ${url}`);
45
72
  }
46
73
  /**
47
74
  * The `file://` URL the SDK falls back to with no explicit config.
@@ -0,0 +1,16 @@
1
+ import type { EmitContext, EmitResult, EmitterPort } from "./index.js";
2
+ /** `concierge-grounded` → `ConciergeGrounded`. */
3
+ export declare function camel(name: string): string;
4
+ /** Split a DNA model coordinate into agent-framework `{id, provider}`. */
5
+ export declare function splitModel(model: string | null, providerHint?: string | null): {
6
+ id: string;
7
+ provider: string;
8
+ } | null;
9
+ export declare class AgentFrameworkEmitter implements EmitterPort {
10
+ readonly target = "agent-framework";
11
+ readonly fileExtension = "agent.yaml";
12
+ /** The PURE de-para: {@link EmitContext} → the PromptAgent object. Field
13
+ * order is intentional and preserved by js-yaml (insertion order). */
14
+ toPromptAgent(ctx: EmitContext): Record<string, unknown>;
15
+ emit(ctx: EmitContext): EmitResult;
16
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * DNA → Microsoft **agent-framework** emitter (TS twin of python
3
+ * `dna.emit.agent_framework`). Materializes an {@link EmitContext} into the
4
+ * declarative `PromptAgent` YAML that `agent-framework-declarative`'s
5
+ * `AgentFactory` loads.
6
+ *
7
+ * The de-para (DNA field → PromptAgent field):
8
+ * metadata.name -> name (CamelCased id)
9
+ * metadata.description -> description
10
+ * Soul + guardrails + instruction -> instructions (flat — kernel-composed)
11
+ * spec.model (or Genome default_llm)-> model.{id, provider}
12
+ * spec.tools[] (Tool Kind surfaces) -> tools[] (kind: function)
13
+ * spec.output_schema -> outputSchema (only when present)
14
+ *
15
+ * `toPromptAgent` is the PURE de-para and is parity-critical: it must build the
16
+ * SAME object the Python `to_prompt_agent` builds from the same context.
17
+ */
18
+ import yaml from "js-yaml";
19
+ /** DNA provider token → agent-framework `model.provider` value. Unknown tokens
20
+ * pass through unchanged so a future provider needs no code change. */
21
+ const PROVIDER_MAP = {
22
+ azure: "AzureOpenAI",
23
+ azureopenai: "AzureOpenAI",
24
+ azure_openai: "AzureOpenAI",
25
+ openai: "OpenAI",
26
+ anthropic: "Anthropic",
27
+ foundry: "AzureAIFoundry",
28
+ azureaifoundry: "AzureAIFoundry",
29
+ };
30
+ /** Bare model with no provider token and no `--provider` → AzureOpenAI (the
31
+ * provider the spike proved). Documented default, never silently wrong. */
32
+ const DEFAULT_PROVIDER = "AzureOpenAI";
33
+ /** `concierge-grounded` → `ConciergeGrounded`. */
34
+ export function camel(name) {
35
+ return String(name)
36
+ .replace(/_/g, "-")
37
+ .split("-")
38
+ .filter(Boolean)
39
+ .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
40
+ .join("");
41
+ }
42
+ /** Split a DNA model coordinate into agent-framework `{id, provider}`. */
43
+ export function splitModel(model, providerHint) {
44
+ if (!model)
45
+ return null;
46
+ let token = null;
47
+ let ident = model;
48
+ for (const sep of [":", "/"]) {
49
+ const i = model.indexOf(sep);
50
+ if (i >= 0) {
51
+ token = model.slice(0, i);
52
+ ident = model.slice(i + 1);
53
+ break;
54
+ }
55
+ }
56
+ let provider;
57
+ if (providerHint)
58
+ provider = providerHint;
59
+ else if (token)
60
+ provider = PROVIDER_MAP[token.trim().toLowerCase()] ?? token;
61
+ else
62
+ provider = DEFAULT_PROVIDER;
63
+ return { id: ident.trim(), provider };
64
+ }
65
+ function emitTools(tools) {
66
+ return tools.map((t) => {
67
+ const entry = {
68
+ name: t.name,
69
+ kind: "function", // AgentSchema function-tool kind (NOT `type`)
70
+ description: t.description ?? "",
71
+ };
72
+ if (t.parameters && Object.keys(t.parameters).length > 0) {
73
+ entry.parameters = t.parameters;
74
+ }
75
+ return entry;
76
+ });
77
+ }
78
+ export class AgentFrameworkEmitter {
79
+ target = "agent-framework";
80
+ fileExtension = "agent.yaml";
81
+ /** The PURE de-para: {@link EmitContext} → the PromptAgent object. Field
82
+ * order is intentional and preserved by js-yaml (insertion order). */
83
+ toPromptAgent(ctx) {
84
+ const providerHint = ctx.options?.provider ?? null;
85
+ const doc = { kind: "Prompt", name: camel(ctx.name) };
86
+ if (ctx.description)
87
+ doc.description = ctx.description;
88
+ const model = splitModel(ctx.model, providerHint);
89
+ if (model)
90
+ doc.model = model;
91
+ if (ctx.tools.length > 0)
92
+ doc.tools = emitTools(ctx.tools);
93
+ doc.instructions = ctx.instructions; // verbatim — the byte-equal gate
94
+ if (ctx.outputSchema)
95
+ doc.outputSchema = ctx.outputSchema;
96
+ return doc;
97
+ }
98
+ emit(ctx) {
99
+ const promptAgent = this.toPromptAgent(ctx);
100
+ const artifact = yaml.dump(promptAgent, { sortKeys: false, lineWidth: -1 });
101
+ const losses = [
102
+ "composition structure — Soul reuse + wired Guardrails flatten to one " +
103
+ "`instructions` string (no `soul:`/`guardrails:` slot in a PromptAgent)",
104
+ "tenant overlay — a per-tenant persona without a fork has no PromptAgent field",
105
+ "eval-as-contract — prompt invariants (EvalCases) have no PromptAgent slot",
106
+ ];
107
+ if (ctx.model === null) {
108
+ losses.push("model unbound in DNA and none supplied — emitted PromptAgent has no " +
109
+ "`model:` block; pass provider/model or set spec.model / Genome default_llm");
110
+ }
111
+ const mapping = {
112
+ "metadata.name": "name (CamelCase)",
113
+ "metadata.description": "description",
114
+ "buildPrompt (Soul+guardrails+instruction)": "instructions",
115
+ "spec.model / Genome.default_llm": "model.{id,provider}",
116
+ "spec.tools[] (Tool Kind)": "tools[] (kind: function)",
117
+ "spec.output_schema": "outputSchema",
118
+ };
119
+ return {
120
+ artifact,
121
+ target: this.target,
122
+ filename: `${ctx.name}.${this.fileExtension}`,
123
+ losses,
124
+ mapping,
125
+ };
126
+ }
127
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `emit` — the vendor-neutral EMITTER layer (TS twin of python `dna.emit`).
3
+ *
4
+ * DNA authors an agent ONCE — a persona (Soul), an instruction (Agent), wired
5
+ * Guardrails, and Tools — as declarative Kinds. This module materializes that
6
+ * one neutral definition into the NATIVE artifact each runtime framework
7
+ * consumes. The concrete first step of "DNA as the Terraform of agents": author
8
+ * once, emit per runtime, swap runtimes without rewriting the agent.
9
+ *
10
+ * Parity: the pure de-para (`toPromptAgent` in `agentFramework.ts`) mirrors the
11
+ * Python emitter field-for-field; the serialization (js-yaml vs PyYAML) is a
12
+ * rendering detail, so the parity contract is the emitted OBJECT, not the bytes.
13
+ *
14
+ * Shape: {@link EmitContext} (runtime-agnostic view of a composed agent) →
15
+ * {@link EmitterPort} (a target) → {@link EmitResult} (artifact + honest
16
+ * `losses`). {@link registerEmitter}/{@link getEmitter}/{@link availableTargets}
17
+ * are the pluggable registry — a new target is a class + one register call.
18
+ */
19
+ import type { ManifestInstance } from "../kernel/instance.js";
20
+ /** Runtime-agnostic view of ONE composed DNA agent. */
21
+ export interface EmitContext {
22
+ /** The DNA agent slug (`metadata.name`). */
23
+ name: string;
24
+ /** `metadata.description`, or "". */
25
+ description: string;
26
+ /** DNA-composed system prompt (Soul + guardrails + instruction, flat). The
27
+ * byte-equal gate: an emitter MUST carry it verbatim. */
28
+ instructions: string;
29
+ /** Raw DNA model coordinate (`openai:gpt-4o-mini` / `azure/gpt-4o` / bare), or
30
+ * null when the DNA leaves the model unbound. */
31
+ model: string | null;
32
+ /** Resolved tool surfaces (`spec.tools` → Tool Kind). */
33
+ tools: EmitTool[];
34
+ /** Optional response JSON Schema (`spec.output_schema`), or null. */
35
+ outputSchema: Record<string, unknown> | null;
36
+ /** Scope the agent was composed from (provenance). */
37
+ scope: string | null;
38
+ /** Per-emitter hints (e.g. a `--provider` override). */
39
+ options: Record<string, unknown>;
40
+ }
41
+ export interface EmitTool {
42
+ name: string;
43
+ description: string;
44
+ parameters: Record<string, unknown>;
45
+ }
46
+ /** The emitted native artifact + an honest account of the de-para. */
47
+ export interface EmitResult {
48
+ /** The serialized native artifact. */
49
+ artifact: string;
50
+ /** The target runtime id. */
51
+ target: string;
52
+ /** Suggested filename. */
53
+ filename: string;
54
+ /** DNA axes with NO slot in this target — what did NOT survive the emit. */
55
+ losses: string[];
56
+ /** Field-level de-para (`dnaField -> targetField`). */
57
+ mapping: Record<string, string>;
58
+ }
59
+ /** A runtime emitter — pure: reads an {@link EmitContext}, returns an
60
+ * {@link EmitResult}. No kernel I/O, no network. */
61
+ export interface EmitterPort {
62
+ readonly target: string;
63
+ readonly fileExtension: string;
64
+ emit(ctx: EmitContext): EmitResult;
65
+ }
66
+ export declare class EmitError extends Error {
67
+ }
68
+ export declare class UnknownTarget extends EmitError {
69
+ readonly target: string;
70
+ readonly available: string[];
71
+ constructor(target: string, available: string[]);
72
+ }
73
+ /** Register an emitter under its `target` (last registration wins). */
74
+ export declare function registerEmitter(emitter: EmitterPort): EmitterPort;
75
+ /** Look up a registered emitter or throw {@link UnknownTarget}. */
76
+ export declare function getEmitter(target: string): Promise<EmitterPort>;
77
+ /** Sorted list of registered target ids. */
78
+ export declare function availableTargets(): Promise<string[]>;
79
+ export interface BuildEmitContextOpts {
80
+ model?: string | null;
81
+ provider?: string | null;
82
+ }
83
+ /** Compose a DNA agent through the kernel and project it to an
84
+ * {@link EmitContext}. `mi` is a live ManifestInstance. */
85
+ export declare function buildEmitContext(mi: ManifestInstance, agent: string, opts?: BuildEmitContextOpts): Promise<EmitContext>;
86
+ /** Compose `agent` from `mi` and emit it for `target`. */
87
+ export declare function emitAgent(mi: ManifestInstance, agent: string, target: string, opts?: BuildEmitContextOpts): Promise<EmitResult>;
@@ -0,0 +1,95 @@
1
+ import { ToolLibrary } from "../tools.js";
2
+ export class EmitError extends Error {
3
+ }
4
+ export class UnknownTarget extends EmitError {
5
+ target;
6
+ available;
7
+ constructor(target, available) {
8
+ super(`no emitter registered for target '${target}'; available: ` +
9
+ (available.join(", ") || "(none)"));
10
+ this.target = target;
11
+ this.available = available;
12
+ this.name = "UnknownTarget";
13
+ }
14
+ }
15
+ // ── registry ──────────────────────────────────────────────────────────────
16
+ const EMITTER_REGISTRY = new Map();
17
+ let builtinsWired = false;
18
+ async function ensureBuiltins() {
19
+ if (builtinsWired)
20
+ return;
21
+ builtinsWired = true;
22
+ const { AgentFrameworkEmitter } = await import("./agentFramework.js");
23
+ const e = new AgentFrameworkEmitter();
24
+ if (!EMITTER_REGISTRY.has(e.target))
25
+ EMITTER_REGISTRY.set(e.target, e);
26
+ }
27
+ /** Register an emitter under its `target` (last registration wins). */
28
+ export function registerEmitter(emitter) {
29
+ EMITTER_REGISTRY.set(emitter.target, emitter);
30
+ return emitter;
31
+ }
32
+ /** Look up a registered emitter or throw {@link UnknownTarget}. */
33
+ export async function getEmitter(target) {
34
+ await ensureBuiltins();
35
+ const e = EMITTER_REGISTRY.get(target);
36
+ if (!e)
37
+ throw new UnknownTarget(target, await availableTargets());
38
+ return e;
39
+ }
40
+ /** Sorted list of registered target ids. */
41
+ export async function availableTargets() {
42
+ await ensureBuiltins();
43
+ return [...EMITTER_REGISTRY.keys()].sort();
44
+ }
45
+ /** Compose a DNA agent through the kernel and project it to an
46
+ * {@link EmitContext}. `mi` is a live ManifestInstance. */
47
+ export async function buildEmitContext(mi, agent, opts = {}) {
48
+ const doc = mi.findAgent(agent);
49
+ if (!doc) {
50
+ throw new EmitError(`agent '${agent}' not found in scope '${mi.scope ?? "?"}'`);
51
+ }
52
+ const spec = (doc.spec ?? {});
53
+ const meta = (doc.metadata ?? {});
54
+ const description = meta.description ?? "";
55
+ const instructions = await mi.buildPrompt({ agent });
56
+ let resolvedModel = opts.model ?? spec.model ?? null;
57
+ if (!resolvedModel) {
58
+ const root = mi.root;
59
+ const rootSpec = (root?.spec ?? {});
60
+ resolvedModel = rootSpec.default_llm ?? null;
61
+ }
62
+ const tools = resolveTools(mi, spec);
63
+ const outputSchema = spec.output_schema ?? null;
64
+ return {
65
+ name: doc.name ?? agent,
66
+ description,
67
+ instructions,
68
+ model: resolvedModel,
69
+ tools,
70
+ outputSchema,
71
+ scope: mi.scope ?? null,
72
+ options: opts.provider ? { provider: opts.provider } : {},
73
+ };
74
+ }
75
+ function resolveTools(mi, spec) {
76
+ const names = spec.tools;
77
+ if (!names || names.length === 0)
78
+ return [];
79
+ const lib = new ToolLibrary(mi);
80
+ return names.map((name) => {
81
+ const surface = lib.get(name); // throws ToolNotFound (fail-loud)
82
+ return {
83
+ name,
84
+ description: surface.description,
85
+ parameters: { ...surface.parameters },
86
+ };
87
+ });
88
+ }
89
+ // ── high-level surface ──────────────────────────────────────────────────────
90
+ /** Compose `agent` from `mi` and emit it for `target`. */
91
+ export async function emitAgent(mi, agent, target, opts = {}) {
92
+ const emitter = await getEmitter(target);
93
+ const ctx = await buildEmitContext(mi, agent, opts);
94
+ return emitter.emit(ctx);
95
+ }
@@ -0,0 +1,240 @@
1
+ # Tool — a declarative, invocable capability an agent can call (record plane).
2
+ #
3
+ # "Tools as data" (f-dna-tools-as-data). The DNA already governs persona,
4
+ # instruction and guardrails declaratively; a Tool moves the AGENT-FACING
5
+ # surface of a tool into the same declarative plane — the ``description`` the
6
+ # model reads to decide whether to call it (``metadata.description``) and the
7
+ # ``parameters`` JSON Schema of its arguments (``spec.input_schema``). One
8
+ # source of truth, versioned, testable, and overridable per tenant, served
9
+ # identically to a Python backend (`@tool`) and a TypeScript frontend
10
+ # (CopilotKit `useCopilotAction`) via ``dna.load_tools`` / ``loadTools``.
11
+ #
12
+ # F3 migration (s-tool-kind-descriptor): this WAS a hand-written ``ToolKind``
13
+ # class (helix/__init__.py + helix.ts) with a ``TypedTool``/``ToolSpec``
14
+ # model. It is now a ``kinds/tool.kind.yaml`` descriptor — a record Kind
15
+ # expressed as data, per the repo's own ratchet (record Kinds are
16
+ # descriptors, never classes). The invocation surface (type/endpoint/mcp/
17
+ # python/shell + input/output schema + auth + read_only/requires_confirmation)
18
+ # is preserved verbatim; only the IMPLEMENTATION moved class → descriptor and
19
+ # the plane moved composition → record (a Tool is not a prompt target and
20
+ # never composes into an agent prompt, so it carries no composition signal —
21
+ # writing a Tool no longer invalidates the composition schema cache).
22
+ #
23
+ # Agents reference Tools by name via ``dep_filters.tools`` (unchanged — the
24
+ # alias ``helix-tool`` is stable). Stored as ``tools/<name>.yaml`` —
25
+ # marketplace-shareable as standalone bundles.
26
+ #
27
+ # tenant_scope intentionally NOT declared — permissive (base Tool + optional
28
+ # per-tenant overlay). A tenant may legitimately override a tool's
29
+ # description/parameters (the SaaS value hook); the base stays intact and the
30
+ # máxima "inheritable ⇒ never tenanted" holds.
31
+ #
32
+ # PARITY-CRITICAL package data: byte-identical mirror under both runtimes'
33
+ # helix/kinds/ (tests/test_descriptor_hash_parity.py enforces).
34
+ apiVersion: github.com/ruinosus/dna/core/v1
35
+ kind: KindDefinition
36
+ metadata:
37
+ name: tool
38
+ spec:
39
+ target_api_version: github.com/ruinosus/dna/v1
40
+ target_kind: Tool
41
+ alias: helix-tool
42
+ origin: github.com/ruinosus/dna/tool
43
+ plane: record
44
+ prompt_target: false
45
+ flatten_in_context: false
46
+ prompt_target_priority: 0
47
+ storage:
48
+ type: yaml
49
+ container: tools
50
+ schema:
51
+ type: object
52
+ properties:
53
+ type:
54
+ type: string
55
+ enum:
56
+ - http
57
+ - mcp
58
+ - python
59
+ - shell
60
+ - builtin
61
+ description: How the tool is executed. builtin | http | mcp | python
62
+ | shell.
63
+ endpoint:
64
+ type: string
65
+ description: URL called when type=http. Supports {placeholder}
66
+ templating.
67
+ method:
68
+ type: string
69
+ description: HTTP method when type=http (default POST).
70
+ mcp_server:
71
+ type: string
72
+ description: MCP server name when type=mcp.
73
+ mcp_tool:
74
+ type: string
75
+ description: Tool name on the MCP server when type=mcp.
76
+ python_module:
77
+ type: string
78
+ description: Dotted import path when type=python.
79
+ python_callable:
80
+ type: string
81
+ description: Attribute on the module (function or class) when
82
+ type=python.
83
+ shell_command:
84
+ type: string
85
+ description: Command template when type=shell. Never executed without
86
+ confirmation.
87
+ input_schema:
88
+ type: object
89
+ description: JSON Schema of the arguments the agent passes when
90
+ invoking the tool — the "parameters" the model fills in. Surfaced
91
+ as ``parameters`` by ``dna.load_tools`` / ``loadTools``.
92
+ output_schema:
93
+ type: object
94
+ description: JSON Schema describing the shape of the tool's response.
95
+ auth_type:
96
+ type: string
97
+ enum:
98
+ - none
99
+ - api_key
100
+ - bearer
101
+ - oauth2
102
+ description: Credential strategy for the invocation.
103
+ auth_env_var:
104
+ type: string
105
+ description: Environment variable holding the credential (e.g.
106
+ GITHUB_TOKEN).
107
+ read_only:
108
+ type: boolean
109
+ description: False = the tool may mutate state (DB writes, file
110
+ changes, external side effects).
111
+ requires_confirmation:
112
+ type: boolean
113
+ description: Force user approval before each invocation.
114
+ tags:
115
+ type: array
116
+ items:
117
+ type: string
118
+ description: Free-form labels for filtering and search.
119
+ examples:
120
+ type: array
121
+ items:
122
+ type: object
123
+ description: Usage examples ([{input, output}]).
124
+ ui:
125
+ mode: build
126
+ label:
127
+ en: Tools
128
+ pt-BR: Ferramentas
129
+ description:
130
+ en: '@tool functions available to agents.'
131
+ pt-BR: Ferramentas (@tool) disponíveis aos agentes.
132
+ routes:
133
+ list: docs/Tool
134
+ detail: docs/Tool/:name
135
+ permissions:
136
+ list: any
137
+ detail: any
138
+ in_sidebar: true
139
+ display_order: 53
140
+ ui_schema:
141
+ type:
142
+ widget: select
143
+ label: Invocation type
144
+ help: 'How the tool is executed: http | mcp | python | shell | builtin.'
145
+ order: 10
146
+ endpoint:
147
+ widget: text
148
+ label: HTTP endpoint
149
+ help: URL called when type=http. Supports {placeholder} templating.
150
+ order: 20
151
+ method:
152
+ widget: select
153
+ label: HTTP method
154
+ order: 25
155
+ mcp_server:
156
+ widget: text
157
+ label: MCP server
158
+ help: Server name when type=mcp.
159
+ order: 30
160
+ mcp_tool:
161
+ widget: text
162
+ label: MCP tool name
163
+ order: 35
164
+ python_module:
165
+ widget: text
166
+ label: Python module
167
+ help: Dotted import path when type=python.
168
+ order: 40
169
+ python_callable:
170
+ widget: text
171
+ label: Python callable
172
+ help: Attribute on the module (function or class).
173
+ order: 45
174
+ shell_command:
175
+ widget: textarea
176
+ label: Shell command
177
+ help: Command template when type=shell. Never executed without
178
+ confirmation.
179
+ order: 50
180
+ input_schema:
181
+ widget: code
182
+ language: yaml
183
+ label: Input schema (JSON Schema)
184
+ help: Validates the arguments the agent passes when invoking the tool.
185
+ height: 260
186
+ order: 60
187
+ output_schema:
188
+ widget: code
189
+ language: yaml
190
+ label: Output schema (JSON Schema)
191
+ help: Describes the shape of the tool's response.
192
+ height: 220
193
+ order: 70
194
+ auth_type:
195
+ widget: select
196
+ label: Auth type
197
+ help: none | api_key | bearer | oauth2.
198
+ order: 80
199
+ auth_env_var:
200
+ widget: text
201
+ label: Auth env var
202
+ help: Environment variable holding the credential.
203
+ order: 85
204
+ read_only:
205
+ widget: checkbox
206
+ label: Read-only
207
+ help: Uncheck if the tool mutates state (database writes, file changes,
208
+ external side effects).
209
+ order: 90
210
+ requires_confirmation:
211
+ widget: checkbox
212
+ label: Requires confirmation
213
+ help: Force user approval before each invocation.
214
+ order: 95
215
+ tags:
216
+ widget: tags
217
+ label: Tags
218
+ order: 100
219
+ examples:
220
+ widget: readonly
221
+ label: Examples
222
+ help: Usage examples. Nested list — edit via YAML for now.
223
+ order: 110
224
+ graph_style:
225
+ fill: '#14B8A6'
226
+ stroke: '#0D9488'
227
+ text_color: '#fff'
228
+ ascii_icon: 🔧
229
+ display_label: Tools
230
+ docs: A Tool is a declarative, invocable capability an agent can call — an
231
+ HTTP endpoint, an MCP server tool, a Python callable, a shell command, or
232
+ a builtin. It bridges DNA with OpenAI/Anthropic tool-calling conventions.
233
+ The agent-facing surface is its ``metadata.description`` (the text the
234
+ model reads to decide to call it) and its ``spec.input_schema`` (the
235
+ "parameters" JSON Schema of the arguments); ``dna.load_tools`` /
236
+ ``loadTools`` serve exactly that surface, identically to Python and
237
+ TypeScript consumers from this one source. It also declares an auth
238
+ strategy and read_only / requires_confirmation flags the host honors at
239
+ runtime. Agents reference Tools via ``dep_filters.tools``. Stored as
240
+ ``tools/<name>.yaml`` — marketplace-shareable as standalone bundles.