Skip to content
Open
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
5 changes: 5 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ server that `veadk frontend` launches — no separate backend.
Deployable source can be sent to Runtime manually; an incomplete verification
report requires an explicit confirmation. No separate “start verification”
action is required.
- **Agent runtime selection**: custom Studio Agent drafts can choose the inner
execution loop for each LLM Agent: the default ADK loop, Codex, or PiAgent.
Generated projects preserve the selection in `Agent(runtime=...)`, add the
Codex runtime packages when needed, and preinstall the PiAgent binary in the
generated Dockerfile for cloud deployment.
- **Existing Agent migration**: upload a local project ZIP for read-only
analysis, confirm the detected framework and entry point, then migrate and
validate it in a temporary Sandbox. Successful migration source is saved as
Expand Down
62 changes: 62 additions & 0 deletions frontend/src/create/CustomCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
import {
type CreateModeProps,
type AgentDraft,
type AgentRuntime,
type CloudEnvironmentConfig,
type HarnessSidecarOptionId,
type HarnessSidecarProfileId,
Expand Down Expand Up @@ -256,6 +257,7 @@ function downloadText(filename: string, text: string, mime = "text/plain") {
type StepId =
| "type"
| "basic"
| "runtime"
| "model"
| "tools"
| "skills"
Expand Down Expand Up @@ -296,6 +298,13 @@ const STEPS: StepMeta[] = [
{ id: "review", label: "traditional.sections.review.label", hint: "traditional.sections.review.hint", icon: Rocket },
];

const RUNTIME_SECTION: StepMeta = {
id: "runtime",
label: "traditional.runtime.sectionLabel",
hint: "traditional.runtime.sectionHint",
icon: Bot,
};

/** Root-only reset mark: a tilted eraser clearing the current draft. */
function ClearAgentIcon({ className }: { className?: string }) {
return (
Expand Down Expand Up @@ -405,6 +414,8 @@ const AGENT_TYPE_BAR_LABELS: Record<AgentType, string> = {
a2a: "traditional.agentTypes.a2a.label",
};

const AGENT_RUNTIME_OPTIONS: AgentRuntime[] = ["adk", "codex", "piagent"];

const A2A_REGISTRY_ENV_TO_FIELD = {
REGISTRY_SPACE_ID: "registrySpaceId",
REGISTRY_TOP_K: "registryTopK",
Expand Down Expand Up @@ -4014,6 +4025,7 @@ export function CustomCreate({
if (agentType === "a2a") {
patch({
agentType,
runtime: "adk",
a2aRegistry: {
...(node.a2aRegistry ?? {
registrySpaceId: "",
Expand All @@ -4028,6 +4040,7 @@ export function CustomCreate({
}
patch({
agentType,
runtime: agentType === "llm" ? (node.runtime ?? "adk") : "adk",
a2aRegistry: node.a2aRegistry
? { ...node.a2aRegistry, enabled: false }
: undefined,
Expand Down Expand Up @@ -4156,6 +4169,9 @@ export function CustomCreate({
modelName: nextModelName,
});
};
const selectAgentRuntime = (runtime: AgentRuntime) => {
patch({ runtime });
};

// Inline error flags for the selected node.
const duplicateNames = useMemo(() => duplicateAgentNames(draft), [draft]);
Expand Down Expand Up @@ -5610,6 +5626,52 @@ export function CustomCreate({
Root LLM agents additionally own memory and tracing. */}
{!orchestrator && !a2a && (
<>
<Section meta={RUNTIME_SECTION}>
<div className="cw-form">
<div className="cw-field cw-agent-runtime-field">
<label className="cw-label">
{t("traditional.runtime.label")}
</label>
<RadioGroup<AgentRuntime>
className="cw-model-source-options cw-agent-runtime-options"
aria-label={t("traditional.runtime.ariaLabel")}
value={node.runtime ?? "adk"}
onChange={selectAgentRuntime}
>
{AGENT_RUNTIME_OPTIONS.map((runtime) => {
const on =
(node.runtime ?? "adk") === runtime;
return (
<div
key={runtime}
className={`cw-model-source-option cw-agent-runtime-option ${
on ? "is-on" : ""
}`}
title={t(
`traditional.runtime.options.${runtime}.description`,
)}
>
<RadioGroup.Item
value={runtime}
block
className="cw-model-source-control"
>
<span>
{t(
`traditional.runtime.options.${runtime}.label`,
)}
</span>
</RadioGroup.Item>
</div>
);
})}
</RadioGroup>
<span className="cw-help">
{t("traditional.runtime.help")}
</span>
</div>
</div>
</Section>
<Section meta={metaOf("model")}>
<div className="cw-form">
<div className="cw-field cw-model-source-field">
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/create/configYaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ function toConfig(draft: AgentDraft, root = true): Record<string, unknown> {
o.name = draft.name;
o.description = draft.description;
o.instruction = draft.instruction;
if (draft.agentType === "llm" && draft.runtime && draft.runtime !== "adk") {
o.runtime = draft.runtime;
}
if (draft.agentType === "loop") o.maxIterations = draft.maxIterations ?? 3;
if (draft.modelName?.trim()) o.modelName = draft.modelName.trim();
if (draft.modelSource) o.modelSource = draft.modelSource;
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/create/normalizeDraft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
emptyDraft,
type A2aRegistryConfig,
type AgentDraft,
type AgentRuntime,
type CloudCliToolId,
type CustomTool,
type HarnessSidecarIntent,
Expand Down Expand Up @@ -40,6 +41,7 @@ const TOOL_IDS = new Set([
"vesearch",
]);
const AGENT_TYPES = new Set(["llm", "sequential", "parallel", "loop", "a2a"]);
const AGENT_RUNTIMES = new Set<AgentRuntime>(["adk", "codex", "piagent"]);
const CLOUD_CLI_TOOL_IDS = new Set<CloudCliToolId>([
"lark-cli",
"github-cli",
Expand Down Expand Up @@ -112,6 +114,12 @@ function asAgentType(v: unknown): NonNullable<AgentDraft["agentType"]> {
: "llm";
}

function asAgentRuntime(v: unknown): AgentRuntime {
return typeof v === "string" && AGENT_RUNTIMES.has(v as AgentRuntime)
? (v as AgentRuntime)
: "adk";
}

function asCloudProvider(v: unknown): NonNullable<AgentDraft["cloudProvider"]> {
return v === "byteplus" ? "byteplus" : "volcengine";
}
Expand Down Expand Up @@ -176,6 +184,7 @@ function parseSubAgents(
description: asString(so.description),
instruction: asString(so.instruction),
agentType,
runtime: agentType === "llm" ? asAgentRuntime(so.runtime) : "adk",
maxIterations: asMaxIterations(so.maxIterations),
a2aUrl: asString(so.a2aUrl),
modelName: asString(so.modelName),
Expand Down Expand Up @@ -330,6 +339,7 @@ export function normalizeDraft(raw: unknown): AgentDraft {
instruction: asString(o.instruction) || "You are a helpful assistant.",
dynamicAgentDelegation: asBool(o.dynamicAgentDelegation),
agentType,
runtime: agentType === "llm" ? asAgentRuntime(o.runtime) : "adk",
maxIterations: asMaxIterations(o.maxIterations),
a2aUrl: asString(o.a2aUrl),
modelName: asString(o.modelName),
Expand Down
75 changes: 73 additions & 2 deletions frontend/src/create/runtimeModelName.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CloudProvider } from "../adk/cloudProvider";
import { emptyDraft, type AgentDraft } from "./types";
import { emptyDraft, type AgentDraft, type AgentRuntime } from "./types";
import { normalizeHarnessSidecarIntent } from "./harnessSidecarOptions";
import { BUILTIN_TOOLS } from "./veadkCatalog";

Expand All @@ -9,16 +9,70 @@ export interface RuntimeModelConfiguration {
}

export interface RuntimeAgentIntrospection {
id?: string;
name?: string;
description?: string;
instruction?: string;
type?: AgentDraft["agentType"];
runtime?: AgentDraft["runtime"];
model?: string;
tools?: readonly string[];
skills?: readonly { name: string }[];
path?: readonly string[];
children?: readonly RuntimeAgentIntrospection[];
}

const AGENT_RUNTIMES = new Set<AgentRuntime>(["adk", "codex", "piagent"]);

function runtimeFromValue(
value: unknown,
fallback: AgentRuntime = "adk",
): AgentRuntime {
return typeof value === "string" && AGENT_RUNTIMES.has(value as AgentRuntime)
? (value as AgentRuntime)
: fallback;
}

function runtimeNodeIdentityKeys(
node: Pick<RuntimeAgentIntrospection, "id" | "name" | "path"> | AgentDraft,
): string[] {
const keys = [
"id" in node ? node.id : undefined,
node.name,
"path" in node && Array.isArray(node.path)
? node.path[node.path.length - 1]
: undefined,
]
.map((value) => value?.trim())
.filter((value): value is string => Boolean(value));
return Array.from(new Set(keys));
}

function matchingRuntimeChild(
draftChild: AgentDraft,
runtimeChildren: readonly RuntimeAgentIntrospection[],
index: number,
usedIndexes: Set<number>,
): RuntimeAgentIntrospection | undefined {
const draftKeys = new Set(runtimeNodeIdentityKeys(draftChild));
if (draftKeys.size > 0) {
const matchedIndex = runtimeChildren.findIndex(
(runtimeChild, runtimeIndex) =>
!usedIndexes.has(runtimeIndex) &&
runtimeNodeIdentityKeys(runtimeChild).some((key) => draftKeys.has(key)),
);
if (matchedIndex >= 0) {
usedIndexes.add(matchedIndex);
return runtimeChildren[matchedIndex];
}
}
if (index < runtimeChildren.length && !usedIndexes.has(index)) {
usedIndexes.add(index);
return runtimeChildren[index];
}
return undefined;
}

export interface RuntimeCloudAgent extends RuntimeAgentIntrospection {
appName: string;
graph?: RuntimeAgentIntrospection;
Expand Down Expand Up @@ -101,6 +155,7 @@ export function applyRuntimeAgentIntrospection(
runtimeNode?.model || fallbackRoot?.model,
);
const runtimeChildren = runtimeNode?.children ?? [];
const usedRuntimeChildIndexes = new Set<number>();
const runtimeAgentType = runtimeNode?.type;
const agentType =
editableDraft.agentType === "a2a" &&
Expand All @@ -120,13 +175,17 @@ export function applyRuntimeAgentIntrospection(
? editableDraft.instruction
: (runtimeNode?.instruction ?? editableDraft.instruction),
agentType,
runtime:
agentType === "llm"
? runtimeFromValue(runtimeNode?.runtime, editableDraft.runtime ?? "adk")
: "adk",
modelName: runtimeModel.modelName || editableDraft.modelName,
modelProvider: runtimeModel.modelProvider || editableDraft.modelProvider,
skills: runtimeNode?.skills?.map((skill) => skill.name) ?? editableDraft.skills,
subAgents: editableDraft.subAgents.map((child, index) =>
applyRuntimeAgentIntrospection(
child,
runtimeChildren[index],
matchingRuntimeChild(child, runtimeChildren, index, usedRuntimeChildIndexes),
undefined,
preserveDraftInstruction,
),
Expand Down Expand Up @@ -200,6 +259,10 @@ function cloudDraftWithDefaults(
description: draft.description ?? defaults.description,
instruction: draft.instruction ?? defaults.instruction,
agentType: draft.agentType ?? defaults.agentType,
runtime:
(draft.agentType ?? defaults.agentType) === "llm"
? (draft.runtime ?? defaults.runtime ?? "adk")
: "adk",
cloudProvider: provider,
maxIterations: draft.maxIterations ?? defaults.maxIterations,
a2aUrl: draft.a2aUrl ?? defaults.a2aUrl,
Expand Down Expand Up @@ -379,6 +442,10 @@ function cloudGraphToDraft(
description: node.description ?? "",
instruction: node.instruction || defaults.instruction,
agentType: node.type ?? "llm",
runtime:
(node.type ?? "llm") === "llm"
? runtimeFromValue(node.runtime, defaults.runtime ?? "adk")
: "adk",
modelName: runtimeModel.modelName,
modelProvider: runtimeModel.modelProvider,
tools: runtimeTools.filter((name) => !builtinToolNames.has(name)),
Expand Down Expand Up @@ -412,6 +479,10 @@ export function runtimeAgentDraftFromCloud(
description: agent.description ?? "",
instruction: agent.instruction || emptyDraft(provider).instruction,
agentType: agent.type ?? "llm",
runtime:
(agent.type ?? "llm") === "llm"
? runtimeFromValue(agent.runtime)
: "adk",
modelName: runtimeModel.modelName,
modelProvider: runtimeModel.modelProvider,
tools: [...(agent.tools ?? [])],
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/create/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export type HarnessSidecarOptionId =
| "mcp_resilience";

export type HarnessSidecarProfileId = "default" | "ops";
export type AgentRuntime = "adk" | "codex" | "piagent";

export interface HarnessSidecarIntent {
enabled: boolean;
Expand All @@ -136,6 +137,8 @@ export interface AgentDraft {
* A2A center. Defaults to "llm" when absent.
*/
agentType?: "llm" | "sequential" | "parallel" | "loop" | "a2a";
/** Inner execution loop used by an LLM Agent. Defaults to "adk". */
runtime?: AgentRuntime;
/** Cloud provider selected by the Studio shell. */
cloudProvider?: CloudProvider;
/** Max iterations for a "loop" orchestrator (LoopAgent.max_iterations). */
Expand Down Expand Up @@ -208,6 +211,7 @@ export function emptyDraft(cloudProvider: CloudProvider = "volcengine"): AgentDr
instruction: createT("defaults.instruction"),
dynamicAgentDelegation: false,
agentType: "llm",
runtime: "adk",
cloudProvider,
maxIterations: 3,
a2aUrl: "",
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/i18n/resources/en-US/create.json
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,18 @@
"loop": { "label": "Loop", "fullLabel": "Loop agent", "description": "Repeats subagents until the stop condition is met" },
"a2a": { "label": "Remote agent", "fullLabel": "Remote agent", "description": "Calls a remote agent through the A2A protocol" }
},
"runtime": {
"sectionLabel": "Runtime",
"sectionHint": "LLM agent execution runtime",
"label": "Runtime",
"ariaLabel": "Choose runtime",
"help": "Choose the execution runtime for this LLM agent. Configure model source and model parameters below.",
"options": {
"adk": { "label": "ADK", "description": "Use the default ADK execution runtime." },
"codex": { "label": "Codex", "description": "Use Codex as the agent execution runtime." },
"piagent": { "label": "PiAgent", "description": "Use PiAgent as the agent execution runtime." }
}
},
"basic": {
"agentName": "Agent name",
"name": "Name",
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/i18n/resources/zh-CN/create.json
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,18 @@
"loop": { "label": "循环执行", "fullLabel": "循环型智能体", "description": "子 Agent 循环执行到满足条件" },
"a2a": { "label": "远程智能体", "fullLabel": "远程 Agent", "description": "通过 A2A 协议调用远程 Agent" }
},
"runtime": {
"sectionLabel": "运行配置",
"sectionHint": "LLM Agent 执行运行时",
"label": "运行时",
"ariaLabel": "选择运行时",
"help": "选择该 LLM Agent 使用的执行运行时。模型来源和模型参数在下方配置。",
"options": {
"adk": { "label": "ADK", "description": "使用默认 ADK 执行运行时。" },
"codex": { "label": "Codex", "description": "使用 Codex 作为 Agent 执行运行时。" },
"piagent": { "label": "PiAgent", "description": "使用 PiAgent 作为 Agent 执行运行时。" }
}
},
"basic": {
"agentName": "Agent 名称",
"name": "名称",
Expand Down
Loading
Loading