Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.alibaba.dashscope.agentstudio.resource.Deployments;
import com.alibaba.dashscope.agentstudio.resource.Environments;
import com.alibaba.dashscope.agentstudio.resource.Files;
import com.alibaba.dashscope.agentstudio.resource.Security;
import com.alibaba.dashscope.agentstudio.resource.Sessions;
import com.alibaba.dashscope.agentstudio.resource.Skills;
import com.alibaba.dashscope.agentstudio.resource.Vaults;
Expand All @@ -25,6 +26,7 @@ public class AgentStudioClient implements Closeable {
private final Vaults vaults;
private final Files files;
private final WebhookEndpoints webhookEndpoints;
private final Security security;
private final String baseUrl;

public AgentStudioClient() {
Expand Down Expand Up @@ -55,6 +57,7 @@ public AgentStudioClient(
this.skills = new Skills(this.baseUrl, connectionOptions, apiKey, this.files);
this.vaults = new Vaults(this.baseUrl, connectionOptions, apiKey);
this.webhookEndpoints = new WebhookEndpoints(this.baseUrl, connectionOptions, apiKey);
this.security = new Security(this.baseUrl, connectionOptions, apiKey);
}

public static Builder builder() {
Expand Down Expand Up @@ -134,6 +137,10 @@ public WebhookEndpoints webhookEndpoints() {
return webhookEndpoints;
}

public Security security() {
return security;
}

public String getBaseUrl() {
return baseUrl;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ private AgentStudioConstants() {}
public static final class SSEEventType {
public static final String MESSAGE = "message";
public static final String INTERRUPT = "interrupt";
public static final String TOOL_CONFIRMATION = "tool_confirmation";
public static final String TOOL_APPROVAL_RESPONSE = "tool_approval_response";
public static final String FUNCTION_CALL_OUTPUT = "function_call_output";
public static final String TOOL_CALL_OUTPUT = "tool_call_output";
public static final String DEFINE_OUTCOME = "define_outcome";
Expand All @@ -34,6 +34,7 @@ public static final class SSEEventType {
public static final String REASONING = "reasoning";
public static final String MCP_CALL = "mcp_call";
public static final String MCP_CALL_OUTPUT = "mcp_call_output";
public static final String TOOL_APPROVAL_REQUEST = "tool_approval_request";
public static final String SESSION_STATUS = "session_status";
public static final String ERROR = "error";
public static final String SESSION_UPDATED = "session_updated";
Expand Down Expand Up @@ -72,12 +73,30 @@ private BlockType() {}
public static final class SessionStatusValue {
public static final String IDLE = "idle";
public static final String RUNNING = "running";
public static final String RESCHEDULING = "rescheduling";
public static final String RESCHEDULED = "rescheduled";
public static final String TERMINATED = "terminated";
public static final String DELETED = "deleted";

private SessionStatusValue() {}
}

/** {@code stop_reason.type} values carried by {@code session_status} idle events. */
public static final class StopReasonType {
public static final String END_TURN = "end_turn";
public static final String REQUIRES_ACTION = "requires_action";
public static final String RETRIES_EXHAUSTED = "retries_exhausted";

private StopReasonType() {}
}

/** Tool approval policy ({@code permission_policy.type}). */
public static final class PermissionPolicyType {
public static final String ALWAYS_ALLOW = "always_allow";
public static final String ALWAYS_ASK = "always_ask";

private PermissionPolicyType() {}
}

public static String resolveBaseUrl(String workspace, String region) {
String ws = workspace;
if (ws == null || ws.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,22 @@ public static JsonObject userInterrupt(String sessionThreadId) {
return event;
}

public static JsonObject userToolConfirmation(String toolUseId, String result) {
return userToolConfirmation(toolUseId, result, null, null);
}

public static JsonObject userToolConfirmation(
String toolUseId, String result, String denyMessage) {
return userToolConfirmation(toolUseId, result, denyMessage, null);
}

public static JsonObject userToolConfirmation(
String toolUseId, String result, String denyMessage, String sessionThreadId) {
/**
* Submit a tool approval ruling for an {@code always_ask} tool call.
*
* <p>{@code result} must be {@code "allow"} or {@code "deny"}; {@code denyMessage} is optional
* and only meaningful when denying. The approval identity is the {@code (batchId, callId)}
* composite key — {@code callId} may be reused across turns, so never match on it alone. Approval
* responses target the primary thread only (no {@code sessionThreadId}).
*/
public static JsonObject userToolApprovalResponse(
String batchId, String callId, String result, String denyMessage) {
if (!"allow".equals(result) && !"deny".equals(result)) {
throw new IllegalArgumentException("tool_confirmation result must be 'allow' or 'deny'");
throw new IllegalArgumentException("result must be 'allow' or 'deny'");
}
JsonObject data = new JsonObject();
data.addProperty("call_id", toolUseId);
data.addProperty("batch_id", batchId);
data.addProperty("call_id", callId);
data.addProperty("result", result);
if (denyMessage != null && "deny".equals(result)) {
data.addProperty("deny_message", denyMessage);
Expand All @@ -100,12 +100,9 @@ public static JsonObject userToolConfirmation(
JsonArray content = new JsonArray();
content.add(dataBlock);
JsonObject event = new JsonObject();
event.addProperty("type", "tool_confirmation");
event.addProperty("type", "tool_approval_response");
event.addProperty("role", "user");
event.add("content", content);
if (sessionThreadId != null) {
event.addProperty("session_thread_id", sessionThreadId);
}
return event;
}

Expand Down
121 changes: 120 additions & 1 deletion src/main/java/com/alibaba/dashscope/agentstudio/message/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.alibaba.dashscope.utils.JsonUtils;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Data;
Expand Down Expand Up @@ -54,14 +55,28 @@ public class Message {
@SerializedName("message")
private String message;

// ── delta-protocol frame fields (opt-in via event_deltas) ──
@SerializedName("error")
private JsonObject errorPayload;

@SerializedName("event")
private JsonObject eventPayload;

@SerializedName("event_id")
private String deltaEventId;

@SerializedName("delta")
private JsonObject deltaPayload;

/**
* Extract stop_reason from a session_status event's data block.
*
* <p>The SSE session_status idle event carries stop_reason in its data: {@code
* {"stop_reason":{"type":"end_turn"},"session_status":"idle"}}. This helper parses it out so
* callers don't need to manually dig through DataContent blocks.
*
* @return stop_reason (has type, eventIds), or null if not a session_status event or absent
* @return stop_reason (has type, pendingBatchId, pendingCallIds), or null if not a session_status
* event or absent
*/
public Session.StopReason getStopReason() {
if (!"session_status".equals(type) || content == null) return null;
Expand All @@ -75,4 +90,108 @@ public Session.StopReason getStopReason() {
}
return null;
}

// typed data accessors

/** The first content block's {@code data} payload, for events that carry one. */
public JsonObject getData() {
if (content == null) return null;
for (ContentBlock block : content) {
if (block instanceof ContentBlock.DataContent) {
return ((ContentBlock.DataContent) block).getData();
}
}
return null;
}

/** {@code tool_approval_request} payload: batch_id/call_id/name/arguments/tool_type. */
public JsonObject getToolApprovalRequest() {
if (!"tool_approval_request".equals(type)) return null;
return getData();
}

/** {@code {code, message}} from {@code type=error} events. */
public JsonObject getError() {
if (!"error".equals(type)) return null;
return errorPayload;
}

/** Suspend signal {@code {batch_id, call_ids}} from metadata while the approval barrier is up. */
@SuppressWarnings("unchecked")
public Map<String, Object> getPendingToolApprovals() {
if (metadata == null) return null;
Object pending = metadata.get("pending_tool_approvals");
return pending instanceof Map ? (Map<String, Object>) pending : null;
}

/** {@code model_request_end} usage payload. */
public JsonObject getModelRequestEnd() {
if (!"model_request_end".equals(type)) return null;
return getData();
}

/** {@code outcome_evaluation} payload (outcome_id/iteration/phase/result). */
public JsonObject getOutcomeEvaluation() {
if (!"outcome_evaluation".equals(type)) return null;
return getData();
}

/** {@code thread_status} payload (session_thread_id/agent_name/thread_status/stop_reason). */
public JsonObject getThreadStatus() {
if (!"thread_status".equals(type)) return null;
return getData();
}

/** {@code thread_created} payload (session_thread_id/agent_name). */
public JsonObject getThreadCreated() {
if (!"thread_created".equals(type)) return null;
return getData();
}

/** {@code session_updated} payload (title/session_metadata/agent). */
public JsonObject getSessionUpdated() {
if (!"session_updated".equals(type)) return null;
return getData();
}

/** Sub-agent routing from {@code thread_message_sent}/{@code received} metadata. */
public Map<String, Object> getThreadMessageRouting() {
if (!"thread_message_sent".equals(type) && !"thread_message_received".equals(type)) {
return null;
}
if (metadata == null) return null;
Map<String, Object> routing = new HashMap<>();
for (String key :
new String[] {
"to_session_thread_id", "to_agent_name",
"from_session_thread_id", "from_agent_name"
}) {
if (metadata.containsKey(key)) routing.put(key, metadata.get(key));
}
return routing.isEmpty() ? null : routing;
}

// ── delta-protocol frames (opt-in via event_deltas) ──

/** {@code {id, type}} from {@code event_start} frames (preview, no content). */
public JsonObject getEventStart() {
if (!"event_start".equals(type)) return null;
return eventPayload;
}

/** Delta payload from {@code event_delta} frames. */
public JsonObject getEventDelta() {
if (!"event_delta".equals(type)) return null;
return deltaPayload;
}

/** Incremental text from an {@code event_delta}'s {@code content_delta}. */
public String getDeltaText() {
if (!"event_delta".equals(type) || deltaPayload == null) return null;
if (deltaPayload.has("content") && deltaPayload.get("content").isJsonObject()) {
JsonObject c = deltaPayload.getAsJsonObject("content");
if (c.has("text")) return c.get("text").getAsString();
}
return null;
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/alibaba/dashscope/agentstudio/model/Configs.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public static class ToolConfig {
public static class DefaultConfig {
@SerializedName("enabled")
private Boolean enabled;

@SerializedName("permission_policy")
private PermissionPolicy permissionPolicy;
}

@Data
Expand All @@ -44,6 +47,9 @@ public static class PerToolConfig {

@SerializedName("enabled")
private Boolean enabled;

@SerializedName("permission_policy")
private PermissionPolicy permissionPolicy;
}
}

Expand Down Expand Up @@ -97,6 +103,18 @@ public static class RosterEntry {

@SerializedName("version")
private Integer version;

@SerializedName("name")
private String name;

@SerializedName("description")
private String description;
}
}

@Data
public static class PermissionPolicy {
@SerializedName("type")
private String type;
}
}
60 changes: 58 additions & 2 deletions src/main/java/com/alibaba/dashscope/agentstudio/model/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,64 @@ public static class StopReason {
@SerializedName("type")
private String type;

@SerializedName("event_ids")
private List<String> eventIds;
@SerializedName("pending_batch_id")
private String pendingBatchId;

@SerializedName("pending_call_ids")
private List<String> pendingCallIds;
}

@Data
@EqualsAndHashCode(callSuper = true)
public static class SessionResource extends FlattenResultBase {
@SerializedName("id")
private String id;

@SerializedName("type")
private String type;

@SerializedName("file_id")
private String fileId;

@SerializedName("mount_path")
private String mountPath;

@SerializedName("created_at")
private String createdAt;

@SerializedName("updated_at")
private String updatedAt;
}

@Data
@EqualsAndHashCode(callSuper = true)
public static class SessionThread extends FlattenResultBase {
@SerializedName("id")
private String id;

@SerializedName("type")
private String type;

@SerializedName("session_id")
private String sessionId;

@SerializedName("parent_thread_id")
private String parentThreadId;

@SerializedName("agent")
private Map<String, Object> agent;

@SerializedName("status")
private String status;

@SerializedName("created_at")
private String createdAt;

@SerializedName("updated_at")
private String updatedAt;

@SerializedName("archived_at")
private String archivedAt;
}

@Data
Expand Down
Loading
Loading