message and session use

This commit is contained in:
skyfire 2025-12-29 21:44:02 +08:00
parent 422998d7f0
commit 4154493640
39 changed files with 2296 additions and 6 deletions

View file

@ -27,6 +27,7 @@
<checkstyle-maven-plugin.version>3.6.0</checkstyle-maven-plugin.version>
<junit5.version>5.14.1</junit5.version>
<logback-classic.version>1.3.16</logback-classic.version>
<fastjson2.version>2.0.60</fastjson2.version>
</properties>
<dependencyManagement>
@ -51,6 +52,11 @@
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>

View file

@ -0,0 +1,6 @@
package com.alibaba.qwen.code.cli;
import com.alibaba.qwen.code.cli.transport.TransportOptions;
public class Options extends TransportOptions {
}

View file

@ -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<Message> 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<Message> 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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,5 @@
package com.alibaba.qwen.code.cli.protocol.message;
public interface Message {
String getType();
}

View file

@ -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;
}
}

View file

@ -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<String, Usage> modelUsage;
@JSONField(name = "permission_denials")
private List<CLIPermissionDenial> 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<String, Usage> getModelUsage() {
return modelUsage;
}
public void setModelUsage(Map<String, Usage> modelUsage) {
this.modelUsage = modelUsage;
}
public List<CLIPermissionDenial> getPermissionDenials() {
return permissionDenials;
}
public void setPermissionDenials(List<CLIPermissionDenial> 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;
}
}
}

View file

@ -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<String> tools;
@JSONField(name = "mcp_servers")
private List<McpServer> mcpServers;
private String model;
@JSONField(name = "permission_mode")
private String permissionMode;
@JSONField(name = "slash_commands")
private List<String> slashCommands;
@JSONField(name = "qwen_code_version")
private String qwenCodeVersion;
@JSONField(name = "output_style")
private String outputStyle;
private List<String> agents;
private List<String> skills;
private Map<String, Object> 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<String> getTools() {
return tools;
}
public void setTools(List<String> tools) {
this.tools = tools;
}
public List<McpServer> getMcpServers() {
return mcpServers;
}
public void setMcpServers(List<McpServer> 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<String> getSlashCommands() {
return slashCommands;
}
public void setSlashCommands(List<String> 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<String> getAgents() {
return agents;
}
public void setAgents(List<String> agents) {
this.agents = agents;
}
public List<String> getSkills() {
return skills;
}
public void setSkills(List<String> skills) {
this.skills = skills;
}
public Map<String, Object> getCapabilities() {
return capabilities;
}
public void setCapabilities(Map<String, Object> 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;
}
}
}

View file

@ -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<String, String> 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<String, String> getOptions() {
return options;
}
public SDKUserMessage setOptions(Map<String, String> 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;
}
}
}

View file

@ -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<ContentBlock> 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<ContentBlock> getContent() {
return content;
}
public void setContent(List<ContentBlock> content) {
this.content = content;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<Annotation> annotations;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Annotation> getAnnotations() {
return annotations;
}
public void setAnnotations(List<Annotation> annotations) {
this.annotations = annotations;
}
public String toString() {
return JSON.toJSONString(this);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<ContentBlock>
@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;
}
}

View file

@ -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<String, Object> input;
private List<Annotation> 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<String, Object> getInput() {
return input;
}
public void setInput(Map<String, Object> input) {
this.input = input;
}
public List<Annotation> getAnnotations() {
return annotations;
}
public void setAnnotations(List<Annotation> annotations) {
this.annotations = annotations;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<R> extends MessageBase {
@JSONField(name = "request_id")
private String requestId = UUID.randomUUID().toString();
private R request;
public CLIControlRequest() {
super();
type = "control_request";
}
public static <T> CLIControlRequest<T> create(T request) {
CLIControlRequest<T> controlRequest = new CLIControlRequest<>();
controlRequest.setRequest(request);
return controlRequest;
}
public String getRequestId() {
return requestId;
}
public CLIControlRequest<R> setRequestId(String requestId) {
this.requestId = requestId;
return this;
}
public R getRequest() {
return request;
}
public CLIControlRequest<R> setRequest(R request) {
this.request = request;
return this;
}
}

View file

@ -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<R> extends MessageBase {
private Response<R> response;
public CLIControlResponse() {
super();
this.type = "control_response";
}
public Response<R> getResponse() {
return response;
}
public void setResponse(Response<R> response) {
this.response = response;
}
public static class Response<R> {
@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;
}
}
}

View file

@ -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<string, unknown>;
}
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<string, unknown>;
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<string, ModelUsage>;
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<string, ModelUsage>;
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<string, string>;
cwd?: string;
url?: string;
httpUrl?: string;
headers?: Record<string, string>;
tcp?: string;
timeout?: number;
trust?: boolean;
description?: string;
includeTools?: string[];
excludeTools?: string[];
extensionName?: string;
oauth?: Record<string, unknown>;
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<SDKMcpServerConfig, 'instance'>;
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<string, WireSDKMcpServerConfig>;
/**
* External MCP servers that should be managed by the CLI.
*/
mcpServers?: Record<string, MCPServerConfig>;
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<string, unknown>;
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<ModelConfig>;
runConfig?: Partial<RunConfig>;
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',
}

View file

@ -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<CLIControlInitializeResponse> cliControlResponse = JSON.parseObject(response, new TypeReference<CLIControlResponse<CLIControlInitializeResponse>>() {});
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);
}
}
}

View file

@ -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);
}

View file

@ -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) {
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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<String, Boolean> callBackFunction) throws IOException;
void inputNoWaitResponse(String message) throws IOException;
}

View file

@ -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<String, Boolean> 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);

View file

@ -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");

View file

@ -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<Message> result = QwenCli.query("hello world");
log.info("result: {}", result);
assertNotNull(result);
}
}

View file

@ -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);
}
}

View file

@ -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<CLIControlInitializeResponse> response = JSON.parseObject(responseMsg,
new TypeReference<CLIControlResponse<CLIControlInitializeResponse>>() {});
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")));
}
}