Skip to content

Commit 2129dca

Browse files
foleydangclaude
andcommitted
feat(agentstudio): session agent override + env_vars + mcp_configs
Session create/update now support the agent override object form (type=agent_with_overrides with system/tools/mcp_servers/skills) in addition to the Agent ID string, plus the new top-level environment_variables and mcp_configs fields. - SessionCreateParam: agent accepts String (backward compatible), override Map, or SessionAgent; add environmentVariables/mcpConfigs. - SessionUpdateParam: add vaultIds/environmentVariables/agent(patch)/ mcpConfigs; shouldSerializeExplicitNulls preserves null sub-fields in the agent patch (delete-override semantics) on the wire. - Session model: add environment_variables/mcp_configs response fields. - SessionAgent: union helper (ofString/ofOverride + toJsonElement that preserves null sub-fields via serializeNulls gson). - Tests: session create/update body contract + SessionAgent union serialization + null-revoke coverage. Co-Authored-By: Claude Code <noreply@anthropic.com>
1 parent 9db8553 commit 2129dca

5 files changed

Lines changed: 270 additions & 5 deletions

File tree

‎src/main/java/com/alibaba/dashscope/agentstudio/model/Session.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public class Session extends FlattenResultBase {
4444
@SerializedName("vault_ids")
4545
private List<String> vaultIds;
4646

47+
@SerializedName("environment_variables")
48+
private Map<String, String> environmentVariables;
49+
50+
@SerializedName("mcp_configs")
51+
private List<Map<String, Object>> mcpConfigs;
52+
4753
@SerializedName("stats")
4854
private Stats stats;
4955

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (c) Alibaba, Inc. and its affiliates.
2+
package com.alibaba.dashscope.agentstudio.param;
3+
4+
import com.google.gson.Gson;
5+
import com.google.gson.GsonBuilder;
6+
import com.google.gson.JsonElement;
7+
import com.google.gson.JsonPrimitive;
8+
import java.util.Map;
9+
import java.util.Objects;
10+
import lombok.EqualsAndHashCode;
11+
12+
/**
13+
* Session-level agent reference: an Agent ID string or an override object
14+
* carrying system/tools/mcp_servers/skills.
15+
*/
16+
@EqualsAndHashCode
17+
public final class SessionAgent {
18+
private final String string;
19+
private final Map<String, Object> override;
20+
21+
private SessionAgent(String string, Map<String, Object> override) {
22+
this.string = string;
23+
this.override = override;
24+
}
25+
26+
/**
27+
* The Agent ID string form — pins the latest version for the session.
28+
*
29+
* @throws IllegalArgumentException if {@code agentId} is {@code null}.
30+
*/
31+
public static SessionAgent ofString(String agentId) {
32+
if (agentId == null) {
33+
throw new IllegalArgumentException("agentId is required");
34+
}
35+
return new SessionAgent(agentId, null);
36+
}
37+
38+
/**
39+
* The Agent override object form ({@code type=agent_with_overrides} with
40+
* Session-level {@code system}/{@code tools}/{@code mcp_servers}/{@code
41+
* skills} overrides). The mapping is passed through verbatim.
42+
*
43+
* @throws IllegalArgumentException if {@code override} is {@code null}.
44+
*/
45+
public static SessionAgent ofOverride(Map<String, Object> override) {
46+
if (override == null) {
47+
throw new IllegalArgumentException("override is required");
48+
}
49+
return new SessionAgent(null, override);
50+
}
51+
52+
/** @return {@code true} if this is the Agent ID string form. */
53+
public boolean isString() {
54+
return string != null;
55+
}
56+
57+
/** @return the Agent ID string, or {@code null} if this is the override form. */
58+
public String asString() {
59+
return string;
60+
}
61+
62+
/** @return the override mapping, or {@code null} if this is the string form. */
63+
public Map<String, Object> asOverride() {
64+
return override;
65+
}
66+
67+
private static final Gson NULLS_GSON =
68+
new GsonBuilder().serializeNulls().disableHtmlEscaping().create();
69+
70+
/** Serialize this reference to JSON: a string primitive or an override object. */
71+
public JsonElement toJsonElement() {
72+
if (string != null) {
73+
return new JsonPrimitive(string);
74+
}
75+
return NULLS_GSON.toJsonTree(override);
76+
}
77+
78+
/** Serialize a raw agent value (SessionAgent, String, or Map) to JSON. */
79+
public static JsonElement toJsonElement(Object agent) {
80+
if (agent == null) {
81+
throw new IllegalArgumentException("agent is required");
82+
}
83+
if (agent instanceof SessionAgent) {
84+
return ((SessionAgent) agent).toJsonElement();
85+
}
86+
if (agent instanceof String) {
87+
return new JsonPrimitive((String) agent);
88+
}
89+
if (agent instanceof Map) {
90+
@SuppressWarnings("unchecked")
91+
Map<String, Object> override = (Map<String, Object>) agent;
92+
return NULLS_GSON.toJsonTree(override);
93+
}
94+
throw new IllegalArgumentException(
95+
"agent must be a String (Agent ID), a Map (override), or a SessionAgent");
96+
}
97+
98+
@Override
99+
public String toString() {
100+
return string != null ? "SessionAgent{string=" + string + "}"
101+
: "SessionAgent{override=" + override + "}";
102+
}
103+
}

‎src/main/java/com/alibaba/dashscope/agentstudio/param/SessionCreateParam.java‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,20 @@
1616
@SuperBuilder
1717
@EqualsAndHashCode(callSuper = true)
1818
public class SessionCreateParam extends FlattenHalfDuplexParamBase {
19-
@NonNull private String agent;
19+
/** Agent ID string, override Map, or SessionAgent. See {@link SessionAgent}. */
20+
@NonNull private Object agent;
2021
@Default private String environmentId = null;
2122
@Default private String title = null;
2223
@Default private List<Map<String, Object>> resources = null;
2324
@Default private List<String> vaultIds = null;
25+
@Default private Map<String, String> environmentVariables = null;
26+
@Default private List<Map<String, Object>> mcpConfigs = null;
2427
@Default private Map<String, String> metadata = null;
2528

2629
@Override
2730
public JsonObject getHttpBody() {
2831
JsonObject body = new JsonObject();
29-
body.addProperty("agent", agent);
32+
body.add("agent", SessionAgent.toJsonElement(agent));
3033
if (environmentId != null) {
3134
body.addProperty("environment_id", environmentId);
3235
}
@@ -39,6 +42,12 @@ public JsonObject getHttpBody() {
3942
if (vaultIds != null && !vaultIds.isEmpty()) {
4043
body.add("vault_ids", JsonUtils.toJsonElement(vaultIds));
4144
}
45+
if (environmentVariables != null && !environmentVariables.isEmpty()) {
46+
body.add("environment_variables", JsonUtils.toJsonElement(environmentVariables));
47+
}
48+
if (mcpConfigs != null && !mcpConfigs.isEmpty()) {
49+
body.add("mcp_configs", JsonUtils.toJsonElement(mcpConfigs));
50+
}
4251
if (metadata != null && !metadata.isEmpty()) {
4352
body.add("metadata", JsonUtils.toJsonElement(metadata));
4453
}

‎src/main/java/com/alibaba/dashscope/agentstudio/param/SessionUpdateParam.java‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase;
55
import com.alibaba.dashscope.utils.JsonUtils;
66
import com.google.gson.JsonObject;
7+
import java.util.List;
78
import java.util.Map;
89
import lombok.Builder.Default;
910
import lombok.Data;
@@ -16,6 +17,17 @@
1617
public class SessionUpdateParam extends FlattenHalfDuplexParamBase {
1718
@Default private String title = null;
1819
@Default private Map<String, String> metadata = null;
20+
@Default private List<String> vaultIds = null;
21+
@Default private Map<String, String> environmentVariables = null;
22+
/** Agent config patch (override Map or SessionAgent). See {@link SessionAgent}. */
23+
@Default private Object agent = null;
24+
@Default private List<Map<String, Object>> mcpConfigs = null;
25+
26+
@Override
27+
public boolean shouldSerializeExplicitNulls() {
28+
// Preserve null sub-fields inside the agent patch on the wire.
29+
return agent != null;
30+
}
1931

2032
@Override
2133
public JsonObject getHttpBody() {
@@ -26,6 +38,18 @@ public JsonObject getHttpBody() {
2638
if (metadata != null) {
2739
body.add("metadata", JsonUtils.toJsonElement(metadata));
2840
}
41+
if (vaultIds != null) {
42+
body.add("vault_ids", JsonUtils.toJsonElement(vaultIds));
43+
}
44+
if (environmentVariables != null) {
45+
body.add("environment_variables", JsonUtils.toJsonElement(environmentVariables));
46+
}
47+
if (agent != null) {
48+
body.add("agent", SessionAgent.toJsonElement(agent));
49+
}
50+
if (mcpConfigs != null) {
51+
body.add("mcp_configs", JsonUtils.toJsonElement(mcpConfigs));
52+
}
2953
addExtraBody(body);
3054
return body;
3155
}

‎src/test/java/com/alibaba/dashscope/TestAgentStudio.java‎

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.alibaba.dashscope.agentstudio.param.EnvironmentCreateParam;
3434
import com.alibaba.dashscope.agentstudio.param.EnvironmentListParam;
3535
import com.alibaba.dashscope.agentstudio.param.FileListParam;
36+
import com.alibaba.dashscope.agentstudio.param.SessionAgent;
3637
import com.alibaba.dashscope.agentstudio.param.SessionCreateParam;
3738
import com.alibaba.dashscope.agentstudio.param.SessionEventListParam;
3839
import com.alibaba.dashscope.agentstudio.param.SessionListParam;
@@ -50,6 +51,7 @@
5051
import com.alibaba.dashscope.exception.ApiException;
5152
import com.alibaba.dashscope.utils.Constants;
5253
import com.alibaba.dashscope.utils.JsonUtils;
54+
import com.google.gson.JsonElement;
5355
import com.google.gson.JsonObject;
5456
import java.io.ByteArrayInputStream;
5557
import java.io.IOException;
@@ -59,6 +61,8 @@
5961
import java.nio.file.Paths;
6062
import java.util.Arrays;
6163
import java.util.Collections;
64+
import java.util.HashMap;
65+
import java.util.Map;
6266
import okhttp3.mockwebserver.MockResponse;
6367
import okhttp3.mockwebserver.MockWebServer;
6468
import okhttp3.mockwebserver.RecordedRequest;
@@ -288,7 +292,7 @@ public void testSessionCreate() throws Exception {
288292
enqueue("session_response");
289293
Session session =
290294
new Sessions(null, null, null)
291-
.create(SessionCreateParam.builder().agent("agent_xyz").title("Test Session").build());
295+
.create(SessionCreateParam.builder().agent(SessionAgent.ofString("agent_xyz")).title("Test Session").build());
292296
RecordedRequest req = mockServer.takeRequest();
293297
assertEquals("POST", req.getMethod());
294298
assertTrue(req.getPath().endsWith("/sessions"));
@@ -309,7 +313,7 @@ public void testSessionCreateWithVaultIds() throws Exception {
309313
new Sessions(null, null, null)
310314
.create(
311315
SessionCreateParam.builder()
312-
.agent("agent_xyz")
316+
.agent(SessionAgent.ofString("agent_xyz"))
313317
.vaultIds(Arrays.asList("vlt_x"))
314318
.metadata(Collections.singletonMap("biz_ticket_id", "1234"))
315319
.build());
@@ -323,11 +327,130 @@ public void testSessionCreateWithVaultIds() throws Exception {
323327

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

335+
@Test
336+
public void testSessionCreateWithAgentOverride() throws Exception {
337+
// agent accepts an override object (type=agent_with_overrides) in
338+
// addition to the Agent ID string; environment_variables / mcp_configs
339+
// are new top-level fields. Omitted optional fields are not sent.
340+
enqueue("session_response");
341+
Map<String, Object> agent = new HashMap<>();
342+
agent.put("type", "agent_with_overrides");
343+
agent.put("id", "agent_xxx");
344+
agent.put("version", 3);
345+
agent.put("system", "你是此 Session 专用的助手。");
346+
agent.put("tools", Collections.emptyList());
347+
Map<String, Object> mcpServer = new HashMap<>();
348+
mcpServer.put("type", "official");
349+
mcpServer.put("name", "weather");
350+
agent.put("mcp_servers", Collections.singletonList(mcpServer));
351+
352+
Map<String, String> headers = new HashMap<>();
353+
headers.put("X-Tenant-Id", "demo");
354+
Map<String, Object> mcpConfig = new HashMap<>();
355+
mcpConfig.put("mcp_server_name", "weather");
356+
mcpConfig.put("headers", headers);
357+
358+
new Sessions(null, null, null)
359+
.create(
360+
SessionCreateParam.builder()
361+
.agent(SessionAgent.ofOverride(agent))
362+
.title("项目分析")
363+
.environmentVariables(Collections.singletonMap("LANG", "zh_CN.UTF-8"))
364+
.mcpConfigs(Collections.singletonList(mcpConfig))
365+
.build());
366+
JsonObject body = JsonUtils.parse(mockServer.takeRequest().getBody().readUtf8());
367+
assertTrue(body.has("agent"));
368+
assertEquals(
369+
"agent_with_overrides", body.getAsJsonObject("agent").get("type").getAsString());
370+
assertEquals("agent_xxx", body.getAsJsonObject("agent").get("id").getAsString());
371+
assertTrue(body.getAsJsonObject("agent").get("tools").isJsonArray());
372+
assertEquals(
373+
"zh_CN.UTF-8",
374+
body.getAsJsonObject("environment_variables").get("LANG").getAsString());
375+
assertEquals(
376+
"demo",
377+
body.getAsJsonArray("mcp_configs")
378+
.get(0)
379+
.getAsJsonObject()
380+
.getAsJsonObject("headers")
381+
.get("X-Tenant-Id")
382+
.getAsString());
383+
// Omitted fields are absent — never sent as null.
384+
assertFalse(body.has("resources"));
385+
assertFalse(body.has("vault_ids"));
386+
assertFalse(body.has("metadata"));
387+
}
388+
389+
@Test
390+
public void testSessionUpdateAgentPatchAndClear() throws Exception {
391+
// update: agent patch sub-fields — null revokes the override (must be
392+
// preserved as JSON null, not dropped), "" / [] override to empty, an
393+
// omitted sub-field stays absent. Top-level [] / {} explicitly clear.
394+
enqueue("session_response");
395+
Map<String, Object> agentPatch = new HashMap<>();
396+
agentPatch.put("system", "");
397+
agentPatch.put("tools", null);
398+
agentPatch.put("skills", Collections.emptyList());
399+
400+
new Sessions(null, null, null)
401+
.update(
402+
"sess_abc123",
403+
SessionUpdateParam.builder()
404+
.title("更新后")
405+
.vaultIds(Collections.emptyList())
406+
.environmentVariables(new HashMap<String, String>())
407+
.mcpConfigs(Collections.emptyList())
408+
.agent(SessionAgent.ofOverride(agentPatch))
409+
.build());
410+
JsonObject body = JsonUtils.parse(mockServer.takeRequest().getBody().readUtf8());
411+
// Top-level [] / {} clear (sent, not null, not omitted).
412+
assertTrue(body.has("vault_ids"));
413+
assertTrue(body.getAsJsonArray("vault_ids").isEmpty());
414+
assertTrue(body.has("environment_variables"));
415+
assertTrue(body.getAsJsonObject("environment_variables").entrySet().isEmpty());
416+
assertTrue(body.has("mcp_configs"));
417+
assertTrue(body.getAsJsonArray("mcp_configs").isEmpty());
418+
// Agent patch: null preserved (revoke), "" preserved (empty prompt),
419+
// [] preserved (empty), omitted sub-field absent (keep).
420+
JsonObject agent = body.getAsJsonObject("agent");
421+
assertTrue(agent.has("tools"));
422+
assertTrue(agent.get("tools").isJsonNull());
423+
assertEquals("", agent.get("system").getAsString());
424+
assertTrue(agent.getAsJsonArray("skills").isEmpty());
425+
assertFalse(agent.has("mcp_servers"));
426+
}
427+
428+
@Test
429+
public void testSessionAgentUnionSerialization() {
430+
// string form -> JSON string primitive
431+
JsonElement s = SessionAgent.ofString("agent_01").toJsonElement();
432+
assertTrue(s.isJsonPrimitive());
433+
assertEquals("agent_01", s.getAsString());
434+
435+
// override form preserves null sub-fields (revoke override) and [] / ""
436+
Map<String, Object> patch = new HashMap<>();
437+
patch.put("system", "");
438+
patch.put("tools", null);
439+
patch.put("skills", Collections.emptyList());
440+
JsonElement o = SessionAgent.ofOverride(patch).toJsonElement();
441+
assertTrue(o.isJsonObject());
442+
assertTrue(o.getAsJsonObject().get("tools").isJsonNull());
443+
assertEquals("", o.getAsJsonObject().get("system").getAsString());
444+
assertTrue(o.getAsJsonObject().getAsJsonArray("skills").isEmpty());
445+
446+
// static toJsonElement accepts String / Map / SessionAgent
447+
assertEquals("agent_01", SessionAgent.toJsonElement("agent_01").getAsString());
448+
assertTrue(SessionAgent.toJsonElement(patch).isJsonObject());
449+
// unsupported type -> IllegalArgumentException
450+
assertThrows(IllegalArgumentException.class, () -> SessionAgent.toJsonElement(42));
451+
assertThrows(IllegalArgumentException.class, () -> SessionAgent.toJsonElement(null));
452+
}
453+
331454
@Test
332455
public void testSessionRetrieve() throws Exception {
333456
enqueue("session_response");

0 commit comments

Comments
 (0)