Skip to content
Closed
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
112 changes: 110 additions & 2 deletions frontend/src/create/CustomCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
emptyDraft,
} from "./types";
import {
A2A_REGISTRY_DEFAULTS,
A2A_REGISTRY_ENV,
BUILTIN_TOOLS,
STM_BACKENDS,
LTM_BACKENDS,
Expand Down Expand Up @@ -112,6 +114,7 @@ type StepId =
| "skills"
| "knowledge"
| "advanced"
| "a2aCenter"
| "subagents"
| "review";

Expand All @@ -131,6 +134,7 @@ const STEPS: StepMeta[] = [
{ id: "skills", label: "技能", hint: "声明式技能", icon: Sparkles },
{ id: "knowledge", label: "知识库", hint: "外部知识检索", icon: Database },
{ id: "advanced", label: "进阶配置", hint: "记忆与观测", icon: Layers },
{ id: "a2aCenter", label: "A2A 中心", hint: "远程 Agent 发现", icon: Globe },
{ id: "subagents", label: "子 Agent", hint: "嵌套协作", icon: Boxes },
{ id: "review", label: "完成", hint: "预览并创建", icon: Rocket },
];
Expand Down Expand Up @@ -207,6 +211,38 @@ function DebugRunIcon({ className }: { className?: string }) {
}

const AGENT_TYPE_GAP_PX = 4;

const A2A_REGISTRY_ENV_TO_FIELD = {
REGISTRY_SPACE_ID: "registrySpaceId",
REGISTRY_TOP_K: "registryTopK",
REGISTRY_REGION: "registryRegion",
REGISTRY_ENDPOINT: "registryEndpoint",
} as const;

type A2aRegistryEnvKey = keyof typeof A2A_REGISTRY_ENV_TO_FIELD;

function a2aRegistryEnvValues(
registry: AgentDraft["a2aRegistry"] | undefined,
options: { includeDefaults: boolean },
): Record<string, string> {
if (!registry?.enabled) return {};
const values: Record<string, string> = {
REGISTRY_SPACE_ID: registry.registrySpaceId ?? "",
};
if (options.includeDefaults) {
values.REGISTRY_TOP_K =
registry.registryTopK?.trim() || A2A_REGISTRY_DEFAULTS.topK;
values.REGISTRY_REGION =
registry.registryRegion?.trim() || A2A_REGISTRY_DEFAULTS.region;
values.REGISTRY_ENDPOINT =
registry.registryEndpoint?.trim() || A2A_REGISTRY_DEFAULTS.endpoint;
} else {
values.REGISTRY_TOP_K = registry.registryTopK ?? "";
values.REGISTRY_REGION = registry.registryRegion ?? "";
values.REGISTRY_ENDPOINT = registry.registryEndpoint ?? "";
}
return values;
}
/* ---------------------------------------------------------------- *
* Multi-select checklist. Each row = label + desc, toggling the id in
* `selected`. Used for built-in tools and tracing exporters.
Expand Down Expand Up @@ -845,6 +881,9 @@ function nodeProblem(
return (n.a2aUrl ?? "").trim().length === 0 ? "缺少 Agent URL" : null;
if (isOrchestratorType(n.agentType))
return n.subAgents.length === 0 ? "缺少子 Agent" : null;
if (n.a2aRegistry?.enabled && !n.a2aRegistry.registrySpaceId.trim()) {
return "A2A 中心缺少空间 ID";
}
return n.instruction.trim().length === 0 ? "缺少系统提示词" : null;
}

Expand Down Expand Up @@ -879,11 +918,19 @@ function countDraftAgents(root: AgentDraft): number {
/** Collect only settings used by active components across the Agent tree. */
function collectDeploymentEnv(root: AgentDraft): RuntimeEnvConfiguration {
const selections: RuntimeEnvSelection[] = [];
const fixedValues: Record<string, string> = {};
const visit = (node: AgentDraft) => {
for (const toolId of node.builtinTools ?? []) {
const tool = BUILTIN_TOOLS.find((item) => item.id === toolId);
if (tool) selections.push({ env: tool.env });
}
if (node.a2aRegistry?.enabled) {
selections.push({ env: A2A_REGISTRY_ENV });
Object.assign(
fixedValues,
a2aRegistryEnvValues(node.a2aRegistry, { includeDefaults: true }),
);
}
if (node.memory.shortTerm) {
selections.push({
env:
Expand Down Expand Up @@ -922,7 +969,11 @@ function collectDeploymentEnv(root: AgentDraft): RuntimeEnvConfiguration {
node.subAgents.forEach(visit);
};
visit(root);
return runtimeEnvConfiguration(selections);
const config = runtimeEnvConfiguration(selections);
return {
specs: config.specs,
fixedValues: { ...config.fixedValues, ...fixedValues },
};
}

/* ---------------------------------------------------------------- *
Expand Down Expand Up @@ -1531,6 +1582,29 @@ export function CustomCreate({
},
}));

const patchA2aRegistry = (
updates: Partial<NonNullable<AgentDraft["a2aRegistry"]>>,
) =>
patch({
a2aRegistry: {
...(node.a2aRegistry ?? {
enabled: false,
registrySpaceId: "",
registryTopK: "",
registryRegion: "",
registryEndpoint: "",
}),
...updates,
},
});

const patchA2aRegistryEnv = (key: string, value: string) => {
if (!(key in A2A_REGISTRY_ENV_TO_FIELD)) return;
const field = A2A_REGISTRY_ENV_TO_FIELD[key as A2aRegistryEnvKey];
patchA2aRegistry({ [field]: value });
patchDeploymentEnv(key, value);
};

// Replace the whole tree (structural edits from the left tree), optionally
// moving the selection to a new node.
const applyTree = (nextRoot: AgentDraft, select?: NodePath) => {
Expand Down Expand Up @@ -1586,6 +1660,8 @@ export function CustomCreate({
const descriptionMissing = node.description.trim().length === 0;
const instructionMissing = node.instruction.trim().length === 0;
const urlMissing = (node.a2aUrl ?? "").trim().length === 0;
const a2aRegistrySpaceMissing =
!!node.a2aRegistry?.enabled && !node.a2aRegistry.registrySpaceId.trim();
const invalidClass = (missing: boolean) =>
showErrors && missing
? `is-error cw-error-shake-${validationPulse % 2}`
Expand Down Expand Up @@ -1629,6 +1705,7 @@ export function CustomCreate({
node.memory.shortTerm ||
node.memory.longTerm ||
node.tracing,
a2aCenter: !!node.a2aRegistry?.enabled,
subagents: (node.subAgents?.length ?? 0) > 0,
review: canFinish,
}),
Expand All @@ -1638,7 +1715,7 @@ export function CustomCreate({
// The nav only lists the sections actually rendered for THIS node's type —
// orchestrators / A2A leaves have far fewer than an LLM (type lives in the
// top bar; sub-agents live in the left tree; both are excluded here).
const rootOnlyStepIds: StepId[] = isRootAgent ? ["advanced"] : [];
const rootOnlyStepIds: StepId[] = isRootAgent ? ["advanced", "a2aCenter"] : [];
const navStepIds: StepId[] =
orchestrator || a2a
? ["basic"]
Expand Down Expand Up @@ -2466,6 +2543,37 @@ export function CustomCreate({
)}
</AnimatePresence>
</section>
)}
{isRootAgent && (
<Section meta={metaOf("a2aCenter")}>
<div className="cw-form cw-toggle-stack">
<Toggle
checked={!!node.a2aRegistry?.enabled}
onChange={(enabled) =>
patchA2aRegistry({ enabled })
}
title="A2A 中心"
desc="升级该 Agent 为 SupperAgent,支持发现并调用 A2A 注册中心子 Agent"
icon={Globe}
/>
{node.a2aRegistry?.enabled && (
<div className="cw-field cw-subfield">
<RuntimeEnvFields
env={A2A_REGISTRY_ENV}
values={a2aRegistryEnvValues(node.a2aRegistry, {
includeDefaults: false,
})}
onChange={patchA2aRegistryEnv}
/>
{showErrors && a2aRegistrySpaceMissing && (
<span className="cw-error-text">
A2A 注册中心空间 ID 为必填项
</span>
)}
</div>
)}
</div>
</Section>
)}
</>
)}
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/create/configYaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// can be imported back into the custom-mode wizard.

import { parse, stringify } from "yaml";
import { A2A_REGISTRY_DEFAULTS } from "./veadkCatalog";
import { normalizeDraft } from "./normalizeDraft";
import type { AgentDraft } from "./types";

Expand All @@ -28,6 +29,19 @@ function toConfig(draft: AgentDraft): Record<string, unknown> {
if (m.args?.length) e.args = m.args;
return e;
});
if (draft.a2aRegistry?.enabled) {
const registry: Record<string, unknown> = { enabled: true };
if (draft.a2aRegistry.registrySpaceId?.trim())
registry.registrySpaceId = draft.a2aRegistry.registrySpaceId.trim();
registry.registryTopK =
draft.a2aRegistry.registryTopK?.trim() || A2A_REGISTRY_DEFAULTS.topK;
registry.registryRegion =
draft.a2aRegistry.registryRegion?.trim() || A2A_REGISTRY_DEFAULTS.region;
registry.registryEndpoint =
draft.a2aRegistry.registryEndpoint?.trim() ||
A2A_REGISTRY_DEFAULTS.endpoint;
o.a2aRegistry = registry;
}
if (draft.memory?.shortTerm || draft.memory?.longTerm) {
o.memory = { shortTerm: !!draft.memory.shortTerm, longTerm: !!draft.memory.longTerm };
if (draft.memory.shortTerm) o.shortTermBackend = draft.shortTermBackend || "local";
Expand Down Expand Up @@ -69,6 +83,7 @@ function toConfig(draft: AgentDraft): Record<string, unknown> {
const s: Record<string, unknown> = { name: sa.name, description: sa.description, instruction: sa.instruction };
if (sa.builtinTools?.length) s.builtinTools = [...sa.builtinTools];
if (sa.customTools?.length) s.customTools = sa.customTools.map((t) => ({ name: t.name, description: t.description }));
if (sa.a2aRegistry?.enabled) s.a2aRegistry = toConfig(sa).a2aRegistry;
return s;
});
return o;
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/create/normalizeDraft.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { emptyDraft, type AgentDraft, type CustomTool, type SelectedSkill } from "./types";
import {
emptyDraft,
type A2aRegistryConfig,
type AgentDraft,
type CustomTool,
type SelectedSkill,
} from "./types";

const STM_IDS = new Set(["local", "sqlite", "mysql", "postgresql"]);
const LTM_IDS = new Set(["local", "opensearch", "redis", "viking", "mem0"]);
Expand Down Expand Up @@ -57,6 +63,17 @@ function asMaxIterations(v: unknown): number {
return typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.floor(v) : 3;
}

function asA2aRegistry(v: unknown): A2aRegistryConfig {
const o = (v && typeof v === "object" ? v : {}) as Record<string, unknown>;
return {
enabled: asBool(o.enabled),
registrySpaceId: asString(o.registrySpaceId),
registryTopK: asString(o.registryTopK),
registryRegion: asString(o.registryRegion),
registryEndpoint: asString(o.registryEndpoint),
};
}

function parseSubAgents(v: unknown): AgentDraft[] {
if (!Array.isArray(v)) return [];
return v.map((s) => {
Expand All @@ -71,6 +88,7 @@ function parseSubAgents(v: unknown): AgentDraft[] {
a2aUrl: asString(so.a2aUrl),
builtinTools: asStringArray(so.builtinTools).filter((t) => TOOL_IDS.has(t)),
customTools: asCustomTools(so.customTools),
a2aRegistry: asA2aRegistry(so.a2aRegistry),
subAgents: parseSubAgents(so.subAgents),
};
});
Expand Down Expand Up @@ -178,6 +196,7 @@ export function normalizeDraft(raw: unknown): AgentDraft {
builtinTools: asStringArray(o.builtinTools).filter((t) => TOOL_IDS.has(t)),
customTools: asCustomTools(o.customTools),
mcpTools,
a2aRegistry: asA2aRegistry(o.a2aRegistry),
memory: { shortTerm: asBool(mem.shortTerm), longTerm: asBool(mem.longTerm) },
shortTermBackend: pick(o.shortTermBackend, STM_IDS, "local"),
longTermBackend: pick(o.longTermBackend, LTM_IDS, "local"),
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/create/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ export interface NetworkConfig {
enableSharedInternetAccess?: boolean;
}

export interface A2aRegistryConfig {
enabled: boolean;
/** REGISTRY_SPACE_ID. */
registrySpaceId: string;
/** REGISTRY_TOP_K. Empty means the UI supplies the explicit default. */
registryTopK?: string;
/** REGISTRY_REGION. Empty means the UI supplies the explicit default. */
registryRegion?: string;
/** REGISTRY_ENDPOINT. Empty means the UI supplies the explicit default. */
registryEndpoint?: string;
}

export interface DeploymentConfig {
feishuEnabled: boolean;
network?: NetworkConfig;
Expand Down Expand Up @@ -94,6 +106,8 @@ export interface AgentDraft {
customTools?: CustomTool[];
/** MCP tool servers — the backend generator emits an MCPToolset per entry. */
mcpTools?: McpTool[];
/** AgentKit A2A registry center tools. */
a2aRegistry?: A2aRegistryConfig;
/** Chosen backends when memory is enabled. */
shortTermBackend?: string;
longTermBackend?: string;
Expand Down Expand Up @@ -146,6 +160,13 @@ export function emptyDraft(): AgentDraft {
builtinTools: [],
customTools: [],
mcpTools: [],
a2aRegistry: {
enabled: false,
registrySpaceId: "",
registryTopK: "",
registryRegion: "",
registryEndpoint: "",
},
modelName: DEFAULT_MODEL_NAME,
modelProvider: "",
modelApiBase: "",
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/create/veadkCatalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,40 @@ export const FEISHU_ENV: EnvVar[] = [
},
];

export const A2A_REGISTRY_DEFAULTS = {
topK: "3",
region: "cn-beijing",
endpoint: "https://open.volcengineapi.com/",
} as const;

/** AgentKit A2A registry center runtime configuration. */
export const A2A_REGISTRY_ENV: EnvVar[] = [
{
key: "REGISTRY_SPACE_ID",
required: true,
placeholder: "请输入 A2A 空间 ID",
comment: "A2A 注册中心空间 ID",
},
{
key: "REGISTRY_TOP_K",
required: false,
placeholder: A2A_REGISTRY_DEFAULTS.topK,
comment: "召回 Agent 数量",
},
{
key: "REGISTRY_REGION",
required: false,
placeholder: A2A_REGISTRY_DEFAULTS.region,
comment: "A2A 注册中心地域",
},
{
key: "REGISTRY_ENDPOINT",
required: false,
placeholder: A2A_REGISTRY_DEFAULTS.endpoint,
comment: "A2A 注册中心 OpenAPI 地址",
},
];

/* ------------------------------------------------------------------ *
* Built-in tools (curated to ones that load without npx/uvx/AgentKit).
* ------------------------------------------------------------------ */
Expand Down
Loading
Loading