001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.model.client.impl; 017 018import com.agentsflex.core.model.chat.ChatConfig; 019import com.agentsflex.core.model.chat.log.ChatMessageLogger; 020import com.agentsflex.core.model.client.OkHttpClientUtil; 021import com.agentsflex.core.model.client.StreamClient; 022import com.agentsflex.core.model.client.StreamClientListener; 023import okhttp3.*; 024import okhttp3.sse.EventSource; 025import okhttp3.sse.EventSourceListener; 026import okhttp3.sse.EventSources; 027import org.jetbrains.annotations.NotNull; 028import org.jetbrains.annotations.Nullable; 029 030import java.util.Map; 031 032public class SseClient extends EventSourceListener implements StreamClient { 033 034 private OkHttpClient okHttpClient; 035 private EventSource eventSource; 036 private StreamClientListener listener; 037 private ChatConfig config; 038 private boolean isStop = false; 039 040 public SseClient() { 041 this(OkHttpClientUtil.buildDefaultClient()); 042 } 043 044 public SseClient(OkHttpClient okHttpClient) { 045 if (okHttpClient == null) { 046 throw new IllegalArgumentException("OkHttpClient must not be null"); 047 } 048 this.okHttpClient = okHttpClient; 049 } 050 051 public OkHttpClient getOkHttpClient() { 052 return okHttpClient; 053 } 054 055 public void setOkHttpClient(OkHttpClient okHttpClient) { 056 this.okHttpClient = okHttpClient; 057 } 058 059 @Override 060 public void start(String url, Map<String, String> headers, String payload, StreamClientListener listener, ChatConfig config) { 061 this.listener = listener; 062 this.config = config; 063 this.isStop = false; 064 065 Request.Builder builder = new Request.Builder() 066 .url(url); 067 068 if (headers != null && !headers.isEmpty()) { 069 headers.forEach(builder::addHeader); 070 } 071 072 ChatMessageLogger.logRequest(config, payload); 073 074 if (this.listener != null) { 075 this.listener.onStart(this); 076 } 077 078 MediaType mediaType = MediaType.parse("application/json; charset=utf-8"); 079 RequestBody body = RequestBody.create(payload, mediaType); 080 Request request = builder.post(body).build(); 081 082 EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); 083 this.eventSource = factory.newEventSource(request, this); 084 } 085 086 @Override 087 public void stop() { 088 tryToStop(); 089 } 090 091 092 @Override 093 public void onClosed(@NotNull EventSource eventSource) { 094 tryToStop(); 095 } 096 097 @Override 098 public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) { 099 ChatMessageLogger.logResponse(config, data); 100 this.listener.onMessage(this, data); 101 } 102 103 @Override 104 public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable t, @Nullable Response response) { 105 try { 106 this.listener.onFailure(this, Util.getFailureThrowable(t, response)); 107 } finally { 108 tryToStop(); 109 } 110 } 111 112 @Override 113 public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) { 114 //super.onOpen(eventSource, response); 115 } 116 117 118 private void tryToStop() { 119 if (!this.isStop) { 120 try { 121 this.isStop = true; 122 this.listener.onStop(this); 123 } finally { 124 if (eventSource != null) { 125 eventSource.cancel(); 126 eventSource = null; 127 } 128 } 129 } 130 } 131}