diff --git a/samples/AgentStudioSimple.java b/samples/AgentStudioSimple.java new file mode 100644 index 0000000..25a243b --- /dev/null +++ b/samples/AgentStudioSimple.java @@ -0,0 +1,85 @@ +import com.alibaba.dashscope.agentstudio.AgentStudioClient; +import com.alibaba.dashscope.agentstudio.message.ClientEvents; +import com.alibaba.dashscope.agentstudio.message.ContentBlock; +import com.alibaba.dashscope.agentstudio.message.Message; +import com.alibaba.dashscope.agentstudio.model.Agent; +import com.alibaba.dashscope.agentstudio.model.Session; +import com.alibaba.dashscope.agentstudio.param.AgentCreateParam; +import com.alibaba.dashscope.agentstudio.param.SessionCreateParam; +import com.alibaba.dashscope.agentstudio.resource.AgentStudioEventStream; +import java.util.Collections; + +/** + * AgentStudio quick start: create agent, session, send message, stream reply. + * + *

Prerequisites: set DASHSCOPE_API_KEY env var, and either DASHSCOPE_WORKSPACE env var or pass + * workspace to the constructor. + * + *

+ * export DASHSCOPE_API_KEY=sk-xxx
+ * export DASHSCOPE_WORKSPACE=ws_xxxxxxxxxxxx
+ * 
+ */ +public class AgentStudioSimple { + + public static void main(String[] args) throws Exception { + // Option 1: apiKey + workspace (production) + AgentStudioClient client = new AgentStudioClient("sk-xxx", "ws_xxxxxxxxxxxx"); + + // Option 2: all from env vars (DASHSCOPE_API_KEY + DASHSCOPE_WORKSPACE) + // AgentStudioClient client = new AgentStudioClient(); + + // Option 3: custom base URL + // AgentStudioClient client = AgentStudioClient.builder() + // .apiKey("sk-xxx") + // .baseUrl("https://your-custom-host/api/v1/agentstudio") + // .build(); + + // 1. Create Agent + Agent agent = + client + .agents() + .create( + AgentCreateParam.builder() + .name("demo-agent") + .model("qwen-plus") + .systemPrompt("你是一个简洁的助手。") + .build()); + System.out.println("Agent: " + agent.getId()); + + // 2. Create Session + Session session = + client.sessions().create(SessionCreateParam.builder().agent(agent.getId()).build()); + System.out.println("Session: " + session.getId()); + + // 3. Send message + client + .sessions() + .events() + .send(session.getId(), Collections.singletonList(ClientEvents.userMessage("你好"))); + + // 4. Stream reply + try (AgentStudioEventStream stream = client.sessions().events().stream(session.getId())) { + for (Message event : stream) { + if ("message".equals(event.getType()) && event.getContent() != null) { + for (ContentBlock block : event.getContent()) { + if (block instanceof ContentBlock.Text) { + System.out.print(((ContentBlock.Text) block).getText()); + } + } + } else if ("session_status".equals(event.getType())) { + Session.StopReason stopReason = event.getStopReason(); + if (stopReason != null) { + System.out.println("\nstop_reason: " + stopReason.getType()); + } + break; + } + } + } + + // Cleanup + client.sessions().delete(session.getId()); + client.agents().archive(agent.getId()); + client.close(); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioClient.java b/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioClient.java new file mode 100644 index 0000000..98bad68 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioClient.java @@ -0,0 +1,144 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio; + +import com.alibaba.dashscope.agentstudio.resource.Agents; +import com.alibaba.dashscope.agentstudio.resource.Environments; +import com.alibaba.dashscope.agentstudio.resource.Files; +import com.alibaba.dashscope.agentstudio.resource.Sessions; +import com.alibaba.dashscope.agentstudio.resource.Skills; +import com.alibaba.dashscope.agentstudio.resource.Vaults; +import com.alibaba.dashscope.protocol.ConnectionOptions; +import java.io.Closeable; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +public class AgentStudioClient implements Closeable { + private final Agents agents; + private final Sessions sessions; + private final Environments environments; + private final Skills skills; + private final Vaults vaults; + private final Files files; + private final String baseUrl; + + public AgentStudioClient() { + this(null, null, null, null, null); + } + + public AgentStudioClient(String workspace) { + this(null, null, workspace, null, null); + } + + public AgentStudioClient(String apiKey, String workspace) { + this(apiKey, null, workspace, null, null); + } + + public AgentStudioClient( + String apiKey, + String baseUrl, + String workspace, + String region, + ConnectionOptions connectionOptions) { + this.baseUrl = resolveBaseUrl(baseUrl, workspace, region); + this.agents = new Agents(this.baseUrl, connectionOptions, apiKey); + this.sessions = new Sessions(this.baseUrl, connectionOptions, apiKey); + this.environments = new Environments(this.baseUrl, connectionOptions, apiKey); + this.files = new Files(this.baseUrl, connectionOptions, apiKey); + this.skills = new Skills(this.baseUrl, connectionOptions, apiKey, this.files); + this.vaults = new Vaults(this.baseUrl, connectionOptions, apiKey); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String apiKey; + private String baseUrl; + private String workspace; + private String region; + private ConnectionOptions connectionOptions; + + public Builder apiKey(String apiKey) { + this.apiKey = apiKey; + return this; + } + + public Builder baseUrl(String baseUrl) { + this.baseUrl = baseUrl; + return this; + } + + public Builder workspace(String workspace) { + this.workspace = workspace; + return this; + } + + public Builder region(String region) { + this.region = region; + return this; + } + + public Builder connectionOptions(ConnectionOptions connectionOptions) { + this.connectionOptions = connectionOptions; + return this; + } + + public AgentStudioClient build() { + return new AgentStudioClient(apiKey, baseUrl, workspace, region, connectionOptions); + } + } + + public Agents agents() { + return agents; + } + + public Sessions sessions() { + return sessions; + } + + public Environments environments() { + return environments; + } + + public Skills skills() { + return skills; + } + + public Vaults vaults() { + return vaults; + } + + public Files files() { + return files; + } + + public String getBaseUrl() { + return baseUrl; + } + + public CompletableFuture async(Supplier supplier) { + return CompletableFuture.supplyAsync(supplier); + } + + @Override + public void close() { + sessions.events().close(); + skills.close(); + files.close(); + } + + private static String resolveBaseUrl(String explicitUrl, String workspace, String region) { + if (explicitUrl != null && !explicitUrl.isEmpty()) { + return explicitUrl; + } + String envUrl = System.getenv(AgentStudioConstants.ENV_BASE_URL); + if (envUrl == null || envUrl.isEmpty()) { + envUrl = System.getenv(AgentStudioConstants.ENV_BASE_URL_ALT); + } + if (envUrl != null && !envUrl.isEmpty()) { + return envUrl; + } + return AgentStudioConstants.resolveBaseUrl(workspace, region); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioConstants.java b/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioConstants.java new file mode 100644 index 0000000..ec1008a --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioConstants.java @@ -0,0 +1,143 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio; + +import com.alibaba.dashscope.base.HalfDuplexParamBase; +import com.alibaba.dashscope.protocol.GeneralServiceOption; +import com.alibaba.dashscope.protocol.HttpMethod; +import com.alibaba.dashscope.protocol.Protocol; +import com.alibaba.dashscope.protocol.StreamingMode; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +public final class AgentStudioConstants { + public static final String BASE_URL_TEMPLATE = + "https://%s.%s.maas.aliyuncs.com/api/v1/agentstudio"; + public static final String DEFAULT_REGION = "cn-beijing"; + public static final int DEFAULT_TIMEOUT_MS = 600_000; + public static final int DEFAULT_CONNECT_TIMEOUT_MS = 10_000; + public static final String ENV_BASE_URL = "DASHSCOPE_AGENTSTUDIO_URL"; + public static final String ENV_BASE_URL_ALT = "AGENTSTUDIO_URL"; + public static final String ENV_WORKSPACE = "DASHSCOPE_WORKSPACE"; + public static final String ENV_REGION = "DASHSCOPE_API_REGION"; + + 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 FUNCTION_CALL_OUTPUT = "function_call_output"; + public static final String TOOL_CALL_OUTPUT = "tool_call_output"; + public static final String DEFINE_OUTCOME = "define_outcome"; + public static final String FUNCTION_CALL = "function_call"; + public static final String TOOL_CALL = "tool_call"; + 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 SESSION_STATUS = "session_status"; + public static final String ERROR = "error"; + public static final String SESSION_UPDATED = "session_updated"; + public static final String OUTCOME_EVALUATION = "outcome_evaluation"; + + private SSEEventType() {} + } + + public static final class MessageRole { + public static final String USER = "user"; + public static final String ASSISTANT = "assistant"; + public static final String TOOL = "tool"; + + private MessageRole() {} + } + + public static final class BlockType { + public static final String TEXT = "text"; + public static final String IMAGE = "image"; + public static final String AUDIO = "audio"; + public static final String DATA = "data"; + public static final String FILE = "file"; + public static final String REFUSAL = "refusal"; + public static final String ERROR = "error"; + + 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 TERMINATED = "terminated"; + + private SessionStatusValue() {} + } + + public static String resolveBaseUrl(String workspace, String region) { + String ws = workspace; + if (ws == null || ws.isEmpty()) { + ws = System.getenv(ENV_WORKSPACE); + } + if (ws == null || ws.isEmpty()) { + throw new IllegalArgumentException( + "workspace is required. Pass it to the constructor, " + + "set DASHSCOPE_WORKSPACE env var, or set DASHSCOPE_AGENTSTUDIO_URL."); + } + String r = region; + if (r == null || r.isEmpty()) { + r = System.getenv(ENV_REGION); + } + if (r == null || r.isEmpty()) { + r = DEFAULT_REGION; + } + return String.format(BASE_URL_TEMPLATE, ws, r); + } + + /** + * Build a {@link GeneralServiceOption} for an agentstudio endpoint. When {@code baseUrl} is + * non-null it overrides the global default; otherwise the request falls back to {@code + * Constants.baseHttpApiUrl} via {@code GeneralApi}. + */ + public static GeneralServiceOption newServiceOption( + HttpMethod method, String path, String baseUrl) { + GeneralServiceOption opt = + GeneralServiceOption.builder() + .protocol(Protocol.HTTP) + .httpMethod(method) + .streamingMode(StreamingMode.OUT) + .path(path) + .build(); + if (baseUrl != null) { + opt.setBaseHttpUrl(baseUrl); + } + return opt; + } + + /** + * Stamp the client's instance apiKey onto a param if the caller didn't set one. The global + * fallback chain in {@link com.alibaba.dashscope.utils.ApiKey#getApiKey} still applies when + * {@code apiKey} is null. + */ + public static

P withApiKey(String apiKey, P param) { + if (apiKey != null && param.getApiKey() == null) { + param.setApiKey(apiKey); + } + return param; + } + + /** + * Append {@code key=value} (URL-encoded) to {@code sb} if value is non-null. Joins with {@literal + * &}. + */ + public static void appendParam(StringBuilder sb, String key, Object value) { + if (value == null) { + return; + } + if (sb.length() > 0) { + sb.append("&"); + } + try { + sb.append(key).append("=").append(URLEncoder.encode(value.toString(), "UTF-8")); + } catch (UnsupportedEncodingException e) { + sb.append(key).append("=").append(value); + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/message/ClientEvents.java b/src/main/java/com/alibaba/dashscope/agentstudio/message/ClientEvents.java new file mode 100644 index 0000000..249edbb --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/message/ClientEvents.java @@ -0,0 +1,194 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.message; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.util.List; +import java.util.Map; + +public final class ClientEvents { + + private ClientEvents() {} + + public static JsonObject userMessage(String text) { + return userMessage(text, null, null); + } + + public static JsonObject userMessage(List blocks) { + return userMessage(blocks, null, null); + } + + public static JsonObject userMessage(String text, String sessionThreadId) { + return userMessage(text, sessionThreadId, null); + } + + public static JsonObject userMessage( + String text, String sessionThreadId, Map metadata) { + JsonArray content = new JsonArray(); + JsonObject textBlock = new JsonObject(); + textBlock.addProperty("type", "text"); + textBlock.addProperty("text", text); + content.add(textBlock); + return buildUserMessage(content, sessionThreadId, metadata); + } + + public static JsonObject userMessage( + List blocks, String sessionThreadId, Map metadata) { + JsonArray content = new JsonArray(); + for (JsonObject block : blocks) { + content.add(block); + } + return buildUserMessage(content, sessionThreadId, metadata); + } + + private static JsonObject buildUserMessage( + JsonArray content, String sessionThreadId, Map metadata) { + JsonObject event = new JsonObject(); + event.addProperty("type", "message"); + event.addProperty("role", "user"); + event.add("content", content); + if (sessionThreadId != null) { + event.addProperty("session_thread_id", sessionThreadId); + } + if (metadata != null && !metadata.isEmpty()) { + JsonObject meta = new JsonObject(); + for (Map.Entry e : metadata.entrySet()) { + meta.addProperty(e.getKey(), e.getValue()); + } + event.add("metadata", meta); + } + return event; + } + + public static JsonObject userInterrupt() { + return userInterrupt(null); + } + + public static JsonObject userInterrupt(String sessionThreadId) { + JsonObject event = new JsonObject(); + event.addProperty("type", "interrupt"); + event.addProperty("role", "user"); + if (sessionThreadId != null) { + event.addProperty("session_thread_id", 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) { + if (!"allow".equals(result) && !"deny".equals(result)) { + throw new IllegalArgumentException("tool_confirmation result must be 'allow' or 'deny'"); + } + JsonObject data = new JsonObject(); + data.addProperty("call_id", toolUseId); + data.addProperty("result", result); + if (denyMessage != null && "deny".equals(result)) { + data.addProperty("deny_message", denyMessage); + } + JsonObject dataBlock = new JsonObject(); + dataBlock.addProperty("type", "data"); + dataBlock.add("data", data); + JsonArray content = new JsonArray(); + content.add(dataBlock); + JsonObject event = new JsonObject(); + event.addProperty("type", "tool_confirmation"); + event.addProperty("role", "user"); + event.add("content", content); + if (sessionThreadId != null) { + event.addProperty("session_thread_id", sessionThreadId); + } + return event; + } + + public static JsonObject userCustomToolResult( + String customToolUseId, String resultContent, boolean isError) { + return userCustomToolResult(customToolUseId, resultContent, isError, null); + } + + public static JsonObject userCustomToolResult( + String customToolUseId, String resultContent, boolean isError, String sessionThreadId) { + JsonObject data = new JsonObject(); + data.addProperty("call_id", customToolUseId); + data.addProperty("output", resultContent); + JsonObject dataBlock = new JsonObject(); + dataBlock.addProperty("type", "data"); + dataBlock.add("data", data); + JsonArray content = new JsonArray(); + content.add(dataBlock); + JsonObject event = new JsonObject(); + event.addProperty("type", "function_call_output"); + event.addProperty("role", "tool"); + event.add("content", content); + event.addProperty("is_error", isError); + if (sessionThreadId != null) { + event.addProperty("session_thread_id", sessionThreadId); + } + return event; + } + + public static JsonObject userToolResult(String toolUseId, String resultContent, boolean isError) { + return userToolResult(toolUseId, resultContent, isError, null); + } + + public static JsonObject userToolResult( + String toolUseId, String resultContent, boolean isError, String sessionThreadId) { + JsonObject data = new JsonObject(); + data.addProperty("call_id", toolUseId); + data.addProperty("output", resultContent); + JsonObject dataBlock = new JsonObject(); + dataBlock.addProperty("type", "data"); + dataBlock.add("data", data); + JsonArray content = new JsonArray(); + content.add(dataBlock); + JsonObject event = new JsonObject(); + event.addProperty("type", "tool_call_output"); + event.addProperty("role", "tool"); + event.add("content", content); + event.addProperty("is_error", isError); + if (sessionThreadId != null) { + event.addProperty("session_thread_id", sessionThreadId); + } + return event; + } + + public static JsonObject userDefineOutcome( + String description, String rubric, Integer maxIterations) { + return userDefineOutcome(description, rubric, maxIterations, null); + } + + public static JsonObject userDefineOutcome( + String description, String rubric, Integer maxIterations, String sessionThreadId) { + JsonObject data = new JsonObject(); + if (description != null) { + data.addProperty("description", description); + } + if (rubric != null) { + data.addProperty("rubric", rubric); + } + if (maxIterations != null) { + data.addProperty("max_iterations", maxIterations); + } + JsonObject dataBlock = new JsonObject(); + dataBlock.addProperty("type", "data"); + dataBlock.add("data", data); + JsonArray content = new JsonArray(); + content.add(dataBlock); + JsonObject event = new JsonObject(); + event.addProperty("type", "define_outcome"); + event.addProperty("role", "user"); + event.add("content", content); + if (sessionThreadId != null) { + event.addProperty("session_thread_id", sessionThreadId); + } + return event; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/message/ContentBlock.java b/src/main/java/com/alibaba/dashscope/agentstudio/message/ContentBlock.java new file mode 100644 index 0000000..23abff6 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/message/ContentBlock.java @@ -0,0 +1,160 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.message; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +public abstract class ContentBlock { + @SerializedName("type") + private String type; + + @Data + @EqualsAndHashCode(callSuper = true) + public static class Text extends ContentBlock { + @SerializedName("text") + private String text; + + @SerializedName("citations") + private List citations; + } + + @Data + public static class Citation { + @SerializedName("url") + private String url; + + @SerializedName("title") + private String title; + + @SerializedName("cited_text") + private String citedText; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class Image extends ContentBlock { + @SerializedName("image_url") + private String imageUrl; + + @SerializedName("file_id") + private String fileId; + + @SerializedName("image_data") + private String imageData; + + @SerializedName("media_type") + private String mediaType; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class Audio extends ContentBlock { + @SerializedName("data") + private String data; + + @SerializedName("format") + private String format; + + @SerializedName("file_id") + private String fileId; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class DataContent extends ContentBlock { + @SerializedName("data") + private JsonObject data; + + @SerializedName("name") + private String name; + + @SerializedName("title") + private String title; + + @SerializedName("context") + private String context; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class File extends ContentBlock { + @SerializedName("file_url") + private String fileUrl; + + @SerializedName("file_id") + private String fileId; + + @SerializedName("file_data") + private String fileData; + + @SerializedName("media_type") + private String mediaType; + + @SerializedName("filename") + private String filename; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class Refusal extends ContentBlock { + @SerializedName("refusal") + private String refusal; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class Error extends ContentBlock { + @SerializedName("error_code") + private String errorCode; + + @SerializedName("message") + private String message; + } + + public static class Deserializer implements JsonDeserializer { + private static final Map> REGISTRY = new HashMap<>(); + private static final Gson PLAIN_GSON = new GsonBuilder().create(); + + static { + REGISTRY.put("text", Text.class); + REGISTRY.put("image", Image.class); + REGISTRY.put("audio", Audio.class); + REGISTRY.put("data", DataContent.class); + REGISTRY.put("file", File.class); + REGISTRY.put("refusal", Refusal.class); + REGISTRY.put("error", Error.class); + } + + @Override + public ContentBlock deserialize( + JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (!json.isJsonObject()) { + return null; + } + JsonObject obj = json.getAsJsonObject(); + String type = null; + if (obj.has("type") && obj.get("type").isJsonPrimitive()) { + type = obj.get("type").getAsString(); + } + Class clazz = type != null ? REGISTRY.get(type) : null; + if (clazz == null) { + clazz = DataContent.class; + } + return PLAIN_GSON.fromJson(obj, clazz); + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/message/Message.java b/src/main/java/com/alibaba/dashscope/agentstudio/message/Message.java new file mode 100644 index 0000000..2db5eac --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/message/Message.java @@ -0,0 +1,74 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.message; + +import com.alibaba.dashscope.agentstudio.model.Session; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import com.google.gson.annotations.SerializedName; +import java.util.List; +import java.util.Map; +import lombok.Data; + +@Data +public class Message { + @SerializedName("object") + private String object; + + @SerializedName("status") + private String status; + + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("role") + private String role; + + @SerializedName("content") + private List content; + + @SerializedName("metadata") + private Map metadata; + + @SerializedName("is_error") + private Boolean isError; + + @SerializedName("created_at") + private String createdAt; + + @SerializedName("sequence_number") + private Long sequenceNumber; + + @SerializedName("session_thread_id") + private String sessionThreadId; + + @SerializedName("code") + private String code; + + @SerializedName("message") + private String message; + + /** + * Extract stop_reason from a session_status event's data block. + * + *

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 + */ + public Session.StopReason getStopReason() { + if (!"session_status".equals(type) || content == null) return null; + for (ContentBlock block : content) { + if (block instanceof ContentBlock.DataContent) { + JsonObject data = ((ContentBlock.DataContent) block).getData(); + if (data != null && data.has("stop_reason") && !data.get("stop_reason").isJsonNull()) { + return JsonUtils.fromJson(data.get("stop_reason"), Session.StopReason.class); + } + } + } + return null; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/Agent.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/Agent.java new file mode 100644 index 0000000..fc05b9f --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/Agent.java @@ -0,0 +1,66 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.agentstudio.model.Configs.McpServerConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.ModelConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.SkillConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.ToolConfig; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.annotations.SerializedName; +import java.util.List; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Agent extends FlattenResultBase { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("name") + private String name; + + @SerializedName("description") + private String description; + + @SerializedName("model") + private ModelConfig model; + + @SerializedName("system") + private String system; + + @SerializedName("tools") + private List tools; + + @SerializedName("skills") + private List skills; + + @SerializedName("mcp_servers") + private List mcpServers; + + @SerializedName("version") + private Integer version; + + @SerializedName("metadata") + private Map metadata; + + @SerializedName("workspace_id") + private String workspaceId; + + @SerializedName("archived_at") + private String archivedAt; + + @SerializedName("created_at") + private String createdAt; + + @SerializedName("updated_at") + private String updatedAt; + + public String getSystemPrompt() { + return system; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/AgentStudioDeletionStatus.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/AgentStudioDeletionStatus.java new file mode 100644 index 0000000..1e33668 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/AgentStudioDeletionStatus.java @@ -0,0 +1,21 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class AgentStudioDeletionStatus extends FlattenResultBase { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + public boolean isDeleted() { + return type != null && type.contains("deleted"); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/AgentStudioFile.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/AgentStudioFile.java new file mode 100644 index 0000000..1d570c2 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/AgentStudioFile.java @@ -0,0 +1,35 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class AgentStudioFile extends FlattenResultBase { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("filename") + private String filename; + + @SerializedName("downloadable") + private Boolean downloadable; + + @SerializedName("mime_type") + private String mimeType; + + @SerializedName("size_bytes") + private Long sizeBytes; + + @SerializedName("status") + private String status; + + @SerializedName("created_at") + private String createdAt; +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/AgentVersion.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/AgentVersion.java new file mode 100644 index 0000000..c388a43 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/AgentVersion.java @@ -0,0 +1,56 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.agentstudio.model.Configs.McpServerConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.ModelConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.SkillConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.ToolConfig; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.annotations.SerializedName; +import java.util.List; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class AgentVersion extends FlattenResultBase { + @SerializedName("agent_id") + private String agentId; + + @SerializedName("version") + private Integer version; + + @SerializedName("config") + private AgentVersionConfig config; + + @SerializedName("created_at") + private String createdAt; + + @Data + public static class AgentVersionConfig { + @SerializedName("name") + private String name; + + @SerializedName("description") + private String description; + + @SerializedName("model") + private ModelConfig model; + + @SerializedName("system") + private String system; + + @SerializedName("tools") + private List tools; + + @SerializedName("skills") + private List skills; + + @SerializedName("mcp_servers") + private List mcpServers; + + @SerializedName("metadata") + private Map metadata; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/Configs.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/Configs.java new file mode 100644 index 0000000..7c515c1 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/Configs.java @@ -0,0 +1,73 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.google.gson.annotations.SerializedName; +import java.util.List; +import lombok.Data; + +public final class Configs { + private Configs() {} + + @Data + public static class ModelConfig { + @SerializedName("id") + private String id; + + @SerializedName("name") + private String name; + } + + @Data + public static class ToolConfig { + @SerializedName("type") + private String type; + + @SerializedName("mcp_server_name") + private String mcpServerName; + + @SerializedName("default_config") + private DefaultConfig defaultConfig; + + @SerializedName("configs") + private List configs; + + @Data + public static class DefaultConfig { + @SerializedName("enabled") + private Boolean enabled; + } + + @Data + public static class PerToolConfig { + @SerializedName("name") + private String name; + + @SerializedName("enabled") + private Boolean enabled; + } + } + + @Data + public static class SkillConfig { + @SerializedName("type") + private String type; + + @SerializedName("skill_id") + private String skillId; + + @SerializedName("version") + private String version; + } + + @Data + public static class McpServerConfig { + @SerializedName("type") + private String type; + + @SerializedName("name") + private String name; + + @SerializedName("url") + private String url; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/Credential.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/Credential.java new file mode 100644 index 0000000..a15968a --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/Credential.java @@ -0,0 +1,75 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.annotations.SerializedName; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Credential extends FlattenResultBase { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("vault_id") + private String vaultId; + + @SerializedName("display_name") + private String displayName; + + @SerializedName("auth") + private CredentialAuth auth; + + @SerializedName("metadata") + private Map metadata; + + @SerializedName("archived_at") + private String archivedAt; + + @SerializedName("created_at") + private String createdAt; + + @SerializedName("updated_at") + private String updatedAt; + + @Data + public static class Networking { + @SerializedName("type") + private String type; + } + + @Data + public static class CredentialAuth { + @SerializedName("type") + private String type; + + @SerializedName("token") + private String token; + + @SerializedName("secret_name") + private String secretName; + + @SerializedName("secret_value") + private String secretValue; + + @SerializedName("mcp_server_url") + private String mcpServerUrl; + + @SerializedName("access_token") + private String accessToken; + + @SerializedName("expires_at") + private String expiresAt; + + @SerializedName("refresh") + private String refresh; + + @SerializedName("networking") + private Networking networking; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/Environment.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/Environment.java new file mode 100644 index 0000000..c8e0547 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/Environment.java @@ -0,0 +1,85 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.annotations.SerializedName; +import java.util.List; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Environment extends FlattenResultBase { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("name") + private String name; + + @SerializedName("description") + private String description; + + @SerializedName("status") + private String status; + + @SerializedName("config") + private Config config; + + @SerializedName("metadata") + private Map metadata; + + @SerializedName("scope") + private String scope; + + @SerializedName("archived_at") + private String archivedAt; + + @SerializedName("created_at") + private String createdAt; + + @SerializedName("updated_at") + private String updatedAt; + + @Data + public static class Config { + @SerializedName("type") + private String type; + + @SerializedName("networking") + private Networking networking; + + @SerializedName("packages") + private Packages packages; + + @Data + public static class Networking { + @SerializedName("type") + private String type; + } + + @Data + public static class Packages { + @SerializedName("apt") + private List apt; + + @SerializedName("gem") + private List gem; + + @SerializedName("pip") + private List pip; + + @SerializedName("cargo") + private List cargo; + + @SerializedName("go") + private List go; + + @SerializedName("npm") + private List npm; + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/Session.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/Session.java new file mode 100644 index 0000000..b645cff --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/Session.java @@ -0,0 +1,137 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.agentstudio.model.Configs.ModelConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.ToolConfig; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.JsonObject; +import com.google.gson.annotations.SerializedName; +import java.util.List; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Session extends FlattenResultBase { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("title") + private String title; + + @SerializedName("agent") + private SessionAgent agent; + + @SerializedName("environment_id") + private String environmentId; + + @SerializedName("status") + private String status; + + @SerializedName("stop_reason") + private StopReason stopReason; + + @SerializedName("resources") + private List resources; + + @SerializedName("metadata") + private Map metadata; + + @SerializedName("vault_ids") + private List vaultIds; + + @SerializedName("stats") + private Stats stats; + + @SerializedName("usage") + private Usage usage; + + @SerializedName("archived_at") + private String archivedAt; + + @SerializedName("created_at") + private String createdAt; + + @SerializedName("updated_at") + private String updatedAt; + + public String getAgentId() { + if (agent != null) return agent.getId(); + return null; + } + + public Integer getAgentVersion() { + if (agent != null) return agent.getVersion(); + return null; + } + + @Data + public static class SessionAgent { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("version") + private Integer version; + + @SerializedName("name") + private String name; + + @SerializedName("description") + private String description; + + @SerializedName("model") + private ModelConfig model; + + @SerializedName("system") + private String system; + + @SerializedName("tools") + private List tools; + } + + @Data + public static class StopReason { + @SerializedName("type") + private String type; + + @SerializedName("event_ids") + private List eventIds; + } + + @Data + public static class Usage { + @SerializedName("input_tokens") + private Long inputTokens; + + @SerializedName("output_tokens") + private Long outputTokens; + + @SerializedName("cache_creation_input_tokens") + private Long cacheCreationInputTokens; + + @SerializedName("cache_read_input_tokens") + private Long cacheReadInputTokens; + + @SerializedName("cache_creation") + private Long cacheCreation; + + @SerializedName("speed") + private Double speed; + } + + @Data + public static class Stats { + @SerializedName("active_seconds") + private Double activeSeconds; + + @SerializedName("duration_seconds") + private Double durationSeconds; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/Skill.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/Skill.java new file mode 100644 index 0000000..ea6b930 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/Skill.java @@ -0,0 +1,56 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Skill extends FlattenResultBase { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("name") + private String name; + + @SerializedName("description") + private String description; + + @SerializedName("source") + private String source; + + @SerializedName("status") + private String status; + + @SerializedName("latest_version") + private String latestVersion; + + @SerializedName("version") + private String version; + + @SerializedName("file_id") + private String fileId; + + @SerializedName("scope") + private Scope scope; + + @SerializedName("created_at") + private String createdAt; + + @SerializedName("updated_at") + private String updatedAt; + + @Data + public static class Scope { + @SerializedName("type") + private String type; + + @SerializedName("id") + private String id; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/SkillVersion.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/SkillVersion.java new file mode 100644 index 0000000..f0c4d1f --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/SkillVersion.java @@ -0,0 +1,42 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class SkillVersion extends FlattenResultBase { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("skill_id") + private String skillId; + + @SerializedName("name") + private String name; + + @SerializedName("description") + private String description; + + @SerializedName("version") + private String version; + + @SerializedName("status") + private String status; + + @SerializedName("additional_properties") + private JsonElement additionalProperties; + + @SerializedName("created_at") + private String createdAt; + + @SerializedName("updated_at") + private String updatedAt; +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/Vault.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/Vault.java new file mode 100644 index 0000000..0157f40 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/Vault.java @@ -0,0 +1,33 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.model; + +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.annotations.SerializedName; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Vault extends FlattenResultBase { + @SerializedName("id") + private String id; + + @SerializedName("type") + private String type; + + @SerializedName("display_name") + private String displayName; + + @SerializedName("metadata") + private Map metadata; + + @SerializedName("archived_at") + private String archivedAt; + + @SerializedName("created_at") + private String createdAt; + + @SerializedName("updated_at") + private String updatedAt; +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/pagination/CursorPage.java b/src/main/java/com/alibaba/dashscope/agentstudio/pagination/CursorPage.java new file mode 100644 index 0000000..4e875ad --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/pagination/CursorPage.java @@ -0,0 +1,71 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.pagination; + +import com.alibaba.dashscope.common.FlattenResultBase; +import com.google.gson.annotations.SerializedName; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class CursorPage extends FlattenResultBase implements Iterable { + @SerializedName("data") + private List data; + + @SerializedName("next_page") + private String nextPage; + + @EqualsAndHashCode.Exclude + private transient Function>> fetchNext; + + public boolean hasNext() { + return nextPage != null && fetchNext != null; + } + + public CompletableFuture> getNext() { + if (!hasNext()) { + return CompletableFuture.completedFuture(null); + } + return fetchNext.apply(nextPage); + } + + @Override + public Iterator iterator() { + return new Iterator() { + private CursorPage currentPage = CursorPage.this; + private int index = 0; + + @Override + public boolean hasNext() { + if (currentPage == null) { + return false; + } + while (true) { + if (currentPage.data != null && index < currentPage.data.size()) { + return true; + } + if (!currentPage.hasNext()) { + currentPage = null; + return false; + } + currentPage = currentPage.getNext().join(); + if (currentPage == null) { + return false; + } + index = 0; + } + } + + @Override + public T next() { + if (!hasNext()) throw new NoSuchElementException(); + return currentPage.data.get(index++); + } + }; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/AgentCreateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/AgentCreateParam.java new file mode 100644 index 0000000..867e6cf --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/AgentCreateParam.java @@ -0,0 +1,59 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.model.Configs.McpServerConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.SkillConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.ToolConfig; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.List; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class AgentCreateParam extends FlattenHalfDuplexParamBase { + @NonNull private String name; + @NonNull private String model; + @Default private String description = null; + @Default private String systemPrompt = null; + @Default private List tools = null; + @Default private List mcpServers = null; + @Default private List skills = null; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + body.addProperty("name", name); + JsonObject modelObj = new JsonObject(); + modelObj.addProperty("id", model); + body.add("model", modelObj); + if (description != null) { + body.addProperty("description", description); + } + if (systemPrompt != null) { + body.addProperty("system", systemPrompt); + } + if (tools != null) { + body.add("tools", JsonUtils.toJsonElement(tools)); + } + if (mcpServers != null) { + body.add("mcp_servers", JsonUtils.toJsonElement(mcpServers)); + } + if (skills != null) { + body.add("skills", JsonUtils.toJsonElement(skills)); + } + if (metadata != null && !metadata.isEmpty()) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/AgentListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/AgentListParam.java new file mode 100644 index 0000000..a6652a4 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/AgentListParam.java @@ -0,0 +1,32 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonObject; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class AgentListParam extends FlattenHalfDuplexParamBase { + @Default private Integer limit = null; + @Default private String page = null; + @Default private Boolean includeArchived = null; + + @Override + public JsonObject getHttpBody() { + return new JsonObject(); + } + + public String toQueryString() { + StringBuilder sb = new StringBuilder(); + AgentStudioConstants.appendParam(sb, "limit", limit); + AgentStudioConstants.appendParam(sb, "page", page); + AgentStudioConstants.appendParam(sb, "include_archived", includeArchived); + return sb.toString(); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/AgentUpdateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/AgentUpdateParam.java new file mode 100644 index 0000000..fdc89af --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/AgentUpdateParam.java @@ -0,0 +1,78 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.model.Configs.McpServerConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.SkillConfig; +import com.alibaba.dashscope.agentstudio.model.Configs.ToolConfig; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.List; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class AgentUpdateParam extends FlattenHalfDuplexParamBase { + /** Required: must equal the server's current (latest) version, or the update fails with 409. */ + @Default private Integer version = null; + + @Default private String name = null; + @Default private String model = null; + @Default private String description = null; + @Default private String systemPrompt = null; + @Default private List tools = null; + @Default private List mcpServers = null; + @Default private List skills = null; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + if (version != null) { + body.addProperty("version", version); + } + if (name != null) { + body.addProperty("name", name); + } + if (model != null) { + JsonObject modelObj = new JsonObject(); + modelObj.addProperty("id", model); + body.add("model", modelObj); + } + if (description != null) { + body.addProperty("description", description); + } + if (systemPrompt != null) { + body.addProperty("system", systemPrompt); + } + if (tools != null) { + body.add("tools", JsonUtils.toJsonElement(tools)); + } + if (mcpServers != null) { + body.add("mcp_servers", JsonUtils.toJsonElement(mcpServers)); + } + if (skills != null) { + body.add("skills", JsonUtils.toJsonElement(skills)); + } + if (metadata != null && !metadata.isEmpty()) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } + + @Override + public void validate() throws InputRequiredException { + if (version == null) { + throw new InputRequiredException( + "version is required for update. Call agents().retrieve(agentId) first to obtain the" + + " current version (server rejects updates that don't match the latest version)."); + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/CredentialCreateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/CredentialCreateParam.java new file mode 100644 index 0000000..a75f1b2 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/CredentialCreateParam.java @@ -0,0 +1,35 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class CredentialCreateParam extends FlattenHalfDuplexParamBase { + @NonNull private JsonObject auth; + @Default private String displayName = null; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + body.add("auth", auth); + if (displayName != null) { + body.addProperty("display_name", displayName); + } + if (metadata != null && !metadata.isEmpty()) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/CredentialListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/CredentialListParam.java new file mode 100644 index 0000000..6ac1081 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/CredentialListParam.java @@ -0,0 +1,32 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonObject; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class CredentialListParam extends FlattenHalfDuplexParamBase { + @Default private Integer limit = null; + @Default private String page = null; + @Default private Boolean includeArchived = null; + + @Override + public JsonObject getHttpBody() { + return new JsonObject(); + } + + public String toQueryString() { + StringBuilder sb = new StringBuilder(); + AgentStudioConstants.appendParam(sb, "limit", limit); + AgentStudioConstants.appendParam(sb, "page", page); + AgentStudioConstants.appendParam(sb, "include_archived", includeArchived); + return sb.toString(); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/CredentialUpdateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/CredentialUpdateParam.java new file mode 100644 index 0000000..ccb3a02 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/CredentialUpdateParam.java @@ -0,0 +1,36 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class CredentialUpdateParam extends FlattenHalfDuplexParamBase { + @Default private JsonObject auth = null; + @Default private String displayName = null; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + if (auth != null) { + body.add("auth", auth); + } + if (displayName != null) { + body.addProperty("display_name", displayName); + } + if (metadata != null) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/EnvironmentCreateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/EnvironmentCreateParam.java new file mode 100644 index 0000000..d7e0303 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/EnvironmentCreateParam.java @@ -0,0 +1,67 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class EnvironmentCreateParam extends FlattenHalfDuplexParamBase { + @NonNull private String name; + + /** + * Environment runtime configuration (open structure, aligned with server-side {@code + * JSONObject}). + * + *

Known keys: + * + *

    + *
  • {@code type} (String) — hosting type, e.g. "cloud" + *
  • {@code networking} (Map) — e.g. {"type": "unrestricted"} + *
  • {@code packages} (Map<String, List<String>>) — e.g. {"pip": ["numpy"]} + *
+ * + *

Example: + * + *

{@code
+   * Map config = new HashMap<>();
+   * config.put("type", "cloud");
+   * config.put("networking", Collections.singletonMap("type", "unrestricted"));
+   *
+   * Map> packages = new HashMap<>();
+   * packages.put("pip", Arrays.asList("numpy", "pandas"));
+   * config.put("packages", packages);
+   * }
+ */ + @NonNull private Map config; + + @Default private String description = null; + @Default private String scope = null; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + body.addProperty("name", name); + body.add("config", JsonUtils.toJsonElement(config)); + if (description != null) { + body.addProperty("description", description); + } + if (scope != null) { + body.addProperty("scope", scope); + } + if (metadata != null && !metadata.isEmpty()) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/EnvironmentListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/EnvironmentListParam.java new file mode 100644 index 0000000..e94a98a --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/EnvironmentListParam.java @@ -0,0 +1,32 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonObject; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class EnvironmentListParam extends FlattenHalfDuplexParamBase { + @Default private Integer limit = null; + @Default private String page = null; + @Default private Boolean includeArchived = null; + + @Override + public JsonObject getHttpBody() { + return new JsonObject(); + } + + public String toQueryString() { + StringBuilder sb = new StringBuilder(); + AgentStudioConstants.appendParam(sb, "limit", limit); + AgentStudioConstants.appendParam(sb, "page", page); + AgentStudioConstants.appendParam(sb, "include_archived", includeArchived); + return sb.toString(); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/EnvironmentUpdateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/EnvironmentUpdateParam.java new file mode 100644 index 0000000..7079c9b --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/EnvironmentUpdateParam.java @@ -0,0 +1,44 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class EnvironmentUpdateParam extends FlattenHalfDuplexParamBase { + @Default private String name = null; + @Default private String description = null; + @Default private Map config = null; + @Default private String scope = null; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + if (name != null) { + body.addProperty("name", name); + } + if (description != null) { + body.addProperty("description", description); + } + if (config != null) { + body.add("config", JsonUtils.toJsonElement(config)); + } + if (scope != null) { + body.addProperty("scope", scope); + } + if (metadata != null) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/FileListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/FileListParam.java new file mode 100644 index 0000000..ed675f2 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/FileListParam.java @@ -0,0 +1,32 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonObject; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class FileListParam extends FlattenHalfDuplexParamBase { + @Default private Integer limit = null; + @Default private String page = null; + @Default private String scopeId = null; + + @Override + public JsonObject getHttpBody() { + return new JsonObject(); + } + + public String toQueryString() { + StringBuilder sb = new StringBuilder(); + AgentStudioConstants.appendParam(sb, "limit", limit); + AgentStudioConstants.appendParam(sb, "page", page); + AgentStudioConstants.appendParam(sb, "scope_id", scopeId); + return sb.toString(); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionCreateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionCreateParam.java new file mode 100644 index 0000000..3de391d --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionCreateParam.java @@ -0,0 +1,44 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.List; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class SessionCreateParam extends FlattenHalfDuplexParamBase { + @NonNull private String agent; + @Default private String environmentId = null; + @Default private String title = null; + @Default private List> resources = null; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + body.addProperty("agent", agent); + if (environmentId != null) { + body.addProperty("environment_id", environmentId); + } + if (title != null) { + body.addProperty("title", title); + } + if (resources != null && !resources.isEmpty()) { + body.add("resources", JsonUtils.toJsonElement(resources)); + } + if (metadata != null && !metadata.isEmpty()) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionEventListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionEventListParam.java new file mode 100644 index 0000000..218e328 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionEventListParam.java @@ -0,0 +1,45 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonObject; +import java.util.List; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class SessionEventListParam extends FlattenHalfDuplexParamBase { + @Default private List types = null; + @Default private String createdAtGt = null; + @Default private String createdAtGte = null; + @Default private String createdAtLt = null; + @Default private String createdAtLte = null; + @Default private Integer limit = null; + @Default private String order = null; + @Default private String page = null; + + @Override + public JsonObject getHttpBody() { + return new JsonObject(); + } + + public String toQueryString() { + StringBuilder sb = new StringBuilder(); + if (types != null && !types.isEmpty()) { + AgentStudioConstants.appendParam(sb, "types", String.join(",", types)); + } + AgentStudioConstants.appendParam(sb, "created_at[gt]", createdAtGt); + AgentStudioConstants.appendParam(sb, "created_at[gte]", createdAtGte); + AgentStudioConstants.appendParam(sb, "created_at[lt]", createdAtLt); + AgentStudioConstants.appendParam(sb, "created_at[lte]", createdAtLte); + AgentStudioConstants.appendParam(sb, "limit", limit); + AgentStudioConstants.appendParam(sb, "order", order); + AgentStudioConstants.appendParam(sb, "page", page); + return sb.toString(); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionEventSendParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionEventSendParam.java new file mode 100644 index 0000000..bf08c15 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionEventSendParam.java @@ -0,0 +1,30 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class SessionEventSendParam extends FlattenHalfDuplexParamBase { + @NonNull private List input; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + JsonArray arr = new JsonArray(); + for (JsonObject event : input) { + arr.add(event); + } + body.add("input", arr); + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionListParam.java new file mode 100644 index 0000000..251285e --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionListParam.java @@ -0,0 +1,47 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonObject; +import java.util.List; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class SessionListParam extends FlattenHalfDuplexParamBase { + @Default private Integer limit = null; + @Default private String page = null; + @Default private String agentId = null; + @Default private List statuses = null; + @Default private String createdAtGt = null; + @Default private String createdAtGte = null; + @Default private String createdAtLt = null; + @Default private String createdAtLte = null; + + @Override + public JsonObject getHttpBody() { + return new JsonObject(); + } + + public String toQueryString() { + StringBuilder sb = new StringBuilder(); + AgentStudioConstants.appendParam(sb, "limit", limit); + AgentStudioConstants.appendParam(sb, "page", page); + AgentStudioConstants.appendParam(sb, "agent_id", agentId); + if (statuses != null) { + for (String s : statuses) { + AgentStudioConstants.appendParam(sb, "statuses[]", s); + } + } + AgentStudioConstants.appendParam(sb, "created_at[gt]", createdAtGt); + AgentStudioConstants.appendParam(sb, "created_at[gte]", createdAtGte); + AgentStudioConstants.appendParam(sb, "created_at[lt]", createdAtLt); + AgentStudioConstants.appendParam(sb, "created_at[lte]", createdAtLte); + return sb.toString(); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionUpdateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionUpdateParam.java new file mode 100644 index 0000000..73c9e5a --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/SessionUpdateParam.java @@ -0,0 +1,32 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class SessionUpdateParam extends FlattenHalfDuplexParamBase { + @Default private String title = null; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + if (title != null) { + body.addProperty("title", title); + } + if (metadata != null) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/SkillCreateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/SkillCreateParam.java new file mode 100644 index 0000000..1ab1c4f --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/SkillCreateParam.java @@ -0,0 +1,28 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonObject; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class SkillCreateParam extends FlattenHalfDuplexParamBase { + @Default private String fileId = null; + @Default private String file = null; + @Default private String mimeType = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + if (fileId != null) { + body.addProperty("file_id", fileId); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/SkillListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/SkillListParam.java new file mode 100644 index 0000000..b32a9af --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/SkillListParam.java @@ -0,0 +1,32 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonObject; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class SkillListParam extends FlattenHalfDuplexParamBase { + @Default private String source = null; + @Default private Integer limit = null; + @Default private String page = null; + + @Override + public JsonObject getHttpBody() { + return new JsonObject(); + } + + public String toQueryString() { + StringBuilder sb = new StringBuilder(); + AgentStudioConstants.appendParam(sb, "source", source); + AgentStudioConstants.appendParam(sb, "limit", limit); + AgentStudioConstants.appendParam(sb, "page", page); + return sb.toString(); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/VaultCreateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/VaultCreateParam.java new file mode 100644 index 0000000..aa925cf --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/VaultCreateParam.java @@ -0,0 +1,31 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class VaultCreateParam extends FlattenHalfDuplexParamBase { + @NonNull private String displayName; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + body.addProperty("display_name", displayName); + if (metadata != null && !metadata.isEmpty()) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/VaultListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/VaultListParam.java new file mode 100644 index 0000000..1254ce2 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/VaultListParam.java @@ -0,0 +1,32 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.google.gson.JsonObject; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class VaultListParam extends FlattenHalfDuplexParamBase { + @Default private Integer limit = null; + @Default private String page = null; + @Default private Boolean includeArchived = null; + + @Override + public JsonObject getHttpBody() { + return new JsonObject(); + } + + public String toQueryString() { + StringBuilder sb = new StringBuilder(); + AgentStudioConstants.appendParam(sb, "limit", limit); + AgentStudioConstants.appendParam(sb, "page", page); + AgentStudioConstants.appendParam(sb, "include_archived", includeArchived); + return sb.toString(); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/VaultUpdateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/VaultUpdateParam.java new file mode 100644 index 0000000..b880b50 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/VaultUpdateParam.java @@ -0,0 +1,32 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.param; + +import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.util.Map; +import lombok.Builder.Default; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true) +public class VaultUpdateParam extends FlattenHalfDuplexParamBase { + @Default private String displayName = null; + @Default private Map metadata = null; + + @Override + public JsonObject getHttpBody() { + JsonObject body = new JsonObject(); + if (displayName != null) { + body.addProperty("display_name", displayName); + } + if (metadata != null) { + body.add("metadata", JsonUtils.toJsonElement(metadata)); + } + addExtraBody(body); + return body; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/AgentStudioEventStream.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/AgentStudioEventStream.java new file mode 100644 index 0000000..8b75d6f --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/AgentStudioEventStream.java @@ -0,0 +1,245 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.resource; + +import com.alibaba.dashscope.agentstudio.message.ContentBlock; +import com.alibaba.dashscope.agentstudio.message.Message; +import com.alibaba.dashscope.common.Status; +import com.alibaba.dashscope.exception.ApiException; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.Closeable; +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import okhttp3.sse.EventSources; + +@Slf4j +public class AgentStudioEventStream implements Iterable, Closeable { + private static final Message POISON = new Message(); + private static final Gson GSON = + new GsonBuilder() + .registerTypeAdapter(ContentBlock.class, new ContentBlock.Deserializer()) + .create(); + + private final BlockingQueue queue = new LinkedBlockingQueue<>(); + private final AtomicBoolean closed = new AtomicBoolean(false); + private final EventSource eventSource; + private final long timeoutMs; + + public AgentStudioEventStream(OkHttpClient client, Request request, long timeoutMs) { + this.timeoutMs = timeoutMs; + EventSource.Factory factory = EventSources.createFactory(client); + this.eventSource = + factory.newEventSource( + request, + new EventSourceListener() { + @Override + public void onEvent(EventSource es, String id, String type, String data) { + if (closed.get()) return; + if (data == null || data.isEmpty() || "{}".equals(data.trim())) { + return; + } + try { + Message msg = GSON.fromJson(data, Message.class); + if (msg != null && msg.getType() != null) { + queue.put(msg); + } + } catch (Exception e) { + queue.offer(e); + } + } + + @Override + public void onClosed(EventSource es) { + queue.offer(POISON); + } + + @Override + public void onFailure(EventSource es, Throwable t, Response response) { + if (closed.get()) return; + ApiException wrapped = wrapFailure(t, response); + if (wrapped != null) { + queue.offer(wrapped); + } else { + queue.offer(POISON); + } + } + }); + } + + /** Convert OkHttp's onFailure into an ApiException that preserves HTTP status and body. */ + private static ApiException wrapFailure(Throwable t, Response response) { + if (response == null) { + if (t != null) { + return new ApiException(t instanceof Exception ? (Exception) t : new RuntimeException(t)); + } + return null; + } + int code = response.code(); + String body = ""; + try (ResponseBody rb = response.body()) { + if (rb != null) { + body = rb.string(); + } + } catch (IOException e) { + log.debug("Failed to read SSE failure response body", e); + } + Status status = + Status.builder() + .statusCode(code) + .code(code >= 500 ? "server_error" : code == 401 ? "auth_error" : "http_error") + .message(body.isEmpty() ? "HTTP " + code : body) + .build(); + return new ApiException(status, t); + } + + @Override + public Iterator iterator() { + return new Iterator() { + private Message next; + + @Override + public boolean hasNext() { + if (next != null) return true; + if (closed.get()) return false; + try { + Object item = queue.poll(timeoutMs, TimeUnit.MILLISECONDS); + if (item == null) { + // Differentiate timeout from real end-of-stream: POISON is real EOF, null is timeout. + throw new ApiException( + Status.builder() + .statusCode(-1) + .code("stream_timeout") + .message("No event received within " + timeoutMs + "ms") + .build()); + } + if (item == POISON) { + return false; + } + if (item instanceof Throwable) { + throw new ApiException((Throwable) item); + } + next = (Message) item; + return true; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + @Override + public Message next() { + if (!hasNext()) throw new NoSuchElementException(); + Message result = next; + next = null; + return result; + } + }; + } + + public TextStream textStream() { + return new TextStream(this); + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + eventSource.cancel(); + } + } + + public boolean isClosed() { + return closed.get(); + } + + public static class TextStream implements Iterable, Closeable { + private final AgentStudioEventStream source; + + TextStream(AgentStudioEventStream source) { + this.source = source; + } + + @Override + public Iterator iterator() { + final Iterator msgIter = source.iterator(); + return new Iterator() { + private String next; + private boolean done; + + @Override + public boolean hasNext() { + if (next != null) return true; + if (done) return false; + while (msgIter.hasNext()) { + Message msg = msgIter.next(); + String type = msg.getType(); + if ("session_status".equals(type)) { + String status = extractSessionStatus(msg); + if ("idle".equals(status) + || "terminated".equals(status) + || "rescheduling".equals(status)) { + done = true; + return false; + } + } + if ("message".equals(type) && "assistant".equals(msg.getRole())) { + if (msg.getContent() != null) { + for (ContentBlock block : msg.getContent()) { + if (block instanceof ContentBlock.Text) { + String text = ((ContentBlock.Text) block).getText(); + if (text != null && !text.isEmpty()) { + next = text; + return true; + } + } + } + } + } + } + done = true; + return false; + } + + @Override + public String next() { + if (!hasNext()) throw new NoSuchElementException(); + String result = next; + next = null; + return result; + } + }; + } + + private static String extractSessionStatus(Message msg) { + if (msg.getContent() != null) { + for (ContentBlock block : msg.getContent()) { + if (block instanceof ContentBlock.DataContent) { + ContentBlock.DataContent dataBlock = (ContentBlock.DataContent) block; + if (dataBlock.getData() != null + && dataBlock.getData().has("session_status") + && !dataBlock.getData().get("session_status").isJsonNull()) { + return dataBlock.getData().get("session_status").getAsString(); + } + } + } + } + return null; + } + + @Override + public void close() { + source.close(); + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/Agents.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Agents.java new file mode 100644 index 0000000..63c1a5e --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Agents.java @@ -0,0 +1,202 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.resource; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.agentstudio.model.Agent; +import com.alibaba.dashscope.agentstudio.model.AgentVersion; +import com.alibaba.dashscope.agentstudio.pagination.CursorPage; +import com.alibaba.dashscope.agentstudio.param.AgentCreateParam; +import com.alibaba.dashscope.agentstudio.param.AgentListParam; +import com.alibaba.dashscope.agentstudio.param.AgentUpdateParam; +import com.alibaba.dashscope.api.GeneralApi; +import com.alibaba.dashscope.base.HalfDuplexParamBase; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.alibaba.dashscope.common.GeneralGetParam; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.protocol.ConnectionOptions; +import com.alibaba.dashscope.protocol.GeneralServiceOption; +import com.alibaba.dashscope.protocol.HttpMethod; +import com.alibaba.dashscope.utils.StringUtils; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public final class Agents { + private final GeneralApi api; + private final String baseUrl; + private final String apiKey; + + public Agents(String baseUrl, ConnectionOptions connectionOptions, String apiKey) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.api = connectionOptions != null ? new GeneralApi<>(connectionOptions) : new GeneralApi<>(); + } + + public Agent create(AgentCreateParam param) { + return AsyncHelper.joinAndUnwrap(createAsync(param)); + } + + public Agent retrieve(String agentId) { + return retrieve(agentId, null, null, null); + } + + public Agent retrieve(String agentId, Integer version) { + return retrieve(agentId, version, null, null); + } + + public Agent retrieve(String agentId, String apiKey, Map headers) { + return retrieve(agentId, null, apiKey, headers); + } + + public Agent retrieve( + String agentId, Integer version, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(retrieveAsync(agentId, version, apiKey, headers)); + } + + public Agent update(String agentId, AgentUpdateParam param) { + return AsyncHelper.joinAndUnwrap(updateAsync(agentId, param)); + } + + public CursorPage list(AgentListParam param) { + return AsyncHelper.joinAndUnwrap(listAsync(param)); + } + + public Agent archive(String agentId) { + return AsyncHelper.joinAndUnwrap(archiveAsync(agentId)); + } + + public CursorPage listVersions(String agentId, AgentListParam param) { + return AsyncHelper.joinAndUnwrap(listVersionsAsync(agentId, param)); + } + + public CompletableFuture createAsync(AgentCreateParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption(HttpMethod.POST, "agents", baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Agent.class)); + } + + public CompletableFuture retrieveAsync(String agentId) { + return retrieveAsync(agentId, null, null, null); + } + + public CompletableFuture retrieveAsync(String agentId, Integer version) { + return retrieveAsync(agentId, version, null, null); + } + + public CompletableFuture retrieveAsync( + String agentId, String apiKey, Map headers) { + return retrieveAsync(agentId, null, apiKey, headers); + } + + public CompletableFuture retrieveAsync( + String agentId, Integer version, String apiKey, Map headers) { + if (agentId == null || agentId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("agentId is required!")); + } + String path = StringUtils.format("agents/%s", agentId); + if (version != null) { + path += "?version=" + version; + } + GeneralServiceOption opt = AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Agent.class)); + } + + public CompletableFuture updateAsync(String agentId, AgentUpdateParam param) { + if (agentId == null || agentId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("agentId is required!")); + } + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + try { + param.validate(); + } catch (InputRequiredException e) { + return AsyncHelper.failedFuture(e); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("agents/%s", agentId), baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Agent.class)); + } + + public CompletableFuture> listAsync(AgentListParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + String query = param.toQueryString(); + String path = query.isEmpty() ? "agents" : "agents?" + query; + GeneralServiceOption opt = AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listAsync( + AgentListParam.builder() + .limit(param.getLimit()) + .includeArchived(param.getIncludeArchived()) + .page(cursor) + .build())); + return page; + }); + } + + public CompletableFuture archiveAsync(String agentId) { + if (agentId == null || agentId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("agentId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("agents/%s/archive", agentId), baseUrl); + return AsyncHelper.callAsync( + api, AgentStudioConstants.withApiKey(apiKey, AgentUpdateParam.builder().build()), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Agent.class)); + } + + public CompletableFuture> listVersionsAsync( + String agentId, AgentListParam param) { + if (agentId == null || agentId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("agentId is required!")); + } + String query = param != null ? param.toQueryString() : ""; + String path = StringUtils.format("agents/%s/versions", agentId); + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, query.isEmpty() ? path : path + "?" + query, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listVersionsAsync( + agentId, + AgentListParam.builder() + .limit(param != null ? param.getLimit() : null) + .includeArchived(param != null ? param.getIncludeArchived() : null) + .page(cursor) + .build())); + return page; + }); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/AsyncHelper.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/AsyncHelper.java new file mode 100644 index 0000000..a8c836c --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/AsyncHelper.java @@ -0,0 +1,64 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.resource; + +import com.alibaba.dashscope.api.GeneralApi; +import com.alibaba.dashscope.base.HalfDuplexParamBase; +import com.alibaba.dashscope.common.DashScopeResult; +import com.alibaba.dashscope.common.ResultCallback; +import com.alibaba.dashscope.exception.ApiException; +import com.alibaba.dashscope.protocol.ServiceOption; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +final class AsyncHelper { + + private AsyncHelper() {} + + static CompletableFuture callAsync( + GeneralApi api, HalfDuplexParamBase param, ServiceOption opt) { + CompletableFuture future = new CompletableFuture<>(); + try { + api.call( + param, + opt, + new ResultCallback() { + @Override + public void onEvent(DashScopeResult result) { + future.complete(result); + } + + @Override + public void onComplete() {} + + @Override + public void onError(Exception e) { + future.completeExceptionally(e); + } + }); + } catch (Exception e) { + future.completeExceptionally(e); + } + return future; + } + + static CompletableFuture failedFuture(Throwable ex) { + CompletableFuture f = new CompletableFuture<>(); + f.completeExceptionally(ex); + return f; + } + + static T joinAndUnwrap(CompletableFuture future) { + try { + return future.join(); + } catch (CompletionException e) { + Throwable cause = e.getCause(); + if (cause instanceof ApiException) { + throw (ApiException) cause; + } + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + throw new ApiException(cause); + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/Environments.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Environments.java new file mode 100644 index 0000000..ec7e3f4 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Environments.java @@ -0,0 +1,179 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.resource; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.agentstudio.model.AgentStudioDeletionStatus; +import com.alibaba.dashscope.agentstudio.model.Environment; +import com.alibaba.dashscope.agentstudio.pagination.CursorPage; +import com.alibaba.dashscope.agentstudio.param.EnvironmentCreateParam; +import com.alibaba.dashscope.agentstudio.param.EnvironmentListParam; +import com.alibaba.dashscope.agentstudio.param.EnvironmentUpdateParam; +import com.alibaba.dashscope.api.GeneralApi; +import com.alibaba.dashscope.base.HalfDuplexParamBase; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.alibaba.dashscope.common.GeneralGetParam; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.protocol.ConnectionOptions; +import com.alibaba.dashscope.protocol.GeneralServiceOption; +import com.alibaba.dashscope.protocol.HttpMethod; +import com.alibaba.dashscope.utils.StringUtils; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public final class Environments { + private final GeneralApi api; + private final String baseUrl; + private final String apiKey; + + public Environments(String baseUrl, ConnectionOptions connectionOptions, String apiKey) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.api = connectionOptions != null ? new GeneralApi<>(connectionOptions) : new GeneralApi<>(); + } + + public Environment create(EnvironmentCreateParam param) { + return AsyncHelper.joinAndUnwrap(createAsync(param)); + } + + public Environment retrieve(String environmentId) { + return retrieve(environmentId, null, null); + } + + public Environment retrieve(String environmentId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(retrieveAsync(environmentId, apiKey, headers)); + } + + public Environment update(String environmentId, EnvironmentUpdateParam param) { + return AsyncHelper.joinAndUnwrap(updateAsync(environmentId, param)); + } + + public CursorPage list(EnvironmentListParam param) { + return AsyncHelper.joinAndUnwrap(listAsync(param)); + } + + public Environment archive(String environmentId) { + return AsyncHelper.joinAndUnwrap(archiveAsync(environmentId)); + } + + public AgentStudioDeletionStatus delete(String environmentId) { + return delete(environmentId, null, null); + } + + public AgentStudioDeletionStatus delete( + String environmentId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(deleteAsync(environmentId, apiKey, headers)); + } + + public CompletableFuture createAsync(EnvironmentCreateParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption(HttpMethod.POST, "environments", baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Environment.class)); + } + + public CompletableFuture retrieveAsync(String environmentId) { + return retrieveAsync(environmentId, null, null); + } + + public CompletableFuture retrieveAsync( + String environmentId, String apiKey, Map headers) { + if (environmentId == null || environmentId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("environmentId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, StringUtils.format("environments/%s", environmentId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Environment.class)); + } + + public CompletableFuture updateAsync( + String environmentId, EnvironmentUpdateParam param) { + if (environmentId == null || environmentId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("environmentId is required!")); + } + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("environments/%s", environmentId), baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Environment.class)); + } + + public CompletableFuture> listAsync(EnvironmentListParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + String query = param.toQueryString(); + String path = query.isEmpty() ? "environments" : "environments?" + query; + GeneralServiceOption opt = AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listAsync( + EnvironmentListParam.builder() + .limit(param.getLimit()) + .includeArchived(param.getIncludeArchived()) + .page(cursor) + .build())); + return page; + }); + } + + public CompletableFuture archiveAsync(String environmentId) { + if (environmentId == null || environmentId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("environmentId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("environments/%s/archive", environmentId), baseUrl); + return AsyncHelper.callAsync( + api, + AgentStudioConstants.withApiKey(apiKey, EnvironmentUpdateParam.builder().build()), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Environment.class)); + } + + public CompletableFuture deleteAsync(String environmentId) { + return deleteAsync(environmentId, null, null); + } + + public CompletableFuture deleteAsync( + String environmentId, String apiKey, Map headers) { + if (environmentId == null || environmentId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("environmentId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.DELETE, StringUtils.format("environments/%s", environmentId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, AgentStudioDeletionStatus.class)); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/Files.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Files.java new file mode 100644 index 0000000..8397a0e --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Files.java @@ -0,0 +1,301 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.resource; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.agentstudio.model.AgentStudioDeletionStatus; +import com.alibaba.dashscope.agentstudio.model.AgentStudioFile; +import com.alibaba.dashscope.agentstudio.pagination.CursorPage; +import com.alibaba.dashscope.agentstudio.param.FileListParam; +import com.alibaba.dashscope.api.GeneralApi; +import com.alibaba.dashscope.base.HalfDuplexParamBase; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.alibaba.dashscope.common.GeneralGetParam; +import com.alibaba.dashscope.common.Status; +import com.alibaba.dashscope.exception.ApiException; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.protocol.ConnectionOptions; +import com.alibaba.dashscope.protocol.GeneralServiceOption; +import com.alibaba.dashscope.protocol.HttpMethod; +import com.alibaba.dashscope.utils.ApiKey; +import com.alibaba.dashscope.utils.JsonUtils; +import com.alibaba.dashscope.utils.StringUtils; +import com.google.gson.reflect.TypeToken; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public final class Files implements Closeable { + private final GeneralApi api; + private final String baseUrl; + private final String apiKey; + private final OkHttpClient uploadClient; + + public Files(String baseUrl, ConnectionOptions connectionOptions, String apiKey) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.api = connectionOptions != null ? new GeneralApi<>(connectionOptions) : new GeneralApi<>(); + this.uploadClient = + new OkHttpClient.Builder() + .connectTimeout(AgentStudioConstants.DEFAULT_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .readTimeout(AgentStudioConstants.DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .writeTimeout(AgentStudioConstants.DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .build(); + } + + public AgentStudioFile upload(String filePath, String mimeType) { + return AsyncHelper.joinAndUnwrap(uploadAsync(filePath, mimeType)); + } + + public AgentStudioFile upload(String filename, InputStream inputStream, String mimeType) { + return AsyncHelper.joinAndUnwrap(uploadAsync(filename, inputStream, mimeType)); + } + + public AgentStudioFile retrieve(String fileId) { + return retrieve(fileId, null, null); + } + + public AgentStudioFile retrieve(String fileId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(retrieveAsync(fileId, apiKey, headers)); + } + + public CursorPage list(FileListParam param) { + return AsyncHelper.joinAndUnwrap(listAsync(param)); + } + + public AgentStudioDeletionStatus delete(String fileId) { + return delete(fileId, null, null); + } + + public AgentStudioDeletionStatus delete( + String fileId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(deleteAsync(fileId, apiKey, headers)); + } + + public CompletableFuture uploadAsync(String filePath, String mimeType) { + if (filePath == null || filePath.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("filePath is required!")); + } + File file = new File(filePath); + if (!file.exists() || !file.isFile()) { + return AsyncHelper.failedFuture(new InputRequiredException("file not found: " + filePath)); + } + String mt = mimeType != null ? mimeType : guessContentType(filePath); + RequestBody body = RequestBody.create(MediaType.parse(mt), file); + return uploadRequestAsync(file.getName(), body); + } + + public CompletableFuture uploadAsync( + String filename, InputStream inputStream, String mimeType) { + if (filename == null || filename.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("filename is required!")); + } + if (inputStream == null) { + return AsyncHelper.failedFuture(new InputRequiredException("inputStream is required!")); + } + String mt = mimeType != null ? mimeType : "application/octet-stream"; + RequestBody body = streamingBody(MediaType.parse(mt), inputStream); + CompletableFuture future = uploadRequestAsync(filename, body); + future.whenComplete( + (r, t) -> { + if (t != null) { + try { + inputStream.close(); + } catch (IOException e) { + // ignore + } + } + }); + return future; + } + + public CompletableFuture retrieveAsync(String fileId) { + return retrieveAsync(fileId, null, null); + } + + public CompletableFuture retrieveAsync( + String fileId, String apiKey, Map headers) { + if (fileId == null || fileId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("fileId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, StringUtils.format("files/%s", fileId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, AgentStudioFile.class)); + } + + public CompletableFuture> listAsync(FileListParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + String query = param.toQueryString(); + String path = query.isEmpty() ? "files" : "files?" + query; + GeneralServiceOption opt = AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listAsync( + FileListParam.builder() + .limit(param.getLimit()) + .scopeId(param.getScopeId()) + .page(cursor) + .build())); + return page; + }); + } + + public CompletableFuture deleteAsync(String fileId) { + return deleteAsync(fileId, null, null); + } + + public CompletableFuture deleteAsync( + String fileId, String apiKey, Map headers) { + if (fileId == null || fileId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("fileId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.DELETE, StringUtils.format("files/%s", fileId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, AgentStudioDeletionStatus.class)); + } + + private CompletableFuture uploadRequestAsync( + String filename, RequestBody fileBody) { + String key; + try { + key = ApiKey.getApiKey(this.apiKey); + } catch (Exception e) { + return AsyncHelper.failedFuture(e); + } + MultipartBody multipart = + new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", filename, fileBody) + .build(); + String resolvedBase = resolveUploadBaseUrl(); + String url = resolvedBase + "/files"; + Request request = + new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + key) + .post(multipart) + .build(); + + CompletableFuture future = new CompletableFuture<>(); + uploadClient + .newCall(request) + .enqueue( + new Callback() { + @Override + public void onFailure(Call call, IOException e) { + future.completeExceptionally(new ApiException(e)); + } + + @Override + public void onResponse(Call call, Response response) { + try (Response r = response) { + String body = r.body() != null ? r.body().string() : ""; + if (!r.isSuccessful()) { + future.completeExceptionally( + new ApiException( + Status.builder().statusCode(r.code()).message(body).build())); + return; + } + future.complete( + JsonUtils.fromJson(body.isEmpty() ? "{}" : body, AgentStudioFile.class)); + } catch (Exception e) { + future.completeExceptionally(new ApiException(e)); + } + } + }); + return future; + } + + @Override + public void close() { + uploadClient.dispatcher().executorService().shutdown(); + uploadClient.connectionPool().evictAll(); + } + + private String resolveUploadBaseUrl() { + if (baseUrl != null && !baseUrl.isEmpty()) { + return baseUrl; + } + String envUrl = System.getenv(AgentStudioConstants.ENV_BASE_URL); + if (envUrl == null || envUrl.isEmpty()) { + envUrl = System.getenv(AgentStudioConstants.ENV_BASE_URL_ALT); + } + if (envUrl != null && !envUrl.isEmpty()) { + return envUrl; + } + return AgentStudioConstants.resolveBaseUrl(null, null); + } + + private static String guessContentType(String filePath) { + String lower = filePath.toLowerCase(); + if (lower.endsWith(".zip")) return "application/zip"; + if (lower.endsWith(".pdf")) return "application/pdf"; + if (lower.endsWith(".png")) return "image/png"; + if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg"; + if (lower.endsWith(".gif")) return "image/gif"; + if (lower.endsWith(".txt")) return "text/plain"; + if (lower.endsWith(".json")) return "application/json"; + return "application/octet-stream"; + } + + private static RequestBody streamingBody(MediaType mediaType, InputStream inputStream) { + return new RequestBody() { + @Override + public MediaType contentType() { + return mediaType; + } + + @Override + public boolean isOneShot() { + return true; + } + + @Override + public void writeTo(okio.BufferedSink sink) throws java.io.IOException { + try (InputStream is = inputStream; + okio.Source source = okio.Okio.source(is)) { + sink.writeAll(source); + } + } + }; + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/SessionEvents.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/SessionEvents.java new file mode 100644 index 0000000..cb3efef --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/SessionEvents.java @@ -0,0 +1,172 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.resource; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.agentstudio.message.Message; +import com.alibaba.dashscope.agentstudio.pagination.CursorPage; +import com.alibaba.dashscope.agentstudio.param.SessionEventListParam; +import com.alibaba.dashscope.agentstudio.param.SessionEventSendParam; +import com.alibaba.dashscope.api.GeneralApi; +import com.alibaba.dashscope.base.HalfDuplexParamBase; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.alibaba.dashscope.common.GeneralGetParam; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.exception.NoApiKeyException; +import com.alibaba.dashscope.protocol.ConnectionOptions; +import com.alibaba.dashscope.protocol.GeneralServiceOption; +import com.alibaba.dashscope.protocol.HttpMethod; +import com.alibaba.dashscope.utils.ApiKey; +import com.alibaba.dashscope.utils.Constants; +import com.alibaba.dashscope.utils.StringUtils; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import java.io.Closeable; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import okhttp3.OkHttpClient; +import okhttp3.Request; + +public final class SessionEvents implements Closeable { + private final GeneralApi api; + private final String baseUrl; + private final String apiKey; + private final OkHttpClient streamClientTemplate; + + public SessionEvents(String baseUrl, ConnectionOptions connectionOptions, String apiKey) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.api = connectionOptions != null ? new GeneralApi<>(connectionOptions) : new GeneralApi<>(); + this.streamClientTemplate = + new OkHttpClient.Builder() + .connectTimeout(AgentStudioConstants.DEFAULT_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .readTimeout(AgentStudioConstants.DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .writeTimeout(AgentStudioConstants.DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .build(); + } + + private String resolveStreamBaseUrl() { + if (baseUrl != null && !baseUrl.isEmpty()) { + return baseUrl; + } + String envUrl = System.getenv(AgentStudioConstants.ENV_BASE_URL); + if (envUrl == null || envUrl.isEmpty()) { + envUrl = System.getenv(AgentStudioConstants.ENV_BASE_URL_ALT); + } + if (envUrl != null && !envUrl.isEmpty()) { + return envUrl; + } + return Constants.baseHttpApiUrl; + } + + public JsonObject send(String sessionId, List events) { + return AsyncHelper.joinAndUnwrap(sendAsync(sessionId, events)); + } + + public CursorPage list(String sessionId, SessionEventListParam param) { + return AsyncHelper.joinAndUnwrap(listAsync(sessionId, param)); + } + + public CompletableFuture sendAsync(String sessionId, List events) { + if (sessionId == null || sessionId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("sessionId is required!")); + } + if (events == null || events.isEmpty()) { + return AsyncHelper.failedFuture( + new IllegalArgumentException("events must contain at least 1 entry")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("sessions/%s/events", sessionId), baseUrl); + SessionEventSendParam param = SessionEventSendParam.builder().input(events).build(); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply( + result -> { + Object output = result.getOutput(); + if (output instanceof JsonElement && ((JsonElement) output).isJsonObject()) { + return ((JsonElement) output).getAsJsonObject(); + } + return new JsonObject(); + }); + } + + public CompletableFuture> listAsync( + String sessionId, SessionEventListParam param) { + if (sessionId == null || sessionId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("sessionId is required!")); + } + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + String query = param.toQueryString(); + String path = StringUtils.format("sessions/%s/events", sessionId); + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, query.isEmpty() ? path : path + "?" + query, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listAsync( + sessionId, + SessionEventListParam.builder() + .types(param.getTypes()) + .createdAtGt(param.getCreatedAtGt()) + .createdAtGte(param.getCreatedAtGte()) + .createdAtLt(param.getCreatedAtLt()) + .createdAtLte(param.getCreatedAtLte()) + .limit(param.getLimit()) + .order(param.getOrder()) + .page(cursor) + .build())); + return page; + }); + } + + public AgentStudioEventStream stream(String sessionId) { + return stream(sessionId, AgentStudioConstants.DEFAULT_TIMEOUT_MS); + } + + public AgentStudioEventStream stream(String sessionId, long timeoutMs) { + String url = resolveStreamBaseUrl(); + if (!url.endsWith("/")) { + url += "/"; + } + url += StringUtils.format("sessions/%s/events/stream", sessionId); + + String resolvedKey = resolveApiKey(); + OkHttpClient client = + streamClientTemplate.newBuilder().readTimeout(timeoutMs, TimeUnit.MILLISECONDS).build(); + + Request request = + new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + resolvedKey) + .header("Accept", "text/event-stream") + .get() + .build(); + + return new AgentStudioEventStream(client, request, timeoutMs); + } + + @Override + public void close() { + streamClientTemplate.dispatcher().executorService().shutdown(); + streamClientTemplate.connectionPool().evictAll(); + } + + private String resolveApiKey() { + try { + return ApiKey.getApiKey(this.apiKey); + } catch (NoApiKeyException e) { + throw new IllegalStateException("No API key found. Set DASHSCOPE_API_KEY or pass apiKey.", e); + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/Sessions.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Sessions.java new file mode 100644 index 0000000..9434459 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Sessions.java @@ -0,0 +1,187 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.resource; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.agentstudio.model.AgentStudioDeletionStatus; +import com.alibaba.dashscope.agentstudio.model.Session; +import com.alibaba.dashscope.agentstudio.pagination.CursorPage; +import com.alibaba.dashscope.agentstudio.param.SessionCreateParam; +import com.alibaba.dashscope.agentstudio.param.SessionListParam; +import com.alibaba.dashscope.agentstudio.param.SessionUpdateParam; +import com.alibaba.dashscope.api.GeneralApi; +import com.alibaba.dashscope.base.HalfDuplexParamBase; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.alibaba.dashscope.common.GeneralGetParam; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.protocol.ConnectionOptions; +import com.alibaba.dashscope.protocol.GeneralServiceOption; +import com.alibaba.dashscope.protocol.HttpMethod; +import com.alibaba.dashscope.utils.StringUtils; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public final class Sessions { + private final GeneralApi api; + private final String baseUrl; + private final String apiKey; + private final SessionEvents events; + + public Sessions(String baseUrl, ConnectionOptions connectionOptions, String apiKey) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.api = connectionOptions != null ? new GeneralApi<>(connectionOptions) : new GeneralApi<>(); + this.events = new SessionEvents(baseUrl, connectionOptions, apiKey); + } + + public SessionEvents events() { + return events; + } + + public Session create(SessionCreateParam param) { + return AsyncHelper.joinAndUnwrap(createAsync(param)); + } + + public Session retrieve(String sessionId) { + return retrieve(sessionId, null, null); + } + + public Session retrieve(String sessionId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(retrieveAsync(sessionId, apiKey, headers)); + } + + public Session update(String sessionId, SessionUpdateParam param) { + return AsyncHelper.joinAndUnwrap(updateAsync(sessionId, param)); + } + + public CursorPage list(SessionListParam param) { + return AsyncHelper.joinAndUnwrap(listAsync(param)); + } + + public Session archive(String sessionId) { + return AsyncHelper.joinAndUnwrap(archiveAsync(sessionId)); + } + + public AgentStudioDeletionStatus delete(String sessionId) { + return delete(sessionId, null, null); + } + + public AgentStudioDeletionStatus delete( + String sessionId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(deleteAsync(sessionId, apiKey, headers)); + } + + public CompletableFuture createAsync(SessionCreateParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption(HttpMethod.POST, "sessions", baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Session.class)); + } + + public CompletableFuture retrieveAsync(String sessionId) { + return retrieveAsync(sessionId, null, null); + } + + public CompletableFuture retrieveAsync( + String sessionId, String apiKey, Map headers) { + if (sessionId == null || sessionId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("sessionId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, StringUtils.format("sessions/%s", sessionId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Session.class)); + } + + public CompletableFuture updateAsync(String sessionId, SessionUpdateParam param) { + if (sessionId == null || sessionId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("sessionId is required!")); + } + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("sessions/%s", sessionId), baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Session.class)); + } + + public CompletableFuture> listAsync(SessionListParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + String query = param.toQueryString(); + String path = query.isEmpty() ? "sessions" : "sessions?" + query; + GeneralServiceOption opt = AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listAsync( + SessionListParam.builder() + .limit(param.getLimit()) + .agentId(param.getAgentId()) + .statuses(param.getStatuses()) + .createdAtGt(param.getCreatedAtGt()) + .createdAtGte(param.getCreatedAtGte()) + .createdAtLt(param.getCreatedAtLt()) + .createdAtLte(param.getCreatedAtLte()) + .page(cursor) + .build())); + return page; + }); + } + + public CompletableFuture archiveAsync(String sessionId) { + if (sessionId == null || sessionId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("sessionId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("sessions/%s/archive", sessionId), baseUrl); + return AsyncHelper.callAsync( + api, AgentStudioConstants.withApiKey(apiKey, SessionUpdateParam.builder().build()), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Session.class)); + } + + public CompletableFuture deleteAsync(String sessionId) { + return deleteAsync(sessionId, null, null); + } + + public CompletableFuture deleteAsync( + String sessionId, String apiKey, Map headers) { + if (sessionId == null || sessionId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("sessionId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.DELETE, StringUtils.format("sessions/%s", sessionId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, AgentStudioDeletionStatus.class)); + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/Skills.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Skills.java new file mode 100644 index 0000000..2eee7c0 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Skills.java @@ -0,0 +1,274 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.resource; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.agentstudio.model.AgentStudioDeletionStatus; +import com.alibaba.dashscope.agentstudio.model.Skill; +import com.alibaba.dashscope.agentstudio.model.SkillVersion; +import com.alibaba.dashscope.agentstudio.pagination.CursorPage; +import com.alibaba.dashscope.agentstudio.param.SkillCreateParam; +import com.alibaba.dashscope.agentstudio.param.SkillListParam; +import com.alibaba.dashscope.api.GeneralApi; +import com.alibaba.dashscope.base.HalfDuplexParamBase; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.alibaba.dashscope.common.GeneralGetParam; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.protocol.ConnectionOptions; +import com.alibaba.dashscope.protocol.GeneralServiceOption; +import com.alibaba.dashscope.protocol.HttpMethod; +import com.alibaba.dashscope.utils.StringUtils; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import java.io.Closeable; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public final class Skills implements Closeable { + private final GeneralApi api; + private final String baseUrl; + private final String apiKey; + private final Files files; + + public Skills(String baseUrl, ConnectionOptions connectionOptions, String apiKey, Files files) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.api = connectionOptions != null ? new GeneralApi<>(connectionOptions) : new GeneralApi<>(); + this.files = files; + } + + private CompletableFuture resolveFileId(SkillCreateParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + if (param.getFileId() != null) { + return CompletableFuture.completedFuture(param.getFileId()); + } + if (param.getFile() != null) { + if (files == null) { + return AsyncHelper.failedFuture( + new InputRequiredException( + "File upload requires a configured Skills instance (use AgentStudioClient)")); + } + return files.uploadAsync(param.getFile(), param.getMimeType()).thenApply(f -> f.getId()); + } + return AsyncHelper.failedFuture( + new InputRequiredException("Either fileId or file must be provided")); + } + + public Skill create(SkillCreateParam param) { + return AsyncHelper.joinAndUnwrap(createAsync(param)); + } + + public Skill retrieve(String skillId) { + return retrieve(skillId, null, null); + } + + public Skill retrieve(String skillId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(retrieveAsync(skillId, apiKey, headers)); + } + + public CursorPage list(SkillListParam param) { + return AsyncHelper.joinAndUnwrap(listAsync(param)); + } + + public AgentStudioDeletionStatus delete(String skillId) { + return delete(skillId, null, null); + } + + public AgentStudioDeletionStatus delete( + String skillId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(deleteAsync(skillId, apiKey, headers)); + } + + public SkillVersion createVersion(String skillId, SkillCreateParam param) { + return AsyncHelper.joinAndUnwrap(createVersionAsync(skillId, param)); + } + + public CursorPage listVersions(String skillId, SkillListParam param) { + return AsyncHelper.joinAndUnwrap(listVersionsAsync(skillId, param)); + } + + public SkillVersion retrieveVersion(String skillId, String version) { + return AsyncHelper.joinAndUnwrap(retrieveVersionAsync(skillId, version)); + } + + public JsonObject downloadVersion(String skillId, String version) { + return AsyncHelper.joinAndUnwrap(downloadVersionAsync(skillId, version)); + } + + public CompletableFuture createAsync(SkillCreateParam param) { + return resolveFileId(param) + .thenCompose( + fileId -> { + param.setFileId(fileId); + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption(HttpMethod.POST, "skills", baseUrl); + return AsyncHelper.callAsync( + api, AgentStudioConstants.withApiKey(apiKey, param), opt); + }) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Skill.class)); + } + + public CompletableFuture retrieveAsync(String skillId) { + return retrieveAsync(skillId, null, null); + } + + public CompletableFuture retrieveAsync( + String skillId, String apiKey, Map headers) { + if (skillId == null || skillId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("skillId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, StringUtils.format("skills/%s", skillId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Skill.class)); + } + + public CompletableFuture> listAsync(SkillListParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + String query = param.toQueryString(); + String path = query.isEmpty() ? "skills" : "skills?" + query; + GeneralServiceOption opt = AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listAsync( + SkillListParam.builder() + .source(param.getSource()) + .limit(param.getLimit()) + .page(cursor) + .build())); + return page; + }); + } + + public CompletableFuture deleteAsync(String skillId) { + return deleteAsync(skillId, null, null); + } + + public CompletableFuture deleteAsync( + String skillId, String apiKey, Map headers) { + if (skillId == null || skillId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("skillId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.DELETE, StringUtils.format("skills/%s", skillId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, AgentStudioDeletionStatus.class)); + } + + public CompletableFuture createVersionAsync( + String skillId, SkillCreateParam param) { + if (skillId == null || skillId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("skillId is required!")); + } + return resolveFileId(param) + .thenCompose( + fileId -> { + param.setFileId(fileId); + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("skills/%s/versions", skillId), baseUrl); + return AsyncHelper.callAsync( + api, AgentStudioConstants.withApiKey(apiKey, param), opt); + }) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, SkillVersion.class)); + } + + public CompletableFuture> listVersionsAsync( + String skillId, SkillListParam param) { + if (skillId == null || skillId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("skillId is required!")); + } + String query = param != null ? param.toQueryString() : ""; + String path = StringUtils.format("skills/%s/versions", skillId); + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, query.isEmpty() ? path : path + "?" + query, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listVersionsAsync( + skillId, + SkillListParam.builder() + .source(param != null ? param.getSource() : null) + .limit(param != null ? param.getLimit() : null) + .page(cursor) + .build())); + return page; + }); + } + + public CompletableFuture retrieveVersionAsync(String skillId, String version) { + if (skillId == null || skillId.isEmpty() || version == null || version.isEmpty()) { + return AsyncHelper.failedFuture( + new InputRequiredException("skillId and version are required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, StringUtils.format("skills/%s/versions/%s", skillId, version), baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, SkillVersion.class)); + } + + public CompletableFuture downloadVersionAsync(String skillId, String version) { + if (skillId == null || skillId.isEmpty() || version == null || version.isEmpty()) { + return AsyncHelper.failedFuture( + new InputRequiredException("skillId and version are required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, + StringUtils.format("skills/%s/versions/%s/content", skillId, version), + baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + result -> { + Object output = result.getOutput(); + if (output instanceof JsonElement && ((JsonElement) output).isJsonObject()) { + return ((JsonElement) output).getAsJsonObject(); + } + return new JsonObject(); + }); + } + + @Override + public void close() { + if (files != null) { + files.close(); + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/Vaults.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Vaults.java new file mode 100644 index 0000000..03ab2e7 --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/Vaults.java @@ -0,0 +1,360 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.agentstudio.resource; + +import com.alibaba.dashscope.agentstudio.AgentStudioConstants; +import com.alibaba.dashscope.agentstudio.model.AgentStudioDeletionStatus; +import com.alibaba.dashscope.agentstudio.model.Credential; +import com.alibaba.dashscope.agentstudio.model.Vault; +import com.alibaba.dashscope.agentstudio.pagination.CursorPage; +import com.alibaba.dashscope.agentstudio.param.CredentialCreateParam; +import com.alibaba.dashscope.agentstudio.param.CredentialListParam; +import com.alibaba.dashscope.agentstudio.param.CredentialUpdateParam; +import com.alibaba.dashscope.agentstudio.param.VaultCreateParam; +import com.alibaba.dashscope.agentstudio.param.VaultListParam; +import com.alibaba.dashscope.agentstudio.param.VaultUpdateParam; +import com.alibaba.dashscope.api.GeneralApi; +import com.alibaba.dashscope.base.HalfDuplexParamBase; +import com.alibaba.dashscope.common.FlattenResultBase; +import com.alibaba.dashscope.common.GeneralGetParam; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.protocol.ConnectionOptions; +import com.alibaba.dashscope.protocol.GeneralServiceOption; +import com.alibaba.dashscope.protocol.HttpMethod; +import com.alibaba.dashscope.utils.StringUtils; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public final class Vaults { + private final GeneralApi api; + private final String baseUrl; + private final String apiKey; + private final Credentials credentials; + + public Vaults(String baseUrl, ConnectionOptions connectionOptions, String apiKey) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.api = connectionOptions != null ? new GeneralApi<>(connectionOptions) : new GeneralApi<>(); + this.credentials = new Credentials(); + } + + public Credentials credentials() { + return credentials; + } + + public Vault create(VaultCreateParam param) { + return AsyncHelper.joinAndUnwrap(createAsync(param)); + } + + public Vault retrieve(String vaultId) { + return retrieve(vaultId, null, null); + } + + public Vault retrieve(String vaultId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(retrieveAsync(vaultId, apiKey, headers)); + } + + public Vault update(String vaultId, VaultUpdateParam param) { + return AsyncHelper.joinAndUnwrap(updateAsync(vaultId, param)); + } + + public CursorPage list(VaultListParam param) { + return AsyncHelper.joinAndUnwrap(listAsync(param)); + } + + public Vault archive(String vaultId) { + return AsyncHelper.joinAndUnwrap(archiveAsync(vaultId)); + } + + public AgentStudioDeletionStatus delete(String vaultId) { + return delete(vaultId, null, null); + } + + public AgentStudioDeletionStatus delete( + String vaultId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(deleteAsync(vaultId, apiKey, headers)); + } + + public CompletableFuture createAsync(VaultCreateParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption(HttpMethod.POST, "vaults", baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Vault.class)); + } + + public CompletableFuture retrieveAsync(String vaultId) { + return retrieveAsync(vaultId, null, null); + } + + public CompletableFuture retrieveAsync( + String vaultId, String apiKey, Map headers) { + if (vaultId == null || vaultId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("vaultId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, StringUtils.format("vaults/%s", vaultId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Vault.class)); + } + + public CompletableFuture updateAsync(String vaultId, VaultUpdateParam param) { + if (vaultId == null || vaultId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("vaultId is required!")); + } + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("vaults/%s", vaultId), baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Vault.class)); + } + + public CompletableFuture> listAsync(VaultListParam param) { + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + String query = param.toQueryString(); + String path = query.isEmpty() ? "vaults" : "vaults?" + query; + GeneralServiceOption opt = AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listAsync( + VaultListParam.builder() + .limit(param.getLimit()) + .includeArchived(param.getIncludeArchived()) + .page(cursor) + .build())); + return page; + }); + } + + public CompletableFuture archiveAsync(String vaultId) { + if (vaultId == null || vaultId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("vaultId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("vaults/%s/archive", vaultId), baseUrl); + return AsyncHelper.callAsync( + api, AgentStudioConstants.withApiKey(apiKey, VaultUpdateParam.builder().build()), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Vault.class)); + } + + public CompletableFuture deleteAsync(String vaultId) { + return deleteAsync(vaultId, null, null); + } + + public CompletableFuture deleteAsync( + String vaultId, String apiKey, Map headers) { + if (vaultId == null || vaultId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("vaultId is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.DELETE, StringUtils.format("vaults/%s", vaultId), baseUrl); + String resolvedKey = apiKey != null ? apiKey : this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, AgentStudioDeletionStatus.class)); + } + + public final class Credentials { + + public Credential create(String vaultId, CredentialCreateParam param) { + return AsyncHelper.joinAndUnwrap(createAsync(vaultId, param)); + } + + public Credential retrieve(String vaultId, String credentialId) { + return retrieve(vaultId, credentialId, null, null); + } + + public Credential retrieve( + String vaultId, String credentialId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(retrieveAsync(vaultId, credentialId, apiKey, headers)); + } + + public Credential update(String vaultId, String credentialId, CredentialUpdateParam param) { + return AsyncHelper.joinAndUnwrap(updateAsync(vaultId, credentialId, param)); + } + + public CursorPage list(String vaultId, CredentialListParam param) { + return AsyncHelper.joinAndUnwrap(listAsync(vaultId, param)); + } + + public Credential archive(String vaultId, String credentialId) { + return AsyncHelper.joinAndUnwrap(archiveAsync(vaultId, credentialId)); + } + + public AgentStudioDeletionStatus delete(String vaultId, String credentialId) { + return delete(vaultId, credentialId, null, null); + } + + public AgentStudioDeletionStatus delete( + String vaultId, String credentialId, String apiKey, Map headers) { + return AsyncHelper.joinAndUnwrap(deleteAsync(vaultId, credentialId, apiKey, headers)); + } + + public CompletableFuture createAsync(String vaultId, CredentialCreateParam param) { + if (vaultId == null || vaultId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("vaultId is required!")); + } + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, StringUtils.format("vaults/%s/credentials", vaultId), baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Credential.class)); + } + + public CompletableFuture retrieveAsync(String vaultId, String credentialId) { + return retrieveAsync(vaultId, credentialId, null, null); + } + + public CompletableFuture retrieveAsync( + String vaultId, String credentialId, String apiKey, Map headers) { + if (vaultId == null || vaultId.isEmpty() || credentialId == null || credentialId.isEmpty()) { + return AsyncHelper.failedFuture( + new InputRequiredException("vaultId and credentialId are required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.GET, + StringUtils.format("vaults/%s/credentials/%s", vaultId, credentialId), + baseUrl); + String resolvedKey = apiKey != null ? apiKey : Vaults.this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Credential.class)); + } + + public CompletableFuture updateAsync( + String vaultId, String credentialId, CredentialUpdateParam param) { + if (vaultId == null || vaultId.isEmpty() || credentialId == null || credentialId.isEmpty()) { + return AsyncHelper.failedFuture( + new InputRequiredException("vaultId and credentialId are required!")); + } + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, + StringUtils.format("vaults/%s/credentials/%s", vaultId, credentialId), + baseUrl); + return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Credential.class)); + } + + public CompletableFuture> listAsync( + String vaultId, CredentialListParam param) { + if (vaultId == null || vaultId.isEmpty()) { + return AsyncHelper.failedFuture(new InputRequiredException("vaultId is required!")); + } + if (param == null) { + return AsyncHelper.failedFuture(new InputRequiredException("param is required!")); + } + String query = param.toQueryString(); + String path = StringUtils.format("vaults/%s/credentials", vaultId); + if (!query.isEmpty()) { + path = path + "?" + query; + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl); + return AsyncHelper.callAsync( + api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt) + .thenApply( + r -> { + Type type = new TypeToken>() {}.getType(); + CursorPage page = FlattenResultBase.fromDashScopeResult(r, type); + page.setFetchNext( + cursor -> + listAsync( + vaultId, + CredentialListParam.builder() + .limit(param.getLimit()) + .includeArchived(param.getIncludeArchived()) + .page(cursor) + .build())); + return page; + }); + } + + public CompletableFuture archiveAsync(String vaultId, String credentialId) { + if (vaultId == null || vaultId.isEmpty() || credentialId == null || credentialId.isEmpty()) { + return AsyncHelper.failedFuture( + new InputRequiredException("vaultId and credentialId are required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.POST, + StringUtils.format("vaults/%s/credentials/%s/archive", vaultId, credentialId), + baseUrl); + return AsyncHelper.callAsync( + api, + AgentStudioConstants.withApiKey(apiKey, CredentialUpdateParam.builder().build()), + opt) + .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Credential.class)); + } + + public CompletableFuture deleteAsync( + String vaultId, String credentialId) { + return deleteAsync(vaultId, credentialId, null, null); + } + + public CompletableFuture deleteAsync( + String vaultId, String credentialId, String apiKey, Map headers) { + if (vaultId == null || vaultId.isEmpty() || credentialId == null || credentialId.isEmpty()) { + return AsyncHelper.failedFuture( + new InputRequiredException("vaultId and credentialId are required!")); + } + GeneralServiceOption opt = + AgentStudioConstants.newServiceOption( + HttpMethod.DELETE, + StringUtils.format("vaults/%s/credentials/%s", vaultId, credentialId), + baseUrl); + String resolvedKey = apiKey != null ? apiKey : Vaults.this.apiKey; + return AsyncHelper.callAsync( + api, + GeneralGetParam.builder() + .apiKey(resolvedKey) + .headers(headers != null ? headers : new HashMap<>()) + .build(), + opt) + .thenApply( + r -> FlattenResultBase.fromDashScopeResult(r, AgentStudioDeletionStatus.class)); + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/utils/JsonUtils.java b/src/main/java/com/alibaba/dashscope/utils/JsonUtils.java index 8452bee..3740be6 100644 --- a/src/main/java/com/alibaba/dashscope/utils/JsonUtils.java +++ b/src/main/java/com/alibaba/dashscope/utils/JsonUtils.java @@ -1,6 +1,7 @@ // Copyright (c) Alibaba, Inc. and its affiliates. package com.alibaba.dashscope.utils; +import com.alibaba.dashscope.agentstudio.message.ContentBlock; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationMessage; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationMessageAdapter; import com.alibaba.dashscope.common.Message; @@ -49,6 +50,7 @@ MultiModalConversationMessage.class, new MultiModalConversationMessageAdapter()) .registerTypeAdapter(AnnotationBase.class, new AnnotationDeserializer()) .registerTypeAdapter(StepDetailBase.class, new StepDetailDeserializer()) .registerTypeAdapter(ToolCallBase.class, new ToolCallGsonDeserializer()) + .registerTypeAdapter(ContentBlock.class, new ContentBlock.Deserializer()) .addSerializationExclusionStrategy(new AnnotationExclusionStrategy()) .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .disableHtmlEscaping() diff --git a/src/test/java/com/alibaba/dashscope/TestAgentStudio.java b/src/test/java/com/alibaba/dashscope/TestAgentStudio.java new file mode 100644 index 0000000..69a820f --- /dev/null +++ b/src/test/java/com/alibaba/dashscope/TestAgentStudio.java @@ -0,0 +1,968 @@ +package com.alibaba.dashscope; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.alibaba.dashscope.agentstudio.AgentStudioClient; +import com.alibaba.dashscope.agentstudio.message.ClientEvents; +import com.alibaba.dashscope.agentstudio.message.ContentBlock; +import com.alibaba.dashscope.agentstudio.message.Message; +import com.alibaba.dashscope.agentstudio.model.Agent; +import com.alibaba.dashscope.agentstudio.model.AgentStudioDeletionStatus; +import com.alibaba.dashscope.agentstudio.model.AgentStudioFile; +import com.alibaba.dashscope.agentstudio.model.AgentVersion; +import com.alibaba.dashscope.agentstudio.model.Credential; +import com.alibaba.dashscope.agentstudio.model.Environment; +import com.alibaba.dashscope.agentstudio.model.Session; +import com.alibaba.dashscope.agentstudio.model.Skill; +import com.alibaba.dashscope.agentstudio.model.SkillVersion; +import com.alibaba.dashscope.agentstudio.model.Vault; +import com.alibaba.dashscope.agentstudio.pagination.CursorPage; +import com.alibaba.dashscope.agentstudio.param.AgentCreateParam; +import com.alibaba.dashscope.agentstudio.param.AgentListParam; +import com.alibaba.dashscope.agentstudio.param.AgentUpdateParam; +import com.alibaba.dashscope.agentstudio.param.CredentialCreateParam; +import com.alibaba.dashscope.agentstudio.param.CredentialListParam; +import com.alibaba.dashscope.agentstudio.param.CredentialUpdateParam; +import com.alibaba.dashscope.agentstudio.param.EnvironmentCreateParam; +import com.alibaba.dashscope.agentstudio.param.EnvironmentListParam; +import com.alibaba.dashscope.agentstudio.param.FileListParam; +import com.alibaba.dashscope.agentstudio.param.SessionCreateParam; +import com.alibaba.dashscope.agentstudio.param.SessionEventListParam; +import com.alibaba.dashscope.agentstudio.param.SessionListParam; +import com.alibaba.dashscope.agentstudio.param.SessionUpdateParam; +import com.alibaba.dashscope.agentstudio.param.SkillCreateParam; +import com.alibaba.dashscope.agentstudio.param.SkillListParam; +import com.alibaba.dashscope.agentstudio.param.VaultCreateParam; +import com.alibaba.dashscope.agentstudio.param.VaultListParam; +import com.alibaba.dashscope.agentstudio.param.VaultUpdateParam; +import com.alibaba.dashscope.agentstudio.resource.Agents; +import com.alibaba.dashscope.agentstudio.resource.Environments; +import com.alibaba.dashscope.agentstudio.resource.Sessions; +import com.alibaba.dashscope.agentstudio.resource.Skills; +import com.alibaba.dashscope.agentstudio.resource.Vaults; +import com.alibaba.dashscope.exception.ApiException; +import com.alibaba.dashscope.utils.Constants; +import com.alibaba.dashscope.utils.JsonUtils; +import com.google.gson.JsonObject; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +@Execution(ExecutionMode.SAME_THREAD) +public class TestAgentStudio { + private static MockWebServer mockServer; + private static JsonObject fixtures; + + @BeforeAll + public static void before() throws IOException { + mockServer = new MockWebServer(); + mockServer.start(); + Constants.baseHttpApiUrl = String.format("http://127.0.0.1:%s/api/v1/", mockServer.getPort()); + Constants.apiKey = "test-key"; + byte[] content = Files.readAllBytes(Paths.get("./src/test/resources/agentstudio.json")); + fixtures = JsonUtils.parse(new String(content, StandardCharsets.UTF_8)); + } + + @AfterAll + public static void after() throws IOException { + mockServer.close(); + } + + private void enqueue(String fixtureKey) { + JsonObject rsp = fixtures.get(fixtureKey).getAsJsonObject(); + mockServer.enqueue(TestUtils.createMockResponse(JsonUtils.toJson(rsp), 200)); + } + + // ======================== Agents ======================== + + @Test + public void testAgentCreate() throws Exception { + enqueue("agent_response"); + Agent agent = + new Agents(null, null, null) + .create(AgentCreateParam.builder().name("test-agent").model("qwen-max").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().endsWith("/agents")); + assertEquals("agent_xyz", agent.getId()); + assertEquals("test-agent", agent.getName()); + assertEquals(Integer.valueOf(2), agent.getVersion()); + assertEquals("You are a helpful assistant.", agent.getSystemPrompt()); + assertEquals("qwen-max", agent.getModel().getId()); + } + + @Test + public void testAgentCreateHttpBody() { + JsonObject body = + AgentCreateParam.builder() + .name("my-agent") + .model("qwen-plus") + .description("desc") + .systemPrompt("be helpful") + .build() + .getHttpBody(); + assertEquals("my-agent", body.get("name").getAsString()); + assertEquals("qwen-plus", body.getAsJsonObject("model").get("id").getAsString()); + assertEquals("desc", body.get("description").getAsString()); + assertEquals("be helpful", body.get("system").getAsString()); + } + + @Test + public void testAgentRetrieve() throws Exception { + enqueue("agent_response"); + Agent agent = new Agents(null, null, null).retrieve("agent_xyz"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/agents/agent_xyz")); + assertEquals("A test agent", agent.getDescription()); + assertEquals("ws_001", agent.getWorkspaceId()); + } + + @Test + public void testAgentUpdate() throws Exception { + enqueue("agent_response"); + Agent agent = + new Agents(null, null, null) + .update( + "agent_xyz", AgentUpdateParam.builder().version(2).name("updated-agent").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().contains("/agents/agent_xyz")); + assertEquals("agent_xyz", agent.getId()); + } + + @Test + public void testAgentUpdateRequiresVersion() { + assertThrows( + ApiException.class, + () -> + new Agents(null, null, null) + .update("agent_xyz", AgentUpdateParam.builder().name("no-version").build())); + } + + @Test + public void testAgentUpdateRequiresAgentId() { + assertThrows( + ApiException.class, + () -> + new Agents(null, null, null) + .update("", AgentUpdateParam.builder().version(1).name("x").build())); + } + + @Test + public void testAgentUpdateHttpBody() { + JsonObject body = + AgentUpdateParam.builder() + .version(3) + .model("qwen-turbo") + .systemPrompt("new prompt") + .build() + .getHttpBody(); + assertEquals(3, body.get("version").getAsInt()); + assertEquals("qwen-turbo", body.getAsJsonObject("model").get("id").getAsString()); + assertEquals("new prompt", body.get("system").getAsString()); + } + + @Test + public void testAgentList() throws Exception { + enqueue("agent_list_response"); + CursorPage page = + new Agents(null, null, null).list(AgentListParam.builder().limit(20).build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("limit=20")); + assertEquals(2, page.getData().size()); + assertEquals("agent_001", page.getData().get(0).getId()); + assertEquals("cursor_agent", page.getNextPage()); + } + + @Test + public void testAgentListWithIncludeArchived() { + String qs = AgentListParam.builder().limit(10).includeArchived(true).build().toQueryString(); + assertTrue(qs.contains("limit=10")); + assertTrue(qs.contains("include_archived=true")); + } + + @Test + public void testAgentListVersions() throws Exception { + enqueue("agent_version_response"); + CursorPage page = + new Agents(null, null, null).listVersions("agent_xyz", AgentListParam.builder().build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/agents/agent_xyz/versions")); + assertEquals(2, page.getData().size()); + assertEquals("agent_xyz", page.getData().get(0).getAgentId()); + assertEquals(Integer.valueOf(1), page.getData().get(0).getVersion()); + assertNull(page.getNextPage()); + } + + @Test + public void testAgentModelDeserialization() throws Exception { + enqueue("agent_response"); + Agent agent = new Agents(null, null, null).retrieve("agent_xyz"); + mockServer.takeRequest(); + assertEquals("agent", agent.getType()); + assertEquals("test-agent", agent.getName()); + assertEquals(Integer.valueOf(2), agent.getVersion()); + assertEquals("You are a helpful assistant.", agent.getSystem()); + assertNotNull(agent.getTools()); + assertEquals(1, agent.getTools().size()); + assertNotNull(agent.getMetadata()); + assertEquals("test", agent.getMetadata().get("env")); + assertEquals("2025-01-01T00:00:00Z", agent.getCreatedAt()); + } + + // ======================== Sessions ======================== + + @Test + public void testSessionCreate() throws Exception { + enqueue("session_response"); + Session session = + new Sessions(null, null, null) + .create(SessionCreateParam.builder().agent("agent_xyz").title("Test Session").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().endsWith("/sessions")); + assertEquals("sess_abc123", session.getId()); + assertEquals("idle", session.getStatus()); + assertEquals("agent_xyz", session.getAgentId()); + assertNotNull(session.getStopReason()); + assertEquals("end_turn", session.getStopReason().getType()); + assertNotNull(session.getUsage()); + assertEquals(100L, session.getUsage().getInputTokens().longValue()); + assertEquals(42.5, session.getUsage().getSpeed(), 0.01); + } + + @Test + public void testSessionRetrieve() throws Exception { + enqueue("session_response"); + Session session = new Sessions(null, null, null).retrieve("sess_abc123"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/sessions/sess_abc123")); + assertEquals("sess_abc123", session.getId()); + } + + @Test + public void testSessionUpdate() throws Exception { + enqueue("session_response"); + Session session = + new Sessions(null, null, null) + .update("sess_abc123", SessionUpdateParam.builder().title("Updated").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().contains("/sessions/sess_abc123")); + } + + @Test + public void testSessionList() throws Exception { + enqueue("session_list_response"); + CursorPage page = + new Sessions(null, null, null) + .list( + SessionListParam.builder() + .limit(10) + .agentId("agent_xyz") + .statuses(Arrays.asList("idle", "running")) + .build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + String path = req.getPath(); + assertTrue(path.contains("limit=10")); + assertTrue(path.contains("agent_id=agent_xyz")); + assertTrue(path.contains("statuses[]=idle")); + assertEquals(2, page.getData().size()); + assertEquals("cursor_abc", page.getNextPage()); + assertTrue(page.hasNext()); + } + + @Test + public void testSessionDelete() throws Exception { + enqueue("delete_response"); + AgentStudioDeletionStatus status = new Sessions(null, null, null).delete("sess_abc123"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("DELETE", req.getMethod()); + assertEquals("sess_abc123", status.getId()); + assertTrue(status.isDeleted()); + } + + @Test + public void testSessionArchive() throws Exception { + enqueue("session_response"); + Session session = new Sessions(null, null, null).archive("sess_abc123"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().contains("/sessions/sess_abc123/archive")); + } + + @Test + public void testSessionRetrieveRequiresId() { + assertThrows(ApiException.class, () -> new Sessions(null, null, null).retrieve(null)); + } + + @Test + public void testSessionListParamQueryString() { + String qs = + SessionListParam.builder() + .limit(5) + .page("cursor_abc") + .agentId("agent_1") + .statuses(Arrays.asList("idle", "running")) + .createdAtGt("2025-01-01") + .build() + .toQueryString(); + assertTrue(qs.contains("limit=5")); + assertTrue(qs.contains("page=cursor_abc")); + assertTrue(qs.contains("agent_id=agent_1")); + assertTrue(qs.contains("statuses[]=idle")); + assertTrue(qs.contains("created_at[gt]=2025-01-01")); + } + + @Test + public void testSessionListParamEmptyQuery() { + assertEquals("", SessionListParam.builder().build().toQueryString()); + } + + @Test + public void testSessionModelDeserialization() { + String json = JsonUtils.toJson(fixtures.get("session_response").getAsJsonObject()); + Session session = JsonUtils.fromJson(json, Session.class); + assertEquals("sess_abc123", session.getId()); + assertEquals("Test Session", session.getTitle()); + assertNotNull(session.getAgent()); + assertEquals("agent_xyz", session.getAgent().getId()); + assertEquals("test-agent", session.getAgent().getName()); + assertEquals(Integer.valueOf(1), session.getAgentVersion()); + assertEquals("env_001", session.getEnvironmentId()); + assertNotNull(session.getVaultIds()); + assertTrue(session.getVaultIds().isEmpty()); + } + + @Test + public void testSessionEventList() throws Exception { + enqueue("event_list_response"); + CursorPage page = + new Sessions(null, null, null) + .events() + .list("sess_abc123", SessionEventListParam.builder().limit(10).build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/sessions/sess_abc123/events")); + assertEquals(2, page.getData().size()); + Message msg = page.getData().get(0); + assertEquals("evt_1", msg.getId()); + assertEquals("user", msg.getRole()); + assertEquals("event", msg.getObject()); + assertEquals("completed", msg.getStatus()); + assertEquals(Long.valueOf(1), msg.getSequenceNumber()); + assertTrue(msg.getContent().get(0) instanceof ContentBlock.Text); + assertEquals("hello", ((ContentBlock.Text) msg.getContent().get(0)).getText()); + } + + // ======================== Environments ======================== + + @Test + public void testEnvironmentCreate() throws Exception { + enqueue("environment_response"); + Environment env = + new Environments(null, null, null) + .create( + EnvironmentCreateParam.builder() + .name("Test Environment") + .config(Collections.singletonMap("type", "cloud")) + .build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().endsWith("/environments")); + assertEquals("env_001", env.getId()); + assertEquals("Test Environment", env.getName()); + } + + @Test + public void testEnvironmentRetrieve() throws Exception { + enqueue("environment_response"); + Environment env = new Environments(null, null, null).retrieve("env_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/environments/env_001")); + assertEquals("env_001", env.getId()); + } + + @Test + public void testEnvironmentList() throws Exception { + enqueue("environment_list_response"); + CursorPage page = + new Environments(null, null, null).list(EnvironmentListParam.builder().limit(20).build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertEquals(2, page.getData().size()); + assertNull(page.getNextPage()); + } + + @Test + public void testEnvironmentDelete() throws Exception { + enqueue("delete_response"); + AgentStudioDeletionStatus status = new Environments(null, null, null).delete("env_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("DELETE", req.getMethod()); + assertTrue(req.getPath().contains("/environments/env_001")); + assertTrue(status.isDeleted()); + } + + // ======================== Skills ======================== + + @Test + public void testSkillCreate() throws Exception { + enqueue("skill_response"); + Skill skill = + new Skills(null, null, null, null) + .create(SkillCreateParam.builder().fileId("file_001").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().endsWith("/skills")); + assertEquals("skill_001", skill.getId()); + assertEquals("bash_tool", skill.getName()); + } + + @Test + public void testSkillRetrieve() throws Exception { + enqueue("skill_response"); + Skill skill = new Skills(null, null, null, null).retrieve("skill_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/skills/skill_001")); + assertEquals("skill_001", skill.getId()); + } + + @Test + public void testSkillList() throws Exception { + enqueue("skill_list_response"); + CursorPage page = + new Skills(null, null, null, null).list(SkillListParam.builder().build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertEquals(1, page.getData().size()); + } + + @Test + public void testSkillDelete() throws Exception { + enqueue("delete_response"); + AgentStudioDeletionStatus status = new Skills(null, null, null, null).delete("skill_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("DELETE", req.getMethod()); + assertTrue(status.isDeleted()); + } + + @Test + public void testSkillVersionCreate() throws Exception { + enqueue("skill_version_response"); + SkillVersion sv = + new Skills(null, null, null, null) + .createVersion("skill_001", SkillCreateParam.builder().fileId("file_002").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().contains("/skills/skill_001/versions")); + assertEquals("sv_001", sv.getId()); + } + + @Test + public void testSkillVersionList() throws Exception { + enqueue("skill_list_response"); + CursorPage page = + new Skills(null, null, null, null) + .listVersions("skill_001", SkillListParam.builder().build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/skills/skill_001/versions")); + } + + // ======================== ContentBlock ======================== + + @Test + public void testContentBlockText() { + ContentBlock block = + JsonUtils.fromJson( + "{\"type\":\"text\",\"text\":\"hello world\"," + + "\"citations\":[{\"url\":\"http://example.com\"}]}", + ContentBlock.class); + assertTrue(block instanceof ContentBlock.Text); + ContentBlock.Text text = (ContentBlock.Text) block; + assertEquals("hello world", text.getText()); + assertEquals(1, text.getCitations().size()); + } + + @Test + public void testContentBlockImage() { + ContentBlock block = + JsonUtils.fromJson( + "{\"type\":\"image\",\"image_url\":\"http://img.png\"," + + "\"file_id\":\"f1\",\"media_type\":\"image/png\"}", + ContentBlock.class); + assertTrue(block instanceof ContentBlock.Image); + ContentBlock.Image img = (ContentBlock.Image) block; + assertEquals("http://img.png", img.getImageUrl()); + assertEquals("f1", img.getFileId()); + } + + @Test + public void testContentBlockData() { + ContentBlock block = + JsonUtils.fromJson( + "{\"type\":\"data\",\"data\":{\"key\":\"val\"}," + "\"name\":\"test\"}", + ContentBlock.class); + assertTrue(block instanceof ContentBlock.DataContent); + assertEquals("val", ((ContentBlock.DataContent) block).getData().get("key").getAsString()); + } + + @Test + public void testContentBlockError() { + ContentBlock block = + JsonUtils.fromJson( + "{\"type\":\"error\",\"error_code\":\"E001\"," + "\"message\":\"something failed\"}", + ContentBlock.class); + assertTrue(block instanceof ContentBlock.Error); + assertEquals("E001", ((ContentBlock.Error) block).getErrorCode()); + } + + @Test + public void testContentBlockRefusal() { + ContentBlock block = + JsonUtils.fromJson( + "{\"type\":\"refusal\"," + "\"refusal\":\"I cannot do that\"}", ContentBlock.class); + assertTrue(block instanceof ContentBlock.Refusal); + assertEquals("I cannot do that", ((ContentBlock.Refusal) block).getRefusal()); + } + + @Test + public void testContentBlockUnknownFallsBackToData() { + ContentBlock block = + JsonUtils.fromJson( + "{\"type\":\"unknown_future_type\",\"data\":{\"x\":1}}", ContentBlock.class); + assertTrue(block instanceof ContentBlock.DataContent); + } + + // ======================== Vaults ======================== + + @Test + public void testVaultCreate() throws Exception { + enqueue("vault_response"); + Vault vault = + new Vaults(null, null, null) + .create(VaultCreateParam.builder().displayName("Test Vault").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().endsWith("/vaults")); + assertEquals("vault_001", vault.getId()); + assertEquals("Test Vault", vault.getDisplayName()); + assertEquals("vault", vault.getType()); + } + + @Test + public void testVaultRetrieve() throws Exception { + enqueue("vault_response"); + Vault vault = new Vaults(null, null, null).retrieve("vault_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/vaults/vault_001")); + assertEquals("vault_001", vault.getId()); + assertNotNull(vault.getMetadata()); + assertEquals("security", vault.getMetadata().get("team")); + } + + @Test + public void testVaultUpdate() throws Exception { + enqueue("vault_response"); + Vault vault = + new Vaults(null, null, null) + .update("vault_001", VaultUpdateParam.builder().displayName("Updated Vault").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().contains("/vaults/vault_001")); + assertEquals("vault_001", vault.getId()); + } + + @Test + public void testVaultList() throws Exception { + enqueue("vault_list_response"); + CursorPage page = + new Vaults(null, null, null).list(VaultListParam.builder().limit(20).build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("limit=20")); + assertEquals(2, page.getData().size()); + assertEquals("vault_001", page.getData().get(0).getId()); + assertEquals("cursor_vault", page.getNextPage()); + assertTrue(page.hasNext()); + } + + @Test + public void testVaultDelete() throws Exception { + enqueue("delete_response"); + AgentStudioDeletionStatus status = new Vaults(null, null, null).delete("vault_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("DELETE", req.getMethod()); + assertTrue(req.getPath().contains("/vaults/vault_001")); + assertTrue(status.isDeleted()); + } + + @Test + public void testVaultArchive() throws Exception { + enqueue("vault_response"); + Vault vault = new Vaults(null, null, null).archive("vault_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().contains("/vaults/vault_001/archive")); + } + + @Test + public void testVaultRetrieveRequiresId() { + assertThrows(ApiException.class, () -> new Vaults(null, null, null).retrieve(null)); + } + + // ======================== Credentials ======================== + + @Test + public void testCredentialCreate() throws Exception { + enqueue("credential_response"); + JsonObject auth = new JsonObject(); + auth.addProperty("type", "environment_variable"); + auth.addProperty("secret_name", "DASHSCOPE_API_KEY"); + auth.addProperty("secret_value", "sk-test"); + Credential cred = + new Vaults(null, null, null) + .credentials() + .create( + "vault_001", + CredentialCreateParam.builder().auth(auth).displayName("Test Credential").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().endsWith("/vaults/vault_001/credentials")); + assertEquals("cred_001", cred.getId()); + assertEquals("vault_001", cred.getVaultId()); + assertEquals("Test Credential", cred.getDisplayName()); + } + + @Test + public void testCredentialRetrieve() throws Exception { + enqueue("credential_response"); + Credential cred = new Vaults(null, null, null).credentials().retrieve("vault_001", "cred_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/vaults/vault_001/credentials/cred_001")); + assertEquals("cred_001", cred.getId()); + assertNotNull(cred.getAuth()); + assertEquals("environment_variable", cred.getAuth().getType()); + } + + @Test + public void testCredentialUpdate() throws Exception { + enqueue("credential_response"); + Credential cred = + new Vaults(null, null, null) + .credentials() + .update( + "vault_001", + "cred_001", + CredentialUpdateParam.builder().displayName("Updated Cred").build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().contains("/vaults/vault_001/credentials/cred_001")); + } + + @Test + public void testCredentialList() throws Exception { + enqueue("credential_list_response"); + CursorPage page = + new Vaults(null, null, null) + .credentials() + .list("vault_001", CredentialListParam.builder().build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/vaults/vault_001/credentials")); + assertEquals(2, page.getData().size()); + assertEquals("cred_001", page.getData().get(0).getId()); + assertNull(page.getNextPage()); + } + + @Test + public void testCredentialDelete() throws Exception { + enqueue("delete_response"); + AgentStudioDeletionStatus status = + new Vaults(null, null, null).credentials().delete("vault_001", "cred_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("DELETE", req.getMethod()); + assertTrue(req.getPath().contains("/vaults/vault_001/credentials/cred_001")); + assertTrue(status.isDeleted()); + } + + @Test + public void testCredentialArchive() throws Exception { + enqueue("credential_response"); + Credential cred = new Vaults(null, null, null).credentials().archive("vault_001", "cred_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().contains("/vaults/vault_001/credentials/cred_001/archive")); + } + + @Test + public void testCredentialCreateRequiresVaultId() { + JsonObject auth = new JsonObject(); + auth.addProperty("type", "environment_variable"); + auth.addProperty("secret_name", "TEST_KEY"); + auth.addProperty("secret_value", "test-val"); + assertThrows( + ApiException.class, + () -> + new Vaults(null, null, null) + .credentials() + .create(null, CredentialCreateParam.builder().auth(auth).build())); + } + + @Test + public void testCredentialRetrieveRequiresIds() { + assertThrows( + ApiException.class, + () -> new Vaults(null, null, null).credentials().retrieve("vault_001", null)); + } + + @Test + public void testVaultListWithIncludeArchived() { + String qs = VaultListParam.builder().includeArchived(true).limit(10).build().toQueryString(); + assertTrue(qs.contains("include_archived=true")); + assertTrue(qs.contains("limit=10")); + } + + // ======================== Files ======================== + + @Test + public void testFileUpload() throws Exception { + String baseUrl = String.format("http://127.0.0.1:%s/api/v1/agentstudio", mockServer.getPort()); + com.alibaba.dashscope.agentstudio.resource.Files filesResource = + new com.alibaba.dashscope.agentstudio.resource.Files(baseUrl, null, null); + try { + JsonObject rsp = fixtures.get("file_response").getAsJsonObject(); + mockServer.enqueue(TestUtils.createMockResponse(JsonUtils.toJson(rsp), 200)); + java.io.File tempFile = java.io.File.createTempFile("test-upload", ".txt"); + tempFile.deleteOnExit(); + java.nio.file.Files.write(tempFile.toPath(), "hello".getBytes(StandardCharsets.UTF_8)); + AgentStudioFile file = filesResource.upload(tempFile.getAbsolutePath(), "text/plain"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().endsWith("/files")); + assertTrue(req.getHeader("Content-Type").startsWith("multipart/form-data")); + assertEquals("file_001", file.getId()); + assertEquals("test.pdf", file.getFilename()); + assertEquals("application/pdf", file.getMimeType()); + assertEquals(Long.valueOf(12345), file.getSizeBytes()); + } finally { + filesResource.close(); + } + } + + @Test + public void testFileUploadInputStream() throws Exception { + String baseUrl = String.format("http://127.0.0.1:%s/api/v1/agentstudio", mockServer.getPort()); + com.alibaba.dashscope.agentstudio.resource.Files filesResource = + new com.alibaba.dashscope.agentstudio.resource.Files(baseUrl, null, null); + try { + JsonObject rsp = fixtures.get("file_response").getAsJsonObject(); + mockServer.enqueue(TestUtils.createMockResponse(JsonUtils.toJson(rsp), 200)); + final boolean[] closed = {false}; + InputStream is = + new ByteArrayInputStream("test content".getBytes(StandardCharsets.UTF_8)) { + @Override + public void close() throws IOException { + closed[0] = true; + super.close(); + } + }; + AgentStudioFile file = filesResource.upload("test.txt", is, "text/plain"); + mockServer.takeRequest(); + assertEquals("file_001", file.getId()); + assertTrue(closed[0], "InputStream should be closed after upload"); + } finally { + filesResource.close(); + } + } + + @Test + public void testFileRetrieve() throws Exception { + enqueue("file_response"); + com.alibaba.dashscope.agentstudio.resource.Files filesResource = + new com.alibaba.dashscope.agentstudio.resource.Files(null, null, null); + AgentStudioFile file = filesResource.retrieve("file_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("/files/file_001")); + assertEquals("file_001", file.getId()); + assertEquals("completed", file.getStatus()); + } + + @Test + public void testFileList() throws Exception { + enqueue("file_list_response"); + com.alibaba.dashscope.agentstudio.resource.Files filesResource = + new com.alibaba.dashscope.agentstudio.resource.Files(null, null, null); + CursorPage page = + filesResource.list(FileListParam.builder().limit(20).build()); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("GET", req.getMethod()); + assertTrue(req.getPath().contains("limit=20")); + assertEquals(2, page.getData().size()); + assertEquals("file_001", page.getData().get(0).getId()); + assertEquals("cursor_file", page.getNextPage()); + } + + @Test + public void testFileDelete() throws Exception { + enqueue("delete_response"); + com.alibaba.dashscope.agentstudio.resource.Files filesResource = + new com.alibaba.dashscope.agentstudio.resource.Files(null, null, null); + AgentStudioDeletionStatus status = filesResource.delete("file_001"); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("DELETE", req.getMethod()); + assertTrue(req.getPath().contains("/files/file_001")); + assertTrue(status.isDeleted()); + } + + @Test + public void testFileUploadNullFilePath() { + com.alibaba.dashscope.agentstudio.resource.Files filesResource = + new com.alibaba.dashscope.agentstudio.resource.Files(null, null, null); + assertThrows(ApiException.class, () -> filesResource.upload(null, null)); + } + + @Test + public void testFileUploadNullInputStream() { + com.alibaba.dashscope.agentstudio.resource.Files filesResource = + new com.alibaba.dashscope.agentstudio.resource.Files(null, null, null); + assertThrows( + ApiException.class, () -> filesResource.upload("test.txt", (InputStream) null, null)); + } + + @Test + public void testFileRetrieveRequiresId() { + com.alibaba.dashscope.agentstudio.resource.Files filesResource = + new com.alibaba.dashscope.agentstudio.resource.Files(null, null, null); + assertThrows(ApiException.class, () -> filesResource.retrieve(null)); + } + + @Test + public void testFileDeleteRequiresId() { + com.alibaba.dashscope.agentstudio.resource.Files filesResource = + new com.alibaba.dashscope.agentstudio.resource.Files(null, null, null); + assertThrows(ApiException.class, () -> filesResource.delete(null)); + } + + // ======================== AgentStudioClient ======================== + + @Test + public void testAgentStudioClientBuilder() { + String baseUrl = String.format("http://127.0.0.1:%s/api/v1/agentstudio", mockServer.getPort()); + AgentStudioClient client = + AgentStudioClient.builder().apiKey("test-key").baseUrl(baseUrl).build(); + try { + assertNotNull(client.agents()); + assertNotNull(client.sessions()); + assertNotNull(client.environments()); + assertNotNull(client.skills()); + assertNotNull(client.vaults()); + assertNotNull(client.files()); + assertEquals(baseUrl, client.getBaseUrl()); + } finally { + client.close(); + } + } + + @Test + public void testAgentStudioClientClose() { + String baseUrl = String.format("http://127.0.0.1:%s/api/v1/agentstudio", mockServer.getPort()); + AgentStudioClient client = + AgentStudioClient.builder().apiKey("test-key").baseUrl(baseUrl).build(); + client.close(); + } + + // ======================== SessionEvents.send() ======================== + + @Test + public void testSessionEventSend() throws Exception { + enqueue("session_event_send_response"); + JsonObject event = ClientEvents.userMessage("hello"); + JsonObject result = + new Sessions(null, null, null) + .events() + .send("sess_abc123", Collections.singletonList(event)); + RecordedRequest req = mockServer.takeRequest(); + assertEquals("POST", req.getMethod()); + assertTrue(req.getPath().contains("/sessions/sess_abc123/events")); + assertNotNull(result); + } + + @Test + public void testSessionEventSendRequiresSessionId() { + assertThrows( + ApiException.class, + () -> + new Sessions(null, null, null) + .events() + .send(null, Collections.singletonList(new JsonObject()))); + } + + @Test + public void testSessionEventSendRequiresEvents() { + assertThrows( + IllegalArgumentException.class, + () -> new Sessions(null, null, null).events().send("sess_abc123", Collections.emptyList())); + } + + // ======================== URL Encoding ======================== + + @Test + public void testUrlEncodingInQueryParams() { + String qs = + SessionListParam.builder().page("cursor with spaces&special=chars").build().toQueryString(); + assertTrue(qs.contains("page=cursor+with+spaces%26special%3Dchars")); + } + + @Test + public void testUrlEncodingInSessionEventListParam() { + String qs = + SessionEventListParam.builder() + .createdAtGt("2025-01-01T00:00:00+08:00") + .build() + .toQueryString(); + assertTrue(qs.contains("created_at[gt]=2025-01-01T00%3A00%3A00%2B08%3A00")); + } + + // ======================== EnvironmentCreateParam scope type ======================== + + @Test + public void testEnvironmentCreateWithScopeAsString() { + JsonObject body = + EnvironmentCreateParam.builder() + .name("env-test") + .config(Collections.singletonMap("type", "cloud")) + .scope("organization") + .build() + .getHttpBody(); + assertEquals("organization", body.get("scope").getAsString()); + assertTrue(body.get("scope").isJsonPrimitive()); + } +} diff --git a/src/test/resources/agentstudio.json b/src/test/resources/agentstudio.json new file mode 100644 index 0000000..47b00b7 --- /dev/null +++ b/src/test/resources/agentstudio.json @@ -0,0 +1,310 @@ +{ + "agent_response": { + "id": "agent_xyz", + "type": "agent", + "name": "test-agent", + "description": "A test agent", + "model": {"id": "qwen-max"}, + "system": "You are a helpful assistant.", + "tools": [{"type": "bash_tool"}], + "skills": [], + "mcp_servers": [], + "version": 2, + "metadata": {"env": "test"}, + "workspace_id": "ws_001", + "archived_at": null, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T01:00:00Z", + "request_id": "req-010" + }, + "agent_list_response": { + "data": [ + { + "id": "agent_001", + "type": "agent", + "name": "agent-one", + "version": 1, + "created_at": "2025-01-01T00:00:00Z" + }, + { + "id": "agent_002", + "type": "agent", + "name": "agent-two", + "version": 3, + "created_at": "2025-01-02T00:00:00Z" + } + ], + "next_page": "cursor_agent", + "request_id": "req-011" + }, + "agent_version_response": { + "data": [ + { + "agent_id": "agent_xyz", + "version": 1, + "config": {"name": "test-agent", "model": {"id": "qwen-max"}}, + "created_at": "2025-01-01T00:00:00Z" + }, + { + "agent_id": "agent_xyz", + "version": 2, + "config": {"name": "test-agent-v2", "model": {"id": "qwen-plus"}}, + "created_at": "2025-01-02T00:00:00Z" + } + ], + "next_page": null, + "request_id": "req-012" + }, + "session_response": { + "id": "sess_abc123", + "type": "session", + "title": "Test Session", + "agent": { + "id": "agent_xyz", + "type": "agent", + "version": 1, + "name": "test-agent", + "description": null, + "model": {"id": "qwen-max"}, + "system": "You are a test agent.", + "tools": [] + }, + "environment_id": "env_001", + "status": "idle", + "stop_reason": { + "type": "end_turn", + "event_ids": ["evt_1", "evt_2"] + }, + "usage": { + "input_tokens": 100, + "output_tokens": 50, + "cache_creation": null, + "cache_read_input_tokens": 5, + "speed": 42.5 + }, + "vault_ids": [], + "metadata": {"env": "test"}, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T01:00:00Z", + "request_id": "req-001" + }, + "session_list_response": { + "data": [ + { + "id": "sess_1", + "status": "idle", + "agent": {"id": "agent_xyz", "type": "agent", "version": 1}, + "created_at": "2025-01-01T00:00:00Z" + }, + { + "id": "sess_2", + "status": "running", + "agent": {"id": "agent_xyz", "type": "agent", "version": 1}, + "created_at": "2025-01-02T00:00:00Z" + } + ], + "next_page": "cursor_abc", + "request_id": "req-002" + }, + "environment_response": { + "id": "env_001", + "type": "environment", + "name": "Test Environment", + "description": "A test env", + "status": "active", + "config": { + "type": "sandbox", + "networking": {"type": "public"}, + "packages": {"pip": ["requests", "numpy"]} + }, + "scope": "organization", + "metadata": {"team": "infra"}, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T01:00:00Z", + "request_id": "req-003" + }, + "environment_list_response": { + "data": [ + { + "id": "env_001", + "name": "Env1", + "status": "active" + }, + { + "id": "env_002", + "name": "Env2", + "status": "archived" + } + ], + "next_page": null, + "request_id": "req-004" + }, + "skill_response": { + "id": "skill_001", + "type": "skill", + "name": "bash_tool", + "description": "Execute bash commands", + "source": "customer", + "status": "active", + "latest_version": "1.0", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T01:00:00Z", + "request_id": "req-005" + }, + "skill_list_response": { + "data": [ + { + "id": "skill_001", + "type": "skill", + "name": "bash_tool", + "description": "Execute bash commands", + "source": "customer", + "status": "active", + "latest_version": "1.0", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T01:00:00Z" + } + ], + "next_page": null, + "request_id": "req-006" + }, + "skill_version_response": { + "id": "sv_001", + "skill_id": "skill_001", + "version": "1", + "status": "active", + "created_at": "2025-01-01T00:00:00Z", + "request_id": "req-007" + }, + "delete_response": { + "id": "sess_abc123", + "type": "session_deleted", + "request_id": "req-008" + }, + "vault_response": { + "id": "vault_001", + "type": "vault", + "display_name": "Test Vault", + "metadata": {"team": "security"}, + "archived_at": null, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T01:00:00Z", + "request_id": "req-020" + }, + "vault_list_response": { + "data": [ + { + "id": "vault_001", + "type": "vault", + "display_name": "Vault One", + "created_at": "2025-01-01T00:00:00Z" + }, + { + "id": "vault_002", + "type": "vault", + "display_name": "Vault Two", + "created_at": "2025-01-02T00:00:00Z" + } + ], + "next_page": "cursor_vault", + "request_id": "req-021" + }, + "credential_response": { + "id": "cred_001", + "type": "vault_credential", + "vault_id": "vault_001", + "display_name": "Test Credential", + "auth": {"type": "environment_variable", "secret_name": "DASHSCOPE_API_KEY"}, + "metadata": {"service": "openai"}, + "archived_at": null, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T01:00:00Z", + "request_id": "req-022" + }, + "credential_list_response": { + "data": [ + { + "id": "cred_001", + "type": "vault_credential", + "vault_id": "vault_001", + "display_name": "Cred One", + "created_at": "2025-01-01T00:00:00Z" + }, + { + "id": "cred_002", + "type": "vault_credential", + "vault_id": "vault_001", + "display_name": "Cred Two", + "created_at": "2025-01-02T00:00:00Z" + } + ], + "next_page": null, + "request_id": "req-023" + }, + "file_response": { + "id": "file_001", + "type": "file", + "filename": "test.pdf", + "downloadable": true, + "mime_type": "application/pdf", + "size_bytes": 12345, + "status": "completed", + "created_at": "2025-01-01T00:00:00Z" + }, + "file_list_response": { + "data": [ + { + "id": "file_001", + "type": "file", + "filename": "test.pdf", + "mime_type": "application/pdf", + "size_bytes": 12345, + "status": "completed", + "created_at": "2025-01-01T00:00:00Z" + }, + { + "id": "file_002", + "type": "file", + "filename": "data.json", + "mime_type": "application/json", + "size_bytes": 256, + "status": "completed", + "created_at": "2025-01-02T00:00:00Z" + } + ], + "next_page": "cursor_file", + "request_id": "req-030" + }, + "session_event_send_response": { + "id": "evt_send_001", + "type": "event", + "status": "completed", + "request_id": "req-031" + }, + "event_list_response": { + "data": [ + { + "id": "evt_1", + "object": "event", + "type": "message", + "status": "completed", + "role": "user", + "content": [{"type": "text", "text": "hello"}], + "sequence_number": 1, + "created_at": "2025-01-01T00:00:00Z" + }, + { + "id": "evt_2", + "object": "event", + "type": "message", + "status": "completed", + "role": "assistant", + "content": [{"type": "text", "text": "hi there"}], + "sequence_number": 2, + "created_at": "2025-01-01T00:00:01Z" + } + ], + "next_page": null, + "request_id": "req-009" + } +}