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 com.agentsflex.core.util.StringUtil; 024import okhttp3.*; 025import org.jetbrains.annotations.NotNull; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import java.io.BufferedReader; 030import java.io.IOException; 031import java.io.InputStreamReader; 032import java.util.Map; 033 034public class DnjsonClient implements StreamClient, Callback { 035 036 private static final Logger log = LoggerFactory.getLogger(DnjsonClient.class); 037 private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8"); 038 039 private OkHttpClient okHttpClient; 040 private StreamClientListener listener; 041 private ChatConfig config; 042 private boolean isStop = false; 043 044 public DnjsonClient() { 045 this(OkHttpClientUtil.buildDefaultClient()); 046 } 047 048 public DnjsonClient(OkHttpClient okHttpClient) { 049 if (okHttpClient == null) { 050 throw new IllegalArgumentException("OkHttpClient must not be null"); 051 } 052 this.okHttpClient = okHttpClient; 053 } 054 055 public OkHttpClient getOkHttpClient() { 056 return okHttpClient; 057 } 058 059 public void setOkHttpClient(OkHttpClient okHttpClient) { 060 this.okHttpClient = okHttpClient; 061 } 062 063 @Override 064 public void start(String url, Map<String, String> headers, String payload, StreamClientListener listener, ChatConfig config) { 065 if (isStop) { 066 throw new IllegalStateException("DnjsonClient has been stopped and cannot be reused."); 067 } 068 069 this.listener = listener; 070 this.config = config; 071 this.isStop = false; 072 073 Request.Builder builder = new Request.Builder().url(url); 074 if (headers != null && !headers.isEmpty()) { 075 headers.forEach(builder::addHeader); 076 } 077 078 RequestBody body = RequestBody.create(payload, JSON_TYPE); 079 Request request = builder.post(body).build(); 080 081 ChatMessageLogger.logRequest(config, payload); 082 083 if (this.listener != null) { 084 try { 085 this.listener.onStart(this); 086 } catch (Exception e) { 087 log.warn("Error in listener.onStart", e); 088 return; // 可选:是否继续请求? 089 } 090 } 091 092 // 发起异步请求 093 okHttpClient.newCall(request).enqueue(this); 094 } 095 096 @Override 097 public void stop() { 098 // 注意:OkHttp 的 Call 无法取消已开始的 onResponse 099 // 所以 stop() 主要用于标记状态,防止后续回调处理 100 markAsStopped(); 101 } 102 103 104 @Override 105 public void onFailure(@NotNull Call call, @NotNull IOException e) { 106 try { 107 if (listener != null && !isStop) { 108 Throwable error = Util.getFailureThrowable(e, null); 109 listener.onFailure(this, error); 110 } 111 } catch (Exception ex) { 112 log.warn("Error in listener.onFailure", ex); 113 } finally { 114 markAsStopped(); 115 } 116 } 117 118 @Override 119 public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { 120 try { 121 if (!response.isSuccessful()) { 122 if (listener != null && !isStop) { 123 Throwable error = Util.getFailureThrowable(null, response); 124 listener.onFailure(this, error); 125 } 126 return; 127 } 128 129 ResponseBody body = response.body(); 130 if (body == null || isStop) { 131 return; 132 } 133 134 // 使用 try-with-resources 确保流关闭 135 try (ResponseBody responseBody = body; 136 BufferedReader reader = new BufferedReader(new InputStreamReader(responseBody.byteStream()))) { 137 138 String line; 139 while ((line = reader.readLine()) != null) { 140 if (isStop) break; // 支持中途 stop() 141 142 if (!StringUtil.hasText(line)) continue; 143 144 String jsonLine = StringUtil.isJsonObject(line) ? line : "{" + line + "}"; 145 146 if (listener != null && !isStop) { 147 try { 148 ChatMessageLogger.logResponse(config, jsonLine); 149 listener.onMessage(this, jsonLine); 150 } catch (Exception e) { 151 log.warn("Error in listener.onMessage", e); 152 } 153 } 154 } 155 } 156 } finally { 157 markAsStopped(); 158 } 159 } 160 161 162 private void markAsStopped() { 163 if (isStop) return; 164 synchronized (this) { 165 if (isStop) return; 166 isStop = true; 167 if (listener != null) { 168 try { 169 listener.onStop(this); 170 } catch (Exception e) { 171 log.warn("Error in listener.onStop", e); 172 } 173 } 174 } 175 } 176}