1616
1717package com .google .adk .agents ;
1818
19+ import com .google .adk .annotations .Experimental ;
1920import com .google .adk .artifacts .ListArtifactsResponse ;
2021import com .google .adk .events .EventActions ;
2122import com .google .adk .events .ToolConfirmation ;
2223import com .google .adk .memory .SearchMemoryResponse ;
2324import com .google .adk .sessions .State ;
25+ import com .google .adk .workflow .NodeExecutionState ;
26+ import com .google .adk .workflow .Route ;
2427import com .google .common .base .Preconditions ;
28+ import com .google .common .collect .ImmutableList ;
2529import com .google .genai .types .Part ;
2630import io .reactivex .rxjava3 .core .Completable ;
2731import io .reactivex .rxjava3 .core .Maybe ;
3135import org .jspecify .annotations .Nullable ;
3236
3337/**
34- * The context passed to callbacks and tools during an invocation.
38+ * The context passed to callbacks, tools, and workflow nodes during an invocation.
3539 *
3640 * <p>Agent and model callbacks receive a {@link CallbackContext}, and tools and tool callbacks a
3741 * {@link com.google.adk.tools.ToolContext}; both extend this class, so a helper that serves both,
3842 * or a function tool's {@code toolContext} parameter, can take a {@code Context}. {@link
39- * #requestConfirmation()} needs a function call ID, which only a tool call has.
43+ * #requestConfirmation()} requires a function call ID, which only a tool call has, while {@link
44+ * #requireNodeState()} and the other workflow node methods require the context of a workflow node.
4045 */
4146public class Context extends ReadonlyContext {
4247
@@ -45,6 +50,7 @@ public class Context extends ReadonlyContext {
4550
4651 private final State state ;
4752 private final @ Nullable String eventId ;
53+ private final @ Nullable NodeExecutionState nodeState ;
4854 private Optional <String > functionCallId = Optional .empty ();
4955 private Optional <ToolConfirmation > toolConfirmation = Optional .empty ();
5056
@@ -59,10 +65,32 @@ public class Context extends ReadonlyContext {
5965 InvocationContext invocationContext ,
6066 @ Nullable EventActions eventActions ,
6167 @ Nullable String eventId ) {
68+ this (invocationContext , eventActions , eventId , /* nodeState= */ null );
69+ }
70+
71+ /**
72+ * Creates the context of the workflow node run whose state is {@code nodeState}. Only the
73+ * workflow runtime creates a node state.
74+ */
75+ @ Experimental
76+ public Context (InvocationContext invocationContext , NodeExecutionState nodeState ) {
77+ this (
78+ invocationContext ,
79+ /* eventActions= */ null ,
80+ /* eventId= */ null ,
81+ Preconditions .checkNotNull (nodeState ));
82+ }
83+
84+ private Context (
85+ InvocationContext invocationContext ,
86+ @ Nullable EventActions eventActions ,
87+ @ Nullable String eventId ,
88+ @ Nullable NodeExecutionState nodeState ) {
6289 super (invocationContext );
6390 this .eventActions = eventActions != null ? eventActions : EventActions .builder ().build ();
6491 this .state = new State (invocationContext .session ().state (), this .eventActions .stateDelta ());
6592 this .eventId = eventId ;
93+ this .nodeState = nodeState ;
6694 }
6795
6896 /** Returns the delta-aware state of the current context. */
@@ -223,4 +251,68 @@ public Single<SearchMemoryResponse> searchMemory(String query) {
223251 .searchMemory (
224252 invocationContext .session ().appName (), invocationContext .session ().userId (), query );
225253 }
254+
255+ /**
256+ * Returns the state of the workflow node run this context belongs to.
257+ *
258+ * @throws IllegalStateException if this is not the context of a workflow node.
259+ */
260+ @ Experimental
261+ public final NodeExecutionState requireNodeState () {
262+ Preconditions .checkState (
263+ nodeState != null , "Only the context of a workflow node has a node state." );
264+ return nodeState ;
265+ }
266+
267+ /**
268+ * Returns the author of the events this workflow node emits, or empty to use the node's name.
269+ *
270+ * @throws IllegalStateException if this is not the context of a workflow node.
271+ */
272+ @ Experimental
273+ public final String eventAuthor () {
274+ return requireNodeState ().eventAuthor ();
275+ }
276+
277+ /**
278+ * Sets the author of the events this workflow node emits.
279+ *
280+ * @throws IllegalStateException if this is not the context of a workflow node.
281+ */
282+ @ Experimental
283+ public final void eventAuthor (String eventAuthor ) {
284+ requireNodeState ().eventAuthor (eventAuthor );
285+ }
286+
287+ /**
288+ * Returns this workflow node's output, or empty if it has produced none.
289+ *
290+ * @throws IllegalStateException if this is not the context of a workflow node.
291+ */
292+ @ Experimental
293+ public final Optional <Object > output () {
294+ return requireNodeState ().output ();
295+ }
296+
297+ /**
298+ * Returns the routes this workflow node selected, or empty if it made no routing decision. An
299+ * empty list is a decision that selects no route.
300+ *
301+ * @throws IllegalStateException if this is not the context of a workflow node.
302+ */
303+ @ Experimental
304+ public final Optional <ImmutableList <Route >> routes () {
305+ return requireNodeState ().selectedRoutes ();
306+ }
307+
308+ /**
309+ * Selects this workflow node's routes, which decide the outgoing edges to follow, replacing any
310+ * earlier selection.
311+ *
312+ * @throws IllegalStateException if this is not the context of a workflow node.
313+ */
314+ @ Experimental
315+ public final void routes (List <? extends Route > routes ) {
316+ requireNodeState ().selectRoutes (routes );
317+ }
226318}
0 commit comments