1616
1717package com .google .adk .apps ;
1818
19+ import static com .google .common .base .Preconditions .checkNotNull ;
20+
1921import com .google .adk .agents .BaseAgent ;
2022import com .google .adk .agents .ContextCacheConfig ;
23+ import com .google .adk .agents .NodeViewAgent ;
2124import com .google .adk .agents .Role ;
2225import com .google .adk .annotations .Experimental ;
2326import com .google .adk .plugins .Plugin ;
2427import com .google .adk .summarizer .EventsCompactionConfig ;
28+ import com .google .adk .workflow .Node ;
2529import com .google .common .collect .ImmutableList ;
2630import com .google .errorprone .annotations .CanIgnoreReturnValue ;
2731import java .util .List ;
32+ import java .util .Optional ;
2833import java .util .regex .Pattern ;
2934import org .jspecify .annotations .Nullable ;
3035
@@ -41,6 +46,7 @@ public class App {
4146
4247 private final String name ;
4348 private final BaseAgent rootAgent ;
49+ private final @ Nullable Node rootNode ;
4450 private final ImmutableList <? extends Plugin > plugins ;
4551 private final @ Nullable EventsCompactionConfig eventsCompactionConfig ;
4652 private final @ Nullable ContextCacheConfig contextCacheConfig ;
@@ -49,12 +55,14 @@ public class App {
4955 private App (
5056 String name ,
5157 BaseAgent rootAgent ,
58+ @ Nullable Node rootNode ,
5259 List <? extends Plugin > plugins ,
5360 @ Nullable EventsCompactionConfig eventsCompactionConfig ,
5461 @ Nullable ContextCacheConfig contextCacheConfig ,
5562 @ Nullable ResumabilityConfig resumabilityConfig ) {
5663 this .name = name ;
5764 this .rootAgent = rootAgent ;
65+ this .rootNode = rootNode ;
5866 this .plugins = ImmutableList .copyOf (plugins );
5967 this .eventsCompactionConfig = eventsCompactionConfig ;
6068 this .contextCacheConfig = contextCacheConfig ;
@@ -69,6 +77,15 @@ public BaseAgent rootAgent() {
6977 return rootAgent ;
7078 }
7179
80+ /**
81+ * Returns the workflow node the app runs, or empty when it runs an agent tree. When it runs a
82+ * node, {@link #rootAgent()} returns a {@link NodeViewAgent} that stands in for it.
83+ */
84+ @ Experimental
85+ public Optional <Node > rootNode () {
86+ return Optional .ofNullable (rootNode );
87+ }
88+
7289 public List <? extends Plugin > plugins () {
7390 return plugins ;
7491 }
@@ -92,6 +109,7 @@ public ContextCacheConfig contextCacheConfig() {
92109 public static class Builder {
93110 private String name ;
94111 private BaseAgent rootAgent ;
112+ private @ Nullable Node rootNode ;
95113 private List <? extends Plugin > plugins = ImmutableList .of ();
96114 @ Nullable private EventsCompactionConfig eventsCompactionConfig ;
97115 @ Nullable private ContextCacheConfig contextCacheConfig ;
@@ -109,6 +127,17 @@ public Builder rootAgent(BaseAgent rootAgent) {
109127 return this ;
110128 }
111129
130+ /**
131+ * Sets the workflow node the app runs in place of a root agent. If the given node is an agent,
132+ * it becomes the root agent.
133+ */
134+ @ CanIgnoreReturnValue
135+ @ Experimental
136+ public Builder rootNode (Node rootNode ) {
137+ this .rootNode = checkNotNull (rootNode );
138+ return this ;
139+ }
140+
112141 @ CanIgnoreReturnValue
113142 public Builder plugins (List <? extends Plugin > plugins ) {
114143 this .plugins = plugins ;
@@ -144,12 +173,36 @@ public App build() {
144173 if (name == null ) {
145174 throw new IllegalStateException ("App name must be provided." );
146175 }
147- if (rootAgent == null ) {
148- throw new IllegalStateException ("Root agent must be provided." );
149- }
176+ BaseAgent buildRootAgent = resolveRootAgent ();
150177 validateAppName (name );
151178 return new App (
152- name , rootAgent , plugins , eventsCompactionConfig , contextCacheConfig , resumabilityConfig );
179+ name ,
180+ buildRootAgent ,
181+ // An agent set as the root node is the root agent, so the app has no root node.
182+ rootNode instanceof BaseAgent ? null : rootNode ,
183+ plugins ,
184+ eventsCompactionConfig ,
185+ contextCacheConfig ,
186+ resumabilityConfig );
187+ }
188+
189+ /** Returns the root agent, or the agent that stands in for the root node. */
190+ private BaseAgent resolveRootAgent () {
191+ if (rootAgent instanceof NodeViewAgent || rootNode instanceof NodeViewAgent ) {
192+ throw new IllegalStateException (
193+ "A node view agent cannot be the root; set the node it stands in for as the root"
194+ + " node." );
195+ }
196+ if (rootNode == null ) {
197+ if (rootAgent == null ) {
198+ throw new IllegalStateException ("Root agent or root node must be provided." );
199+ }
200+ return rootAgent ;
201+ }
202+ if (rootAgent != null ) {
203+ throw new IllegalStateException ("Set a root agent or a root node, not both." );
204+ }
205+ return rootNode instanceof BaseAgent agent ? agent : new NodeViewAgent (rootNode );
153206 }
154207 }
155208
0 commit comments