55import com .alibaba .dashscope .utils .JsonUtils ;
66import com .google .gson .JsonObject ;
77import com .google .gson .annotations .SerializedName ;
8+ import java .util .HashMap ;
89import java .util .List ;
910import java .util .Map ;
1011import lombok .Data ;
@@ -54,14 +55,28 @@ public class Message {
5455 @ SerializedName ("message" )
5556 private String message ;
5657
58+ // ── delta-protocol frame fields (opt-in via event_deltas) ──
59+ @ SerializedName ("error" )
60+ private JsonObject errorPayload ;
61+
62+ @ SerializedName ("event" )
63+ private JsonObject eventPayload ;
64+
65+ @ SerializedName ("event_id" )
66+ private String deltaEventId ;
67+
68+ @ SerializedName ("delta" )
69+ private JsonObject deltaPayload ;
70+
5771 /**
5872 * Extract stop_reason from a session_status event's data block.
5973 *
6074 * <p>The SSE session_status idle event carries stop_reason in its data: {@code
6175 * {"stop_reason":{"type":"end_turn"},"session_status":"idle"}}. This helper parses it out so
6276 * callers don't need to manually dig through DataContent blocks.
6377 *
64- * @return stop_reason (has type, eventIds), or null if not a session_status event or absent
78+ * @return stop_reason (has type, pendingBatchId, pendingCallIds), or null if not a session_status
79+ * event or absent
6580 */
6681 public Session .StopReason getStopReason () {
6782 if (!"session_status" .equals (type ) || content == null ) return null ;
@@ -75,4 +90,108 @@ public Session.StopReason getStopReason() {
7590 }
7691 return null ;
7792 }
93+
94+ // typed data accessors
95+
96+ /** The first content block's {@code data} payload, for events that carry one. */
97+ public JsonObject getData () {
98+ if (content == null ) return null ;
99+ for (ContentBlock block : content ) {
100+ if (block instanceof ContentBlock .DataContent ) {
101+ return ((ContentBlock .DataContent ) block ).getData ();
102+ }
103+ }
104+ return null ;
105+ }
106+
107+ /** {@code tool_approval_request} payload: batch_id/call_id/name/arguments/tool_type. */
108+ public JsonObject getToolApprovalRequest () {
109+ if (!"tool_approval_request" .equals (type )) return null ;
110+ return getData ();
111+ }
112+
113+ /** {@code {code, message}} from {@code type=error} events. */
114+ public JsonObject getError () {
115+ if (!"error" .equals (type )) return null ;
116+ return errorPayload ;
117+ }
118+
119+ /** Suspend signal {@code {batch_id, call_ids}} from metadata while the approval barrier is up. */
120+ @ SuppressWarnings ("unchecked" )
121+ public Map <String , Object > getPendingToolApprovals () {
122+ if (metadata == null ) return null ;
123+ Object pending = metadata .get ("pending_tool_approvals" );
124+ return pending instanceof Map ? (Map <String , Object >) pending : null ;
125+ }
126+
127+ /** {@code model_request_end} usage payload. */
128+ public JsonObject getModelRequestEnd () {
129+ if (!"model_request_end" .equals (type )) return null ;
130+ return getData ();
131+ }
132+
133+ /** {@code outcome_evaluation} payload (outcome_id/iteration/phase/result). */
134+ public JsonObject getOutcomeEvaluation () {
135+ if (!"outcome_evaluation" .equals (type )) return null ;
136+ return getData ();
137+ }
138+
139+ /** {@code thread_status} payload (session_thread_id/agent_name/thread_status/stop_reason). */
140+ public JsonObject getThreadStatus () {
141+ if (!"thread_status" .equals (type )) return null ;
142+ return getData ();
143+ }
144+
145+ /** {@code thread_created} payload (session_thread_id/agent_name). */
146+ public JsonObject getThreadCreated () {
147+ if (!"thread_created" .equals (type )) return null ;
148+ return getData ();
149+ }
150+
151+ /** {@code session_updated} payload (title/session_metadata/agent). */
152+ public JsonObject getSessionUpdated () {
153+ if (!"session_updated" .equals (type )) return null ;
154+ return getData ();
155+ }
156+
157+ /** Sub-agent routing from {@code thread_message_sent}/{@code received} metadata. */
158+ public Map <String , Object > getThreadMessageRouting () {
159+ if (!"thread_message_sent" .equals (type ) && !"thread_message_received" .equals (type )) {
160+ return null ;
161+ }
162+ if (metadata == null ) return null ;
163+ Map <String , Object > routing = new HashMap <>();
164+ for (String key :
165+ new String [] {
166+ "to_session_thread_id" , "to_agent_name" ,
167+ "from_session_thread_id" , "from_agent_name"
168+ }) {
169+ if (metadata .containsKey (key )) routing .put (key , metadata .get (key ));
170+ }
171+ return routing .isEmpty () ? null : routing ;
172+ }
173+
174+ // ── delta-protocol frames (opt-in via event_deltas) ──
175+
176+ /** {@code {id, type}} from {@code event_start} frames (preview, no content). */
177+ public JsonObject getEventStart () {
178+ if (!"event_start" .equals (type )) return null ;
179+ return eventPayload ;
180+ }
181+
182+ /** Delta payload from {@code event_delta} frames. */
183+ public JsonObject getEventDelta () {
184+ if (!"event_delta" .equals (type )) return null ;
185+ return deltaPayload ;
186+ }
187+
188+ /** Incremental text from an {@code event_delta}'s {@code content_delta}. */
189+ public String getDeltaText () {
190+ if (!"event_delta" .equals (type ) || deltaPayload == null ) return null ;
191+ if (deltaPayload .has ("content" ) && deltaPayload .get ("content" ).isJsonObject ()) {
192+ JsonObject c = deltaPayload .getAsJsonObject ("content" );
193+ if (c .has ("text" )) return c .get ("text" ).getAsString ();
194+ }
195+ return null ;
196+ }
78197}
0 commit comments