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
8 changes: 8 additions & 0 deletions dashscope/agentstudio/resources/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def create(
tools: Optional[Sequence[Mapping[str, Any]]] = None,
mcp_servers: Optional[Sequence[Mapping[str, Any]]] = None,
skills: Optional[Sequence[Mapping[str, Any]]] = None,
multiagent: Optional[Mapping[str, Any]] = None,
metadata: Optional[Mapping[str, Any]] = None,
) -> Agent:
body = AgentCreateParams(
Expand All @@ -52,6 +53,7 @@ def create(
tools=tools,
mcp_servers=mcp_servers,
skills=skills,
multiagent=multiagent,
metadata=metadata,
).to_dict()
resp = self._client.transport.request("POST", _PATH_AGENTS, json=body)
Expand Down Expand Up @@ -89,6 +91,7 @@ def update(
tools: Optional[Sequence[Mapping[str, Any]]] = None,
mcp_servers: Optional[Sequence[Mapping[str, Any]]] = None,
skills: Optional[Sequence[Mapping[str, Any]]] = None,
multiagent: Optional[Mapping[str, Any]] = None,
metadata: Optional[Mapping[str, Any]] = None,
) -> Agent:
"""Update the latest version of an agent.
Expand All @@ -106,6 +109,7 @@ def update(
tools=tools,
mcp_servers=mcp_servers,
skills=skills,
multiagent=multiagent,
metadata=metadata,
).to_dict()
resp = self._client.transport.request(
Expand Down Expand Up @@ -191,6 +195,7 @@ async def create(
tools: Optional[Sequence[Mapping[str, Any]]] = None,
mcp_servers: Optional[Sequence[Mapping[str, Any]]] = None,
skills: Optional[Sequence[Mapping[str, Any]]] = None,
multiagent: Optional[Mapping[str, Any]] = None,
metadata: Optional[Mapping[str, Any]] = None,
) -> Agent:
body = AgentCreateParams(
Expand All @@ -201,6 +206,7 @@ async def create(
tools=tools,
mcp_servers=mcp_servers,
skills=skills,
multiagent=multiagent,
metadata=metadata,
).to_dict()
resp = await self._client.transport.request(
Expand Down Expand Up @@ -242,6 +248,7 @@ async def update(
tools: Optional[Sequence[Mapping[str, Any]]] = None,
mcp_servers: Optional[Sequence[Mapping[str, Any]]] = None,
skills: Optional[Sequence[Mapping[str, Any]]] = None,
multiagent: Optional[Mapping[str, Any]] = None,
metadata: Optional[Mapping[str, Any]] = None,
) -> Agent:
"""Update the latest version of an agent.
Expand All @@ -259,6 +266,7 @@ async def update(
tools=tools,
mcp_servers=mcp_servers,
skills=skills,
multiagent=multiagent,
metadata=metadata,
).to_dict()
resp = await self._client.transport.request(
Expand Down
90 changes: 88 additions & 2 deletions dashscope/agentstudio/resources/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@
ProgressCallback = Callable[[int, int], None]


class FileContent(bytes):
"""Binary content of a downloaded file.

A ``bytes`` subclass so it drops into any code expecting raw bytes,
with a :meth:`write_to_file` helper that mirrors the Anthropic SDK
(``client.beta.files.download(...).write_to_file(path)``).
"""

def write_to_file(
self,
path: Union[str, "os.PathLike[str]"],
) -> Path:
"""Write the content to ``path`` and return it.

Missing parent directories are created.
"""
dest = Path(os.fspath(path))
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(self)
return dest


def _open_file(
file: Union[str, "os.PathLike[str]", BinaryIO, Tuple[str, BinaryIO]],
) -> Tuple[str, BinaryIO, bool]:
Expand Down Expand Up @@ -110,7 +132,7 @@ def _file_size(fileobj: IO[bytes]) -> int:


class Files:
"""File upload / list / delete."""
"""File upload / download / list / delete."""

def __init__(self, client) -> None:
self._client = client
Expand Down Expand Up @@ -165,6 +187,38 @@ def retrieve(self, file_id: str) -> File:
# Alias: get() delegates to retrieve()
get = retrieve # type: ignore[assignment]

def _open_content(self, file_id: str, timeout: Optional[float]):
"""GET the content endpoint and return the streaming response."""
Comment thread
luk384090-cloud marked this conversation as resolved.
return self._client.transport.request(
"GET",
f"{_PATH_FILES}/{file_id}/content",
extra_headers={"Accept": "*/*"},
stream=True,
timeout=timeout,
)

def download(
self,
file_id: str,
*,
timeout: Optional[float] = None,
) -> FileContent:
"""Return the file content as a :class:`FileContent`.

Only files whose ``downloadable`` flag is true can be fetched; the
service answers 403 otherwise. Use :meth:`FileContent.write_to_file`
to persist the bytes to disk.

content = client.files.download("file_xxx")
content.write_to_file("output.txt")
"""
resp = self._open_content(file_id, timeout)
try:
resp.read()
return FileContent(resp.content)
finally:
resp.close()

def list(
self,
*,
Expand Down Expand Up @@ -207,7 +261,7 @@ def delete(self, file_id: str) -> DeleteResponse:


class AsyncFiles:
"""Async file upload / list / delete."""
"""Async file upload / download / list / delete."""

def __init__(self, client) -> None:
self._client = client
Expand Down Expand Up @@ -262,6 +316,38 @@ async def retrieve(self, file_id: str) -> File:
# Alias: get() delegates to retrieve()
get = retrieve # type: ignore[assignment]

async def _open_content(self, file_id: str, timeout: Optional[float]):
"""GET the content endpoint and return the streaming response."""
return await self._client.transport.request(
"GET",
f"{_PATH_FILES}/{file_id}/content",
extra_headers={"Accept": "*/*"},
stream=True,
timeout=timeout,
)

async def download(
self,
file_id: str,
*,
timeout: Optional[float] = None,
) -> FileContent:
"""Return the file content as a :class:`FileContent`.

Only files whose ``downloadable`` flag is true can be fetched; the
service answers 403 otherwise. Use :meth:`FileContent.write_to_file`
to persist the bytes to disk.

content = await client.files.download("file_xxx")
content.write_to_file("output.txt")
"""
resp = await self._open_content(file_id, timeout)
try:
await resp.aread()
return FileContent(resp.content)
finally:
await resp.aclose()

async def list(
self,
*,
Expand Down
4 changes: 4 additions & 0 deletions dashscope/agentstudio/resources/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ def create(
environment_id: Optional[str] = None,
title: Optional[str] = None,
resources: Optional[Sequence[Mapping[str, Any]]] = None,
vault_ids: Optional[Sequence[str]] = None,
metadata: Optional[Mapping[str, Any]] = None,
) -> Session:
body = SessionCreateParams(
agent=agent,
environment_id=environment_id,
title=title,
resources=resources,
vault_ids=vault_ids,
metadata=metadata,
).to_dict()
resp = self._client.transport.request(
Expand Down Expand Up @@ -166,13 +168,15 @@ async def create(
environment_id: Optional[str] = None,
title: Optional[str] = None,
resources: Optional[Sequence[Mapping[str, Any]]] = None,
vault_ids: Optional[Sequence[str]] = None,
metadata: Optional[Mapping[str, Any]] = None,
) -> Session:
body = SessionCreateParams(
agent=agent,
environment_id=environment_id,
title=title,
resources=resources,
vault_ids=vault_ids,
metadata=metadata,
).to_dict()
resp = await self._client.transport.request(
Expand Down
2 changes: 2 additions & 0 deletions dashscope/agentstudio/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
parse_content_blocks,
Agent,
AgentVersion,
MultiAgentConfig,
MultiAgentRosterEntry,
Credential,
CredentialAuth,
Deployment,
Expand Down
41 changes: 41 additions & 0 deletions dashscope/agentstudio/types/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,39 @@ def parse_content_blocks(
# ===========================================================================


class MultiAgentRosterEntry(BaseModel):
"""One entry in a coordinator agent's multiagent roster.

``type`` is ``"agent"`` (reference another agent by ``id`` + optional
``version``) or ``"self"`` (a copy of the coordinator; at most one).
"""

_fields = ("type", "id", "version")


class MultiAgentConfig(BaseModel):
"""Multi-agent coordinator config (the ``multiagent`` field).

``type`` is currently always ``"coordinator"``; ``agents`` is the
roster of 1-20 entries. An empty list clears the roster.
"""

_fields = ("type", "agents")

def __init__(self, **kwargs: Any) -> None:
agents = kwargs.get("agents")
if isinstance(agents, list):
kwargs["agents"] = [
(
MultiAgentRosterEntry(**dict(a))
if isinstance(a, Mapping)
else a
)
for a in agents
]
super().__init__(**kwargs)


class Agent(BaseModel):
_fields = (
"id",
Expand All @@ -345,6 +378,7 @@ class Agent(BaseModel):
"tools",
"mcp_servers",
"skills",
"multiagent",
"metadata",
"workspace_id",
"archived_at",
Expand All @@ -353,6 +387,12 @@ class Agent(BaseModel):
"request_id",
)

def __init__(self, **kwargs: Any) -> None:
multiagent = kwargs.get("multiagent")
if isinstance(multiagent, Mapping):
kwargs["multiagent"] = MultiAgentConfig(**dict(multiagent))
super().__init__(**kwargs)

@property
def system_prompt(self) -> Optional[str]:
"""Alias: server field is ``system``, kept for SDK user convenience."""
Expand Down Expand Up @@ -653,6 +693,7 @@ class Message(BaseModel):
"created_at",
"sequence_number",
"session_thread_id",
"thread_id",
"code",
"message",
)
Expand Down
24 changes: 23 additions & 1 deletion dashscope/agentstudio/types/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class AgentCreateParams(BaseModel):
"tools",
"mcp_servers",
"skills",
"multiagent",
"metadata",
)

Expand All @@ -65,6 +66,7 @@ def __init__(
tools: Optional[Sequence[Mapping[str, Any]]] = None,
mcp_servers: Optional[Sequence[Mapping[str, Any]]] = None,
skills: Optional[Sequence[Mapping[str, Any]]] = None,
multiagent: Optional[Mapping[str, Any]] = None,
metadata: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(
Expand All @@ -79,6 +81,9 @@ def __init__(
else None
),
skills=([dict(s) for s in skills] if skills is not None else None),
multiagent=(
_to_mapping(multiagent) if multiagent is not None else None
),
metadata=(dict(metadata) if metadata is not None else None),
)

Expand All @@ -95,6 +100,7 @@ class AgentUpdateParams(BaseModel):
"tools",
"mcp_servers",
"skills",
"multiagent",
"metadata",
)

Expand All @@ -109,6 +115,7 @@ def __init__(
tools: Optional[Sequence[Mapping[str, Any]]] = None,
mcp_servers: Optional[Sequence[Mapping[str, Any]]] = None,
skills: Optional[Sequence[Mapping[str, Any]]] = None,
multiagent: Optional[Mapping[str, Any]] = None,
metadata: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(
Expand All @@ -124,6 +131,9 @@ def __init__(
else None
),
skills=([dict(s) for s in skills] if skills is not None else None),
multiagent=(
_to_mapping(multiagent) if multiagent is not None else None
),
metadata=(dict(metadata) if metadata is not None else None),
)

Expand Down Expand Up @@ -252,9 +262,19 @@ class SessionCreateParams(BaseModel):
``agent`` is the agent ID string (not the full agent object).
``resources`` is an optional list of file mounts; each item is a
mapping with ``type``, ``file_id`` and ``mount_path`` keys.
``vault_ids`` is create-only — attach vaults (``vlt_*``) whose
credentials are substituted at egress; the session update path does
not accept it.
"""

_fields = ("agent", "environment_id", "title", "resources", "metadata")
_fields = (
"agent",
"environment_id",
"title",
"resources",
"vault_ids",
"metadata",
)

def __init__( # pylint: disable=useless-parent-delegation
self,
Expand All @@ -263,6 +283,7 @@ def __init__( # pylint: disable=useless-parent-delegation
environment_id: Optional[str] = None,
title: Optional[str] = None,
resources: Optional[Sequence[Mapping[str, Any]]] = None,
vault_ids: Optional[Sequence[str]] = None,
metadata: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(
Expand All @@ -272,6 +293,7 @@ def __init__( # pylint: disable=useless-parent-delegation
resources=(
[dict(r) for r in resources] if resources is not None else None
),
vault_ids=(list(vault_ids) if vault_ids is not None else None),
metadata=(dict(metadata) if metadata is not None else None),
)

Expand Down
Loading
Loading