chat(@RequestBody ChatRequest request) {
+ ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
+ .model(Constants.ModelChatGLM4)
+ .messages(Arrays.asList(
+ ChatMessage.builder()
+ .role(ChatMessageRole.USER.value())
+ .content(request.getMessage())
+ .build()
+ ))
+ .build();
+
+ ChatCompletionResponse response = zaiClient.chat().createChatCompletion(params);
+
+ if (response.isSuccess()) {
+ String content = response.getData().getChoices().get(0).getMessage().getContent();
+ return ResponseEntity.ok(content);
+ } else {
+ return ResponseEntity.badRequest().body(response.getMsg());
+ }
+ }
+}
+```
+
+## 🔧 可用服务
+
+### Chat 服务
+- 对话补全(同步/异步)
+- 流式对话
+- 函数调用
+- 多模态对话(文本+图像)
+
+### Embeddings 服务
+- 文本向量化
+- 批量嵌入
+- 多种嵌入模型
+
+### Images 服务
+- 文本到图像生成
+- 图像编辑
+- 图像变体
+
+### Audio 服务
+- 语音转文本
+- 文本转语音
+- 语音翻译
+
+### Files 服务
+- 文件上传
+- 文件管理
+- 文件检索
+
+### Assistants 服务
+- AI助手创建
+- 助手管理
+- 对话线程
+
+### Agents 服务
+- 智能代理
+- 工作流管理
+- 任务执行
+
+### Knowledge 服务
+- 知识库管理
+- 文档处理
+- 知识检索
+
+### Batch 服务
+- 批量处理
+- 异步任务
+- 结果管理
+
+## 🤖 支持的模型
+
+### 文本生成模型
+- `glm-4-plus` - 最新的GLM-4 Plus模型
+- `glm-4-0520` - GLM-4标准版
+- `glm-4-long` - 长文本处理版本
+- `glm-4-airx` - 轻量级版本
+- `glm-4-air` - 快速响应版本
+- `glm-4-flashx` - 超快响应版本
+- `glm-4-flash` - 闪电版本
+
+### 视觉模型
+- `glm-4v-plus` - 多模态理解模型
+- `glm-4v` - 视觉理解模型
+
+### 图像生成模型
+- `cogview-3-plus` - 高质量图像生成
+- `cogview-3` - 标准图像生成
+
+### 嵌入模型
+- `embedding-3` - 最新嵌入模型
+- `embedding-2` - 标准嵌入模型
+
+### 专业模型
+- `charglm-3` - 角色扮演模型
+- `emohaa` - 情感分析模型
+
+## 📈 版本更新
+
+详细的版本更新记录和历史信息,请查看 [Release-Note.md](Release-Note.md)。
+
+## 📄 许可证
+
+本项目基于 MIT 许可证开源 - 详情请查看 [LICENSE](LICENSE) 文件。
+
+## 🤝 贡献
+
+欢迎贡献代码!请随时提交 Pull Request。
+
+## 📞 支持
+
+如有问题和技术支持:
+- Visit [Z.ai Platform](https://z.ai/)
+- Visit [ZHIPU AI Open Platform](http://open.bigmodel.cn/)
+- Check our [Architecture Documentation](ARCHITECTURE.md)
diff --git a/Release-Note.md b/Release-Note.md
new file mode 100644
index 0000000..f1df9cf
--- /dev/null
+++ b/Release-Note.md
@@ -0,0 +1,4 @@
+# Release Notes
+
+### 0.0.1
+- ✅ Initial the Z.ai sdk refer from ZHIPU sdk
\ No newline at end of file
diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 0000000..aa36953
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,237 @@
+
+
+ 4.0.0
+
+ ai.z.openapi
+ z-ai-sdk-parent
+ ${revision}
+ ../pom.xml
+
+ z-ai-sdk
+
+ jar
+
+ Java sdk for Z.ai open api
+ Java SDK for Z.ai Open Platform API
+ https://z.ai
+
+
+ MIT License
+ https://www.opensource.org/licenses/mit-license.php
+ repo
+
+
+
+
+ Z.ai
+ Z.ai
+ user_feedback@z.ai
+ https://z.ai/model-api
+ Z.ai
+ https://z.ai
+
+ architect
+ developer
+
+ Asia/Shanghai
+
+
+
+ scm:git:https://github.com/THUDM/z-ai-sdk-java.git
+ scm:git:https://github.com/THUDM/z-ai-sdk-java.git
+ HEAD
+ https://github.com/THUDM/z-ai-sdk-java
+
+
+
+ 8
+ 8
+ UTF-8
+ 2.0.11
+ 3.14.9
+ 2.11.3
+ 2.9.0
+ 3.1.8
+ 4.2.2
+ 1.18.32
+ 2.9.0
+ 5.10.2
+ 1.19.8
+ 5.15.0
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+ test
+
+
+ org.slf4j
+ slf4j-log4j12
+ ${slf4j.version}
+ pom
+ test
+
+
+
+
+ com.squareup.okhttp3
+ okhttp-sse
+ ${okhttp.version}
+
+
+ com.squareup.okhttp3
+ okhttp
+ ${okhttp.version}
+
+
+ com.squareup.okhttp3
+ logging-interceptor
+ ${okhttp.version}
+
+
+ io.reactivex.rxjava3
+ rxjava
+ ${rxjava.version}
+
+
+ com.squareup.retrofit2
+ adapter-rxjava2
+ ${retrofit2.version}
+
+
+ com.squareup.retrofit2
+ converter-jackson
+ ${retrofit2.version}
+
+
+
+
+ com.auth0
+ java-jwt
+ ${jwt.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${jackson.version}
+
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+ org.apache.tika
+ tika-core
+ ${tika.version}
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.test.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit.test.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.test.version}
+ test
+
+
+
+
+ org.testcontainers
+ testcontainers
+ ${testcontainer.version}
+ test
+
+
+ org.testcontainers
+ mockserver
+ ${testcontainer.version}
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ ${testcontainer.version}
+ test
+
+
+
+
+ org.mock-server
+ mockserver-netty
+ ${mockserver.version}
+ test
+
+
+ org.mock-server
+ mockserver-client-java
+ ${mockserver.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+
+
+
diff --git a/core/src/main/java/ai/z/openapi/ZaiClient.java b/core/src/main/java/ai/z/openapi/ZaiClient.java
new file mode 100644
index 0000000..abe9ccc
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/ZaiClient.java
@@ -0,0 +1,646 @@
+package ai.z.openapi;
+
+import ai.z.openapi.service.AbstractClientBaseService;
+import ai.z.openapi.service.model.ChatError;
+import ai.z.openapi.service.model.ZAiHttpException;
+import ai.z.openapi.service.chat.ChatService;
+import ai.z.openapi.service.chat.ChatServiceImpl;
+import ai.z.openapi.service.agents.AgentService;
+import ai.z.openapi.service.agents.AgentServiceImpl;
+import ai.z.openapi.service.embedding.EmbeddingService;
+import ai.z.openapi.service.embedding.EmbeddingServiceImpl;
+import ai.z.openapi.service.file.FileService;
+import ai.z.openapi.service.file.FileServiceImpl;
+import ai.z.openapi.service.audio.AudioService;
+import ai.z.openapi.service.audio.AudioServiceImpl;
+import ai.z.openapi.service.image.ImageService;
+import ai.z.openapi.service.image.ImageServiceImpl;
+import ai.z.openapi.service.batches.BatchService;
+import ai.z.openapi.service.batches.BatchServiceImpl;
+import ai.z.openapi.service.fine_turning.FineTuningService;
+import ai.z.openapi.service.fine_turning.FineTuningServiceImpl;
+import ai.z.openapi.service.web_search.WebSearchService;
+import ai.z.openapi.service.web_search.WebSearchServiceImpl;
+import ai.z.openapi.service.videos.VideosService;
+import ai.z.openapi.service.videos.VideosServiceImpl;
+import ai.z.openapi.service.knowledge.KnowledgeService;
+import ai.z.openapi.service.knowledge.KnowledgeServiceImpl;
+import ai.z.openapi.service.document.DocumentService;
+import ai.z.openapi.service.document.DocumentServiceImpl;
+import ai.z.openapi.service.assistant.AssistantService;
+import ai.z.openapi.service.assistant.AssistantServiceImpl;
+import ai.z.openapi.core.config.ZaiConfig;
+import ai.z.openapi.core.model.ClientRequest;
+import ai.z.openapi.core.model.ClientResponse;
+import ai.z.openapi.core.model.FlowableClientResponse;
+import ai.z.openapi.utils.FlowableRequestSupplier;
+import ai.z.openapi.utils.OkHttps;
+import ai.z.openapi.utils.RequestSupplier;
+import ai.z.openapi.utils.StringUtils;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+import okhttp3.OkHttpClient;
+import okhttp3.ResponseBody;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
+import retrofit2.converter.jackson.JacksonConverterFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.TimeUnit;
+
+import static ai.z.openapi.core.Constants.ZHIPU_AI_BASE_URL;
+import static ai.z.openapi.core.Constants.Z_AI_BASE_URL;
+
+/**
+ * ZaiClient is the main entry point for interacting with the Z.ai API. This client
+ * provides access to various AI services including chat, embeddings, file operations,
+ * audio processing, image generation, and more.
+ *
+ *
+ * The client supports both Z.ai and ZHIPU AI endpoints and provides thread-safe lazy
+ * initialization of services.
+ *
+ *
+ *
+ * Example usage:
+ *
+ * {@code
+ * ZaiClient client = new ZaiClient.Builder("your-api-key")
+ * .networkConfig(30, 10, 30, 30, TimeUnit.SECONDS)
+ * .build();
+ *
+ * ChatService chatService = client.chat();
+ * // Use the chat service...
+ *
+ * client.close(); // Don't forget to close when done
+ * }
+ *
+ */
+public class ZaiClient extends AbstractClientBaseService {
+
+ /** Logger instance for this class */
+ private static final Logger logger = LoggerFactory.getLogger(ZaiClient.class);
+
+ /** HTTP client for making network requests */
+ private final OkHttpClient httpClient;
+
+ /** Retrofit instance for API communication */
+ private final Retrofit retrofit;
+
+ // Service instances - lazily initialized for thread safety and performance
+ /** Chat service for conversational AI operations */
+ private ChatService chatService;
+
+ /** Agent service for AI agent management */
+ private AgentService agentService;
+
+ /** Embedding service for text embeddings */
+ private EmbeddingService embeddingService;
+
+ /** File service for file operations */
+ private FileService fileService;
+
+ /** Audio service for audio processing */
+ private AudioService audioService;
+
+ /** Image service for image generation and processing */
+ private ImageService imageService;
+
+ /** Batch service for batch processing operations */
+ private BatchService batchService;
+
+ /** Fine-tuning service for model customization */
+ private FineTuningService fineTuningService;
+
+ /** Web search service for internet search capabilities */
+ private WebSearchService webSearchService;
+
+ /** Videos service for video processing */
+ private VideosService videosService;
+
+ /** Knowledge service for knowledge base operations */
+ private KnowledgeService knowledgeService;
+
+ /** Document service for document processing */
+ private DocumentService documentService;
+
+ /** Assistant service for AI assistant functionality */
+ private AssistantService assistantService;
+
+ /**
+ * Constructs a new ZaiClient with the specified configuration. By default, this
+ * client uses the Z.ai OpenAPI endpoint.
+ * @param config the configuration object containing API keys, timeouts, and other
+ * settings
+ * @throws IllegalArgumentException if config is null or invalid
+ */
+ public ZaiClient(ZaiConfig config) {
+ this.httpClient = OkHttps.create(config);
+ this.retrofit = new Retrofit.Builder()
+ .baseUrl(StringUtils.isEmpty(config.getBaseUrl()) ? Z_AI_BASE_URL : config.getBaseUrl())
+ .client(httpClient)
+ .addConverterFactory(JacksonConverterFactory.create(mapper))
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .build();
+ }
+
+ // ==================== Service Accessor Methods ====================
+ // All service methods use lazy initialization with synchronization for thread safety
+
+ /**
+ * Returns the chat service for conversational AI operations. This service handles
+ * chat completions, streaming conversations, and related functionality.
+ * @return the ChatService instance (lazily initialized)
+ */
+ public synchronized ChatService chat() {
+ if (chatService == null) {
+ this.chatService = new ChatServiceImpl(this);
+ }
+ return chatService;
+ }
+
+ /**
+ * Returns the agent service for AI agent management. This service handles agent
+ * creation, configuration, and execution.
+ * @return the AgentService instance (lazily initialized)
+ */
+ public synchronized AgentService agents() {
+ if (agentService == null) {
+ this.agentService = new AgentServiceImpl(this);
+ }
+ return agentService;
+ }
+
+ /**
+ * Returns the embedding service for text embeddings. This service converts text into
+ * numerical vector representations.
+ * @return the EmbeddingService instance (lazily initialized)
+ */
+ public synchronized EmbeddingService embeddings() {
+ if (embeddingService == null) {
+ this.embeddingService = new EmbeddingServiceImpl(this);
+ }
+ return embeddingService;
+ }
+
+ /**
+ * Returns the file service for file operations. This service handles file uploads,
+ * downloads, and management.
+ * @return the FileService instance (lazily initialized)
+ */
+ public synchronized FileService files() {
+ if (fileService == null) {
+ this.fileService = new FileServiceImpl(this);
+ }
+ return fileService;
+ }
+
+ /**
+ * Returns the audio service for audio processing. This service handles
+ * speech-to-text, text-to-speech, and audio analysis.
+ * @return the AudioService instance (lazily initialized)
+ */
+ public synchronized AudioService audio() {
+ if (audioService == null) {
+ this.audioService = new AudioServiceImpl(this);
+ }
+ return audioService;
+ }
+
+ /**
+ * Returns the image service for image generation and processing. This service handles
+ * image creation, editing, and analysis.
+ * @return the ImageService instance (lazily initialized)
+ */
+ public synchronized ImageService images() {
+ if (imageService == null) {
+ this.imageService = new ImageServiceImpl(this);
+ }
+ return imageService;
+ }
+
+ /**
+ * Returns the batch service for batch processing operations. This service handles
+ * large-scale batch processing of requests.
+ * @return the BatchService instance (lazily initialized)
+ */
+ public synchronized BatchService batches() {
+ if (batchService == null) {
+ this.batchService = new BatchServiceImpl(this);
+ }
+ return batchService;
+ }
+
+ /**
+ * Returns the fine-tuning service for model customization. This service handles
+ * training custom models on user data.
+ * @return the FineTuningService instance (lazily initialized)
+ */
+ public synchronized FineTuningService fineTuning() {
+ if (fineTuningService == null) {
+ this.fineTuningService = new FineTuningServiceImpl(this);
+ }
+ return fineTuningService;
+ }
+
+ /**
+ * Returns the web search service for internet search capabilities. This service
+ * provides AI-powered web search functionality.
+ * @return the WebSearchService instance (lazily initialized)
+ */
+ public synchronized WebSearchService webSearch() {
+ if (webSearchService == null) {
+ this.webSearchService = new WebSearchServiceImpl(this);
+ }
+ return webSearchService;
+ }
+
+ /**
+ * Returns the videos service for video processing. This service handles video
+ * analysis, generation, and manipulation.
+ * @return the VideosService instance (lazily initialized)
+ */
+ public synchronized VideosService videos() {
+ if (videosService == null) {
+ this.videosService = new VideosServiceImpl(this);
+ }
+ return videosService;
+ }
+
+ /**
+ * Returns the knowledge service for knowledge base operations. This service manages
+ * knowledge bases and retrieval operations.
+ * @return the KnowledgeService instance (lazily initialized)
+ */
+ public synchronized KnowledgeService knowledge() {
+ if (knowledgeService == null) {
+ this.knowledgeService = new KnowledgeServiceImpl(this);
+ }
+ return knowledgeService;
+ }
+
+ /**
+ * Returns the document service for document processing. This service handles document
+ * parsing, analysis, and manipulation.
+ * @return the DocumentService instance (lazily initialized)
+ */
+ public synchronized DocumentService documents() {
+ if (documentService == null) {
+ this.documentService = new DocumentServiceImpl(this);
+ }
+ return documentService;
+ }
+
+ /**
+ * Returns the assistant service for AI assistant functionality. This service provides
+ * advanced AI assistant capabilities.
+ * @return the AssistantService instance (lazily initialized)
+ */
+ public synchronized AssistantService assistants() {
+ if (assistantService == null) {
+ this.assistantService = new AssistantServiceImpl(this);
+ }
+ return assistantService;
+ }
+
+ // ==================== Utility Methods ====================
+
+ /**
+ * Returns the underlying Retrofit instance used for API communication. This method is
+ * primarily intended for advanced users who need direct access to the Retrofit client
+ * for custom API calls.
+ * @return the Retrofit instance
+ */
+ public Retrofit retrofit() {
+ return retrofit;
+ }
+
+ /**
+ * Closes the ZAi client and releases all associated resources. This method shuts down
+ * the HTTP client's connection pool and executor service. After calling this method,
+ * the client should not be used for further requests.
+ *
+ *
+ * Important: Always call this method when you're done with the
+ * client to prevent resource leaks.
+ *
+ */
+ public void close() {
+ if (httpClient != null) {
+ httpClient.dispatcher().executorService().shutdown();
+ }
+ }
+
+ // ==================== Core Request Execution Methods ====================
+
+ /**
+ * Executes a synchronous API request and returns the response. This method handles
+ * the complete request lifecycle including error handling and response wrapping.
+ * @param the type of data expected in the response
+ * @param the type of parameters for the request
+ * @param the type of client request
+ * @param the type of client response
+ * @param request the client request containing parameters
+ * @param requestSupplier the supplier that creates the actual API call
+ * @param tRespClass the class of the response type
+ * @return the wrapped response containing either success data or error information
+ */
+ @Override
+ public , TResp extends ClientResponse> TResp executeRequest(
+ TReq request, RequestSupplier requestSupplier, Class tRespClass) {
+ Single apiCall = requestSupplier.get((Param) request);
+
+ TResp tResp = convertToClientResponse(tRespClass);
+ try {
+ // Execute the API call synchronously
+ Data response = execute(apiCall);
+ tResp.setCode(200);
+ tResp.setMsg("Call successful");
+ tResp.setData(response);
+ tResp.setSuccess(true);
+ }
+ catch (ZAiHttpException e) {
+ logger.error("API request failed with business error", e);
+ tResp.setCode(e.statusCode);
+ tResp.setMsg("Business error");
+ tResp.setSuccess(false);
+ ChatError chatError = new ChatError();
+ chatError.setCode(Integer.parseInt(e.code));
+ chatError.setMessage(e.getMessage());
+ tResp.setError(chatError);
+ }
+ return tResp;
+ }
+
+ /**
+ * Executes a streaming API request and returns a response containing a Flowable
+ * stream. This method is used for requests that return data as a continuous stream,
+ * such as chat completions with streaming enabled.
+ * @param the type of data expected in each stream element
+ * @param the type of parameters for the request
+ * @param the type of client request
+ * @param the type of flowable client response
+ * @param request the client request containing parameters
+ * @param requestSupplier the supplier that creates the actual streaming API call
+ * @param tRespClass the class of the response type
+ * @param tDataClass the class of the data type for stream elements
+ * @return the wrapped response containing either a success stream or error
+ * information
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public , TResp extends FlowableClientResponse> TResp streamRequest(
+ TReq request, FlowableRequestSupplier> requestSupplier,
+ Class tRespClass, Class tDataClass) {
+ retrofit2.Call apiCall = requestSupplier.get((Param) request);
+
+ TResp tResp = convertToClientResponse(tRespClass);
+ try {
+ // Create a streaming response using the provided API call
+ Flowable stream = stream(apiCall, tDataClass);
+ tResp.setCode(200);
+ tResp.setMsg("Stream initialized successfully");
+ tResp.setSuccess(true);
+ tResp.setFlowable(stream);
+ }
+ catch (ZAiHttpException e) {
+ logger.error("Streaming API request failed with business error", e);
+ tResp.setCode(e.statusCode);
+ tResp.setMsg("Business error");
+ tResp.setSuccess(false);
+ ChatError chatError = new ChatError();
+ chatError.setCode(Integer.parseInt(e.code));
+ chatError.setMessage(e.getMessage());
+ tResp.setError(chatError);
+ }
+ return tResp;
+ }
+
+ /**
+ * Creates a new instance of the specified response class using reflection. This
+ * helper method is used to instantiate response objects dynamically.
+ * @param the type of data contained in the response
+ * @param the type of client response
+ * @param tRespClass the class of the response type to instantiate
+ * @return a new instance of the response class
+ * @throws RuntimeException if the response object cannot be created
+ */
+ private > TResp convertToClientResponse(Class tRespClass) {
+ try {
+ return tRespClass.getDeclaredConstructor().newInstance();
+ }
+ catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
+ throw new RuntimeException("Failed to create response object of type: " + tRespClass.getSimpleName(), e);
+ }
+ }
+
+ // ==================== Builder Pattern Implementation ====================
+
+ /**
+ * Builder class for creating ZaiClient instances with custom configurations. This
+ * builder provides a fluent API for setting up the client with various options
+ * including authentication, network settings, and connection pooling.
+ *
+ *
+ * Example usage:
+ *
+ * {@code
+ * ZaiClient client = new ZaiClient.Builder("your-api-key")
+ * .networkConfig(30, 10, 30, 30, TimeUnit.SECONDS)
+ * .connectionPool(10, 5, TimeUnit.MINUTES)
+ * .enableTokenCache()
+ * .build();
+ * }
+ */
+ public static final class Builder {
+
+ /** Configuration object that accumulates all builder settings */
+ private final ZaiConfig config = new ZaiConfig();
+
+ public Builder() {
+ }
+
+ /**
+ * Creates a new builder with the specified API key.
+ * @param apiKey the API key for authentication
+ * @throws IllegalArgumentException if apiKey is null or empty
+ */
+ public Builder(String apiKey) {
+ if (apiKey == null || apiKey.trim().isEmpty()) {
+ throw new IllegalArgumentException("API key cannot be null or empty");
+ }
+ config.setApiKey(apiKey);
+ }
+
+ /**
+ * Creates a new builder with a custom base URL and API secret key.
+ * @param baseUrl the custom base URL for the API endpoint
+ * @param apiKey the API secret key for authentication
+ * @throws IllegalArgumentException if any parameter is null or empty
+ */
+ public Builder(String baseUrl, String apiKey) {
+ if (baseUrl == null || baseUrl.trim().isEmpty()) {
+ throw new IllegalArgumentException("Base URL cannot be null or empty");
+ }
+ if (apiKey == null || apiKey.trim().isEmpty()) {
+ throw new IllegalArgumentException("API secret key cannot be null or empty");
+ }
+ config.setBaseUrl(baseUrl);
+ config.setApiKey(apiKey);
+ }
+
+ /**
+ * Config the api service base url
+ * @param baseUrl base url
+ * @return this Builder instance for method chaining
+ */
+ public Builder baseUrl(String baseUrl) {
+ if (baseUrl == null || baseUrl.trim().isEmpty()) {
+ throw new IllegalArgumentException("Base URL cannot be null or empty");
+ }
+ config.setBaseUrl(baseUrl);
+ return this;
+ }
+
+ /**
+ * Config the apikey
+ * @param apiKey api key
+ * @return this Builder instance for method chaining
+ */
+ public Builder apiKey(String apiKey) {
+ if (apiKey == null || apiKey.trim().isEmpty()) {
+ throw new IllegalArgumentException("API secret key cannot be null or empty");
+ }
+ config.setApiKey(apiKey);
+ return this;
+ }
+
+ /**
+ * Use the ZHIPU AI base url
+ * @return this Builder instance for method chaining
+ */
+ public Builder ofZHIPU() {
+ config.setBaseUrl(ZHIPU_AI_BASE_URL);
+ return this;
+ }
+
+ /**
+ * Use the Z AI base url
+ * @return this Builder instance for method chaining
+ */
+ public Builder ofZAI() {
+ config.setBaseUrl(Z_AI_BASE_URL);
+ return this;
+ }
+
+ /**
+ * Disables token caching, forcing the client to use API keys for direct requests.
+ * This is the default behavior.
+ * @return this Builder instance for method chaining
+ */
+ public Builder disableTokenCache() {
+ config.setDisableTokenCache(true);
+ return this;
+ }
+
+ /**
+ * Enables token caching, allowing the client to use access tokens for requests.
+ * This can improve performance by reducing authentication overhead.
+ * @return this Builder instance for method chaining
+ */
+ public Builder enableTokenCache() {
+ config.setDisableTokenCache(false);
+ return this;
+ }
+
+ /**
+ * Configures the HTTP connection pool settings.
+ * @param maxIdleConnections maximum number of idle connections to keep in the
+ * pool
+ * @param keepAliveDuration how long to keep idle connections alive
+ * @param timeUnit the time unit for the keep alive duration
+ * @return this Builder instance for method chaining
+ */
+ public Builder connectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
+ config.setConnectionPoolMaxIdleConnections(maxIdleConnections);
+ config.setConnectionPoolKeepAliveDuration(keepAliveDuration);
+ config.setConnectionPoolTimeUnit(timeUnit);
+ return this;
+ }
+
+ /**
+ * Sets the token expiration time in milliseconds.
+ * @param expireMillis the token expiration time in milliseconds
+ * @return this Builder instance for method chaining
+ */
+ public Builder tokenExpire(int expireMillis) {
+ config.setExpireMillis(expireMillis);
+ return this;
+ }
+
+ /**
+ * Configures network request timeout settings.
+ * @param requestTimeOut the overall request timeout (see
+ * {@link OkHttpClient.Builder#callTimeout(long, TimeUnit)})
+ * @param connectTimeout the connection timeout (see
+ * {@link OkHttpClient.Builder#connectTimeout(long, TimeUnit)})
+ * @param readTimeout the read timeout (see
+ * {@link OkHttpClient.Builder#readTimeout(long, TimeUnit)})
+ * @param writeTimeout the write timeout (see
+ * {@link OkHttpClient.Builder#writeTimeout(long, TimeUnit)})
+ * @param timeUnit the time unit for all timeout values
+ * @return this Builder instance for method chaining
+ */
+ public Builder networkConfig(int requestTimeOut, int connectTimeout, int readTimeout, int writeTimeout,
+ TimeUnit timeUnit) {
+ config.setRequestTimeOut(requestTimeOut);
+ config.setConnectTimeout(connectTimeout);
+ config.setReadTimeout(readTimeout);
+ config.setWriteTimeout(writeTimeout);
+ config.setTimeOutTimeUnit(timeUnit);
+ return this;
+ }
+
+ /**
+ * Builds and returns a new ZaiClient instance with the configured settings.
+ * @return a new ZaiClient instance
+ * @throws IllegalStateException if the configuration is invalid
+ */
+ public ZaiClient build() {
+ return new ZaiClient(config);
+ }
+
+ }
+
+ // ==================== Static Factory Methods ====================
+
+ /**
+ * Creates a new Builder instance for constructing ZaiClient.
+ * @return a new Builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates a ZaiClient configured for ZHIPU AI platform with the specified API key.
+ * @param apiKey the API key for authentication
+ * @return a new ZaiClient instance configured for ZHIPU AI
+ * @throws IllegalArgumentException if apiKey is null or empty
+ */
+ public static Builder ofZHIPU(String apiKey) {
+ return new Builder().apiKey(apiKey).ofZHIPU();
+ }
+
+ /**
+ * Creates a ZaiClient configured for ZHIPU AI platform with the specified API key.
+ * @return a new ZaiClient instance configured for ZHIPU AI
+ * @throws IllegalArgumentException if apiKey is null or empty
+ */
+ public static Builder ofZHIPU() {
+ return new Builder().ofZHIPU();
+ }
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/agents/AgentsApi.java b/core/src/main/java/ai/z/openapi/api/agents/AgentsApi.java
new file mode 100644
index 0000000..efe0dcf
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/agents/AgentsApi.java
@@ -0,0 +1,25 @@
+package ai.z.openapi.api.agents;
+
+import ai.z.openapi.service.agents.AgentAsyncResultRetrieveParams;
+import ai.z.openapi.service.agents.AgentsCompletionRequest;
+import ai.z.openapi.service.model.ModelData;
+import io.reactivex.Single;
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.POST;
+import retrofit2.http.Streaming;
+
+public interface AgentsApi {
+
+ @Streaming
+ @POST("v1/agents")
+ Call agentsCompletionStream(@Body AgentsCompletionRequest request);
+
+ @POST("v1/agents")
+ Single agentsCompletionSync(@Body AgentsCompletionRequest request);
+
+ @POST("v1/agents/async-result")
+ Single queryAgentsAsyncResult(@Body AgentAsyncResultRetrieveParams request);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/assistant/AssistantApi.java b/core/src/main/java/ai/z/openapi/api/assistant/AssistantApi.java
new file mode 100644
index 0000000..2690cba
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/assistant/AssistantApi.java
@@ -0,0 +1,31 @@
+package ai.z.openapi.api.assistant;
+
+import ai.z.openapi.service.assistant.AssistantCompletion;
+import ai.z.openapi.service.assistant.AssistantParameters;
+import ai.z.openapi.service.assistant.conversation.ConversationUsageListStatus;
+import ai.z.openapi.service.assistant.query_support.AssistantSupportStatus;
+import ai.z.openapi.service.assistant.conversation.ConversationParameters;
+import ai.z.openapi.service.assistant.query_support.QuerySupportParams;
+import io.reactivex.Single;
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.POST;
+import retrofit2.http.Streaming;
+
+public interface AssistantApi {
+
+ @Streaming
+ @POST("assistant")
+ Call assistantCompletionStream(@Body AssistantParameters request);
+
+ @POST("assistant")
+ Single assistantCompletion(@Body AssistantParameters request);
+
+ @POST("assistant/list")
+ Single querySupport(@Body QuerySupportParams request);
+
+ @POST("assistant/conversation/list")
+ Single queryConversationUsage(@Body ConversationParameters request);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/audio/AudioApi.java b/core/src/main/java/ai/z/openapi/api/audio/AudioApi.java
new file mode 100644
index 0000000..f8c05fb
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/audio/AudioApi.java
@@ -0,0 +1,50 @@
+package ai.z.openapi.api.audio;
+
+import ai.z.openapi.service.audio.AudioSpeechRequest;
+import ai.z.openapi.service.model.ModelData;
+import io.reactivex.Single;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Multipart;
+import retrofit2.http.POST;
+import retrofit2.http.Part;
+import retrofit2.http.PartMap;
+import retrofit2.http.Streaming;
+
+import java.util.Map;
+
+public interface AudioApi {
+
+ /**
+ * TTS interface (Text to speech)
+ * @param request
+ * @return
+ */
+ @POST("audio/speech")
+ Single audioSpeech(@Body AudioSpeechRequest request);
+
+ /**
+ * Voice cloning interface
+ * @param request
+ * @return
+ */
+ @Multipart
+ @POST("audio/customization")
+ Single audioCustomization(@PartMap Map request,
+ @Part MultipartBody.Part voiceData);
+
+ @Streaming
+ @POST("audio/transcriptions")
+ @Multipart
+ Call audioTranscriptionsStream(@PartMap Map request,
+ @Part MultipartBody.Part file);
+
+ @POST("audio/transcriptions")
+ @Multipart
+ Single audioTranscriptions(@PartMap Map request, @Part MultipartBody.Part file);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/batches/BatchesApi.java b/core/src/main/java/ai/z/openapi/api/batches/BatchesApi.java
new file mode 100644
index 0000000..3456d74
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/batches/BatchesApi.java
@@ -0,0 +1,27 @@
+package ai.z.openapi.api.batches;
+
+import ai.z.openapi.service.batches.Batch;
+import ai.z.openapi.service.batches.BatchCreateParams;
+import ai.z.openapi.service.batches.BatchPage;
+import io.reactivex.Single;
+import retrofit2.http.Body;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+public interface BatchesApi {
+
+ @POST("batches")
+ Single batchesCreate(@Body BatchCreateParams batchCreateParams);
+
+ @GET("batches/{batch_id}")
+ Single batchesRetrieve(@Path("batch_id") String batchId);
+
+ @GET("batches")
+ Single batchesList(@Query("after") String after, @Query("limit") Integer limit);
+
+ @POST("batches/{batch_id}/cancel")
+ Single batchesCancel(@Path("batch_id") String batchId);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/chat/ChatApi.java b/core/src/main/java/ai/z/openapi/api/chat/ChatApi.java
new file mode 100644
index 0000000..c89b208
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/chat/ChatApi.java
@@ -0,0 +1,29 @@
+package ai.z.openapi.api.chat;
+
+import ai.z.openapi.service.model.ChatCompletionCreateParams;
+import ai.z.openapi.service.model.ModelData;
+import io.reactivex.Single;
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+import retrofit2.http.Streaming;
+
+public interface ChatApi {
+
+ @Streaming
+ @POST("chat/completions")
+ Call createChatCompletionStream(@Body ChatCompletionCreateParams request);
+
+ @POST("async/chat/completions")
+ Single createChatCompletionAsync(@Body ChatCompletionCreateParams request);
+
+ @POST("chat/completions")
+ Single createChatCompletion(@Body ChatCompletionCreateParams request);
+
+ @GET("async-result/{id}")
+ Single queryAsyncResult(@Path("id") String id);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/embedding/EmbeddingApi.java b/core/src/main/java/ai/z/openapi/api/embedding/EmbeddingApi.java
new file mode 100644
index 0000000..e41334b
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/embedding/EmbeddingApi.java
@@ -0,0 +1,14 @@
+package ai.z.openapi.api.embedding;
+
+import ai.z.openapi.service.embedding.EmbeddingCreateParams;
+import ai.z.openapi.service.embedding.EmbeddingResult;
+import io.reactivex.Single;
+import retrofit2.http.Body;
+import retrofit2.http.POST;
+
+public interface EmbeddingApi {
+
+ @POST("embeddings")
+ Single createEmbeddings(@Body EmbeddingCreateParams request);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/file/FileApi.java b/core/src/main/java/ai/z/openapi/api/file/FileApi.java
new file mode 100644
index 0000000..a439c24
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/file/FileApi.java
@@ -0,0 +1,37 @@
+package ai.z.openapi.api.file;
+
+import ai.z.openapi.service.file.File;
+import ai.z.openapi.service.file.FileDeleted;
+import ai.z.openapi.service.file.QueryFileResult;
+import io.reactivex.Single;
+import okhttp3.MultipartBody;
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+import retrofit2.http.Streaming;
+
+public interface FileApi {
+
+ @POST("files")
+ Single uploadFile(@Body MultipartBody multipartBody);
+
+ @GET("files/{file_id}")
+ Single retrieveFile(@Path("file_id") String fileId);
+
+ @DELETE("files/{file_id}")
+ Single deletedFile(@Path("file_id") String fileId);
+
+ @GET("files")
+ Single queryFileList(@Query("after") String after, @Query("purpose") String purpose,
+ @Query("order") String order, @Query("limit") Integer limit);
+
+ @Streaming
+ @GET("files/{file_id}/content")
+ Call fileContent(@Path("file_id") String fileId);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/fine_tuning/FineTuningApi.java b/core/src/main/java/ai/z/openapi/api/fine_tuning/FineTuningApi.java
new file mode 100644
index 0000000..ec3f9a4
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/fine_tuning/FineTuningApi.java
@@ -0,0 +1,42 @@
+package ai.z.openapi.api.fine_tuning;
+
+import ai.z.openapi.service.fine_turning.FineTunedModelsStatus;
+import ai.z.openapi.service.fine_turning.FineTuningEvent;
+import ai.z.openapi.service.fine_turning.FineTuningJob;
+import ai.z.openapi.service.fine_turning.FineTuningJobRequest;
+import ai.z.openapi.service.fine_turning.PersonalFineTuningJob;
+import io.reactivex.Single;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+public interface FineTuningApi {
+
+ @POST("fine_tuning/jobs")
+ Single createFineTuningJob(@Body FineTuningJobRequest request);
+
+ @GET("fine_tuning/jobs/{fine_tuning_job_id}/events")
+ Single listFineTuningJobEvents(@Path("fine_tuning_job_id") String fineTuningJobId,
+ @Query("limit") Integer limit, @Query("after") String after);
+
+ @GET("fine_tuning/jobs/{fine_tuning_job_id}")
+ Single retrieveFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId,
+ @Query("limit") Integer limit, @Query("after") String after);
+
+ @GET("fine_tuning/jobs")
+ Single queryPersonalFineTuningJobs(@Query("limit") Integer limit,
+ @Query("after") String after);
+
+ @POST("fine_tuning/jobs/{fine_tuning_job_id}/cancel")
+ Single cancelFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId);
+
+ @DELETE("fine_tuning/jobs/{fine_tuning_job_id}")
+ Single deleteFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId);
+
+ @DELETE("fine_tuning/fine_tuned_models/{fine_tuned_model}")
+ Single deleteFineTuningModel(@Path("fine_tuned_model") String fineTunedModel);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/images/ImagesApi.java b/core/src/main/java/ai/z/openapi/api/images/ImagesApi.java
new file mode 100644
index 0000000..45121be
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/images/ImagesApi.java
@@ -0,0 +1,14 @@
+package ai.z.openapi.api.images;
+
+import ai.z.openapi.service.image.CreateImageRequest;
+import ai.z.openapi.service.image.ImageResult;
+import io.reactivex.Single;
+import retrofit2.http.Body;
+import retrofit2.http.POST;
+
+public interface ImagesApi {
+
+ @POST("images/generations")
+ Single createImage(@Body CreateImageRequest request);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/knowledge/KnowledgeApi.java b/core/src/main/java/ai/z/openapi/api/knowledge/KnowledgeApi.java
new file mode 100644
index 0000000..139df43
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/knowledge/KnowledgeApi.java
@@ -0,0 +1,36 @@
+package ai.z.openapi.api.knowledge;
+
+import ai.z.openapi.service.knowledge.KnowledgeBaseParams;
+import ai.z.openapi.service.knowledge.KnowledgeInfo;
+import ai.z.openapi.service.knowledge.KnowledgePage;
+import ai.z.openapi.service.knowledge.KnowledgeUsed;
+import io.reactivex.Single;
+
+import retrofit2.Response;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.PUT;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+public interface KnowledgeApi {
+
+ @POST("knowledge")
+ Single knowledgeCreate(@Body KnowledgeBaseParams knowledgeBaseParams);
+
+ @PUT("knowledge/{knowledge_id}")
+ Single> knowledgeModify(@Path("knowledge_id") String knowledge_id,
+ @Body KnowledgeBaseParams knowledgeBaseParams);
+
+ @GET("knowledge")
+ Single knowledgeQuery(@Query("page") Integer page, @Query("size") Integer size);
+
+ @DELETE("knowledge/{knowledge_id}")
+ Single> knowledgeDelete(@Path("knowledge_id") String knowledge_id);
+
+ @GET("knowledge/capacity")
+ Single knowledgeUsed();
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/knowledge/document/DocumentApi.java b/core/src/main/java/ai/z/openapi/api/knowledge/document/DocumentApi.java
new file mode 100644
index 0000000..f50b029
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/knowledge/document/DocumentApi.java
@@ -0,0 +1,37 @@
+package ai.z.openapi.api.knowledge.document;
+
+import ai.z.openapi.service.knowledge.document.DocumentData;
+import ai.z.openapi.service.knowledge.document.DocumentEditParams;
+import ai.z.openapi.service.knowledge.document.DocumentObject;
+import ai.z.openapi.service.knowledge.document.DocumentPage;
+import io.reactivex.Single;
+import okhttp3.MultipartBody;
+import retrofit2.Response;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.PUT;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+public interface DocumentApi {
+
+ @POST("files")
+ Single createDocument(@Body MultipartBody document);
+
+ @PUT("document/{document_id}")
+ Single> modifyDocument(@Path("document_id") String documentId,
+ @Body DocumentEditParams documentEditParams);
+
+ @DELETE("document/{document_id}")
+ Single> deleteDocument(@Path("document_id") String documentId);
+
+ @GET("files")
+ Single queryDocumentList(@Query("knowledge_id") String knowledgeId, @Query("purpose") String purpose,
+ @Query("page") Integer page, @Query("limit") Integer limit, @Query("order") String order);
+
+ @GET("document/{document_id}")
+ Single retrieveDocument(@Path("document_id") String documentId);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/tools/ToolsApi.java b/core/src/main/java/ai/z/openapi/api/tools/ToolsApi.java
new file mode 100644
index 0000000..a2b9683
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/tools/ToolsApi.java
@@ -0,0 +1,21 @@
+package ai.z.openapi.api.tools;
+
+import ai.z.openapi.service.tools.WebSearchParamsRequest;
+import ai.z.openapi.service.tools.WebSearchPro;
+import io.reactivex.Single;
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.POST;
+import retrofit2.http.Streaming;
+
+public interface ToolsApi {
+
+ @Streaming
+ @POST("tools")
+ Call webSearchStreaming(@Body WebSearchParamsRequest request);
+
+ @POST("tools")
+ Single webSearch(@Body WebSearchParamsRequest request);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/videos/VideosApi.java b/core/src/main/java/ai/z/openapi/api/videos/VideosApi.java
new file mode 100644
index 0000000..3327a2f
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/videos/VideosApi.java
@@ -0,0 +1,19 @@
+package ai.z.openapi.api.videos;
+
+import ai.z.openapi.service.videos.VideoCreateParams;
+import ai.z.openapi.service.videos.VideoObject;
+import io.reactivex.Single;
+import retrofit2.http.Body;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+
+public interface VideosApi {
+
+ @POST("videos/generations")
+ Single videoGenerations(@Body VideoCreateParams request);
+
+ @GET("async-result/{id}")
+ Single videoGenerationsResult(@Path("id") String id);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/api/web_search/WebSearchApi.java b/core/src/main/java/ai/z/openapi/api/web_search/WebSearchApi.java
new file mode 100644
index 0000000..f1728ea
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/api/web_search/WebSearchApi.java
@@ -0,0 +1,14 @@
+package ai.z.openapi.api.web_search;
+
+import ai.z.openapi.service.web_search.WebSearchDTO;
+import ai.z.openapi.service.web_search.WebSearchRequest;
+import io.reactivex.Single;
+import retrofit2.http.Body;
+import retrofit2.http.POST;
+
+public interface WebSearchApi {
+
+ @POST("web_search")
+ Single webSearch(@Body WebSearchRequest request);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/Constants.java b/core/src/main/java/ai/z/openapi/core/Constants.java
new file mode 100644
index 0000000..e9a29f4
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/Constants.java
@@ -0,0 +1,155 @@
+package ai.z.openapi.core;
+
+/**
+ * Constants class containing all the configuration values and model identifiers used
+ * throughout the Z.AI OpenAPI SDK.
+ *
+ * This class provides centralized access to: - API base URLs - Model identifiers for
+ * different AI capabilities - Invocation method constants
+ *
+ * @author Z.AI SDK Team
+ * @since 1.0.0
+ */
+public final class Constants {
+
+ // Private constructor to prevent instantiation
+ private Constants() {
+ throw new UnsupportedOperationException("Constants class cannot be instantiated");
+ }
+
+ // =============================================================================
+ // API Configuration
+ // =============================================================================
+
+ /**
+ * Base URL for the ZHIPU AI OpenAPI service. All API requests will be made to
+ * endpoints under this base URL.
+ */
+ public static final String ZHIPU_AI_BASE_URL = "https://open.bigmodel.cn/api/paas/v4/";
+
+ /**
+ * Base URL for the Z.AI OpenAPI service. All API requests will be made to endpoints
+ * under this base URL.
+ */
+ public static final String Z_AI_BASE_URL = "https://api.z.ai/api/paas/v4/";
+
+ // =============================================================================
+ // Text Generation Models
+ // =============================================================================
+
+ /**
+ * GLM-4 Plus model - Enhanced version with improved capabilities.
+ */
+ public static final String ModelChatGLM4Plus = "glm-4-plus";
+
+ /**
+ * GLM-4 Air model - Lightweight version optimized for speed.
+ */
+ public static final String ModelChatGLM4Air = "glm-4-air";
+
+ /**
+ * GLM-4 Flash model - Ultra-fast response model.
+ */
+ public static final String ModelChatGLM4Flash = "glm-4-flash";
+
+ /**
+ * GLM-4 standard model - Balanced performance and capability.
+ */
+ public static final String ModelChatGLM4 = "glm-4";
+
+ /**
+ * GLM-4 model version 0520 - Specific version release.
+ */
+ public static final String ModelChatGLM40520 = "glm-4-0520";
+
+ /**
+ * GLM-4 AirX model - Extended Air model with additional features.
+ */
+ public static final String ModelChatGLM4Airx = "glm-4-airx";
+
+ /**
+ * GLM-4 Long model - Optimized for long-context conversations.
+ */
+ public static final String ModelChatGLMLong = "glm-4-long";
+
+ /**
+ * GLM-4 Voice model - Specialized for voice-related tasks.
+ */
+ public static final String ModelChatGLM4Voice = "glm-4-voice";
+
+ // =============================================================================
+ // Vision Models (Image Understanding)
+ // =============================================================================
+
+ /**
+ * GLM-4V Plus model - Enhanced vision model for image understanding.
+ */
+ public static final String ModelChatGLM4VPlus = "glm-4v-plus";
+
+ /**
+ * GLM-4V standard model - Standard vision model for image analysis.
+ */
+ public static final String ModelChatGLM4V = "glm-4v";
+
+ // =============================================================================
+ // Image Generation Models
+ // =============================================================================
+
+ /**
+ * CogView-3 Plus model - Enhanced image generation capabilities.
+ */
+ public static final String ModelCogView3Plus = "cogview-3-plus";
+
+ /**
+ * CogView-3 standard model - Standard image generation model.
+ */
+ public static final String ModelCogView = "cogview-3";
+
+ // =============================================================================
+ // Embedding Models
+ // =============================================================================
+
+ /**
+ * Embedding model version 2 - Text embedding generation.
+ */
+ public static final String ModelEmbedding2 = "embedding-2";
+
+ /**
+ * Embedding model version 3 - Latest text embedding generation.
+ */
+ public static final String ModelEmbedding3 = "embedding-3";
+
+ // =============================================================================
+ // Specialized Models
+ // =============================================================================
+
+ /**
+ * CharGLM-3 model - Anthropomorphic character interaction model.
+ */
+ public static final String ModelCharGLM3 = "charglm-3";
+
+ /**
+ * CogTTS model - Text-to-Speech synthesis model.
+ */
+ public static final String ModelTTS = "cogtts";
+
+ // =============================================================================
+ // API Invocation Methods
+ // =============================================================================
+
+ /**
+ * Asynchronous invocation method - For non-blocking API calls.
+ */
+ public static final String INVOKE_METHOD_ASYNC = "async-invoke";
+
+ /**
+ * Server-Sent Events invocation method - For streaming responses.
+ */
+ public static final String INVOKE_METHOD_SSE = "sse-invoke";
+
+ /**
+ * Standard synchronous invocation method - For blocking API calls.
+ */
+ public static final String INVOKE_METHOD = "invoke";
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/cache/ICache.java b/core/src/main/java/ai/z/openapi/core/cache/ICache.java
new file mode 100644
index 0000000..c130839
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/cache/ICache.java
@@ -0,0 +1,27 @@
+package ai.z.openapi.core.cache;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Token cache interface with default LocalCache implementation. Can be replaced with
+ * distributed cache (e.g., Redis) as needed.
+ */
+public interface ICache {
+
+ /**
+ * Retrieves cached value by key.
+ * @param key the cache key
+ * @return cached value or empty string if not found or expired
+ */
+ String get(String key);
+
+ /**
+ * Sets cache value with expiration time.
+ * @param key the cache key
+ * @param value the value to cache
+ * @param expire expiration duration
+ * @param timeUnit time unit for expiration
+ */
+ void set(String key, String value, int expire, TimeUnit timeUnit);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/cache/LocalCache.java b/core/src/main/java/ai/z/openapi/core/cache/LocalCache.java
new file mode 100644
index 0000000..ea73457
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/cache/LocalCache.java
@@ -0,0 +1,81 @@
+package ai.z.openapi.core.cache;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Thread-safe local cache implementation using ConcurrentHashMap. Provides basic caching
+ * functionality with expiration support.
+ */
+public class LocalCache implements ICache {
+
+ private static final Logger log = LoggerFactory.getLogger(LocalCache.class);
+
+ private static final ConcurrentMap CACHE = new ConcurrentHashMap<>(8);
+
+ /**
+ * Private constructor to prevent direct instantiation.
+ */
+ private LocalCache() {
+ }
+
+ /**
+ * Gets singleton instance of LocalCache.
+ * @return LocalCache instance
+ */
+ public static LocalCache getInstance() {
+ return Inner.LOCAL_CACHE;
+ }
+
+ @Override
+ public String get(String key) {
+ Value v = LocalCache.CACHE.get(key);
+ if (v == null || new Date().after(v.end)) {
+ return "";
+ }
+
+ log.debug("Retrieved key: {}, time left: {}s", key, (v.end.getTime() - new Date().getTime()) / 1000);
+ return v.value;
+ }
+
+ @Override
+ public void set(String key, String value, int expire, TimeUnit timeUnit) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.add(Calendar.SECOND, (int) timeUnit.toSeconds(expire));
+ Value v = new Value(value, calendar.getTime());
+ log.debug("Cached key: {}, expire time: {}", key, calendar.getTime());
+ LocalCache.CACHE.put(key, v);
+ }
+
+ /**
+ * Internal value wrapper with expiration time.
+ */
+ private static class Value {
+
+ final String value;
+
+ final Date end;
+
+ public Value(String value, Date time) {
+ this.value = value;
+ this.end = time;
+ }
+
+ }
+
+ /**
+ * Singleton holder pattern for thread-safe lazy initialization.
+ */
+ private static class Inner {
+
+ private static final LocalCache LOCAL_CACHE = new LocalCache();
+
+ }
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/config/ZaiConfig.java b/core/src/main/java/ai/z/openapi/core/config/ZaiConfig.java
new file mode 100644
index 0000000..8405bf9
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/config/ZaiConfig.java
@@ -0,0 +1,400 @@
+package ai.z.openapi.core.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.concurrent.TimeUnit;
+
+import static ai.z.openapi.core.Constants.Z_AI_BASE_URL;
+
+/**
+ * Configuration class for ZAI SDK containing API credentials, JWT settings, HTTP client
+ * configurations, and cache settings. Supports reading configuration values from
+ * environment variables with memory values taking priority.
+ */
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ZaiConfig {
+
+ // Environment variable names
+ private static final String ENV_BASE_URL = "ZAI_BASE_URL";
+
+ private static final String ENV_API_KEY = "ZAI_API_KEY";
+
+ private static final String ENV_EXPIRE_MILLIS = "ZAI_EXPIRE_MILLIS";
+
+ private static final String ENV_ALG = "ZAI_ALG";
+
+ private static final String ENV_DISABLE_TOKEN_CACHE = "ZAI_DISABLE_TOKEN_CACHE";
+
+ private static final String ENV_CONNECTION_POOL_MAX_IDLE = "ZAI_CONNECTION_POOL_MAX_IDLE";
+
+ private static final String ENV_CONNECTION_POOL_KEEP_ALIVE = "ZAI_CONNECTION_POOL_KEEP_ALIVE";
+
+ private static final String ENV_REQUEST_TIMEOUT = "ZAI_REQUEST_TIMEOUT";
+
+ private static final String ENV_CONNECT_TIMEOUT = "ZAI_CONNECT_TIMEOUT";
+
+ private static final String ENV_READ_TIMEOUT = "ZAI_READ_TIMEOUT";
+
+ private static final String ENV_WRITE_TIMEOUT = "ZAI_WRITE_TIMEOUT";
+
+ /**
+ * Base URL for API endpoints.
+ */
+ private String baseUrl;
+
+ /**
+ * Combined API secret key in format: {apiId}.{apiSecret}
+ */
+ private String apiKey;
+
+ /**
+ * API id component.
+ */
+ private String apiId;
+
+ /**
+ * API secret component.
+ */
+ private String apiSecret;
+
+ /**
+ * JWT token expiration time in milliseconds (default: 30 minutes).
+ */
+ private int expireMillis = 30 * 60 * 1000;
+
+ /**
+ * JWT encryption algorithm (default: HS256).
+ */
+ private String alg = "HS256";
+
+ /**
+ * Flag to disable token caching.
+ */
+ private boolean disableTokenCache;
+
+ /**
+ * Maximum number of idle connections in the connection pool.
+ */
+ private int connectionPoolMaxIdleConnections = 5;
+
+ /**
+ * Keep alive duration for connections in the pool (in seconds).
+ */
+ private long connectionPoolKeepAliveDuration = 1;
+
+ /**
+ * Time unit for connection pool keep alive duration.
+ */
+ private TimeUnit connectionPoolTimeUnit = TimeUnit.SECONDS;
+
+ /**
+ * Request timeout in specified time unit.
+ */
+ private int requestTimeOut = 300;
+
+ /**
+ * Connection timeout in specified time unit.
+ */
+ private int connectTimeout = 100;
+
+ /**
+ * Read timeout in specified time unit.
+ */
+ private int readTimeout = 100;
+
+ /**
+ * Write timeout in specified time unit.
+ */
+ private int writeTimeout = 100;
+
+ /**
+ * Time unit for timeout configurations.
+ */
+ private TimeUnit timeOutTimeUnit = TimeUnit.SECONDS;
+
+ /**
+ * Source channel identifier for request tracking.
+ */
+ private String source_channel = "java-sdk";
+
+ /**
+ * Constructor with combined API secret key.
+ * @param apiKey combined secret key in format {apiKey}.{apiSecret}
+ * @throws RuntimeException if apiSecretKey format is invalid
+ */
+ public ZaiConfig(String apiKey) {
+ this.apiKey = apiKey;
+ String[] arrStr = apiKey.split("\\.");
+ if (arrStr.length != 2) {
+ throw new RuntimeException("invalid apiSecretKey");
+ }
+ this.apiId = arrStr[0];
+ this.apiSecret = arrStr[1];
+ }
+
+ /**
+ * Sets API key and parses it into id and secret components.
+ * @param apiKey combined secret key in format {apiId}.{apiSecret}
+ * @throws RuntimeException if apiSecretKey format is invalid
+ */
+ public void setApiKey(String apiKey) {
+ this.apiKey = apiKey;
+ String[] arrStr = apiKey.split("\\.");
+ if (arrStr.length != 2) {
+ throw new RuntimeException("invalid api Key");
+ }
+ this.apiId = arrStr[0];
+ this.apiSecret = arrStr[1];
+ }
+
+ /**
+ * Gets base URL with system property and environment variable fallback.
+ */
+ public String getBaseUrl() {
+ if (baseUrl != null) {
+ return baseUrl;
+ }
+ String propValue = System.getProperty(ENV_BASE_URL);
+ propValue = propValue != null ? propValue : System.getenv(ENV_BASE_URL);
+ return propValue != null ? propValue : Z_AI_BASE_URL;
+ }
+
+ /**
+ * Gets API secret key with system property and environment variable fallback.
+ */
+ public String getApiKey() {
+ if (apiKey != null && !apiKey.isEmpty()) {
+ return apiKey;
+ }
+ String propValue = System.getProperty(ENV_API_KEY);
+ String value = propValue != null ? propValue : System.getenv(ENV_API_KEY);
+ if (value != null && !value.isEmpty()) {
+ // Parse value and set components
+ this.apiKey = value;
+ String[] arrStr = value.split("\\.");
+ if (arrStr.length == 2) {
+ this.apiId = arrStr[0];
+ this.apiSecret = arrStr[1];
+ }
+ return value;
+ }
+ return apiKey;
+ }
+
+ /**
+ * Gets API key with system property and environment variable fallback.
+ */
+ public String getApiId() {
+ if (apiId != null && !apiId.isEmpty()) {
+ return apiId;
+ }
+ getApiKey();
+ return apiId;
+ }
+
+ /**
+ * Gets API secret with system property and environment variable fallback.
+ */
+ public String getApiSecret() {
+ if (apiSecret != null && !apiSecret.isEmpty()) {
+ return apiSecret;
+ }
+ getApiKey();
+ return apiSecret;
+ }
+
+ /**
+ * Gets expire millis with system property and environment variable fallback.
+ */
+ public int getExpireMillis() {
+ if (expireMillis != 30 * 60 * 1000) { // If not default value
+ return expireMillis;
+ }
+ String propValue = System.getProperty(ENV_EXPIRE_MILLIS);
+ String value = propValue != null ? propValue : System.getenv(ENV_EXPIRE_MILLIS);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ }
+ catch (NumberFormatException e) {
+ // Return default value if parsing fails
+ }
+ }
+ return expireMillis;
+ }
+
+ /**
+ * Gets algorithm with system property and environment variable fallback.
+ */
+ public String getAlg() {
+ if (!"HS256".equals(alg)) {
+ return alg;
+ }
+ String propValue = System.getProperty(ENV_ALG);
+ String value = propValue != null ? propValue : System.getenv(ENV_ALG);
+ return value != null ? value : alg;
+ }
+
+ /**
+ * Gets disable token cache flag with system property and environment variable
+ * fallback.
+ */
+ public boolean isDisableTokenCache() {
+ if (disableTokenCache) {
+ return true;
+ }
+ String propValue = System.getProperty(ENV_DISABLE_TOKEN_CACHE);
+ String value = propValue != null ? propValue : System.getenv(ENV_DISABLE_TOKEN_CACHE);
+ return Boolean.parseBoolean(value);
+ }
+
+ /**
+ * Gets connection pool max idle connections with system property and environment
+ * variable fallback.
+ */
+ public int getConnectionPoolMaxIdleConnections() {
+ if (connectionPoolMaxIdleConnections != 5) { // If not default value
+ return connectionPoolMaxIdleConnections;
+ }
+ String propValue = System.getProperty(ENV_CONNECTION_POOL_MAX_IDLE);
+ String value = propValue != null ? propValue : System.getenv(ENV_CONNECTION_POOL_MAX_IDLE);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ }
+ catch (NumberFormatException e) {
+ // Return default value if parsing fails
+ }
+ }
+ return connectionPoolMaxIdleConnections;
+ }
+
+ /**
+ * Gets connection pool keep alive duration with system property and environment
+ * variable fallback.
+ */
+ public long getConnectionPoolKeepAliveDuration() {
+ if (connectionPoolKeepAliveDuration != 1) { // If not default value
+ return connectionPoolKeepAliveDuration;
+ }
+ String propValue = System.getProperty(ENV_CONNECTION_POOL_KEEP_ALIVE);
+ String value = propValue != null ? propValue : System.getenv(ENV_CONNECTION_POOL_KEEP_ALIVE);
+ if (value != null) {
+ try {
+ return Long.parseLong(value);
+ }
+ catch (NumberFormatException e) {
+ // Return default value if parsing fails
+ }
+ }
+ return connectionPoolKeepAliveDuration;
+ }
+
+ /**
+ * Gets connection pool time unit (always returns the set value or default).
+ */
+ public TimeUnit getConnectionPoolTimeUnit() {
+ return connectionPoolTimeUnit != null ? connectionPoolTimeUnit : TimeUnit.SECONDS;
+ }
+
+ /**
+ * Gets request timeout with system property and environment variable fallback.
+ */
+ public int getRequestTimeOut() {
+ if (requestTimeOut != 300) {
+ return requestTimeOut;
+ }
+ String propValue = System.getProperty(ENV_REQUEST_TIMEOUT);
+ String value = propValue != null ? propValue : System.getenv(ENV_REQUEST_TIMEOUT);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ }
+ catch (NumberFormatException e) {
+ // Return default value if parsing fails
+ }
+ }
+ return requestTimeOut;
+ }
+
+ /**
+ * Gets connect timeout with system property and environment variable fallback.
+ */
+ public int getConnectTimeout() {
+ if (connectTimeout != 100) {
+ return connectTimeout;
+ }
+ String propValue = System.getProperty(ENV_CONNECT_TIMEOUT);
+ String value = propValue != null ? propValue : System.getenv(ENV_CONNECT_TIMEOUT);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ }
+ catch (NumberFormatException e) {
+ // Return default value if parsing fails
+ }
+ }
+ return connectTimeout;
+ }
+
+ /**
+ * Gets read timeout with system property and environment variable fallback.
+ */
+ public int getReadTimeout() {
+ if (readTimeout != 100) {
+ return readTimeout;
+ }
+ String propValue = System.getProperty(ENV_READ_TIMEOUT);
+ String value = propValue != null ? propValue : System.getenv(ENV_READ_TIMEOUT);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ }
+ catch (NumberFormatException e) {
+ // Return default value if parsing fails
+ }
+ }
+ return readTimeout;
+ }
+
+ /**
+ * Gets write timeout with system property and environment variable fallback.
+ */
+ public int getWriteTimeout() {
+ if (writeTimeout != 100) {
+ return writeTimeout;
+ }
+ String propValue = System.getProperty(ENV_WRITE_TIMEOUT);
+ String value = propValue != null ? propValue : System.getenv(ENV_WRITE_TIMEOUT);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ }
+ catch (NumberFormatException e) {
+ // Return default value if parsing fails
+ }
+ }
+ return writeTimeout;
+ }
+
+ /**
+ * Gets timeout time unit (always returns the set value or null).
+ */
+ public TimeUnit getTimeOutTimeUnit() {
+ return timeOutTimeUnit;
+ }
+
+ /**
+ * Gets source channel with system property.
+ */
+ public String getSource_channel() {
+ return source_channel;
+ }
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/model/ClientRequest.java b/core/src/main/java/ai/z/openapi/core/model/ClientRequest.java
new file mode 100644
index 0000000..601f8d0
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/model/ClientRequest.java
@@ -0,0 +1,5 @@
+package ai.z.openapi.core.model;
+
+public interface ClientRequest {
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/model/ClientResponse.java b/core/src/main/java/ai/z/openapi/core/model/ClientResponse.java
new file mode 100644
index 0000000..8b0ef37
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/model/ClientResponse.java
@@ -0,0 +1,19 @@
+package ai.z.openapi.core.model;
+
+import ai.z.openapi.service.model.ChatError;
+
+public interface ClientResponse {
+
+ T getData();
+
+ void setData(T data);
+
+ void setCode(int code);
+
+ void setMsg(String msg);
+
+ void setSuccess(boolean b);
+
+ void setError(ChatError chatError);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/model/FlowableClientResponse.java b/core/src/main/java/ai/z/openapi/core/model/FlowableClientResponse.java
new file mode 100644
index 0000000..d995477
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/model/FlowableClientResponse.java
@@ -0,0 +1,19 @@
+package ai.z.openapi.core.model;
+
+import io.reactivex.Flowable;
+
+/**
+ * Client response interface with reactive stream support. Extends ClientResponse to
+ * provide Flowable stream functionality.
+ *
+ * @param response data type
+ */
+public interface FlowableClientResponse extends ClientResponse {
+
+ /**
+ * Sets the reactive stream for this response.
+ * @param stream Flowable stream containing response data
+ */
+ void setFlowable(Flowable stream);
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/response/HttpxBinaryResponseContent.java b/core/src/main/java/ai/z/openapi/core/response/HttpxBinaryResponseContent.java
new file mode 100644
index 0000000..e2e3537
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/response/HttpxBinaryResponseContent.java
@@ -0,0 +1,156 @@
+package ai.z.openapi.core.response;
+
+import java.io.Closeable;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Objects;
+import okhttp3.ResponseBody;
+import okio.BufferedSource;
+
+public class HttpxBinaryResponseContent implements Closeable {
+
+ private final retrofit2.Response response;
+
+ public HttpxBinaryResponseContent(retrofit2.Response response) {
+ if (response == null || response.body() == null) {
+ throw new IllegalArgumentException("Response or ResponseBody cannot be null");
+ }
+ this.response = response;
+ }
+
+ public byte[] getContent() throws IOException {
+ if (response.body() == null) {
+ throw new IOException("ResponseBody is null");
+ }
+ try (BufferedSource source = response.body().source()) {
+ return source.readByteArray();
+ }
+ }
+
+ public String getText() throws IOException {
+ if (response.body() == null) {
+ throw new IOException("ResponseBody is null");
+ }
+ try (BufferedSource source = response.body().source()) {
+ return source.readUtf8();
+ }
+ }
+
+ public String getEncoding() {
+ return response.body() != null && response.body().contentType() != null
+ ? Objects.requireNonNull(Objects.requireNonNull(response.body().contentType()).charset()).toString()
+ : null;
+ }
+
+ public Iterator iterBytes(int chunkSize) throws IOException {
+ if (response.body() == null) {
+ throw new IOException("ResponseBody is null");
+ }
+ return new Iterator() {
+ final BufferedSource source = response.body().source();
+
+ final byte[] buffer = new byte[chunkSize];
+
+ boolean hasMore = true;
+
+ @Override
+ public boolean hasNext() {
+ try {
+ if (source.exhausted()) {
+ hasMore = false;
+ }
+ }
+ catch (IOException e) {
+ hasMore = false;
+ }
+ return hasMore;
+ }
+
+ @Override
+ public byte[] next() {
+ try {
+ source.read(buffer);
+ }
+ catch (IOException e) {
+ // Handle the exception
+ }
+ return buffer;
+ }
+ };
+ }
+
+ public Iterator iterText(int chunkSize) throws IOException {
+ if (response.body() == null) {
+ throw new IOException("ResponseBody is null");
+ }
+ return new Iterator() {
+ final BufferedSource source = response.body().source();
+
+ final byte[] buffer = new byte[chunkSize];
+
+ boolean hasMore = true;
+
+ @Override
+ public boolean hasNext() {
+ try {
+ if (source.exhausted()) {
+ hasMore = false;
+ }
+ }
+ catch (IOException e) {
+ hasMore = false;
+ }
+ return hasMore;
+ }
+
+ @Override
+ public String next() {
+ try {
+ int bytesRead = source.read(buffer);
+ return new String(buffer, 0, bytesRead);
+ }
+ catch (IOException e) {
+ // Handle the exception
+ }
+ return "";
+ }
+ };
+ }
+
+ public void writeToFile(String file) throws IOException {
+ if (response.body() == null) {
+ throw new IOException("ResponseBody is null");
+ }
+
+ try (BufferedSource source = response.body().source(); FileOutputStream fos = new FileOutputStream(file)) {
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = source.read(buffer)) != -1) {
+ fos.write(buffer, 0, bytesRead);
+ }
+ }
+ }
+
+ public void streamToFile(String file, int chunkSize) throws IOException {
+ if (response.body() == null) {
+ throw new IOException("ResponseBody is null");
+ }
+
+ try (BufferedSource source = response.body().source(); FileOutputStream fos = new FileOutputStream(file)) {
+ byte[] buffer = new byte[chunkSize];
+ int bytesRead;
+ while ((bytesRead = source.read(buffer)) != -1) {
+ fos.write(buffer, 0, bytesRead);
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (response.body() != null) {
+ response.body().close();
+ }
+ }
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/token/AuthenticationInterceptor.java b/core/src/main/java/ai/z/openapi/core/token/AuthenticationInterceptor.java
new file mode 100644
index 0000000..322da34
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/token/AuthenticationInterceptor.java
@@ -0,0 +1,47 @@
+package ai.z.openapi.core.token;
+
+import ai.z.openapi.core.config.ZaiConfig;
+import ai.z.openapi.utils.StringUtils;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * OkHttp Interceptor that adds an authorization token header
+ */
+public class AuthenticationInterceptor implements Interceptor {
+
+ private final ZaiConfig config;
+
+ public AuthenticationInterceptor(ZaiConfig config) {
+ Objects.requireNonNull(config.getApiKey(), "Z.ai token required");
+ this.config = config;
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ String accessToken;
+ if (this.config.isDisableTokenCache()) {
+ accessToken = this.config.getApiKey();
+ }
+ else {
+ TokenManager tokenManager = GlobalTokenManager.getTokenManagerV4();
+ accessToken = tokenManager.getToken(this.config);
+ }
+ String source_channel = "java-sdk";
+ if (StringUtils.isNotEmpty(config.getSource_channel())) {
+ source_channel = config.getSource_channel();
+ }
+ Request request = chain.request()
+ .newBuilder()
+ .header("Authorization", "Bearer " + accessToken)
+ .header("x-source-channel", source_channel)
+ .header("Accept-Language", "en-US,en")
+ .build();
+ return chain.proceed(request);
+ }
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/token/GlobalTokenManager.java b/core/src/main/java/ai/z/openapi/core/token/GlobalTokenManager.java
new file mode 100644
index 0000000..05a6b63
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/token/GlobalTokenManager.java
@@ -0,0 +1,32 @@
+package ai.z.openapi.core.token;
+
+import ai.z.openapi.core.cache.LocalCache;
+
+/**
+ * Global token manager providing singleton access to TokenManager instance. Uses
+ * LocalCache as default cache implementation.
+ */
+public class GlobalTokenManager {
+
+ /**
+ * Global TokenManager instance with LocalCache.
+ */
+ private static volatile TokenManager globalTokenManager = new TokenManager(LocalCache.getInstance());
+
+ /**
+ * Gets the global TokenManager instance.
+ * @return TokenManager instance
+ */
+ public static TokenManager getTokenManagerV4() {
+ return globalTokenManager;
+ }
+
+ /**
+ * Sets custom TokenManager implementation.
+ * @param tokenManager custom TokenManager instance
+ */
+ public static void setTokenManager(TokenManager tokenManager) {
+ globalTokenManager = tokenManager;
+ }
+
+}
diff --git a/core/src/main/java/ai/z/openapi/core/token/TokenManager.java b/core/src/main/java/ai/z/openapi/core/token/TokenManager.java
new file mode 100644
index 0000000..33a24f9
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/core/token/TokenManager.java
@@ -0,0 +1,100 @@
+package ai.z.openapi.core.token;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import ai.z.openapi.core.config.ZaiConfig;
+import ai.z.openapi.core.cache.ICache;
+import ai.z.openapi.utils.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * JWT token manager for handling token generation and caching.
+ */
+public class TokenManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(TokenManager.class);
+
+ private final ICache cache;
+
+ private static final String TOKEN_KEY_PREFIX = "zai_oapi_token";
+
+ /**
+ * Additional delay time (5 minutes) to prevent token expiration issues.
+ */
+ private static final Long DELAY_EXPIRE_TIME = 5 * 60 * 1000L;
+
+ /**
+ * Constructs TokenManager with specified cache implementation.
+ * @param cache cache implementation for token storage
+ */
+ public TokenManager(ICache cache) {
+ this.cache = cache;
+ }
+
+ /**
+ * Gets valid JWT token, either from cache or generates new one.
+ * @param config ZAI configuration containing API credentials
+ * @return valid JWT token
+ */
+ public String getToken(ZaiConfig config) {
+ String tokenCacheKey = genTokenCacheKey(config.getApiId());
+ String cacheToken = cache.get(tokenCacheKey);
+ if (StringUtils.isNotEmpty(cacheToken)) {
+ return cacheToken;
+ }
+ String newToken = createJwt(config);
+ cache.set(tokenCacheKey, newToken, config.getExpireMillis(), TimeUnit.MILLISECONDS);
+ return newToken;
+ }
+
+ /**
+ * Creates JWT token using HMAC256 algorithm.
+ * @param config ZAI configuration
+ * @return JWT token string or null if creation fails
+ */
+ private static String createJwt(ZaiConfig config) {
+ Algorithm alg;
+ String algId = config.getAlg();
+ if ("HS256".equals(algId)) {
+ try {
+ alg = Algorithm.HMAC256(config.getApiSecret().getBytes(StandardCharsets.UTF_8));
+ }
+ catch (Exception e) {
+ logger.error("Failed to create HMAC256 algorithm", e);
+ return null;
+ }
+ }
+ else {
+ // Currently only HS256 is supported
+ logger.error("Algorithm: {} not supported", algId);
+ return null;
+ }
+
+ Map payload = new HashMap<>();
+ // here the api_key is the apiId
+ payload.put("api_key", config.getApiId());
+ payload.put("exp", System.currentTimeMillis() + config.getExpireMillis() + DELAY_EXPIRE_TIME);
+ payload.put("timestamp", Calendar.getInstance().getTimeInMillis());
+ Map headerClaims = new HashMap<>();
+ headerClaims.put("alg", "HS256");
+ headerClaims.put("sign_type", "SIGN");
+ return JWT.create().withPayload(payload).withHeader(headerClaims).sign(alg);
+ }
+
+ /**
+ * Generates cache key for token storage.
+ * @param apiKey API key
+ * @return formatted cache key
+ */
+ private String genTokenCacheKey(String apiKey) {
+ return String.format("%s-%s", TOKEN_KEY_PREFIX, apiKey);
+ }
+
+}
diff --git a/core/src/main/java/ai/z/openapi/service/AbstractClientBaseService.java b/core/src/main/java/ai/z/openapi/service/AbstractClientBaseService.java
new file mode 100644
index 0000000..27c6864
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/service/AbstractClientBaseService.java
@@ -0,0 +1,147 @@
+package ai.z.openapi.service;
+
+import ai.z.openapi.core.model.ClientRequest;
+import ai.z.openapi.core.model.ClientResponse;
+import ai.z.openapi.core.model.FlowableClientResponse;
+import ai.z.openapi.service.deserialize.MessageDeserializeFactory;
+import ai.z.openapi.service.model.ResponseBodyCallback;
+import ai.z.openapi.service.model.SSE;
+import ai.z.openapi.service.model.ZAiError;
+import ai.z.openapi.service.model.ZAiHttpException;
+import ai.z.openapi.utils.FlowableRequestSupplier;
+import ai.z.openapi.utils.RequestSupplier;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+import okhttp3.ResponseBody;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import retrofit2.Call;
+import retrofit2.HttpException;
+import retrofit2.Response;
+
+import java.io.IOException;
+
+/**
+ * Abstract base service class that provides common functionality for API client
+ * implementations. This class handles request execution, response processing, and error
+ * handling for both synchronous and streaming API calls.
+ */
+public abstract class AbstractClientBaseService {
+
+ protected final static Logger logger = LoggerFactory.getLogger(AbstractClientBaseService.class);
+
+ protected static final ObjectMapper mapper = MessageDeserializeFactory.defaultObjectMapper();
+
+ /**
+ * Executes a synchronous API request.
+ * @param the type of data returned by the API
+ * @param the type of parameters sent to the API
+ * @param the type of the request object
+ * @param the type of the response object
+ * @param request the request object containing parameters
+ * @param requestSupplier the supplier that creates the API call
+ * @param tRespClass the class of the response type
+ * @return the response object containing the API result
+ */
+ public abstract , TResp extends ClientResponse> TResp executeRequest(
+ TReq request, RequestSupplier requestSupplier, Class tRespClass);
+
+ /**
+ * Executes a streaming API request that returns a continuous stream of data.
+ * @param the type of data returned by the API stream
+ * @param the type of parameters sent to the API
+ * @param the type of the request object
+ * @param the type of the streaming response object
+ * @param request the request object containing parameters
+ * @param requestSupplier the supplier that creates the streaming API call
+ * @param tRespClass the class of the response type
+ * @param tDataClass the class of the data type in the stream
+ * @return the streaming response object containing the API result stream
+ */
+ public abstract , TResp extends FlowableClientResponse> TResp streamRequest(
+ TReq request, FlowableRequestSupplier> requestSupplier, Class tRespClass,
+ Class tDataClass);
+
+ /**
+ * Executes a Single API call synchronously and handles errors.
+ * @param the type of the response
+ * @param apiCall the Single API call to execute
+ * @return the response from the API call
+ * @throws ZAiHttpException if an HTTP error occurs with a parseable error body
+ * @throws HttpException if an HTTP error occurs that cannot be parsed
+ */
+ public static T execute(Single apiCall) {
+ try {
+ T response = apiCall.blockingGet();
+
+ // Check status code if the response is a Response type object
+ if (response instanceof Response) {
+ handleResponse((Response>) response);
+ }
+
+ return response;
+ }
+ catch (HttpException e) {
+ logger.error("HTTP exception: {}", e.getMessage());
+ try {
+ if (e.response() == null || e.response().errorBody() == null) {
+ throw e;
+ }
+ String errorBody = e.response().errorBody().string();
+
+ ZAiError error = mapper.readValue(errorBody, ZAiError.class);
+
+ throw new ZAiHttpException(error, e, e.code());
+ }
+ catch (IOException ex) {
+ // couldn't parse ZAiError error
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Handles HTTP response and throws exception if not successful.
+ * @param response the HTTP response to handle
+ * @throws HttpException if the response is not successful
+ */
+ private static void handleResponse(Response> response) {
+ if (!response.isSuccessful()) {
+ throw new HttpException(response);
+ }
+ }
+
+ /**
+ * Creates a streaming Flowable that maps SSE data to the specified class type.
+ * @param the type to map the SSE data to
+ * @param apiCall the API call that returns streaming data
+ * @param cl the class to map the SSE data to
+ * @return a Flowable of the specified type
+ */
+ public Flowable stream(retrofit2.Call apiCall, Class cl) {
+ return stream(apiCall).map(sse -> mapper.readValue(sse.getData(), cl));
+ }
+
+ /**
+ * Creates a streaming Flowable of SSE events without emitting done events.
+ * @param apiCall the API call that returns streaming data
+ * @return a Flowable of SSE events
+ */
+ public static Flowable stream(retrofit2.Call apiCall) {
+ return stream(apiCall, false);
+ }
+
+ /**
+ * Creates a streaming Flowable of SSE events with optional done event emission.
+ * @param apiCall the API call that returns streaming data
+ * @param emitDone whether to emit done events
+ * @return a Flowable of SSE events
+ */
+ public static Flowable stream(retrofit2.Call apiCall, boolean emitDone) {
+ return Flowable.create(emitter -> apiCall.enqueue(new ResponseBodyCallback(emitter, emitDone)),
+ BackpressureStrategy.BUFFER);
+ }
+
+}
diff --git a/core/src/main/java/ai/z/openapi/service/CommonRequest.java b/core/src/main/java/ai/z/openapi/service/CommonRequest.java
new file mode 100644
index 0000000..71a703f
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/service/CommonRequest.java
@@ -0,0 +1,38 @@
+package ai.z.openapi.service;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.Map;
+
+/**
+ * Common request base class with extra JSON fields. Other ZAI request classes should
+ * extend this class to inherit common request parameters.
+ */
+
+@NoArgsConstructor
+@AllArgsConstructor
+@SuperBuilder
+@Data
+public class CommonRequest {
+
+ /**
+ * Request ID provided by the client, must be unique. Used to distinguish each
+ * request. If not provided by the client, the platform will generate one by default.
+ */
+ @JsonProperty("request_id")
+ private String requestId;
+
+ /**
+ * A unique identifier representing your end-user, which will help ZAI to monitor and
+ * detect abuse.
+ */
+ @JsonProperty("user_id")
+ private String userId;
+
+ private Map extraJson;
+
+}
diff --git a/core/src/main/java/ai/z/openapi/service/agents/AgentAsyncResultRetrieveParams.java b/core/src/main/java/ai/z/openapi/service/agents/AgentAsyncResultRetrieveParams.java
new file mode 100644
index 0000000..5d80dc5
--- /dev/null
+++ b/core/src/main/java/ai/z/openapi/service/agents/AgentAsyncResultRetrieveParams.java
@@ -0,0 +1,43 @@
+package ai.z.openapi.service.agents;
+
+import ai.z.openapi.core.model.ClientRequest;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parameters for retrieving agent asynchronous task results. This class contains the
+ * necessary parameters to query the result of an agent's async task.
+ */
+@EqualsAndHashCode(callSuper = false)
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class AgentAsyncResultRetrieveParams implements ClientRequest