mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-01-10 10:59:14 +00:00
add permission
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.cli.transport;
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
public enum PermissionMode {
|
||||
DEFAULT("default"),
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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[] {});
|
||||
}
|
||||
|
||||
|
||||
@@ -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}}}";
|
||||
|
||||
@@ -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
6
packages/sdk-java/todo
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user