1212import io .github .easy4j .hermes .api .sse .SseQueueSubscription ;
1313import io .github .easy4j .hermes .api .sse .SseSubscription ;
1414import io .github .easy4j .hermes .exception .HermesHttpException ;
15+ import io .github .easy4j .hermes .transport .RequestSemantics ;
1516import lombok .extern .slf4j .Slf4j ;
1617import okhttp3 .MediaType ;
1718import okhttp3 .OkHttpClient ;
@@ -180,7 +181,7 @@ public SseSubscription subscribeChat(ChatRequest request, Map<String, String> he
180181 Consumer <Throwable > onError ) {
181182 String url = config .getBaseUrl () + PATH_CHAT_COMPLETIONS ;
182183 return start (() -> buildPostSseRequest (url , request , headers ), consumer ,
183- onComplete , onError , false , "chat" );
184+ onComplete , onError , RequestSemantics . AGENT_CREATE , "chat" );
184185 }
185186
186187 /**
@@ -195,7 +196,7 @@ public SseSubscription subscribeRunEvents(String runId, Consumer<SseEvent> consu
195196 String url = config .getBaseUrl () + PATH_RUNS + "/" + HermesHttpClient .encodePathSegment (runId ) + "/events" ;
196197 return start (() -> buildGetSseRequest (url ), consumer , () -> { },
197198 error -> log .warn ("Hermes run SSE stopped: runId={}, error={}" , runId , error .getMessage ()),
198- true , "run:" + runId );
199+ RequestSemantics . OBSERVE , "run:" + runId );
199200 }
200201
201202 /**
@@ -229,22 +230,22 @@ public SseSubscription subscribeSessionEvents(String sessionId, String input,
229230 return start (() -> buildPostSseRequest (url , Collections .singletonMap ("input" , input ), null ),
230231 consumer , () -> { },
231232 error -> log .warn ("Hermes session SSE stopped: sessionId={}, error={}" ,
232- sessionId , error .getMessage ()), false , "session:" + sessionId );
233+ sessionId , error .getMessage ()), RequestSemantics . AGENT_CREATE , "session:" + sessionId );
233234 }
234235
235236 private SseSubscription start (RequestFactory requestFactory , Consumer <SseEvent > consumer ,
236237 Runnable onComplete , Consumer <Throwable > onError ,
237- boolean reconnect , String label ) {
238+ RequestSemantics semantics , String label ) {
238239 Objects .requireNonNull (consumer , "consumer" );
239240 SubscriptionState subscription = new SubscriptionState ();
240241 activeSubscriptions .add (subscription );
241- connect (subscription , requestFactory , consumer , onComplete , onError , reconnect , label );
242+ connect (subscription , requestFactory , consumer , onComplete , onError , semantics , label );
242243 return subscription .handle ;
243244 }
244245
245246 private void connect (SubscriptionState subscription , RequestFactory requestFactory ,
246247 Consumer <SseEvent > consumer , Runnable onComplete ,
247- Consumer <Throwable > onError , boolean reconnect , String label ) {
248+ Consumer <Throwable > onError , RequestSemantics semantics , String label ) {
248249 if (!subscription .handle .isActive ()) {
249250 finish (subscription );
250251 return ;
@@ -343,11 +344,12 @@ public void onClosed(EventSource eventSource) {
343344 finish (subscription );
344345 return ;
345346 }
346- if (reconnect && subscription .handle .isActive ()) {
347+ if (semantics . isObservationReattachAllowed () && subscription .handle .isActive ()) {
347348 scheduleReconnect (subscription , requestFactory , consumer , onComplete ,
348- onError , label , new IOException ("SSE stream closed" ));
349+ onError , semantics , label , new IOException ("SSE stream closed" ));
349350 } else {
350- onComplete .run ();
351+ onError .accept (new io .github .easy4j .hermes .api .sse .SseStreamInterruptedException (
352+ "Hermes SSE stream closed before a verified terminal marker: " + label ));
351353 finish (subscription );
352354 }
353355 }
@@ -371,9 +373,9 @@ public void onFailure(EventSource eventSource, Throwable error, Response respons
371373 }
372374 Throwable failure = Objects .nonNull (error ) ? error
373375 : new HermesHttpException (Objects .nonNull (response ) ? response .code () : -1 , "" );
374- if (reconnect && subscription .handle .isActive ()) {
376+ if (semantics . isObservationReattachAllowed () && subscription .handle .isActive ()) {
375377 scheduleReconnect (subscription , requestFactory , consumer , onComplete ,
376- onError , label , failure );
378+ onError , semantics , label , failure );
377379 } else {
378380 onError .accept (failure );
379381 finish (subscription );
@@ -385,9 +387,9 @@ public void onFailure(EventSource eventSource, Throwable error, Response respons
385387 source .cancel ();
386388 }
387389 } catch (Exception error ) {
388- if (reconnect && subscription .handle .isActive ()) {
390+ if (semantics . isObservationReattachAllowed () && subscription .handle .isActive ()) {
389391 scheduleReconnect (subscription , requestFactory , consumer , onComplete ,
390- onError , label , error );
392+ onError , semantics , label , error );
391393 } else {
392394 onError .accept (error );
393395 finish (subscription );
@@ -397,12 +399,14 @@ public void onFailure(EventSource eventSource, Throwable error, Response respons
397399
398400 private void scheduleReconnect (SubscriptionState subscription , RequestFactory requestFactory ,
399401 Consumer <SseEvent > consumer , Runnable onComplete ,
400- Consumer <Throwable > onError , String label , Throwable cause ) {
402+ Consumer <Throwable > onError , RequestSemantics semantics ,
403+ String label , Throwable cause ) {
401404 if (!subscription .handle .isActive () || reconnectScheduler .isShutdown ()) {
402405 finish (subscription );
403406 return ;
404407 }
405408 int attempt = subscription .reconnectAttempts .incrementAndGet ();
409+ subscription .handle .markContinuityUnverified ();
406410 if (attempt > Math .max (0 , config .getStreamReconnectMaxAttempts ())) {
407411 log .warn ("Hermes SSE reconnect exhausted: label={}, attempts={}, error={}" ,
408412 label , attempt - 1 , cause .getMessage ());
@@ -417,7 +421,7 @@ private void scheduleReconnect(SubscriptionState subscription, RequestFactory re
417421 ScheduledFuture <?> future ;
418422 try {
419423 future = reconnectScheduler .schedule (
420- () -> connect (subscription , requestFactory , consumer , onComplete , onError , true , label ),
424+ () -> connect (subscription , requestFactory , consumer , onComplete , onError , semantics , label ),
421425 delay , TimeUnit .MILLISECONDS );
422426 } catch (java .util .concurrent .RejectedExecutionException ignored ) {
423427 finish (subscription );
0 commit comments