dna-sdk 0.8.0 → 0.9.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/emit/index.js +2 -1
- package/dist/emit/vertex.d.ts +19 -0
- package/dist/emit/vertex.js +155 -0
- package/dist/extensions/sdlc.js +3 -0
- package/package.json +1 -1
package/dist/emit/index.js
CHANGED
|
@@ -21,7 +21,8 @@ async function ensureBuiltins() {
|
|
|
21
21
|
builtinsWired = true;
|
|
22
22
|
const { AgentFrameworkEmitter } = await import("./agentFramework.js");
|
|
23
23
|
const { BedrockEmitter } = await import("./bedrock.js");
|
|
24
|
-
|
|
24
|
+
const { VertexEmitter } = await import("./vertex.js");
|
|
25
|
+
for (const e of [new AgentFrameworkEmitter(), new BedrockEmitter(), new VertexEmitter()]) {
|
|
25
26
|
if (!EMITTER_REGISTRY.has(e.target))
|
|
26
27
|
EMITTER_REGISTRY.set(e.target, e);
|
|
27
28
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EmitContext, EmitResult, EmitterPort } from "./index.js";
|
|
2
|
+
/** `concierge-grounded` → `concierge_grounded` (a valid ADK agent name — ADK
|
|
3
|
+
* requires a Python identifier). Mirrors Bedrock's `camel` logical-id transform. */
|
|
4
|
+
export declare function snake(name: string): string;
|
|
5
|
+
/** Project a DNA model coordinate → an ADK `model` id. A known DNA provider token
|
|
6
|
+
* (`vertex/gemini-2.0-flash` → `gemini-2.0-flash`, `openai:gpt-4o` → `gpt-4o`) is
|
|
7
|
+
* stripped; a bare id passes through. */
|
|
8
|
+
export declare function vertexModelId(model: string | null): string | null;
|
|
9
|
+
/** Whether an emitted `model` id is a native Gemini id (else needs `model_code`). */
|
|
10
|
+
export declare function isGemini(modelId: string | null): boolean;
|
|
11
|
+
export declare class VertexEmitter implements EmitterPort {
|
|
12
|
+
readonly target = "vertex";
|
|
13
|
+
readonly fileExtension = "adk.yaml";
|
|
14
|
+
/** The PURE de-para: {@link EmitContext} → the ADK Agent Config object. Field
|
|
15
|
+
* order is intentional; the schema-header comment is prepended at serialization
|
|
16
|
+
* time, not part of this object. */
|
|
17
|
+
toAgentConfig(ctx: EmitContext): Record<string, unknown>;
|
|
18
|
+
emit(ctx: EmitContext): EmitResult;
|
|
19
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DNA → **Google ADK Agent Config** emitter (TS twin of python
|
|
3
|
+
* `dna.emit.vertex`). Materializes an {@link EmitContext} into a **Google Agent
|
|
4
|
+
* Development Kit (ADK) Agent Config** YAML — the declarative, code-free way to
|
|
5
|
+
* define an ADK `LlmAgent` (loaded with `config_agent_utils.from_config(...)`).
|
|
6
|
+
* The THIRD runtime the SAME DNA source emits to (after Microsoft agent-framework
|
|
7
|
+
* and Amazon Bedrock): the portability proof — author once, emit per runtime.
|
|
8
|
+
*
|
|
9
|
+
* Why the ADK **Agent Config** (not the code-first `LlmAgent` object or Vertex AI
|
|
10
|
+
* Agent Engine): only Agent Config has a published, field-for-field *declarative*
|
|
11
|
+
* schema (`agent_class`, `name`, `model`, `instruction`, `tools`, …), so it is the
|
|
12
|
+
* only honest de-para target. Emitting the YAML gives a lintable artifact that
|
|
13
|
+
* needs no GCP credential to produce or validate structurally (the
|
|
14
|
+
* `# yaml-language-server` header wires the real schema into any editor).
|
|
15
|
+
*
|
|
16
|
+
* The de-para (DNA field → ADK `LlmAgentConfig` field):
|
|
17
|
+
* (fixed) -> agent_class: LlmAgent
|
|
18
|
+
* metadata.name -> name (snake_cased — valid identifier)
|
|
19
|
+
* metadata.description -> description (when present)
|
|
20
|
+
* Soul + guardrails + instruction -> instruction (flat, BYTE-EQUAL)
|
|
21
|
+
* spec.model (or Genome default_llm)-> model (Gemini id; provider stripped)
|
|
22
|
+
* spec.tools[] (Tool Kind surfaces) -> tools[].name (a CODE reference — see loss)
|
|
23
|
+
*
|
|
24
|
+
* `toAgentConfig` is the PURE de-para and is parity-critical: it must build the
|
|
25
|
+
* SAME object the Python `to_agent_config` builds from the same context.
|
|
26
|
+
*/
|
|
27
|
+
import yaml from "js-yaml";
|
|
28
|
+
/** The published ADK Agent Config JSON Schema — emitted as a leading
|
|
29
|
+
* `# yaml-language-server` header so any editor / validator binds the artifact to
|
|
30
|
+
* the REAL schema (the credential-free structural-validation hook). */
|
|
31
|
+
const ADK_SCHEMA_URL = "https://raw.githubusercontent.com/google/adk-python/refs/heads/main/" +
|
|
32
|
+
"src/google/adk/agents/config_schemas/AgentConfig.json";
|
|
33
|
+
/** The ADK `agent_class` this emitter targets (the declarative LLM agent). */
|
|
34
|
+
const AGENT_CLASS = "LlmAgent";
|
|
35
|
+
/** DNA provider tokens (the `prov:model` / `prov/model` prefixes). Stripped to
|
|
36
|
+
* expose the bare model id; a bare Gemini id passes through unchanged. */
|
|
37
|
+
const DNA_PROVIDER_TOKENS = new Set([
|
|
38
|
+
"azure", "azureopenai", "azure_openai", "openai", "foundry", "azureaifoundry",
|
|
39
|
+
"vertex", "google", "gemini", "anthropic",
|
|
40
|
+
]);
|
|
41
|
+
/** `concierge-grounded` → `concierge_grounded` (a valid ADK agent name — ADK
|
|
42
|
+
* requires a Python identifier). Mirrors Bedrock's `camel` logical-id transform. */
|
|
43
|
+
export function snake(name) {
|
|
44
|
+
let ident = String(name)
|
|
45
|
+
.trim()
|
|
46
|
+
.split("")
|
|
47
|
+
.map((ch) => (/[a-zA-Z0-9_]/.test(ch) ? ch : "_"))
|
|
48
|
+
.join("")
|
|
49
|
+
.replace(/^_+|_+$/g, "")
|
|
50
|
+
.toLowerCase();
|
|
51
|
+
if (!ident)
|
|
52
|
+
ident = "agent";
|
|
53
|
+
if (/^[0-9]/.test(ident))
|
|
54
|
+
ident = `_${ident}`;
|
|
55
|
+
return ident;
|
|
56
|
+
}
|
|
57
|
+
/** Project a DNA model coordinate → an ADK `model` id. A known DNA provider token
|
|
58
|
+
* (`vertex/gemini-2.0-flash` → `gemini-2.0-flash`, `openai:gpt-4o` → `gpt-4o`) is
|
|
59
|
+
* stripped; a bare id passes through. */
|
|
60
|
+
export function vertexModelId(model) {
|
|
61
|
+
if (!model)
|
|
62
|
+
return null;
|
|
63
|
+
const ident = model.trim();
|
|
64
|
+
const slash = ident.indexOf("/");
|
|
65
|
+
if (slash >= 0) {
|
|
66
|
+
const token = ident.slice(0, slash).trim().toLowerCase();
|
|
67
|
+
if (DNA_PROVIDER_TOKENS.has(token))
|
|
68
|
+
return ident.slice(slash + 1).trim();
|
|
69
|
+
return ident;
|
|
70
|
+
}
|
|
71
|
+
const colon = ident.indexOf(":");
|
|
72
|
+
if (colon >= 0) {
|
|
73
|
+
const token = ident.slice(0, colon).trim().toLowerCase();
|
|
74
|
+
if (DNA_PROVIDER_TOKENS.has(token))
|
|
75
|
+
return ident.slice(colon + 1).trim();
|
|
76
|
+
}
|
|
77
|
+
return ident;
|
|
78
|
+
}
|
|
79
|
+
/** Whether an emitted `model` id is a native Gemini id (else needs `model_code`). */
|
|
80
|
+
export function isGemini(modelId) {
|
|
81
|
+
return !!modelId && modelId.trim().toLowerCase().startsWith("gemini");
|
|
82
|
+
}
|
|
83
|
+
/** Project neutral tool surfaces → ADK `tools` code references (`- name: <fqn>`).
|
|
84
|
+
* ADK has no inline function-schema slot (unlike agent-framework / Bedrock): it
|
|
85
|
+
* derives a tool's description + parameters from the referenced Python callable at
|
|
86
|
+
* load, so the faithful de-para carries the tool NAME as a placeholder reference. */
|
|
87
|
+
function emitTools(tools) {
|
|
88
|
+
return tools.map((t) => ({ name: t.name }));
|
|
89
|
+
}
|
|
90
|
+
export class VertexEmitter {
|
|
91
|
+
target = "vertex";
|
|
92
|
+
fileExtension = "adk.yaml";
|
|
93
|
+
/** The PURE de-para: {@link EmitContext} → the ADK Agent Config object. Field
|
|
94
|
+
* order is intentional; the schema-header comment is prepended at serialization
|
|
95
|
+
* time, not part of this object. */
|
|
96
|
+
toAgentConfig(ctx) {
|
|
97
|
+
const doc = { agent_class: AGENT_CLASS, name: snake(ctx.name) };
|
|
98
|
+
if (ctx.description)
|
|
99
|
+
doc.description = ctx.description;
|
|
100
|
+
const modelId = vertexModelId(ctx.model);
|
|
101
|
+
if (modelId)
|
|
102
|
+
doc.model = modelId;
|
|
103
|
+
doc.instruction = ctx.instructions; // verbatim — the byte-equal gate
|
|
104
|
+
if (ctx.tools.length > 0)
|
|
105
|
+
doc.tools = emitTools(ctx.tools);
|
|
106
|
+
return doc;
|
|
107
|
+
}
|
|
108
|
+
emit(ctx) {
|
|
109
|
+
const config = this.toAgentConfig(ctx);
|
|
110
|
+
const body = yaml.dump(config, { sortKeys: false, lineWidth: -1 });
|
|
111
|
+
const artifact = `# yaml-language-server: $schema=${ADK_SCHEMA_URL}\n${body}`;
|
|
112
|
+
const losses = [
|
|
113
|
+
"composition structure — Soul reuse + wired Guardrails flatten to one " +
|
|
114
|
+
"`instruction` string (Agent Config has no `soul:`/`guardrails:` slot)",
|
|
115
|
+
"tenant overlay — a per-tenant persona without a fork has no Agent Config field",
|
|
116
|
+
"eval-as-contract — prompt invariants (EvalCases) have no Agent Config slot",
|
|
117
|
+
];
|
|
118
|
+
if (ctx.tools.length > 0) {
|
|
119
|
+
losses.push("tool binding — ADK binds a tool by a CODE reference (a fully qualified " +
|
|
120
|
+
"Python path or a built-in name), not a declarative schema; a Tool's " +
|
|
121
|
+
"`description` and `parameters` (JSON Schema) have no Agent Config slot " +
|
|
122
|
+
"(ADK derives them from the Python callable at load). Each emitted `- name` " +
|
|
123
|
+
"is a placeholder to repoint to the tool's real FQN");
|
|
124
|
+
}
|
|
125
|
+
if (ctx.outputSchema) {
|
|
126
|
+
losses.push("output_schema — ADK `output_schema` is a `CodeConfig` (a reference to a " +
|
|
127
|
+
"Pydantic class by FQN), not an inline JSON Schema; DNA's inline " +
|
|
128
|
+
"`spec.output_schema` has no inline Agent Config slot");
|
|
129
|
+
}
|
|
130
|
+
const modelId = vertexModelId(ctx.model);
|
|
131
|
+
if (modelId === null) {
|
|
132
|
+
losses.push("model unbound in DNA and none supplied — emitted config has no `model`; " +
|
|
133
|
+
"ADK inherits the ancestor / system default (gemini)");
|
|
134
|
+
}
|
|
135
|
+
else if (!isGemini(modelId)) {
|
|
136
|
+
losses.push("model coordinate — ADK `model` natively accepts a Gemini id; a DNA " +
|
|
137
|
+
"`azure/openai` coordinate is not a Gemini id and needs `model_code` (a " +
|
|
138
|
+
"`LiteLlm` CodeConfig) at deploy, plus a GCP project/region");
|
|
139
|
+
}
|
|
140
|
+
const mapping = {
|
|
141
|
+
"metadata.name": "name (snake_case identifier)",
|
|
142
|
+
"metadata.description": "description",
|
|
143
|
+
"buildPrompt (Soul+guardrails+instruction)": "instruction (byte-equal)",
|
|
144
|
+
"spec.model / Genome.default_llm": "model (Gemini id; provider token stripped)",
|
|
145
|
+
"spec.tools[] (Tool Kind)": "tools[].name (code reference)",
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
artifact,
|
|
149
|
+
target: this.target,
|
|
150
|
+
filename: `${ctx.name}.${this.fileExtension}`,
|
|
151
|
+
losses,
|
|
152
|
+
mapping,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
package/dist/extensions/sdlc.js
CHANGED
|
@@ -1357,6 +1357,9 @@ class HtmlArtifactKind extends KindBase {
|
|
|
1357
1357
|
title: aj.title ?? null,
|
|
1358
1358
|
source: aj.source ?? null,
|
|
1359
1359
|
created_at: aj.created_at ?? null,
|
|
1360
|
+
// published_url — canonical hosted location (e.g. claude.ai artifact
|
|
1361
|
+
// URL) so the gallery renders a clickable link. Lives in artifact_json.
|
|
1362
|
+
published_url: aj.published_url ?? null,
|
|
1360
1363
|
html_bytes: (spec.html ?? "").length,
|
|
1361
1364
|
};
|
|
1362
1365
|
}
|