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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public class Session extends FlattenResultBase {
@SerializedName("vault_ids")
private List<String> vaultIds;

@SerializedName("environment_variables")
private Map<String, String> environmentVariables;

@SerializedName("mcp_configs")
private List<Map<String, Object>> mcpConfigs;

@SerializedName("stats")
private Stats stats;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) Alibaba, Inc. and its affiliates.
package com.alibaba.dashscope.agentstudio.param;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import java.util.Map;
import lombok.EqualsAndHashCode;

/**
* Session-level agent reference: an Agent ID string or an override object carrying
* system/tools/mcp_servers/skills.
*/
@EqualsAndHashCode
public final class SessionAgent {
private final String string;
private final Map<String, Object> override;

private SessionAgent(String string, Map<String, Object> override) {
this.string = string;
this.override = override;
}

/**
* The Agent ID string form — pins the latest version for the session.
*
* @throws IllegalArgumentException if {@code agentId} is {@code null}.
*/
public static SessionAgent ofString(String agentId) {
if (agentId == null) {
throw new IllegalArgumentException("agentId is required");
}
return new SessionAgent(agentId, null);
}

/**
* The Agent override object form ({@code type=agent_with_overrides} with Session-level {@code
* system}/{@code tools}/{@code mcp_servers}/{@code skills} overrides). The mapping is passed
* through verbatim.
*
* @throws IllegalArgumentException if {@code override} is {@code null}.
*/
public static SessionAgent ofOverride(Map<String, Object> override) {
if (override == null) {
throw new IllegalArgumentException("override is required");
}
return new SessionAgent(null, override);
}

/** @return {@code true} if this is the Agent ID string form. */
public boolean isString() {
return string != null;
}

/** @return the Agent ID string, or {@code null} if this is the override form. */
public String asString() {
return string;
}

/** @return the override mapping, or {@code null} if this is the string form. */
public Map<String, Object> asOverride() {
return override;
}

private static final Gson NULLS_GSON =
new GsonBuilder().serializeNulls().disableHtmlEscaping().create();

/** Serialize this reference to JSON: a string primitive or an override object. */
public JsonElement toJsonElement() {
if (string != null) {
return new JsonPrimitive(string);
}
return NULLS_GSON.toJsonTree(override);
}

/** Serialize a raw agent value (SessionAgent, String, or Map) to JSON. */
public static JsonElement toJsonElement(Object agent) {
if (agent == null) {
throw new IllegalArgumentException("agent is required");
}
if (agent instanceof SessionAgent) {
return ((SessionAgent) agent).toJsonElement();
}
if (agent instanceof String) {
return new JsonPrimitive((String) agent);
}
if (agent instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> override = (Map<String, Object>) agent;
return NULLS_GSON.toJsonTree(override);
}
throw new IllegalArgumentException(
"agent must be a String (Agent ID), a Map (override), or a SessionAgent");
}

@Override
public String toString() {
return string != null
? "SessionAgent{string=" + string + "}"
: "SessionAgent{override=" + override + "}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
public class SessionCreateParam extends FlattenHalfDuplexParamBase {
@NonNull private String agent;
/** Agent ID string, override Map, or SessionAgent. See {@link SessionAgent}. */
@NonNull private Object agent;

@Default private String environmentId = null;
@Default private String title = null;
@Default private List<Map<String, Object>> resources = null;
@Default private List<String> vaultIds = null;
@Default private Map<String, String> environmentVariables = null;
@Default private List<Map<String, Object>> mcpConfigs = null;
@Default private Map<String, String> metadata = null;

@Override
public JsonObject getHttpBody() {
JsonObject body = new JsonObject();
body.addProperty("agent", agent);
body.add("agent", SessionAgent.toJsonElement(agent));
if (environmentId != null) {
body.addProperty("environment_id", environmentId);
}
Expand All @@ -39,6 +43,12 @@ public JsonObject getHttpBody() {
if (vaultIds != null && !vaultIds.isEmpty()) {
body.add("vault_ids", JsonUtils.toJsonElement(vaultIds));
}
if (environmentVariables != null && !environmentVariables.isEmpty()) {
body.add("environment_variables", JsonUtils.toJsonElement(environmentVariables));
}
if (mcpConfigs != null && !mcpConfigs.isEmpty()) {
body.add("mcp_configs", JsonUtils.toJsonElement(mcpConfigs));
}
if (metadata != null && !metadata.isEmpty()) {
body.add("metadata", JsonUtils.toJsonElement(metadata));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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;
Expand All @@ -16,6 +17,18 @@
public class SessionUpdateParam extends FlattenHalfDuplexParamBase {
@Default private String title = null;
@Default private Map<String, String> metadata = null;
@Default private List<String> vaultIds = null;
@Default private Map<String, String> environmentVariables = null;
/** Agent config patch (override Map or SessionAgent). See {@link SessionAgent}. */
@Default private Object agent = null;

@Default private List<Map<String, Object>> mcpConfigs = null;

@Override
public boolean shouldSerializeExplicitNulls() {
// Preserve null sub-fields inside the agent patch on the wire.
return agent != null;
}

@Override
public JsonObject getHttpBody() {
Expand All @@ -26,6 +39,18 @@ public JsonObject getHttpBody() {
if (metadata != null) {
body.add("metadata", JsonUtils.toJsonElement(metadata));
}
if (vaultIds != null) {
body.add("vault_ids", JsonUtils.toJsonElement(vaultIds));
}
if (environmentVariables != null) {
body.add("environment_variables", JsonUtils.toJsonElement(environmentVariables));
}
if (agent != null) {
body.add("agent", SessionAgent.toJsonElement(agent));
}
if (mcpConfigs != null) {
body.add("mcp_configs", JsonUtils.toJsonElement(mcpConfigs));
}
addExtraBody(body);
return body;
}
Expand Down
132 changes: 129 additions & 3 deletions src/test/java/com/alibaba/dashscope/TestAgentStudio.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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.SessionAgent;
import com.alibaba.dashscope.agentstudio.param.SessionCreateParam;
import com.alibaba.dashscope.agentstudio.param.SessionEventListParam;
import com.alibaba.dashscope.agentstudio.param.SessionListParam;
Expand All @@ -50,6 +51,7 @@
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.utils.Constants;
import com.alibaba.dashscope.utils.JsonUtils;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.ByteArrayInputStream;
import java.io.IOException;
Expand All @@ -59,6 +61,8 @@
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
Expand Down Expand Up @@ -288,7 +292,11 @@ 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());
.create(
SessionCreateParam.builder()
.agent(SessionAgent.ofString("agent_xyz"))
.title("Test Session")
.build());
RecordedRequest req = mockServer.takeRequest();
assertEquals("POST", req.getMethod());
assertTrue(req.getPath().endsWith("/sessions"));
Expand All @@ -309,7 +317,7 @@ public void testSessionCreateWithVaultIds() throws Exception {
new Sessions(null, null, null)
.create(
SessionCreateParam.builder()
.agent("agent_xyz")
.agent(SessionAgent.ofString("agent_xyz"))
.vaultIds(Arrays.asList("vlt_x"))
.metadata(Collections.singletonMap("biz_ticket_id", "1234"))
.build());
Expand All @@ -323,11 +331,129 @@ public void testSessionCreateWithVaultIds() throws Exception {

// Omitted vault_ids must not produce a null vault_ids in the body.
enqueue("session_response");
new Sessions(null, null, null).create(SessionCreateParam.builder().agent("agent_xyz").build());
new Sessions(null, null, null)
.create(SessionCreateParam.builder().agent(SessionAgent.ofString("agent_xyz")).build());
JsonObject body2 = JsonUtils.parse(mockServer.takeRequest().getBody().readUtf8());
assertFalse(body2.has("vault_ids"));
}

@Test
public void testSessionCreateWithAgentOverride() throws Exception {
// agent accepts an override object (type=agent_with_overrides) in
// addition to the Agent ID string; environment_variables / mcp_configs
// are new top-level fields. Omitted optional fields are not sent.
enqueue("session_response");
Map<String, Object> agent = new HashMap<>();
agent.put("type", "agent_with_overrides");
agent.put("id", "agent_xxx");
agent.put("version", 3);
agent.put("system", "你是此 Session 专用的助手。");
agent.put("tools", Collections.emptyList());
Map<String, Object> mcpServer = new HashMap<>();
mcpServer.put("type", "official");
mcpServer.put("name", "weather");
agent.put("mcp_servers", Collections.singletonList(mcpServer));

Map<String, String> headers = new HashMap<>();
headers.put("X-Tenant-Id", "demo");
Map<String, Object> mcpConfig = new HashMap<>();
mcpConfig.put("mcp_server_name", "weather");
mcpConfig.put("headers", headers);

new Sessions(null, null, null)
.create(
SessionCreateParam.builder()
.agent(SessionAgent.ofOverride(agent))
.title("项目分析")
.environmentVariables(Collections.singletonMap("LANG", "zh_CN.UTF-8"))
.mcpConfigs(Collections.singletonList(mcpConfig))
.build());
JsonObject body = JsonUtils.parse(mockServer.takeRequest().getBody().readUtf8());
assertTrue(body.has("agent"));
assertEquals("agent_with_overrides", body.getAsJsonObject("agent").get("type").getAsString());
assertEquals("agent_xxx", body.getAsJsonObject("agent").get("id").getAsString());
assertTrue(body.getAsJsonObject("agent").get("tools").isJsonArray());
assertEquals(
"zh_CN.UTF-8", body.getAsJsonObject("environment_variables").get("LANG").getAsString());
assertEquals(
"demo",
body.getAsJsonArray("mcp_configs")
.get(0)
.getAsJsonObject()
.getAsJsonObject("headers")
.get("X-Tenant-Id")
.getAsString());
// Omitted fields are absent — never sent as null.
assertFalse(body.has("resources"));
assertFalse(body.has("vault_ids"));
assertFalse(body.has("metadata"));
}

@Test
public void testSessionUpdateAgentPatchAndClear() throws Exception {
// update: agent patch sub-fields — null revokes the override (must be
// preserved as JSON null, not dropped), "" / [] override to empty, an
// omitted sub-field stays absent. Top-level [] / {} explicitly clear.
enqueue("session_response");
Map<String, Object> agentPatch = new HashMap<>();
agentPatch.put("system", "");
agentPatch.put("tools", null);
agentPatch.put("skills", Collections.emptyList());

new Sessions(null, null, null)
.update(
"sess_abc123",
SessionUpdateParam.builder()
.title("更新后")
.vaultIds(Collections.emptyList())
.environmentVariables(new HashMap<String, String>())
.mcpConfigs(Collections.emptyList())
.agent(SessionAgent.ofOverride(agentPatch))
.build());
JsonObject body = JsonUtils.parse(mockServer.takeRequest().getBody().readUtf8());
// Top-level [] / {} clear (sent, not null, not omitted).
assertTrue(body.has("vault_ids"));
assertTrue(body.getAsJsonArray("vault_ids").isEmpty());
assertTrue(body.has("environment_variables"));
assertTrue(body.getAsJsonObject("environment_variables").entrySet().isEmpty());
assertTrue(body.has("mcp_configs"));
assertTrue(body.getAsJsonArray("mcp_configs").isEmpty());
// Agent patch: null preserved (revoke), "" preserved (empty prompt),
// [] preserved (empty), omitted sub-field absent (keep).
JsonObject agent = body.getAsJsonObject("agent");
assertTrue(agent.has("tools"));
assertTrue(agent.get("tools").isJsonNull());
assertEquals("", agent.get("system").getAsString());
assertTrue(agent.getAsJsonArray("skills").isEmpty());
assertFalse(agent.has("mcp_servers"));
}

@Test
public void testSessionAgentUnionSerialization() {
// string form -> JSON string primitive
JsonElement s = SessionAgent.ofString("agent_01").toJsonElement();
assertTrue(s.isJsonPrimitive());
assertEquals("agent_01", s.getAsString());

// override form preserves null sub-fields (revoke override) and [] / ""
Map<String, Object> patch = new HashMap<>();
patch.put("system", "");
patch.put("tools", null);
patch.put("skills", Collections.emptyList());
JsonElement o = SessionAgent.ofOverride(patch).toJsonElement();
assertTrue(o.isJsonObject());
assertTrue(o.getAsJsonObject().get("tools").isJsonNull());
assertEquals("", o.getAsJsonObject().get("system").getAsString());
assertTrue(o.getAsJsonObject().getAsJsonArray("skills").isEmpty());

// static toJsonElement accepts String / Map / SessionAgent
assertEquals("agent_01", SessionAgent.toJsonElement("agent_01").getAsString());
assertTrue(SessionAgent.toJsonElement(patch).isJsonObject());
// unsupported type -> IllegalArgumentException
assertThrows(IllegalArgumentException.class, () -> SessionAgent.toJsonElement(42));
assertThrows(IllegalArgumentException.class, () -> SessionAgent.toJsonElement(null));
}

@Test
public void testSessionRetrieve() throws Exception {
enqueue("session_response");
Expand Down
Loading