Skip to content

Commit 97ebcac

Browse files
committed
fix(sse): sync H-104 terminal and reattach semantics
1 parent 6a86979 commit 97ebcac

1 file changed

Lines changed: 19 additions & 15 deletions

File tree

‎src/main/java/io/github/easy4j/hermes/api/HermesSseClient.java‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.github.easy4j.hermes.api.sse.SseQueueSubscription;
1313
import io.github.easy4j.hermes.api.sse.SseSubscription;
1414
import io.github.easy4j.hermes.exception.HermesHttpException;
15+
import io.github.easy4j.hermes.transport.RequestSemantics;
1516
import lombok.extern.slf4j.Slf4j;
1617
import okhttp3.MediaType;
1718
import 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

Comments
 (0)