add permission

This commit is contained in:
skyfire
2025-12-30 20:08:05 +08:00
parent 4154493640
commit ac7ba95d65
24 changed files with 648 additions and 93 deletions

View File

@@ -31,12 +31,12 @@ public class QwenCli {
try {
session.sendPrompt(prompt, new SessionEventSimpleConsumers() {
@Override
public void onSystemMessage(SDKSystemMessage systemMessage) {
public void onSystemMessage(Session session, SDKSystemMessage systemMessage) {
response.add(systemMessage);
}
@Override
public void onAssistantMessage(SDKAssistantMessage assistantMessage) {
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
response.add(assistantMessage);
}
});

View File

@@ -1,4 +1,4 @@
package com.alibaba.qwen.code.cli.transport;
package com.alibaba.qwen.code.cli.protocol.data;
public enum PermissionMode {
DEFAULT("default"),

View File

@@ -0,0 +1,23 @@
package com.alibaba.qwen.code.cli.protocol.data.behavior;
import java.util.Map;
import com.alibaba.fastjson2.annotation.JSONType;
@JSONType(typeKey = "operation", typeName = "allow")
public class Allow extends Behavior {
public Allow() {
super();
this.behavior = Operation.allow;
}
Map<String, Object> updatedInput;
public Map<String, Object> getUpdatedInput() {
return updatedInput;
}
public Allow setUpdatedInput(Map<String, Object> updatedInput) {
this.updatedInput = updatedInput;
return this;
}
}

View File

@@ -0,0 +1,25 @@
package com.alibaba.qwen.code.cli.protocol.data.behavior;
import com.alibaba.fastjson2.annotation.JSONType;
@JSONType(typeKey = "operation", typeName = "Behavior", seeAlso = {Allow.class, Deny.class})
public class Behavior {
Operation behavior;
public Operation getBehavior() {
return behavior;
}
public void setBehavior(Operation behavior) {
this.behavior = behavior;
}
public enum Operation {
allow,
deny
}
public static Behavior defaultBehavior() {
return new Deny().setMessage("Default Behavior Permission denied");
}
}

View File

@@ -0,0 +1,22 @@
package com.alibaba.qwen.code.cli.protocol.data.behavior;
import com.alibaba.fastjson2.annotation.JSONType;
@JSONType(typeKey = "operation", typeName = "deny")
public class Deny extends Behavior {
public Deny() {
super();
this.behavior = Operation.deny;
}
String message;
public String getMessage() {
return message;
}
public Deny setMessage(String message) {
this.message = message;
return this;
}
}

View File

@@ -0,0 +1,13 @@
package com.alibaba.qwen.code.cli.protocol.message.control;
public class CLIControlInterruptRequest {
String subtype = "interrupt";
public String getSubtype() {
return subtype;
}
public void setSubtype(String subtype) {
this.subtype = subtype;
}
}

View File

@@ -0,0 +1,112 @@
package com.alibaba.qwen.code.cli.protocol.message.control;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson2.annotation.JSONField;
public class CLIControlPermissionRequest {
private String subtype;
@JSONField(name = "tool_name")
private String toolName;
@JSONField(name = "tool_use_id")
private String toolUseId;
private Map<String, Object> input;
@JSONField(name = "permission_suggestions")
private List<PermissionSuggestion> permissionSuggestions;
@JSONField(name = "blocked_path")
private String blockedPath;
public String getSubtype() {
return subtype;
}
public void setSubtype(String subtype) {
this.subtype = subtype;
}
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 Map<String, Object> getInput() {
return input;
}
public void setInput(Map<String, Object> input) {
this.input = input;
}
public List<PermissionSuggestion> getPermissionSuggestions() {
return permissionSuggestions;
}
public void setPermissionSuggestions(
List<PermissionSuggestion> permissionSuggestions) {
this.permissionSuggestions = permissionSuggestions;
}
public String getBlockedPath() {
return blockedPath;
}
public void setBlockedPath(String blockedPath) {
this.blockedPath = blockedPath;
}
public static class PermissionSuggestion {
private String type; // 'allow' | 'deny' | 'modify'
private String label;
private String description;
private Object modifiedInput;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Object getModifiedInput() {
return modifiedInput;
}
public void setModifiedInput(Object modifiedInput) {
this.modifiedInput = modifiedInput;
}
}
}

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.behavior.Behavior;
public class CLIControlPermissionResponse {
private String subtype = "can_use_tool";
@JSONField(unwrapped = true)
Behavior behavior;
public String getSubtype() {
return subtype;
}
public void setSubtype(String subtype) {
this.subtype = subtype;
}
public Behavior getBehavior() {
return behavior;
}
public CLIControlPermissionResponse setBehavior(Behavior behavior) {
this.behavior = behavior;
return this;
}
}

View File

@@ -21,34 +21,43 @@ public class CLIControlResponse<R> extends MessageBase {
this.response = response;
}
public Response<R> createResponse() {
Response<R> response = new Response<>();
this.setResponse(response);
return response;
}
public static class Response<R> {
@JSONField(name = "request_id")
private String requestId;
private String subtype;
private String subtype = "success";
R response;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
public Response<R> setRequestId(String requestId) {
this.requestId = requestId;
return this;
}
public String getSubtype() {
return subtype;
}
public void setSubtype(String subtype) {
public Response<R> setSubtype(String subtype) {
this.subtype = subtype;
return this;
}
public R getResponse() {
return response;
}
public void setResponse(R response) {
public Response<R> setResponse(R response) {
this.response = response;
return this;
}
}
}

View File

@@ -0,0 +1,22 @@
package com.alibaba.qwen.code.cli.protocol.message.control;
public class CLIControlSetModelRequest {
String subtype = "set_model";
String model;
public String getSubtype() {
return subtype;
}
public void setSubtype(String subtype) {
this.subtype = subtype;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}

View File

@@ -0,0 +1,23 @@
package com.alibaba.qwen.code.cli.protocol.message.control;
public class CLIControlSetPermissionModeRequest {
String subtype = "set_permission_mode";
String mode;
public String getSubtype() {
return subtype;
}
public void setSubtype(String subtype) {
this.subtype = subtype;
}
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
}

View File

@@ -1,59 +1,141 @@
package com.alibaba.qwen.code.cli.session;
import java.io.IOException;
import java.util.Optional;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONReader.Feature;
import com.alibaba.fastjson2.TypeReference;
import com.alibaba.qwen.code.cli.protocol.data.Capabilities;
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow;
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
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.CLIControlInterruptRequest;
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlPermissionRequest;
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlPermissionResponse;
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.control.CLIControlSetModelRequest;
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlSetPermissionModeRequest;
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.SessionControlException;
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.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Session {
private final Transport transport;
private Capabilities capabilities;
private CLIControlInitializeResponse lastCliControlInitializeResponse;
private SDKSystemMessage lastSdkSystemMessage;
private static final Logger log = LoggerFactory.getLogger(Session.class);
public Session(Transport transport) throws SessionStartException {
public Session(Transport transport) throws SessionControlException {
if (transport == null || !transport.isAvailable()) {
throw new SessionStartException("Transport is not available");
throw new SessionControlException("Transport is not available");
}
this.transport = transport;
start();
}
private void start() throws SessionStartException {
public void start() throws SessionControlException {
try {
if (!transport.isAvailable()) {
transport.start();
}
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();
CLIControlResponse<CLIControlInitializeResponse> cliControlResponse = JSON.parseObject(response,
new TypeReference<CLIControlResponse<CLIControlInitializeResponse>>() {});
this.lastCliControlInitializeResponse = cliControlResponse.getResponse().getResponse();
} catch (Exception e) {
throw new SessionStartException("Failed to initialize the session", e);
throw new SessionControlException("Failed to initialize the session", e);
}
}
public void close() throws SessionCloseException {
public void interrupt() throws SessionControlException {
if (!isAvailable()) {
throw new SessionControlException("Session is not available");
}
try {
transport.inputNoWaitResponse(
new CLIControlRequest<CLIControlInterruptRequest>().setRequest(new CLIControlInterruptRequest()).toString());
} catch (Exception e) {
throw new SessionControlException("Failed to interrupt the session", e);
}
}
public void setModel(String modelName) throws SessionControlException {
if (!isAvailable()) {
throw new SessionControlException("Session is not available");
}
CLIControlSetModelRequest cliControlSetModelRequest = new CLIControlSetModelRequest();
cliControlSetModelRequest.setModel(modelName);
try {
transport.inputNoWaitResponse(new CLIControlRequest<CLIControlSetModelRequest>().setRequest(cliControlSetModelRequest).toString());
} catch (Exception e) {
throw new SessionControlException("Failed to set model", e);
}
}
public void setPermissionMode(PermissionMode permissionMode) throws SessionControlException {
if (!isAvailable()) {
throw new SessionControlException("Session is not available");
}
CLIControlSetPermissionModeRequest cliControlSetPermissionModeRequest = new CLIControlSetPermissionModeRequest();
cliControlSetPermissionModeRequest.setMode(permissionMode.getValue());
try {
transport.inputNoWaitResponse(
new CLIControlRequest<CLIControlSetPermissionModeRequest>().setRequest(cliControlSetPermissionModeRequest).toString());
} catch (Exception e) {
throw new SessionControlException("Failed to set model", e);
}
}
public void continueSession() throws SessionControlException {
resumeSession(getSessionId());
}
public void resumeSession(String sessionId) throws SessionControlException {
if (!isAvailable()) {
throw new SessionControlException("Session is not available");
}
if (StringUtils.isNotBlank(sessionId)) {
transport.getTransportOptions().setResumeSessionId(sessionId);
}
this.start();
}
public String getSessionId() {
return Optional.ofNullable(lastSdkSystemMessage).map(SDKSystemMessage::getSessionId).orElse(null);
}
public void close() throws SessionControlException {
try {
transport.close();
} catch (Exception e) {
throw new SessionCloseException("Failed to close the session", e);
throw new SessionControlException("Failed to close the session", e);
}
}
public boolean isAvailable() {
return transport.isAvailable();
}
public Capabilities getCapabilities() {
return capabilities;
return Optional.ofNullable(lastCliControlInitializeResponse).map(CLIControlInitializeResponse::getCapabilities).orElse(new Capabilities());
}
public void sendPrompt(String prompt, SessionEventConsumers sessionEventConsumers) throws SessionSendPromptException {
@@ -65,20 +147,33 @@ public class Session {
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));
lastSdkSystemMessage = jsonObject.to(SDKSystemMessage.class);
sessionEventConsumers.onSystemMessage(this, lastSdkSystemMessage);
return false;
} else if ("assistant".equals(messageType)) {
sessionEventConsumers.onAssistantMessage(JSON.parseObject(line, SDKAssistantMessage.class));
sessionEventConsumers.onAssistantMessage(this, jsonObject.to(SDKAssistantMessage.class));
return false;
} else if ("user".equals(messageType)) {
sessionEventConsumers.onUserMessage(this, jsonObject.to(SDKUserMessage.class, Feature.FieldBased));
return false;
} else if ("result".equals(messageType)) {
sessionEventConsumers.onResultMessage(JSON.parseObject(line, SDKResultMessage.class));
sessionEventConsumers.onResultMessage(this, jsonObject.to(SDKResultMessage.class));
return true;
} else if ("control_response".equals(messageType)) {
sessionEventConsumers.onControlResponse(this, jsonObject.to(CLIControlResponse.class));
if (!"error".equals(jsonObject.getString("subtype"))) {
return false;
} else {
log.info("control_response error: {}", jsonObject.toJSONString());
return "error".equals(jsonObject.getString("subtype"));
}
} else if ("control_request".equals(messageType)) {
return processControlRequest(jsonObject, sessionEventConsumers);
} else {
log.warn("unknown message type: {}", messageType);
sessionEventConsumers.onOtherMessage(line);
sessionEventConsumers.onOtherMessage(this, line);
return false;
}
});
@@ -86,4 +181,53 @@ public class Session {
throw new SessionSendPromptException("Failed to send prompt", e);
}
}
private boolean processControlRequest(JSONObject jsonObject, SessionEventConsumers sessionEventConsumers) {
String subType = Optional.of(jsonObject)
.map(cr -> cr.getJSONObject("request"))
.map(r -> r.getString("subtype"))
.orElse("");
if ("can_use_tool".equals(subType)) {
try {
return processPermissionResponse(jsonObject, sessionEventConsumers);
} catch (IOException e) {
log.error("Failed to process permission response", e);
return false;
}
} else {
CLIControlResponse<?> cliControlResponse = sessionEventConsumers.onControlRequest(this,
jsonObject.to(new TypeReference<CLIControlRequest<?>>() {}));
if (cliControlResponse != null) {
try {
transport.inputNoWaitResponse(cliControlResponse.toString());
} catch (Exception e) {
log.error("Failed to process control response", e);
return false;
}
}
return false;
}
}
private boolean processPermissionResponse(JSONObject jsonObject, SessionEventConsumers sessionEventConsumers) throws IOException {
CLIControlRequest<CLIControlPermissionRequest> permissionRequest = jsonObject.to(new TypeReference<CLIControlRequest<CLIControlPermissionRequest>>() {});
Behavior behavior = Optional.ofNullable(sessionEventConsumers.onPermissionRequest(this, permissionRequest))
.map(b -> {
if (b instanceof Allow) {
Allow allow = (Allow) b;
if (allow.getUpdatedInput() == null) {
allow.setUpdatedInput(permissionRequest.getRequest().getInput());
}
}
return b;
})
.orElse(Behavior.defaultBehavior());
CLIControlResponse<CLIControlPermissionResponse> permissionResponse = new CLIControlResponse<>();
permissionResponse.createResponse().setResponse(new CLIControlPermissionResponse().setBehavior(behavior)).setRequestId(permissionRequest.getRequestId());
String permissionMessage = permissionResponse.toString();
log.debug("send permission message to agent: {}", permissionMessage);
transport.inputNoWaitResponse(permissionMessage);
return false;
}
}

View File

@@ -1,15 +1,29 @@
package com.alibaba.qwen.code.cli.session.event;
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
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.CLIControlPermissionRequest;
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.Session;
public interface SessionEventConsumers {
void onSystemMessage(SDKSystemMessage systemMessage);
void onSystemMessage(Session session, SDKSystemMessage systemMessage);
void onResultMessage(SDKResultMessage resultMessage);
void onResultMessage(Session session, SDKResultMessage resultMessage);
void onAssistantMessage(SDKAssistantMessage assistantMessage);
void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage);
void onOtherMessage(String message);
void onUserMessage(Session session, SDKUserMessage userMessage);
void onOtherMessage(Session session, String message);
void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse);
CLIControlResponse<?> onControlRequest(Session session, CLIControlRequest<?> cliControlRequest);
Behavior onPermissionRequest(Session session, CLIControlRequest<CLIControlPermissionRequest> permissionRequest);
}

View File

@@ -1,23 +1,47 @@
package com.alibaba.qwen.code.cli.session.event;
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
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.CLIControlPermissionRequest;
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.Session;
public class SessionEventSimpleConsumers implements SessionEventConsumers {
@Override
public void onSystemMessage(SDKSystemMessage systemMessage) {
public void onSystemMessage(Session session, SDKSystemMessage systemMessage) {
}
@Override
public void onResultMessage(SDKResultMessage resultMessage) {
public void onResultMessage(Session session, SDKResultMessage resultMessage) {
}
@Override
public void onAssistantMessage(SDKAssistantMessage assistantMessage) {
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
}
@Override
public void onOtherMessage(String message) {
public void onUserMessage(Session session, SDKUserMessage userMessage) {
}
@Override
public void onOtherMessage(Session session, String message) {
}
@Override
public void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse) {
}
@Override
public CLIControlResponse<?> onControlRequest(Session session, CLIControlRequest<?> cliControlRequest) {
return new CLIControlResponse<>();
}
@Override
public Behavior onPermissionRequest(Session session, CLIControlRequest<CLIControlPermissionRequest> permissionRequest) {
return Behavior.defaultBehavior();
}
}

View File

@@ -1,22 +0,0 @@
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 SessionControlException extends Exception {
public SessionControlException() {
}
public SessionControlException(String message) {
super(message);
}
public SessionControlException(String message, Throwable cause) {
super(message, cause);
}
public SessionControlException(Throwable cause) {
super(cause);
}
public SessionControlException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,22 +0,0 @@
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

@@ -6,6 +6,10 @@ import java.util.concurrent.TimeoutException;
import java.util.function.Function;
public interface Transport {
TransportOptions getTransportOptions();
void start() throws IOException;
void close() throws IOException;
boolean isAvailable();

View File

@@ -3,6 +3,8 @@ package com.alibaba.qwen.code.cli.transport;
import java.util.List;
import java.util.Map;
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
public class TransportOptions implements Cloneable {
private String pathToQwenExecutable;
private String cwd;
@@ -17,6 +19,7 @@ public class TransportOptions implements Cloneable {
private Boolean includePartialMessages;
private Long turnTimeoutMs;
private Long messageTimeoutMs;
private String resumeSessionId;
public String getPathToQwenExecutable() {
return pathToQwenExecutable;
@@ -122,6 +125,14 @@ public class TransportOptions implements Cloneable {
this.messageTimeoutMs = messageTimeoutMs;
}
public String getResumeSessionId() {
return resumeSessionId;
}
public void setResumeSessionId(String resumeSessionId) {
this.resumeSessionId = resumeSessionId;
}
@Override
public TransportOptions clone() {
try {

View File

@@ -22,10 +22,9 @@ import java.util.function.Function;
public class ProcessTransport implements Transport {
private static final Logger log = LoggerFactory.getLogger(ProcessTransport.class);
TransportOptionsAdapter transportOptionsAdapter;
protected final Long turnTimeoutMs;
protected final Long messageTimeoutMs;
private final TransportOptions transportOptions;
protected Long turnTimeoutMs;
protected Long messageTimeoutMs;
protected Process process;
protected BufferedWriter processInput;
@@ -37,13 +36,21 @@ public class ProcessTransport implements Transport {
}
public ProcessTransport(TransportOptions transportOptions) throws IOException {
this.transportOptionsAdapter = new TransportOptionsAdapter(transportOptions);
turnTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeoutMs();
messageTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getMessageTimeoutMs();
this.transportOptions = transportOptions;
start();
}
protected void start() throws IOException {
@Override
public TransportOptions getTransportOptions() {
return transportOptions;
}
@Override
public void start() throws IOException {
TransportOptionsAdapter transportOptionsAdapter = new TransportOptionsAdapter(transportOptions);
this.turnTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeoutMs();
this.messageTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getMessageTimeoutMs();
String[] commandArgs = transportOptionsAdapter.buildCommandArgs();
log.debug("trans to command args: {}", transportOptionsAdapter);

View File

@@ -72,6 +72,11 @@ class TransportOptionsAdapter {
if (transportOptions.getIncludePartialMessages() != null && transportOptions.getIncludePartialMessages()) {
args.add("--include-partial-messages");
}
if (StringUtils.isNotBlank(transportOptions.getResumeSessionId())) {
args.add("--resume");
args.add(transportOptions.getResumeSessionId());
}
return args.toArray(new String[] {});
}

View File

@@ -3,17 +3,23 @@ package com.alibaba.qwen.code.cli.session;
import java.io.IOException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow;
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
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.protocol.message.control.CLIControlPermissionRequest;
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.event.SessionEventSimpleConsumers;
import com.alibaba.qwen.code.cli.session.exception.SessionCloseException;
import com.alibaba.qwen.code.cli.session.exception.SessionControlException;
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.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,34 +27,111 @@ import org.slf4j.LoggerFactory;
class SessionTest {
private static final Logger log = LoggerFactory.getLogger(SessionTest.class);
@Test
void sendPrompt() throws IOException, SessionStartException, SessionSendPromptException, SessionCloseException {
void setPermissionModeSuccessfully() throws IOException, SessionControlException, SessionSendPromptException {
Transport transport = new ProcessTransport();
Session session = new Session(transport);
session.sendPrompt("hello world", new SessionEventSimpleConsumers() {
session.setPermissionMode(PermissionMode.YOLO);
session.sendPrompt("in the dir src/test/temp/, create file empty file test.touch", new SessionEventSimpleConsumers());
session.setPermissionMode(PermissionMode.PLAN);
session.sendPrompt("rename test.touch to test_rename.touch", new SessionEventSimpleConsumers());
session.setPermissionMode(PermissionMode.AUTO_EDIT);
session.sendPrompt("rename test.touch to test_rename.touch", new SessionEventSimpleConsumers());
session.sendPrompt("rename test.touch to test_rename.touch again user will allow", new SessionEventSimpleConsumers() {
public Behavior onPermissionRequest(Session session, CLIControlRequest<CLIControlPermissionRequest> permissionRequest) {
log.info("permissionRequest: {}", permissionRequest);
return new Allow().setUpdatedInput(permissionRequest.getRequest().getInput());
}
});
session.close();
}
@Test
void sendPromptAndSetModelSuccessfully() throws IOException, SessionControlException, SessionSendPromptException {
Transport transport = new ProcessTransport();
Session session = new Session(transport);
session.setModel("qwen3-coder-flash");
writeSplitLine("setModel 1 end");
session.sendPrompt("hello world", new SessionEventSimpleConsumers());
writeSplitLine("prompt 1 end");
session.setModel("qwen3-coder-plus");
writeSplitLine("setModel 1 end");
session.sendPrompt("查看下当前目录有多少个文件", new SessionEventSimpleConsumers());
writeSplitLine("prompt 2 end");
session.setModel("qwen3-max");
writeSplitLine("setModel 1 end");
session.sendPrompt("查看下当前目录有多少个xml文件", new SessionEventSimpleConsumers());
writeSplitLine("prompt 3 end");
session.close();
}
@Test
void sendPromptAndInterruptContinueSuccessfully() throws IOException, SessionControlException, SessionSendPromptException {
Transport transport = new ProcessTransport();
Session session = new Session(transport);
SessionEventConsumers sessionEventConsumers = new SessionEventSimpleConsumers() {
@Override
public void onSystemMessage(SDKSystemMessage systemMessage) {
public void onSystemMessage(Session session, SDKSystemMessage systemMessage) {
log.info("systemMessage: {}", systemMessage);
}
@Override
public void onResultMessage(SDKResultMessage resultMessage) {
public void onResultMessage(Session session, SDKResultMessage resultMessage) {
log.info("resultMessage: {}", resultMessage);
}
@Override
public void onAssistantMessage(SDKAssistantMessage assistantMessage) {
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
log.info("assistantMessage: {}", assistantMessage);
try {
session.interrupt();
} catch (SessionControlException e) {
log.error("interrupt error", e);
}
}
@Override
public void onOtherMessage(String message) {
public void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse) {
log.info("cliControlResponse: {}", cliControlResponse);
}
@Override
public void onOtherMessage(Session session, String message) {
log.info("otherMessage: {}", message);
}
});
};
session.sendPrompt("查看下当前目录有多少个文件", sessionEventConsumers);
writeSplitLine("prompt 1 end");
session.continueSession();
session.sendPrompt("hello world", sessionEventConsumers);
writeSplitLine("prompt 2 end");
session.continueSession();
session.sendPrompt("当前目录有多少个java文件", sessionEventConsumers);
writeSplitLine("prompt 3 end");
session.close();
}
public void writeSplitLine(String line) {
log.info("{} {}",line, StringUtils.repeat("=", 300));
}
@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}}}";

View File

@@ -1,5 +1,7 @@
package com.alibaba.qwen.code.cli.transport;
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

6
packages/sdk-java/todo Normal file
View File

@@ -0,0 +1,6 @@
1、event timeout
2、mcp servers
3、errorHandle
4、review QwenCli
https://github.com/QwenLM/qwen-code/tree/main/packages/sdk-typescript#custom-permission-handler