Skip to content

Commit 76ef5fb

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. - SessionCreateParams: agent accepts str (backward compatible) or override mapping; add environment_variables/mcp_configs. - SessionUpdateParams: add vault_ids/environment_variables/agent(patch)/ mcp_configs; null sub-fields in the agent patch are preserved on the wire (delete-override semantics). - Session model: add environment_variables/mcp_configs response fields. - sessions resource: sync create/update signatures (sync + async). - Tests: session create/update body contract + agent patch null-revoke coverage, consolidated into test_agentstudio_protocol. Co-Authored-By: Claude Code <noreply@anthropic.com>
1 parent b0b4469 commit 76ef5fb

4 files changed

Lines changed: 229 additions & 12 deletions

File tree

‎dashscope/agentstudio/resources/sessions.py‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ def __init__(self, client) -> None:
4949
def create(
5050
self,
5151
*,
52-
agent: str,
52+
agent: Any,
5353
environment_id: Optional[str] = None,
5454
title: Optional[str] = None,
5555
resources: Optional[Sequence[Mapping[str, Any]]] = None,
5656
vault_ids: Optional[Sequence[str]] = None,
57+
environment_variables: Optional[Mapping[str, str]] = None,
58+
mcp_configs: Optional[Sequence[Mapping[str, Any]]] = None,
5759
metadata: Optional[Mapping[str, Any]] = None,
5860
) -> Session:
5961
body = SessionCreateParams(
@@ -62,6 +64,8 @@ def create(
6264
title=title,
6365
resources=resources,
6466
vault_ids=vault_ids,
67+
environment_variables=environment_variables,
68+
mcp_configs=mcp_configs,
6569
metadata=metadata,
6670
).to_dict()
6771
resp = self._client.transport.request(
@@ -90,10 +94,18 @@ def update(
9094
*,
9195
title: Optional[str] = None,
9296
metadata: Optional[Mapping[str, Any]] = None,
97+
vault_ids: Optional[Sequence[str]] = None,
98+
environment_variables: Optional[Mapping[str, str]] = None,
99+
agent: Any = None,
100+
mcp_configs: Optional[Sequence[Mapping[str, Any]]] = None,
93101
) -> Session:
94102
body = SessionUpdateParams(
95103
title=title,
96104
metadata=metadata,
105+
vault_ids=vault_ids,
106+
environment_variables=environment_variables,
107+
agent=agent,
108+
mcp_configs=mcp_configs,
97109
).to_dict()
98110
resp = self._client.transport.request(
99111
"POST",
@@ -176,11 +188,13 @@ def __init__(self, client) -> None:
176188
async def create(
177189
self,
178190
*,
179-
agent: str,
191+
agent: Any,
180192
environment_id: Optional[str] = None,
181193
title: Optional[str] = None,
182194
resources: Optional[Sequence[Mapping[str, Any]]] = None,
183195
vault_ids: Optional[Sequence[str]] = None,
196+
environment_variables: Optional[Mapping[str, str]] = None,
197+
mcp_configs: Optional[Sequence[Mapping[str, Any]]] = None,
184198
metadata: Optional[Mapping[str, Any]] = None,
185199
) -> Session:
186200
body = SessionCreateParams(
@@ -189,6 +203,8 @@ async def create(
189203
title=title,
190204
resources=resources,
191205
vault_ids=vault_ids,
206+
environment_variables=environment_variables,
207+
mcp_configs=mcp_configs,
192208
metadata=metadata,
193209
).to_dict()
194210
resp = await self._client.transport.request(
@@ -217,10 +233,18 @@ async def update(
217233
*,
218234
title: Optional[str] = None,
219235
metadata: Optional[Mapping[str, Any]] = None,
236+
vault_ids: Optional[Sequence[str]] = None,
237+
environment_variables: Optional[Mapping[str, str]] = None,
238+
agent: Any = None,
239+
mcp_configs: Optional[Sequence[Mapping[str, Any]]] = None,
220240
) -> Session:
221241
body = SessionUpdateParams(
222242
title=title,
223243
metadata=metadata,
244+
vault_ids=vault_ids,
245+
environment_variables=environment_variables,
246+
agent=agent,
247+
mcp_configs=mcp_configs,
224248
).to_dict()
225249
resp = await self._client.transport.request(
226250
"POST",

‎dashscope/agentstudio/types/models.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ class Session(BaseModel):
558558
"stats",
559559
"usage",
560560
"vault_ids",
561+
"environment_variables",
562+
"mcp_configs",
561563
"archived_at",
562564
"created_at",
563565
"updated_at",

‎dashscope/agentstudio/types/params.py‎

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -259,12 +259,14 @@ def __init__( # pylint: disable=useless-parent-delegation
259259
class SessionCreateParams(BaseModel):
260260
"""Request body for ``POST /sessions``.
261261
262-
``agent`` is the agent ID string (not the full agent object).
263-
``resources`` is an optional list of file mounts; each item is a
264-
mapping with ``type``, ``file_id`` and ``mount_path`` keys.
265-
``vault_ids`` is create-only — attach vaults (``vlt_*``) whose
266-
credentials are substituted at egress; the session update path does
267-
not accept it.
262+
``agent`` accepts the Agent ID string (backward compatible) **or** an
263+
Agent reference/override object (a mapping with
264+
``type="agent_with_overrides"``) that establishes Session-level
265+
overrides for ``system``/``tools``/``mcp_servers``/``skills``.
266+
267+
``environment_variables`` and ``mcp_configs`` are Session-level
268+
settings independent of the agent. ``resources`` is create-only — it
269+
cannot be modified via ``updateSession``.
268270
"""
269271

270272
_fields = (
@@ -273,45 +275,92 @@ class SessionCreateParams(BaseModel):
273275
"title",
274276
"resources",
275277
"vault_ids",
278+
"environment_variables",
279+
"mcp_configs",
276280
"metadata",
277281
)
278282

279283
def __init__( # pylint: disable=useless-parent-delegation
280284
self,
281285
*,
282-
agent: str,
286+
agent: Any,
283287
environment_id: Optional[str] = None,
284288
title: Optional[str] = None,
285289
resources: Optional[Sequence[Mapping[str, Any]]] = None,
286290
vault_ids: Optional[Sequence[str]] = None,
291+
environment_variables: Optional[Mapping[str, str]] = None,
292+
mcp_configs: Optional[Sequence[Mapping[str, Any]]] = None,
287293
metadata: Optional[Mapping[str, Any]] = None,
288294
) -> None:
289295
super().__init__(
290-
agent=agent,
296+
agent=_to_mapping(agent),
291297
environment_id=environment_id,
292298
title=title,
293299
resources=(
294300
[dict(r) for r in resources] if resources is not None else None
295301
),
296302
vault_ids=(list(vault_ids) if vault_ids is not None else None),
303+
environment_variables=(
304+
dict(environment_variables)
305+
if environment_variables is not None
306+
else None
307+
),
308+
mcp_configs=(
309+
[_to_mapping(m) for m in mcp_configs]
310+
if mcp_configs is not None
311+
else None
312+
),
297313
metadata=(dict(metadata) if metadata is not None else None),
298314
)
299315

300316

301317
class SessionUpdateParams(BaseModel):
302-
"""Request body for ``POST /sessions/{id}``."""
318+
"""Request body for ``POST /sessions/{id}``.
319+
320+
Omitted (``None``) fields are not sent — the existing value is kept;
321+
``[]`` / ``{}`` explicitly clear the value. ``agent`` is a Session-level
322+
Agent config patch (a plain mapping carrying only ``system``/``tools``/
323+
``mcp_servers``/``skills``); the mapping is passed through verbatim so
324+
an explicit ``None`` sub-field serializes to JSON ``null`` (delete the
325+
override and re-inherit), an omitted sub-field stays absent (keep the
326+
override), and ``[]`` / ``""`` override to empty. The server enforces
327+
the override principle; the SDK does not read or merge the agent source.
328+
"""
303329

304-
_fields = ("title", "metadata")
330+
_fields = (
331+
"title",
332+
"metadata",
333+
"vault_ids",
334+
"environment_variables",
335+
"agent",
336+
"mcp_configs",
337+
)
305338

306339
def __init__( # pylint: disable=useless-parent-delegation
307340
self,
308341
*,
309342
title: Optional[str] = None,
310343
metadata: Optional[Mapping[str, Any]] = None,
344+
vault_ids: Optional[Sequence[str]] = None,
345+
environment_variables: Optional[Mapping[str, str]] = None,
346+
agent: Any = None,
347+
mcp_configs: Optional[Sequence[Mapping[str, Any]]] = None,
311348
) -> None:
312349
super().__init__(
313350
title=title,
314351
metadata=(dict(metadata) if metadata is not None else None),
352+
vault_ids=(list(vault_ids) if vault_ids is not None else None),
353+
environment_variables=(
354+
dict(environment_variables)
355+
if environment_variables is not None
356+
else None
357+
),
358+
agent=(_to_mapping(agent) if agent is not None else None),
359+
mcp_configs=(
360+
[_to_mapping(m) for m in mcp_configs]
361+
if mcp_configs is not None
362+
else None
363+
),
315364
)
316365

317366

‎tests/unit/test_agentstudio_protocol.py‎

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,148 @@ def test_session_thread_model_fields():
715715
assert "title" not in d # legacy field removed
716716

717717

718+
def test_session_create_params_agent_string_and_object():
719+
"""createSession ``agent`` accepts the Agent ID string (forward-compat)
720+
or an override object; ``environment_variables`` / ``mcp_configs`` are
721+
new top-level fields. Omitted fields are not sent (no null leak)."""
722+
from dashscope.agentstudio.types.params import SessionCreateParams
723+
724+
# String agent — backward compatible.
725+
assert SessionCreateParams(agent="agent_01", title="x").to_dict() == {
726+
"agent": "agent_01",
727+
"title": "x",
728+
}
729+
# Override object + new top-level fields (doc §2.3 example).
730+
body = SessionCreateParams(
731+
agent={
732+
"type": "agent_with_overrides",
733+
"id": "agent_xxx",
734+
"version": 3,
735+
"system": "你是此 Session 专用的助手。",
736+
"tools": [],
737+
"mcp_servers": [{"type": "official", "name": "weather"}],
738+
"skills": [
739+
{
740+
"type": "customer",
741+
"skill_id": "skill_xxx",
742+
"version": "1.0",
743+
}
744+
],
745+
},
746+
environment_variables={"LANG": "zh_CN.UTF-8"},
747+
mcp_configs=[
748+
{"mcp_server_name": "weather", "headers": {"key1": "value1"}}
749+
],
750+
).to_dict()
751+
assert body["agent"]["type"] == "agent_with_overrides"
752+
assert body["agent"]["id"] == "agent_xxx"
753+
assert body["environment_variables"] == {"LANG": "zh_CN.UTF-8"}
754+
assert body["mcp_configs"] == [
755+
{"mcp_server_name": "weather", "headers": {"key1": "value1"}}
756+
]
757+
# Omitted optional fields are absent — never sent as null.
758+
for absent in (
759+
"environment_id",
760+
"resources",
761+
"vault_ids",
762+
"metadata",
763+
):
764+
assert absent not in body
765+
766+
767+
def test_session_update_params_patch_and_clear_semantics():
768+
"""updateSession: omitted top-level fields are not sent (keep existing);
769+
``[]`` / ``{}`` clear; the ``agent`` patch is passed through verbatim so
770+
an explicit ``None`` sub-field serializes to ``null`` (revoke override),
771+
an omitted sub-field stays absent (keep), ``[]`` / ``""`` override empty.
772+
"""
773+
from dashscope.agentstudio.types.params import SessionUpdateParams
774+
775+
# Only title — everything else omitted, no null uploaded.
776+
assert SessionUpdateParams(title="x").to_dict() == {"title": "x"}
777+
# [] / {} explicitly clear (not null).
778+
assert SessionUpdateParams(
779+
vault_ids=[], environment_variables={}, mcp_configs=[], metadata={}
780+
).to_dict() == {
781+
"vault_ids": [],
782+
"environment_variables": {},
783+
"mcp_configs": [],
784+
"metadata": {},
785+
}
786+
# Full doc §3.3 example body.
787+
body = SessionUpdateParams(
788+
title="更新后的项目分析",
789+
metadata={"project": "demo", "phase": "review"},
790+
vault_ids=["vault_project", "vault_shared"],
791+
environment_variables={"LANG": "zh_CN.UTF-8", "REPORT_MODE": "strict"},
792+
agent={
793+
"system": "你是本 Session 专用的项目分析助手。",
794+
"tools": [],
795+
"mcp_servers": [],
796+
"skills": [
797+
{
798+
"type": "customer",
799+
"skill_id": "skill_report_writer",
800+
"version": "1.0",
801+
}
802+
],
803+
},
804+
mcp_configs=[
805+
{
806+
"mcp_server_name": "weather",
807+
"headers": {"X-Tenant-Id": "demo"},
808+
}
809+
],
810+
).to_dict()
811+
assert body["agent"]["system"] == "你是本 Session 专用的项目分析助手。"
812+
assert body["agent"]["skills"] == [
813+
{
814+
"type": "customer",
815+
"skill_id": "skill_report_writer",
816+
"version": "1.0",
817+
}
818+
]
819+
assert body["environment_variables"] == {
820+
"LANG": "zh_CN.UTF-8",
821+
"REPORT_MODE": "strict",
822+
}
823+
# Agent patch: omitted sub-field absent (keep), None → null (revoke),
824+
# [] / "" override to empty. Mapping passed through verbatim.
825+
patch = SessionUpdateParams(
826+
agent={"system": "", "tools": None, "skills": []}
827+
).to_dict()["agent"]
828+
assert patch == {"system": "", "tools": None, "skills": []}
829+
assert "mcp_servers" not in patch
830+
831+
832+
def test_session_model_hydrates_override_agent_and_new_fields():
833+
"""Session response parses the agent override object and the new
834+
``environment_variables`` / ``mcp_configs`` fields."""
835+
from dashscope.agentstudio.types import Agent, Session
836+
837+
s = Session(
838+
id="sesn_1",
839+
agent={
840+
"id": "agent_01",
841+
"type": "agent_with_overrides",
842+
"version": 3,
843+
"system": "hi",
844+
},
845+
environment_variables={"LANG": "zh_CN.UTF-8"},
846+
mcp_configs=[
847+
{"mcp_server_name": "weather", "headers": {"k": "v"}}
848+
],
849+
)
850+
assert isinstance(s.agent, Agent)
851+
assert s.agent.id == "agent_01"
852+
assert s.agent.type == "agent_with_overrides"
853+
assert s.agent_id == "agent_01" # works for string and object forms
854+
assert s.environment_variables == {"LANG": "zh_CN.UTF-8"}
855+
assert s.mcp_configs == [
856+
{"mcp_server_name": "weather", "headers": {"k": "v"}}
857+
]
858+
859+
718860
def test_message_error_and_pending_signal():
719861
from dashscope.agentstudio.types import parse_message
720862

0 commit comments

Comments
 (0)