7474 */
7575public class RequestContext {
7676
77- private @ Nullable MessageSendParams params ;
78- private @ Nullable String taskId ;
79- private @ Nullable String contextId ;
80- private @ Nullable Task task ;
81- private List <Task > relatedTasks ;
77+ private final @ Nullable MessageSendParams params ;
78+ private final String taskId ;
79+ private final String contextId ;
80+ private final @ Nullable Task task ;
81+ private final List <Task > relatedTasks ;
8282 private final @ Nullable ServerCallContext callContext ;
8383
84- public RequestContext (
84+ /**
85+ * Constructor with all fields already validated and initialized.
86+ * <p>
87+ * <b>Note:</b> Use {@link Builder} instead of calling this constructor directly.
88+ * The builder handles ID generation and validation.
89+ * </p>
90+ *
91+ * @param params the message send parameters (can be null for cancel operations)
92+ * @param taskId the task identifier (must not be null)
93+ * @param contextId the context identifier (must not be null)
94+ * @param task the existing task state (null for new conversations)
95+ * @param relatedTasks other tasks in the same context (must not be null, can be empty)
96+ * @param callContext the server call context (can be null)
97+ */
98+ private RequestContext (
8599 @ Nullable MessageSendParams params ,
86- @ Nullable String taskId ,
87- @ Nullable String contextId ,
100+ String taskId ,
101+ String contextId ,
88102 @ Nullable Task task ,
89- @ Nullable List <Task > relatedTasks ,
90- @ Nullable ServerCallContext callContext ) throws InvalidParamsError {
103+ List <Task > relatedTasks ,
104+ @ Nullable ServerCallContext callContext ) {
91105 this .params = params ;
92106 this .taskId = taskId ;
93107 this .contextId = contextId ;
94108 this .task = task ;
95- this .relatedTasks = relatedTasks == null ? new ArrayList <>() : relatedTasks ;
109+ this .relatedTasks = relatedTasks ;
96110 this .callContext = callContext ;
97-
98- // If the taskId and contextId were specified, they must match the params
99- if (params != null ) {
100- if (taskId != null && !taskId .equals (params .message ().taskId ())) {
101- throw new InvalidParamsError ("bad task id" );
102- }
103- this .taskId = checkOrGenerateTaskId ();
104- if (contextId != null && !contextId .equals (params .message ().contextId ())) {
105- throw new InvalidParamsError ("bad context id" );
106- }
107- this .contextId = checkOrGenerateContextId ();
108- }
109111 }
110112
111113 /**
112114 * Returns the task identifier.
113115 * <p>
114- * This is auto-generated (UUID) if not provided by the client in the message parameters.
115- * It can be null if the context was not created from message parameters .
116+ * This is auto-generated (UUID) by the builder if not provided by the client
117+ * in the message parameters. This value is never null .
116118 * </p>
117119 *
118- * @return the task ID
120+ * @return the task ID (never null)
119121 */
120- public @ Nullable String getTaskId () {
122+ public String getTaskId () {
121123 return taskId ;
122124 }
123125
124126 /**
125127 * Returns the conversation context identifier.
126128 * <p>
127129 * Conversation contexts group related tasks together (e.g., multiple tasks
128- * in the same user session). This is auto-generated (UUID) if not provided by the client
129- * in the message parameters. It can be null if the context was not created from message parameters .
130+ * in the same user session). This is auto-generated (UUID) by the builder if
131+ * not provided by the client in the message parameters. This value is never null .
130132 * </p>
131133 *
132- * @return the context ID
134+ * @return the context ID (never null)
133135 */
134- public @ Nullable String getContextId () {
136+ public String getContextId () {
135137 return contextId ;
136138 }
137139
@@ -209,6 +211,19 @@ public List<Task> getRelatedTasks() {
209211 return callContext ;
210212 }
211213
214+ /**
215+ * Returns the tenant identifier from the request parameters.
216+ * <p>
217+ * The tenant is used in multi-tenant environments to identify which
218+ * customer or organization the request belongs to.
219+ * </p>
220+ *
221+ * @return the tenant identifier, or null if no params or tenant not set
222+ */
223+ public @ Nullable String getTenant () {
224+ return params != null ? params .tenant () : null ;
225+ }
226+
212227 /**
213228 * Extracts all text content from the message and joins with the specified delimiter.
214229 * <p>
@@ -240,48 +255,20 @@ public String getUserInput(String delimiter) {
240255 return getMessageText (params .message (), delimiter );
241256 }
242257
258+ /**
259+ * Attaches a related task to this context.
260+ * <p>
261+ * This is primarily used by the framework to populate related tasks after
262+ * construction. Agent implementations should use {@link #getRelatedTasks()}
263+ * to access related tasks.
264+ * </p>
265+ *
266+ * @param task the task to attach
267+ */
243268 public void attachRelatedTask (Task task ) {
244269 relatedTasks .add (task );
245270 }
246271
247- private @ Nullable String checkOrGenerateTaskId () {
248- if (params == null ) {
249- return taskId ;
250- }
251- if (taskId == null && params .message ().taskId () == null ) {
252- // Message is immutable, create new one with generated taskId
253- String generatedTaskId = UUID .randomUUID ().toString ();
254- Message updatedMessage = Message .builder (params .message ())
255- .taskId (generatedTaskId )
256- .build ();
257- params = new MessageSendParams (updatedMessage , params .configuration (), params .metadata ());
258- return generatedTaskId ;
259- }
260- if (params .message ().taskId () != null ) {
261- return params .message ().taskId ();
262- }
263- return taskId ;
264- }
265-
266- private @ Nullable String checkOrGenerateContextId () {
267- if (params == null ) {
268- return contextId ;
269- }
270- if (contextId == null && params .message ().contextId () == null ) {
271- // Message is immutable, create new one with generated contextId
272- String generatedContextId = UUID .randomUUID ().toString ();
273- Message updatedMessage = Message .builder (params .message ())
274- .contextId (generatedContextId )
275- .build ();
276- params = new MessageSendParams (updatedMessage , params .configuration (), params .metadata ());
277- return generatedContextId ;
278- }
279- if (params .message ().contextId () != null ) {
280- return params .message ().contextId ();
281- }
282- return contextId ;
283- }
284-
285272 private String getMessageText (Message message , String delimiter ) {
286273 List <String > textParts = getTextParts (message .parts ());
287274 return String .join (delimiter , textParts );
@@ -295,6 +282,18 @@ private List<String> getTextParts(List<Part<?>> parts) {
295282 .collect (Collectors .toList ());
296283 }
297284
285+ /**
286+ * Builder for creating {@link RequestContext} instances.
287+ * <p>
288+ * The builder handles ID generation and validation automatically:
289+ * </p>
290+ * <ul>
291+ * <li>TaskId and ContextId are auto-generated (UUID) if not provided</li>
292+ * <li>IDs are validated against message parameters if both are present</li>
293+ * <li>Message parameters are updated with generated IDs</li>
294+ * <li>Related tasks list is initialized to empty list if null</li>
295+ * </ul>
296+ */
298297 public static class Builder {
299298 private @ Nullable MessageSendParams params ;
300299 private @ Nullable String taskId ;
@@ -357,8 +356,56 @@ public Builder setServerCallContext(@Nullable ServerCallContext serverCallContex
357356 return serverCallContext ;
358357 }
359358
360- public RequestContext build () {
361- return new RequestContext (params , taskId , contextId , task , relatedTasks , serverCallContext );
359+ /**
360+ * Builds the RequestContext with ID generation and validation.
361+ *
362+ * @return the constructed RequestContext
363+ * @throws InvalidParamsError if taskId or contextId don't match message parameters
364+ */
365+ public RequestContext build () throws InvalidParamsError {
366+ // 1. Initialize relatedTasks to empty list if null
367+ List <Task > finalRelatedTasks = relatedTasks != null ? relatedTasks : new ArrayList <>();
368+
369+ // 2. Extract message IDs upfront (or null if no params)
370+ String messageTaskId = params != null ? params .message ().taskId () : null ;
371+ String messageContextId = params != null ? params .message ().contextId () : null ;
372+
373+ // 3. Validate: if both builder and message provide an ID, they must match
374+ if (taskId != null && messageTaskId != null && !taskId .equals (messageTaskId )) {
375+ throw new InvalidParamsError ("bad task id" );
376+ }
377+ if (contextId != null && messageContextId != null && !contextId .equals (messageContextId )) {
378+ throw new InvalidParamsError ("bad context id" );
379+ }
380+
381+ // 4. Determine final IDs using coalesce pattern: builder → message → generate
382+ String finalTaskId = taskId != null ? taskId :
383+ messageTaskId != null ? messageTaskId :
384+ UUID .randomUUID ().toString ();
385+
386+ String finalContextId = contextId != null ? contextId :
387+ messageContextId != null ? messageContextId :
388+ UUID .randomUUID ().toString ();
389+
390+ // 5. Update params if message needs to be updated with final IDs
391+ MessageSendParams finalParams = params ;
392+ if (params != null && (!finalTaskId .equals (messageTaskId ) || !finalContextId .equals (messageContextId ))) {
393+ Message updatedMessage = Message .builder (params .message ())
394+ .taskId (finalTaskId )
395+ .contextId (finalContextId )
396+ .build ();
397+ // Preserve all original fields including tenant
398+ finalParams = MessageSendParams .builder ()
399+ .message (updatedMessage )
400+ .configuration (params .configuration ())
401+ .metadata (params .metadata ())
402+ .tenant (params .tenant ())
403+ .build ();
404+ }
405+
406+ // 6. Call constructor with finalized values (IDs guaranteed non-null)
407+ return new RequestContext (finalParams , finalTaskId , finalContextId ,
408+ task , finalRelatedTasks , serverCallContext );
362409 }
363410 }
364411
0 commit comments