From 4154493640559ffbcc052ba5ef65347c3ebf0f2c Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 29 Dec 2025 21:44:02 +0800 Subject: [PATCH] message and session use --- packages/sdk-java/pom.xml | 6 + .../com/alibaba/qwen/code/cli/Options.java | 6 + .../com/alibaba/qwen/code/cli/QwenCli.java | 54 ++ .../protocol/data/CLIPermissionDenial.java | 38 ++ .../code/cli/protocol/data/Capabilities.java | 60 ++ .../code/cli/protocol/data/ExtendedUsage.java | 67 ++ .../cli/protocol/data/InitializeConfig.java | 40 ++ .../code/cli/protocol/data/ModelUsage.java | 58 ++ .../qwen/code/cli/protocol/data/Usage.java | 56 ++ .../code/cli/protocol/message/Message.java | 5 + .../cli/protocol/message/MessageBase.java | 22 + .../protocol/message/SDKResultMessage.java | 151 +++++ .../protocol/message/SDKSystemMessage.java | 213 +++++++ .../cli/protocol/message/SDKUserMessage.java | 90 +++ .../assistant/APIAssistantMessage.java | 76 +++ .../assistant/SDKAssistantMessage.java | 49 ++ .../message/assistant/block/Annotation.java | 28 + .../message/assistant/block/ContentBlock.java | 32 + .../message/assistant/block/TextBlock.java | 16 + .../assistant/block/ThinkingBlock.java | 25 + .../assistant/block/ToolResultBlock.java | 40 ++ .../message/assistant/block/ToolUseBlock.java | 49 ++ .../control/CLIControlInitializeRequest.java | 28 + .../control/CLIControlInitializeResponse.java | 24 + .../message/control/CLIControlRequest.java | 44 ++ .../message/control/CLIControlResponse.java | 54 ++ .../qwen/code/cli/protocol/protocol.ts | 594 ++++++++++++++++++ .../qwen/code/cli/session/Session.java | 89 +++ .../session/event/SessionEventConsumers.java | 15 + .../event/SessionEventSimpleConsumers.java | 23 + .../exception/SessionCloseException.java | 22 + .../exception/SessionSendPromptException.java | 22 + .../exception/SessionStartException.java | 22 + .../qwen/code/cli/transport/Transport.java | 18 + .../transport/process/ProcessTransport.java | 16 +- .../process/TransportOptionsAdapter.java | 4 +- .../alibaba/qwen/code/cli/QwenCliTest.java | 23 + .../qwen/code/cli/session/SessionTest.java | 58 ++ .../process/ProcessTransportTest.java | 65 +- 39 files changed, 2296 insertions(+), 6 deletions(-) create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/protocol.ts create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java create mode 100644 packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java create mode 100644 packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 1d1c7c25a..45c9ea895 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -27,6 +27,7 @@ 3.6.0 5.14.1 1.3.16 + 2.0.60 @@ -51,6 +52,11 @@ commons-lang3 3.20.0 + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + org.junit.jupiter junit-jupiter diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java new file mode 100644 index 000000000..82b0a4652 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java @@ -0,0 +1,6 @@ +package com.alibaba.qwen.code.cli; + +import com.alibaba.qwen.code.cli.transport.TransportOptions; + +public class Options extends TransportOptions { +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java new file mode 100644 index 000000000..065ff9e73 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java @@ -0,0 +1,54 @@ +package com.alibaba.qwen.code.cli; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.qwen.code.cli.protocol.message.Message; +import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; +import com.alibaba.qwen.code.cli.session.Session; +import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers; +import com.alibaba.qwen.code.cli.transport.Transport; +import com.alibaba.qwen.code.cli.transport.process.ProcessTransport; + +public class QwenCli { + public static List query(String prompt) { + Transport transport; + try { + transport = new ProcessTransport(); + } catch (Exception e) { + throw new RuntimeException("initialized ProcessTransport error!", e); + } + + Session session; + try { + session = new Session(transport); + } catch (Exception e) { + throw new RuntimeException("initialized Session error!", e); + } + + final List response = new ArrayList<>(); + try { + session.sendPrompt(prompt, new SessionEventSimpleConsumers() { + @Override + public void onSystemMessage(SDKSystemMessage systemMessage) { + response.add(systemMessage); + } + + @Override + public void onAssistantMessage(SDKAssistantMessage assistantMessage) { + response.add(assistantMessage); + } + }); + } catch (Exception e) { + throw new RuntimeException("sendPrompt error!", e); + } + + try { + session.close(); + } catch (Exception e) { + throw new RuntimeException("close Session error!", e); + } + return response; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java new file mode 100644 index 000000000..4abd68bc3 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java @@ -0,0 +1,38 @@ +package com.alibaba.qwen.code.cli.protocol.data; + +import com.alibaba.fastjson2.annotation.JSONField; + +public class CLIPermissionDenial { + @JSONField(name = "tool_name") + private String toolName; + + @JSONField(name = "tool_use_id") + private String toolUseId; + + @JSONField(name = "tool_input") + private Object toolInput; + + public String getToolName() { + return toolName; + } + + public void setToolName(String toolName) { + this.toolName = toolName; + } + + public String getToolUseId() { + return toolUseId; + } + + public void setToolUseId(String toolUseId) { + this.toolUseId = toolUseId; + } + + public Object getToolInput() { + return toolInput; + } + + public void setToolInput(Object toolInput) { + this.toolInput = toolInput; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java new file mode 100644 index 000000000..13200b654 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java @@ -0,0 +1,60 @@ +package com.alibaba.qwen.code.cli.protocol.data; + +import com.alibaba.fastjson2.annotation.JSONField; + +public class Capabilities { + @JSONField(name = "can_handle_can_use_tool") + boolean canHandleCanUseTool; + + @JSONField(name = "can_handle_hook_callback") + boolean canHandleHookCallback; + + @JSONField(name = "can_set_permission_mode") + boolean canSetPermissionMode; + + @JSONField(name = "can_set_model") + boolean canSetModel; + + @JSONField(name = "can_handle_mcp_message") + boolean canHandleMcpMessage; + + public boolean isCanHandleCanUseTool() { + return canHandleCanUseTool; + } + + public void setCanHandleCanUseTool(boolean canHandleCanUseTool) { + this.canHandleCanUseTool = canHandleCanUseTool; + } + + public boolean isCanHandleHookCallback() { + return canHandleHookCallback; + } + + public void setCanHandleHookCallback(boolean canHandleHookCallback) { + this.canHandleHookCallback = canHandleHookCallback; + } + + public boolean isCanSetPermissionMode() { + return canSetPermissionMode; + } + + public void setCanSetPermissionMode(boolean canSetPermissionMode) { + this.canSetPermissionMode = canSetPermissionMode; + } + + public boolean isCanSetModel() { + return canSetModel; + } + + public void setCanSetModel(boolean canSetModel) { + this.canSetModel = canSetModel; + } + + public boolean isCanHandleMcpMessage() { + return canHandleMcpMessage; + } + + public void setCanHandleMcpMessage(boolean canHandleMcpMessage) { + this.canHandleMcpMessage = canHandleMcpMessage; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java new file mode 100644 index 000000000..4965f4b8c --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java @@ -0,0 +1,67 @@ +package com.alibaba.qwen.code.cli.protocol.data; + +import com.alibaba.fastjson2.annotation.JSONField; + +public class ExtendedUsage extends Usage { + @JSONField(name = "server_tool_use") + private ServerToolUse serverToolUse; + + @JSONField(name = "service_tier") + private String serviceTier; + + @JSONField(name = "cache_creation") + private CacheCreation cacheCreation; + + public ServerToolUse getServerToolUse() { + return serverToolUse; + } + + public void setServerToolUse(ServerToolUse serverToolUse) { + this.serverToolUse = serverToolUse; + } + + public String getServiceTier() { + return serviceTier; + } + + public void setServiceTier(String serviceTier) { + this.serviceTier = serviceTier; + } + + public CacheCreation getCacheCreation() { + return cacheCreation; + } + + public void setCacheCreation(CacheCreation cacheCreation) { + this.cacheCreation = cacheCreation; + } + + public static class ServerToolUse { + @JSONField(name = "web_search_requests") + private int webSearchRequests; + } + + public static class CacheCreation { + @JSONField(name = "ephemeral_1h_input_tokens") + private int ephemeral1hInputTokens; + + @JSONField(name = "ephemeral_5m_input_tokens") + private int ephemeral5mInputTokens; + + public int getEphemeral1hInputTokens() { + return ephemeral1hInputTokens; + } + + public void setEphemeral1hInputTokens(int ephemeral1hInputTokens) { + this.ephemeral1hInputTokens = ephemeral1hInputTokens; + } + + public int getEphemeral5mInputTokens() { + return ephemeral5mInputTokens; + } + + public void setEphemeral5mInputTokens(int ephemeral5mInputTokens) { + this.ephemeral5mInputTokens = ephemeral5mInputTokens; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java new file mode 100644 index 000000000..ccafed4f0 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java @@ -0,0 +1,40 @@ +package com.alibaba.qwen.code.cli.protocol.data; + +public class InitializeConfig { + String hooks; + String sdkMcpServers; + String mcpServers; + String agents; + + public String getHooks() { + return hooks; + } + + public void setHooks(String hooks) { + this.hooks = hooks; + } + + public String getSdkMcpServers() { + return sdkMcpServers; + } + + public void setSdkMcpServers(String sdkMcpServers) { + this.sdkMcpServers = sdkMcpServers; + } + + public String getMcpServers() { + return mcpServers; + } + + public void setMcpServers(String mcpServers) { + this.mcpServers = mcpServers; + } + + public String getAgents() { + return agents; + } + + public void setAgents(String agents) { + this.agents = agents; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java new file mode 100644 index 000000000..22787f232 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java @@ -0,0 +1,58 @@ +package com.alibaba.qwen.code.cli.protocol.data; + +public class ModelUsage { + private int inputTokens; + private int outputTokens; + private int cacheReadInputTokens; + private int cacheCreationInputTokens; + private int webSearchRequests; + private int contextWindow; + + public int getInputTokens() { + return inputTokens; + } + + public void setInputTokens(int inputTokens) { + this.inputTokens = inputTokens; + } + + public int getOutputTokens() { + return outputTokens; + } + + public void setOutputTokens(int outputTokens) { + this.outputTokens = outputTokens; + } + + public int getCacheReadInputTokens() { + return cacheReadInputTokens; + } + + public void setCacheReadInputTokens(int cacheReadInputTokens) { + this.cacheReadInputTokens = cacheReadInputTokens; + } + + public int getCacheCreationInputTokens() { + return cacheCreationInputTokens; + } + + public void setCacheCreationInputTokens(int cacheCreationInputTokens) { + this.cacheCreationInputTokens = cacheCreationInputTokens; + } + + public int getWebSearchRequests() { + return webSearchRequests; + } + + public void setWebSearchRequests(int webSearchRequests) { + this.webSearchRequests = webSearchRequests; + } + + public int getContextWindow() { + return contextWindow; + } + + public void setContextWindow(int contextWindow) { + this.contextWindow = contextWindow; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java new file mode 100644 index 000000000..1222b16f2 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java @@ -0,0 +1,56 @@ +package com.alibaba.qwen.code.cli.protocol.data; + +import com.alibaba.fastjson2.annotation.JSONField; + +public class Usage { + @JSONField(name = "input_tokens") + private Integer inputTokens; + @JSONField(name = "output_tokens") + private Integer outputTokens; + @JSONField(name = "cache_creation_input_tokens") + private Integer cacheCreationInputTokens; + @JSONField(name = "cache_read_input_tokens") + private Integer cacheReadInputTokens; + @JSONField(name = "total_tokens") + private Integer totalTokens; + + public Integer getInputTokens() { + return inputTokens; + } + + public void setInputTokens(Integer inputTokens) { + this.inputTokens = inputTokens; + } + + public Integer getOutputTokens() { + return outputTokens; + } + + public void setOutputTokens(Integer outputTokens) { + this.outputTokens = outputTokens; + } + + public Integer getCacheCreationInputTokens() { + return cacheCreationInputTokens; + } + + public void setCacheCreationInputTokens(Integer cacheCreationInputTokens) { + this.cacheCreationInputTokens = cacheCreationInputTokens; + } + + public Integer getCacheReadInputTokens() { + return cacheReadInputTokens; + } + + public void setCacheReadInputTokens(Integer cacheReadInputTokens) { + this.cacheReadInputTokens = cacheReadInputTokens; + } + + public Integer getTotalTokens() { + return totalTokens; + } + + public void setTotalTokens(Integer totalTokens) { + this.totalTokens = totalTokens; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java new file mode 100644 index 000000000..de43924df --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java @@ -0,0 +1,5 @@ +package com.alibaba.qwen.code.cli.protocol.message; + +public interface Message { + String getType(); +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java new file mode 100644 index 000000000..c66df12c4 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java @@ -0,0 +1,22 @@ +package com.alibaba.qwen.code.cli.protocol.message; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.annotation.JSONType; + +@JSONType(alphabetic = false, typeKey = "type", typeName = "MessageBase") +public class MessageBase implements Message{ + protected String type; + + public String toString() { + return JSON.toJSONString(this); + } + + @Override + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java new file mode 100644 index 000000000..dfa2275ff --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java @@ -0,0 +1,151 @@ +package com.alibaba.qwen.code.cli.protocol.message; + +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; +import com.alibaba.qwen.code.cli.protocol.data.CLIPermissionDenial; +import com.alibaba.qwen.code.cli.protocol.data.ExtendedUsage; +import com.alibaba.qwen.code.cli.protocol.data.Usage; + +@JSONType(typeKey = "type", typeName = "result") +public class SDKResultMessage extends MessageBase { + private String subtype; // 'error_max_turns' | 'error_during_execution' + private String uuid; + + @JSONField(name = "session_id") + private String sessionId; + + @JSONField(name = "is_error") + private boolean isError = true; + + @JSONField(name = "duration_ms") + private Long durationMs; + + @JSONField(name = "duration_api_ms") + private Long durationApiMs; + + @JSONField(name = "num_turns") + private Integer numTurns; + private ExtendedUsage usage; + private Map modelUsage; + + @JSONField(name = "permission_denials") + private List permissionDenials; + private Error error; + + public SDKResultMessage() { + super(); + this.type = "result"; + } + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public boolean isError() { + return isError; + } + + public void setError(boolean error) { + isError = error; + } + + public Long getDurationMs() { + return durationMs; + } + + public void setDurationMs(Long durationMs) { + this.durationMs = durationMs; + } + + public Long getDurationApiMs() { + return durationApiMs; + } + + public void setDurationApiMs(Long durationApiMs) { + this.durationApiMs = durationApiMs; + } + + public Integer getNumTurns() { + return numTurns; + } + + public void setNumTurns(Integer numTurns) { + this.numTurns = numTurns; + } + + public ExtendedUsage getUsage() { + return usage; + } + + public void setUsage(ExtendedUsage usage) { + this.usage = usage; + } + + public Map getModelUsage() { + return modelUsage; + } + + public void setModelUsage(Map modelUsage) { + this.modelUsage = modelUsage; + } + + public List getPermissionDenials() { + return permissionDenials; + } + + public void setPermissionDenials(List permissionDenials) { + this.permissionDenials = permissionDenials; + } + + public Error getError() { + return error; + } + + public void setError(Error error) { + this.error = error; + } + + public static class Error { + private String type; + private String message; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java new file mode 100644 index 000000000..22870cb85 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java @@ -0,0 +1,213 @@ +package com.alibaba.qwen.code.cli.protocol.message; + +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; + +@JSONType(typeKey = "type", typeName = "system") +public class SDKSystemMessage extends MessageBase { + private String subtype; + private String uuid; + @JSONField(name = "session_id") + private String sessionId; + private Object data; + private String cwd; + private List tools; + @JSONField(name = "mcp_servers") + private List mcpServers; + private String model; + @JSONField(name = "permission_mode") + private String permissionMode; + @JSONField(name = "slash_commands") + private List slashCommands; + @JSONField(name = "qwen_code_version") + private String qwenCodeVersion; + @JSONField(name = "output_style") + private String outputStyle; + private List agents; + private List skills; + private Map capabilities; + @JSONField(name = "compact_metadata") + private CompactMetadata compactMetadata; + + public SDKSystemMessage() { + super(); + this.type = "system"; + } + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public String getCwd() { + return cwd; + } + + public void setCwd(String cwd) { + this.cwd = cwd; + } + + public List getTools() { + return tools; + } + + public void setTools(List tools) { + this.tools = tools; + } + + public List getMcpServers() { + return mcpServers; + } + + public void setMcpServers(List mcpServers) { + this.mcpServers = mcpServers; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public String getPermissionMode() { + return permissionMode; + } + + public void setPermissionMode(String permissionMode) { + this.permissionMode = permissionMode; + } + + public List getSlashCommands() { + return slashCommands; + } + + public void setSlashCommands(List slashCommands) { + this.slashCommands = slashCommands; + } + + public String getQwenCodeVersion() { + return qwenCodeVersion; + } + + public void setQwenCodeVersion(String qwenCodeVersion) { + this.qwenCodeVersion = qwenCodeVersion; + } + + public String getOutputStyle() { + return outputStyle; + } + + public void setOutputStyle(String outputStyle) { + this.outputStyle = outputStyle; + } + + public List getAgents() { + return agents; + } + + public void setAgents(List agents) { + this.agents = agents; + } + + public List getSkills() { + return skills; + } + + public void setSkills(List skills) { + this.skills = skills; + } + + public Map getCapabilities() { + return capabilities; + } + + public void setCapabilities(Map capabilities) { + this.capabilities = capabilities; + } + + public CompactMetadata getCompactMetadata() { + return compactMetadata; + } + + public void setCompactMetadata(CompactMetadata compactMetadata) { + this.compactMetadata = compactMetadata; + } + + public static class McpServer { + private String name; + private String status; + + // Getters and setters + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + } + + public static class CompactMetadata { + private String trigger; + + @JSONField(name = "pre_tokens") + private Integer preTokens; + + // Getters and setters + public String getTrigger() { + return trigger; + } + + public void setTrigger(String trigger) { + this.trigger = trigger; + } + + public Integer getPreTokens() { + return preTokens; + } + + public void setPreTokens(Integer preTokens) { + this.preTokens = preTokens; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java new file mode 100644 index 000000000..e896b08c4 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java @@ -0,0 +1,90 @@ +package com.alibaba.qwen.code.cli.protocol.message; + +import java.util.Map; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; + +@JSONType(typeKey = "type", typeName = "user") +public class SDKUserMessage extends MessageBase { + private String uuid; + + @JSONField(name = "session_id") + private String sessionId; + private final APIUserMessage message = new APIUserMessage(); + + @JSONField(name = "parent_tool_use_id") + private String parentToolUseId; + private Map options; + + public SDKUserMessage() { + super(); + this.setType("user"); + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getSessionId() { + return sessionId; + } + + public SDKUserMessage setSessionId(String sessionId) { + this.sessionId = sessionId; + return this; + } + + public SDKUserMessage setContent(String content) { + message.setContent(content); + return this; + } + + public String getContent() { + return message.getContent(); + } + + public String getParentToolUseId() { + return parentToolUseId; + } + + public SDKUserMessage setParentToolUseId(String parentToolUseId) { + this.parentToolUseId = parentToolUseId; + return this; + } + + public Map getOptions() { + return options; + } + + public SDKUserMessage setOptions(Map options) { + this.options = options; + return this; + } + + public static class APIUserMessage { + private String role = "user"; + private String content; + + // Getters and Setters + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java new file mode 100644 index 000000000..5a0b3776c --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java @@ -0,0 +1,76 @@ +package com.alibaba.qwen.code.cli.protocol.message.assistant; + +import java.util.List; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.qwen.code.cli.protocol.data.Usage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock; + +public class APIAssistantMessage { + private String id; + private String type = "message"; + private String role = "assistant"; + private String model; + private List content; + + @JSONField(name = "stop_reason") + private String stopReason; + private Usage usage; + + // Getters and setters + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public String getStopReason() { + return stopReason; + } + + public void setStopReason(String stopReason) { + this.stopReason = stopReason; + } + + public Usage getUsage() { + return usage; + } + + public void setUsage(Usage usage) { + this.usage = usage; + } + + public List getContent() { + return content; + } + + public void setContent(List content) { + this.content = content; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java new file mode 100644 index 000000000..7e906fc44 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java @@ -0,0 +1,49 @@ +package com.alibaba.qwen.code.cli.protocol.message.assistant; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; +import com.alibaba.qwen.code.cli.protocol.message.MessageBase; + +@JSONType(typeKey = "type", typeName = "assistant") +public class SDKAssistantMessage extends MessageBase { + private String uuid; + + @JSONField(name = "session_id") + private String sessionId; + private APIAssistantMessage message; + + @JSONField(name = "parent_tool_use_id") + private String parentToolUseId; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public APIAssistantMessage getMessage() { + return message; + } + + public void setMessage(APIAssistantMessage message) { + this.message = message; + } + + public String getParentToolUseId() { + return parentToolUseId; + } + + public void setParentToolUseId(String parentToolUseId) { + this.parentToolUseId = parentToolUseId; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java new file mode 100644 index 000000000..5e8b9a2b5 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java @@ -0,0 +1,28 @@ +package com.alibaba.qwen.code.cli.protocol.message.assistant.block; + +import com.alibaba.fastjson2.annotation.JSONField; + +public class Annotation { + @JSONField(name = "type") + private String type; + + @JSONField(name = "value") + private String value; + + // Getters and setters + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java new file mode 100644 index 000000000..3e72ad7d0 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java @@ -0,0 +1,32 @@ +package com.alibaba.qwen.code.cli.protocol.message.assistant.block; + +import java.util.List; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.annotation.JSONType; + +@JSONType(typeKey = "type", typeName = "ContentBlock", seeAlso = { TextBlock.class, ToolResultBlock.class, ThinkingBlock.class, ToolUseBlock.class }) +public class ContentBlock { + protected String type; + protected List annotations; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getAnnotations() { + return annotations; + } + + public void setAnnotations(List annotations) { + this.annotations = annotations; + } + + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java new file mode 100644 index 000000000..86e5513d3 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java @@ -0,0 +1,16 @@ +package com.alibaba.qwen.code.cli.protocol.message.assistant.block; + +import com.alibaba.fastjson2.annotation.JSONType; + +@JSONType(typeKey = "type", typeName = "text") +public class TextBlock extends ContentBlock { + private String text; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java new file mode 100644 index 000000000..fa479563f --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java @@ -0,0 +1,25 @@ +package com.alibaba.qwen.code.cli.protocol.message.assistant.block; + +import com.alibaba.fastjson2.annotation.JSONType; + +@JSONType(typeKey = "type", typeName = "thinking") +public class ThinkingBlock extends ContentBlock{ + private String thinking; + private String signature; + + public String getThinking() { + return thinking; + } + + public void setThinking(String thinking) { + this.thinking = thinking; + } + + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java new file mode 100644 index 000000000..3d7acfea2 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java @@ -0,0 +1,40 @@ +package com.alibaba.qwen.code.cli.protocol.message.assistant.block; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; + +@JSONType(typeKey = "type", typeName = "tool_result") +public class ToolResultBlock extends ContentBlock { + @JSONField(name = "tool_use_id") + private String toolUseId; + + @JSONField(name = "content") + private Object content; // Can be String or List + + @JSONField(name = "is_error") + private Boolean isError; + + public String getToolUseId() { + return toolUseId; + } + + public void setToolUseId(String toolUseId) { + this.toolUseId = toolUseId; + } + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } + + public Boolean getIsError() { + return isError; + } + + public void setIsError(Boolean isError) { + this.isError = isError; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java new file mode 100644 index 000000000..58a3bd4fc --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java @@ -0,0 +1,49 @@ +package com.alibaba.qwen.code.cli.protocol.message.assistant.block; + +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson2.annotation.JSONType; + +@JSONType(typeKey = "type", typeName = "tool_use") +public class ToolUseBlock extends ContentBlock { + private String id; + private String name; + private Map input; + private List annotations; + + // 构造函数 + public ToolUseBlock() {} + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map getInput() { + return input; + } + + public void setInput(Map input) { + this.input = input; + } + + public List getAnnotations() { + return annotations; + } + + public void setAnnotations(List annotations) { + this.annotations = annotations; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java new file mode 100644 index 000000000..3d217289c --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java @@ -0,0 +1,28 @@ +package com.alibaba.qwen.code.cli.protocol.message.control; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.qwen.code.cli.protocol.data.InitializeConfig; + +public class CLIControlInitializeRequest { + String subtype = "initialize"; + + @JSONField(unwrapped = true) + InitializeConfig initializeConfig = new InitializeConfig(); + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } + + public InitializeConfig getInitializeConfig() { + return initializeConfig; + } + + public CLIControlInitializeRequest setInitializeConfig(InitializeConfig initializeConfig) { + this.initializeConfig = initializeConfig; + return this; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java new file mode 100644 index 000000000..284781a76 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java @@ -0,0 +1,24 @@ +package com.alibaba.qwen.code.cli.protocol.message.control; + +import com.alibaba.qwen.code.cli.protocol.data.Capabilities; + +public class CLIControlInitializeResponse { + String subtype = "initialize"; + Capabilities capabilities; + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } + + public Capabilities getCapabilities() { + return capabilities; + } + + public void setCapabilities(Capabilities capabilities) { + this.capabilities = capabilities; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java new file mode 100644 index 000000000..e6ea7b956 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java @@ -0,0 +1,44 @@ +package com.alibaba.qwen.code.cli.protocol.message.control; + +import java.util.UUID; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; +import com.alibaba.qwen.code.cli.protocol.message.MessageBase; + +@JSONType(typeKey = "type", typeName = "control_request") +public class CLIControlRequest extends MessageBase { + @JSONField(name = "request_id") + private String requestId = UUID.randomUUID().toString(); + + private R request; + + public CLIControlRequest() { + super(); + type = "control_request"; + } + + public static CLIControlRequest create(T request) { + CLIControlRequest controlRequest = new CLIControlRequest<>(); + controlRequest.setRequest(request); + return controlRequest; + } + + public String getRequestId() { + return requestId; + } + + public CLIControlRequest setRequestId(String requestId) { + this.requestId = requestId; + return this; + } + + public R getRequest() { + return request; + } + + public CLIControlRequest setRequest(R request) { + this.request = request; + return this; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java new file mode 100644 index 000000000..5e193e2cd --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java @@ -0,0 +1,54 @@ +package com.alibaba.qwen.code.cli.protocol.message.control; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; +import com.alibaba.qwen.code.cli.protocol.message.MessageBase; + +@JSONType(typeKey = "type", typeName = "control_response") +public class CLIControlResponse extends MessageBase { + private Response response; + + public CLIControlResponse() { + super(); + this.type = "control_response"; + } + + public Response getResponse() { + return response; + } + + public void setResponse(Response response) { + this.response = response; + } + + public static class Response { + @JSONField(name = "request_id") + private String requestId; + private String subtype; + R response; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } + + public R getResponse() { + return response; + } + + public void setResponse(R response) { + this.response = response; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/protocol.ts b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/protocol.ts new file mode 100644 index 000000000..e5eeb1212 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/protocol.ts @@ -0,0 +1,594 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +export interface Annotation { + type: string; + value: string; +} + +export interface Usage { + input_tokens: number; + output_tokens: number; + cache_creation_input_tokens?: number; + cache_read_input_tokens?: number; + total_tokens?: number; +} + +export interface ExtendedUsage extends Usage { + server_tool_use?: { + web_search_requests: number; + }; + service_tier?: string; + cache_creation?: { + ephemeral_1h_input_tokens: number; + ephemeral_5m_input_tokens: number; + }; +} + +export interface ModelUsage { + inputTokens: number; + outputTokens: number; + cacheReadInputTokens: number; + cacheCreationInputTokens: number; + webSearchRequests: number; + contextWindow: number; +} + +export interface CLIPermissionDenial { + tool_name: string; + tool_use_id: string; + tool_input: unknown; +} + +export interface TextBlock { + type: 'text'; + text: string; + annotations?: Annotation[]; +} + +export interface ThinkingBlock { + type: 'thinking'; + thinking: string; + signature?: string; + annotations?: Annotation[]; +} + +export interface ToolUseBlock { + type: 'tool_use'; + id: string; + name: string; + input: unknown; + annotations?: Annotation[]; +} + +export interface ToolResultBlock { + type: 'tool_result'; + tool_use_id: string; + content?: string | ContentBlock[]; + is_error?: boolean; + annotations?: Annotation[]; +} + +export type ContentBlock = + | TextBlock + | ThinkingBlock + | ToolUseBlock + | ToolResultBlock; + +export interface APIUserMessage { + role: 'user'; + content: string | ContentBlock[]; +} + +export interface APIAssistantMessage { + id: string; + type: 'message'; + role: 'assistant'; + model: string; + content: ContentBlock[]; + stop_reason?: string | null; + usage: Usage; +} + +export interface SDKUserMessage { + type: 'user'; + uuid?: string; + session_id: string; + message: APIUserMessage; + parent_tool_use_id: string | null; + options?: Record; +} + +export interface SDKAssistantMessage { + type: 'assistant'; + uuid: string; + session_id: string; + message: APIAssistantMessage; + parent_tool_use_id: string | null; +} + +export interface SDKSystemMessage { + type: 'system'; + subtype: string; + uuid: string; + session_id: string; + data?: unknown; + cwd?: string; + tools?: string[]; + mcp_servers?: Array<{ + name: string; + status: string; + }>; + model?: string; + permission_mode?: string; + slash_commands?: string[]; + qwen_code_version?: string; + output_style?: string; + agents?: string[]; + skills?: string[]; + capabilities?: Record; + compact_metadata?: { + trigger: 'manual' | 'auto'; + pre_tokens: number; + }; +} + +export interface SDKResultMessageSuccess { + type: 'result'; + subtype: 'success'; + uuid: string; + session_id: string; + is_error: false; + duration_ms: number; + duration_api_ms: number; + num_turns: number; + result: string; + usage: ExtendedUsage; + modelUsage?: Record; + permission_denials: CLIPermissionDenial[]; + [key: string]: unknown; +} + +export interface SDKResultMessageError { + type: 'result'; + subtype: 'error_max_turns' | 'error_during_execution'; + uuid: string; + session_id: string; + is_error: true; + duration_ms: number; + duration_api_ms: number; + num_turns: number; + usage: ExtendedUsage; + modelUsage?: Record; + permission_denials: CLIPermissionDenial[]; + error?: { + type?: string; + message: string; + [key: string]: unknown; + }; + [key: string]: unknown; +} + +export type SDKResultMessage = SDKResultMessageSuccess | SDKResultMessageError; + +export interface MessageStartStreamEvent { + type: 'message_start'; + message: { + id: string; + role: 'assistant'; + model: string; + }; +} + +export interface ContentBlockStartEvent { + type: 'content_block_start'; + index: number; + content_block: ContentBlock; +} + +export type ContentBlockDelta = + | { + type: 'text_delta'; + text: string; + } + | { + type: 'thinking_delta'; + thinking: string; + } + | { + type: 'input_json_delta'; + partial_json: string; + }; + +export interface ContentBlockDeltaEvent { + type: 'content_block_delta'; + index: number; + delta: ContentBlockDelta; +} + +export interface ContentBlockStopEvent { + type: 'content_block_stop'; + index: number; +} + +export interface MessageStopStreamEvent { + type: 'message_stop'; +} + +export type StreamEvent = + | MessageStartStreamEvent + | ContentBlockStartEvent + | ContentBlockDeltaEvent + | ContentBlockStopEvent + | MessageStopStreamEvent; + +export interface SDKPartialAssistantMessage { + type: 'stream_event'; + uuid: string; + session_id: string; + event: StreamEvent; + parent_tool_use_id: string | null; +} + +export type PermissionMode = 'default' | 'plan' | 'auto-edit' | 'yolo'; + +/** + * TODO: Align with `ToolCallConfirmationDetails` + */ +export interface PermissionSuggestion { + type: 'allow' | 'deny' | 'modify'; + label: string; + description?: string; + modifiedInput?: unknown; +} + +export interface HookRegistration { + event: string; + callback_id: string; +} + +export interface HookCallbackResult { + shouldSkip?: boolean; + shouldInterrupt?: boolean; + suppressOutput?: boolean; + message?: string; +} + +export interface CLIControlInterruptRequest { + subtype: 'interrupt'; +} + +export interface CLIControlPermissionRequest { + subtype: 'can_use_tool'; + tool_name: string; + tool_use_id: string; + input: unknown; + permission_suggestions: PermissionSuggestion[] | null; + blocked_path: string | null; +} + +export enum AuthProviderType { + DYNAMIC_DISCOVERY = 'dynamic_discovery', + GOOGLE_CREDENTIALS = 'google_credentials', + SERVICE_ACCOUNT_IMPERSONATION = 'service_account_impersonation', +} + +export interface MCPServerConfig { + command?: string; + args?: string[]; + env?: Record; + cwd?: string; + url?: string; + httpUrl?: string; + headers?: Record; + tcp?: string; + timeout?: number; + trust?: boolean; + description?: string; + includeTools?: string[]; + excludeTools?: string[]; + extensionName?: string; + oauth?: Record; + authProviderType?: AuthProviderType; + targetAudience?: string; + targetServiceAccount?: string; +} + +/** + * SDK MCP Server configuration + * + * SDK MCP servers run in the SDK process and are connected via in-memory transport. + * Tool calls are routed through the control plane between SDK and CLI. + */ +export interface SDKMcpServerConfig { + /** + * Type identifier for SDK MCP servers + */ + type: 'sdk'; + /** + * Server name for identification and routing + */ + name: string; + /** + * The MCP Server instance created by createSdkMcpServer() + */ + instance: McpServer; +} + +/** + * Wire format for SDK MCP servers sent to the CLI + */ +export type WireSDKMcpServerConfig = Omit; + +export interface CLIControlInitializeRequest { + subtype: 'initialize'; + hooks?: HookRegistration[] | null; + /** + * SDK MCP servers config + * These are MCP servers running in the SDK process, connected via control plane. + * External MCP servers are configured separately in settings, not via initialization. + */ + sdkMcpServers?: Record; + /** + * External MCP servers that should be managed by the CLI. + */ + mcpServers?: Record; + agents?: SubagentConfig[]; +} + +export interface CLIControlSetPermissionModeRequest { + subtype: 'set_permission_mode'; + mode: PermissionMode; +} + +export interface CLIHookCallbackRequest { + subtype: 'hook_callback'; + callback_id: string; + input: unknown; + tool_use_id: string | null; +} + +export interface CLIControlMcpMessageRequest { + subtype: 'mcp_message'; + server_name: string; + message: { + jsonrpc?: string; + method: string; + params?: Record; + id?: string | number | null; + }; +} + +export interface CLIControlSetModelRequest { + subtype: 'set_model'; + model: string; +} + +export interface CLIControlMcpStatusRequest { + subtype: 'mcp_server_status'; +} + +export interface CLIControlSupportedCommandsRequest { + subtype: 'supported_commands'; +} + +export type ControlRequestPayload = + | CLIControlInterruptRequest + | CLIControlPermissionRequest + | CLIControlInitializeRequest + | CLIControlSetPermissionModeRequest + | CLIHookCallbackRequest + | CLIControlMcpMessageRequest + | CLIControlSetModelRequest + | CLIControlMcpStatusRequest + | CLIControlSupportedCommandsRequest; + +export interface CLIControlRequest { + type: 'control_request'; + request_id: string; + request: ControlRequestPayload; +} + +export interface PermissionApproval { + allowed: boolean; + reason?: string; + modifiedInput?: unknown; +} + +export interface ControlResponse { + subtype: 'success'; + request_id: string; + response: unknown; +} + +export interface ControlErrorResponse { + subtype: 'error'; + request_id: string; + error: string | { message: string; [key: string]: unknown }; +} + +export interface CLIControlResponse { + type: 'control_response'; + response: ControlResponse | ControlErrorResponse; +} + +export interface ControlCancelRequest { + type: 'control_cancel_request'; + request_id?: string; +} + +export type ControlMessage = + | CLIControlRequest + | CLIControlResponse + | ControlCancelRequest; + +/** + * Union of all SDK message types + */ +export type SDKMessage = + | SDKUserMessage + | SDKAssistantMessage + | SDKSystemMessage + | SDKResultMessage + | SDKPartialAssistantMessage; + +export function isSDKUserMessage(msg: any): msg is SDKUserMessage { + return ( + msg && typeof msg === 'object' && msg.type === 'user' && 'message' in msg + ); +} + +export function isSDKAssistantMessage(msg: any): msg is SDKAssistantMessage { + return ( + msg && + typeof msg === 'object' && + msg.type === 'assistant' && + 'uuid' in msg && + 'message' in msg && + 'session_id' in msg && + 'parent_tool_use_id' in msg + ); +} + +export function isSDKSystemMessage(msg: any): msg is SDKSystemMessage { + return ( + msg && + typeof msg === 'object' && + msg.type === 'system' && + 'subtype' in msg && + 'uuid' in msg && + 'session_id' in msg + ); +} + +export function isSDKResultMessage(msg: any): msg is SDKResultMessage { + return ( + msg && + typeof msg === 'object' && + msg.type === 'result' && + 'subtype' in msg && + 'duration_ms' in msg && + 'is_error' in msg && + 'uuid' in msg && + 'session_id' in msg + ); +} + +export function isSDKPartialAssistantMessage( + msg: any, +): msg is SDKPartialAssistantMessage { + return ( + msg && + typeof msg === 'object' && + msg.type === 'stream_event' && + 'uuid' in msg && + 'session_id' in msg && + 'event' in msg && + 'parent_tool_use_id' in msg + ); +} + +export function isControlRequest(msg: any): msg is CLIControlRequest { + return ( + msg && + typeof msg === 'object' && + msg.type === 'control_request' && + 'request_id' in msg && + 'request' in msg + ); +} + +export function isControlResponse(msg: any): msg is CLIControlResponse { + return ( + msg && + typeof msg === 'object' && + msg.type === 'control_response' && + 'response' in msg + ); +} + +export function isControlCancel(msg: any): msg is ControlCancelRequest { + return ( + msg && + typeof msg === 'object' && + msg.type === 'control_cancel_request' && + 'request_id' in msg + ); +} + +export function isTextBlock(block: any): block is TextBlock { + return block && typeof block === 'object' && block.type === 'text'; +} + +export function isThinkingBlock(block: any): block is ThinkingBlock { + return block && typeof block === 'object' && block.type === 'thinking'; +} + +export function isToolUseBlock(block: any): block is ToolUseBlock { + return block && typeof block === 'object' && block.type === 'tool_use'; +} + +export function isToolResultBlock(block: any): block is ToolResultBlock { + return block && typeof block === 'object' && block.type === 'tool_result'; +} + +export type SubagentLevel = 'session'; + +export interface ModelConfig { + model?: string; + temp?: number; + top_p?: number; +} + +export interface RunConfig { + max_time_minutes?: number; + max_turns?: number; +} + +export interface SubagentConfig { + name: string; + description: string; + tools?: string[]; + systemPrompt: string; + level: SubagentLevel; + filePath?: string; + modelConfig?: Partial; + runConfig?: Partial; + color?: string; + readonly isBuiltin?: boolean; +} + +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Control Request Types + * + * Centralized enum for all control request subtypes supported by the CLI. + * This enum should be kept in sync with the controllers in: + * - packages/cli/src/services/control/controllers/systemController.ts + * - packages/cli/src/services/control/controllers/permissionController.ts + * - packages/cli/src/services/control/controllers/mcpController.ts + * - packages/cli/src/services/control/controllers/hookController.ts + */ +export enum ControlRequestType { + // SystemController requests + INITIALIZE = 'initialize', + INTERRUPT = 'interrupt', + SET_MODEL = 'set_model', + SUPPORTED_COMMANDS = 'supported_commands', + + // PermissionController requests + CAN_USE_TOOL = 'can_use_tool', + SET_PERMISSION_MODE = 'set_permission_mode', + + // MCPController requests + MCP_MESSAGE = 'mcp_message', + MCP_SERVER_STATUS = 'mcp_server_status', + + // HookController requests + HOOK_CALLBACK = 'hook_callback', +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java new file mode 100644 index 000000000..346b0f1f2 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java @@ -0,0 +1,89 @@ +package com.alibaba.qwen.code.cli.session; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import com.alibaba.qwen.code.cli.protocol.data.Capabilities; +import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage; +import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage; +import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlInitializeRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlInitializeResponse; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse; +import com.alibaba.qwen.code.cli.session.event.SessionEventConsumers; +import com.alibaba.qwen.code.cli.session.exception.SessionCloseException; +import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException; +import com.alibaba.qwen.code.cli.session.exception.SessionStartException; +import com.alibaba.qwen.code.cli.transport.Transport; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Session { + private final Transport transport; + private Capabilities capabilities; + private static final Logger log = LoggerFactory.getLogger(Session.class); + + public Session(Transport transport) throws SessionStartException { + if (transport == null || !transport.isAvailable()) { + throw new SessionStartException("Transport is not available"); + } + this.transport = transport; + start(); + } + + private void start() throws SessionStartException { + try { + String response = transport.inputWaitForOneLine(CLIControlRequest.create(new CLIControlInitializeRequest()).toString()); + CLIControlResponse cliControlResponse = JSON.parseObject(response, new TypeReference>() {}); + this.capabilities = cliControlResponse.getResponse().getResponse().getCapabilities(); + } catch (Exception e) { + throw new SessionStartException("Failed to initialize the session", e); + } + } + + public void close() throws SessionCloseException { + try { + transport.close(); + } catch (Exception e) { + throw new SessionCloseException("Failed to close the session", e); + } + } + + public Capabilities getCapabilities() { + return capabilities; + } + + public void sendPrompt(String prompt, SessionEventConsumers sessionEventConsumers) throws SessionSendPromptException { + if (!transport.isAvailable()) { + throw new SessionSendPromptException("Session is not available"); + } + + try { + transport.inputWaitForMultiLine(new SDKUserMessage().setContent(prompt).toString(), (line) -> { + log.debug("read a message from agent {}", line); + JSONObject jsonObject = JSON.parseObject(line); + + String messageType = jsonObject.getString("type"); + if ("system".equals(messageType)) { + sessionEventConsumers.onSystemMessage(JSON.parseObject(line, SDKSystemMessage.class)); + return false; + } else if ("assistant".equals(messageType)) { + sessionEventConsumers.onAssistantMessage(JSON.parseObject(line, SDKAssistantMessage.class)); + return false; + } else if ("result".equals(messageType)) { + sessionEventConsumers.onResultMessage(JSON.parseObject(line, SDKResultMessage.class)); + return true; + } else { + log.warn("unknown message type: {}", messageType); + sessionEventConsumers.onOtherMessage(line); + return false; + } + }); + } catch (Exception e) { + throw new SessionSendPromptException("Failed to send prompt", e); + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java new file mode 100644 index 000000000..9f9bb6fc1 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java @@ -0,0 +1,15 @@ +package com.alibaba.qwen.code.cli.session.event; + +import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage; +import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; + +public interface SessionEventConsumers { + void onSystemMessage(SDKSystemMessage systemMessage); + + void onResultMessage(SDKResultMessage resultMessage); + + void onAssistantMessage(SDKAssistantMessage assistantMessage); + + void onOtherMessage(String message); +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java new file mode 100644 index 000000000..584354d43 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java @@ -0,0 +1,23 @@ +package com.alibaba.qwen.code.cli.session.event; + +import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage; +import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; + +public class SessionEventSimpleConsumers implements SessionEventConsumers { + @Override + public void onSystemMessage(SDKSystemMessage systemMessage) { + } + + @Override + public void onResultMessage(SDKResultMessage resultMessage) { + } + + @Override + public void onAssistantMessage(SDKAssistantMessage assistantMessage) { + } + + @Override + public void onOtherMessage(String message) { + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java new file mode 100644 index 000000000..3db39e793 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java @@ -0,0 +1,22 @@ +package com.alibaba.qwen.code.cli.session.exception; + +public class SessionCloseException extends Exception { + public SessionCloseException() { + } + + public SessionCloseException(String message) { + super(message); + } + + public SessionCloseException(String message, Throwable cause) { + super(message, cause); + } + + public SessionCloseException(Throwable cause) { + super(cause); + } + + public SessionCloseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java new file mode 100644 index 000000000..74de3bba7 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java @@ -0,0 +1,22 @@ +package com.alibaba.qwen.code.cli.session.exception; + +public class SessionSendPromptException extends Exception { + public SessionSendPromptException() { + } + + public SessionSendPromptException(String message) { + super(message); + } + + public SessionSendPromptException(String message, Throwable cause) { + super(message, cause); + } + + public SessionSendPromptException(Throwable cause) { + super(cause); + } + + public SessionSendPromptException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java new file mode 100644 index 000000000..9d30f2367 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java @@ -0,0 +1,22 @@ +package com.alibaba.qwen.code.cli.session.exception; + +public class SessionStartException extends Exception { + public SessionStartException() { + } + + public SessionStartException(String message) { + super(message); + } + + public SessionStartException(String message, Throwable cause) { + super(message, cause); + } + + public SessionStartException(Throwable cause) { + super(cause); + } + + public SessionStartException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java new file mode 100644 index 000000000..5b66cbc90 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java @@ -0,0 +1,18 @@ +package com.alibaba.qwen.code.cli.transport; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; + +public interface Transport { + void close() throws IOException; + + boolean isAvailable(); + + String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException; + + void inputWaitForMultiLine(String message, Function callBackFunction) throws IOException; + + void inputNoWaitResponse(String message) throws IOException; +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java index 3adc1072b..b7d62b5d7 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java @@ -1,5 +1,6 @@ package com.alibaba.qwen.code.cli.transport.process; +import com.alibaba.qwen.code.cli.transport.Transport; import com.alibaba.qwen.code.cli.transport.TransportOptions; import org.apache.commons.lang3.exception.ContextedRuntimeException; @@ -19,7 +20,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Function; -public class ProcessTransport { +public class ProcessTransport implements Transport { private static final Logger log = LoggerFactory.getLogger(ProcessTransport.class); TransportOptionsAdapter transportOptionsAdapter; @@ -31,6 +32,10 @@ public class ProcessTransport { protected BufferedReader processOutput; protected BufferedReader processError; + public ProcessTransport() throws IOException { + this(new TransportOptions()); + } + public ProcessTransport(TransportOptions transportOptions) throws IOException { this.transportOptionsAdapter = new TransportOptionsAdapter(transportOptions); turnTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeoutMs(); @@ -56,6 +61,7 @@ public class ProcessTransport { startErrorReading(); } + @Override public void close() throws IOException { if (processInput != null) { processInput.close(); @@ -71,6 +77,12 @@ public class ProcessTransport { } } + @Override + public boolean isAvailable() { + return process != null && process.isAlive(); + } + + @Override public String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException { return inputWaitForOneLine(message, turnTimeoutMs); } @@ -106,6 +118,7 @@ public class ProcessTransport { } } + @Override public void inputWaitForMultiLine(String message, Function callBackFunction) throws IOException { inputWaitForMultiLine(message, callBackFunction, turnTimeoutMs); } @@ -132,6 +145,7 @@ public class ProcessTransport { } } + @Override public void inputNoWaitResponse(String message) throws IOException { log.debug("input message to agent: {}", message); processInput.write(message); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java index 66113e8cd..4c6b7d48d 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java @@ -76,7 +76,9 @@ class TransportOptionsAdapter { } private TransportOptions addDefaultTransportOptions(TransportOptions userTransportOptions) { - TransportOptions transportOptions = userTransportOptions.clone(); + TransportOptions transportOptions = Optional.ofNullable(userTransportOptions) + .map(TransportOptions::clone) + .orElse(new TransportOptions()); if (StringUtils.isBlank(transportOptions.getPathToQwenExecutable())) { transportOptions.setPathToQwenExecutable("qwen"); diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java new file mode 100644 index 000000000..01295ce6c --- /dev/null +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java @@ -0,0 +1,23 @@ +package com.alibaba.qwen.code.cli; + +import java.util.List; + +import com.alibaba.qwen.code.cli.protocol.message.Message; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.*; + +class QwenCliTest { + + private static final Logger log = LoggerFactory.getLogger(QwenCliTest.class); + @Test + void query() { + List result = QwenCli.query("hello world"); + log.info("result: {}", result); + assertNotNull(result); + } +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java new file mode 100644 index 000000000..69898b948 --- /dev/null +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java @@ -0,0 +1,58 @@ +package com.alibaba.qwen.code.cli.session; + +import java.io.IOException; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage; +import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; +import com.alibaba.qwen.code.cli.session.event.SessionEventConsumers; +import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers; +import com.alibaba.qwen.code.cli.session.exception.SessionCloseException; +import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException; +import com.alibaba.qwen.code.cli.session.exception.SessionStartException; +import com.alibaba.qwen.code.cli.transport.Transport; +import com.alibaba.qwen.code.cli.transport.process.ProcessTransport; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class SessionTest { + + private static final Logger log = LoggerFactory.getLogger(SessionTest.class); + @Test + void sendPrompt() throws IOException, SessionStartException, SessionSendPromptException, SessionCloseException { + Transport transport = new ProcessTransport(); + Session session = new Session(transport); + session.sendPrompt("hello world", new SessionEventSimpleConsumers() { + @Override + public void onSystemMessage(SDKSystemMessage systemMessage) { + log.info("systemMessage: {}", systemMessage); + } + + @Override + public void onResultMessage(SDKResultMessage resultMessage) { + log.info("resultMessage: {}", resultMessage); + } + + @Override + public void onAssistantMessage(SDKAssistantMessage assistantMessage) { + log.info("assistantMessage: {}", assistantMessage); + } + + @Override + public void onOtherMessage(String message) { + log.info("otherMessage: {}", message); + } + }); + session.close(); + } + + @Test + void testJSON() { + String json = "{\"type\":\"assistant\",\"uuid\":\"ed8374fe-a4eb-4fc0-9780-9bd2fd831cda\",\"session_id\":\"166badc0-e6d3-4978-ae47-4ccd51c468ef\",\"message\":{\"content\":[{\"text\":\"Hello! How can I help you with the Qwen Code SDK for Java today?\",\"type\":\"text\"}],\"id\":\"ed8374fe-a4eb-4fc0-9780-9bd2fd831cda\",\"model\":\"qwen3-coder-plus\",\"role\":\"assistant\",\"type\":\"message\",\"usage\":{\"cache_read_input_tokens\":12766,\"input_tokens\":12770,\"output_tokens\":17,\"total_tokens\":12787}}}"; + SDKAssistantMessage assistantMessage = JSON.parseObject(json, SDKAssistantMessage.class); + log.info("the assistantMessage: {}", assistantMessage); + } +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java index bdedf3047..721b203db 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java @@ -1,29 +1,86 @@ package com.alibaba.qwen.code.cli.transport.process; import java.io.IOException; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlInitializeRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlInitializeResponse; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse; +import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage; +import com.alibaba.qwen.code.cli.transport.Transport; import com.alibaba.qwen.code.cli.transport.TransportOptions; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class ProcessTransportTest { + private static final Logger logger = LoggerFactory.getLogger(ProcessTransportTest.class); + @Test void shouldStartAndCloseSuccessfully() throws IOException { TransportOptions transportOptions = new TransportOptions(); - ProcessTransport processTransport = new ProcessTransport(transportOptions); - processTransport.close(); + Transport transport = new ProcessTransport(transportOptions); + transport.close(); } @Test void shouldInputWaitForOneLineSuccessfully() throws IOException, ExecutionException, InterruptedException, TimeoutException { TransportOptions transportOptions = new TransportOptions(); - ProcessTransport processTransport = new ProcessTransport(transportOptions); + Transport transport = new ProcessTransport(transportOptions); String message = "{\"type\": \"control_request\", \"request_id\": \"1\", \"request\": {\"subtype\": \"initialize\"} }"; - System.out.println(processTransport.inputWaitForOneLine(message)); + System.out.println(transport.inputWaitForOneLine(message)); + } + + @Test + void shouldInitializeSuccessfully() throws IOException, ExecutionException, InterruptedException, TimeoutException { + Transport transport = new ProcessTransport(); + + String message = CLIControlRequest.create(new CLIControlInitializeRequest()).toString(); + String responseMsg = transport.inputWaitForOneLine(message); + logger.info("responseMsg: {}", responseMsg); + CLIControlResponse response = JSON.parseObject(responseMsg, + new TypeReference>() {}); + logger.info("response: {}", response); + } + + @Test + void shouldSdkMessageSuccessfully() throws IOException, ExecutionException, InterruptedException, TimeoutException { + Transport transport = new ProcessTransport(); + String message = CLIControlRequest.create(new CLIControlInitializeRequest()).toString(); + transport.inputWaitForOneLine(message); + + String sessionId = "session-" + UUID.randomUUID().toString(); + String userMessage = new SDKUserMessage().setSessionId(sessionId).setContent("hello world").toString(); + transport.inputWaitForMultiLine(userMessage, line -> { + return "result".equals(JSON.parseObject(line).getString("type")); + }); + + String userMessage2 = new SDKUserMessage().setSessionId(sessionId).setContent("请使用中文").toString(); + transport.inputWaitForMultiLine(userMessage2, line -> { + return "result".equals(JSON.parseObject(line).getString("type")); + }); + + String userMessage3 = new SDKUserMessage().setSessionId(sessionId).setContent("当前工作区有多少个文件").toString(); + transport.inputWaitForMultiLine(userMessage3, line -> { + return "result".equals(JSON.parseObject(line).getString("type")); + }); + + String userMessage4 = new SDKUserMessage().setSessionId("session-sec" + UUID.randomUUID()).setContent("有多少个xml文件").toString(); + transport.inputWaitForMultiLine(userMessage4, line -> { + return "result".equals(JSON.parseObject(line).getString("type")); + }); + + transport.inputWaitForOneLine(CLIControlRequest.create(new CLIControlInitializeRequest()).toString()); + transport.inputWaitForMultiLine(new SDKUserMessage().setContent("您好").toString(), + line -> "result".equals(JSON.parseObject(line).getString("type"))); } }