Skip to content

Commit a4476e3

Browse files
Merge pull request #192 from dashscope/dev/agent-delta-feat
feat: agentstudio add feature tool approval, event deltas, session resources,security
2 parents 9457fb1 + 9012de6 commit a4476e3

13 files changed

Lines changed: 1764 additions & 83 deletions

File tree

‎dashscope/agentstudio/__init__.py‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,17 @@
5252
)
5353
from .types import (
5454
Message,
55+
PermissionPolicy,
56+
SecurityAlert,
57+
SecurityAlertList,
58+
SecurityOverview,
5559
ServerEvent,
60+
SessionResource,
61+
StopReason,
5662
user_define_outcome,
5763
user_interrupt,
5864
user_message,
59-
user_tool_confirmation,
65+
user_tool_approval_response,
6066
user_custom_tool_result,
6167
user_tool_result,
6268
)
@@ -75,6 +81,10 @@
7581
WebhookEventListParams,
7682
)
7783
from .constants import (
84+
PermissionPolicyType,
85+
SSEEventType,
86+
SessionStatus,
87+
StopReasonType,
7888
WebhookDeliveryStatus,
7989
WebhookDisabledReason,
8090
WebhookEventType,
@@ -108,11 +118,17 @@
108118
"AsyncCursorPage",
109119
# unified message type
110120
"Message",
121+
"PermissionPolicy",
122+
"SecurityAlert",
123+
"SecurityAlertList",
124+
"SecurityOverview",
111125
"ServerEvent",
126+
"SessionResource",
127+
"StopReason",
112128
# client event helpers (re-exported for convenience)
113129
"user_message",
114130
"user_interrupt",
115-
"user_tool_confirmation",
131+
"user_tool_approval_response",
116132
"user_custom_tool_result",
117133
"user_tool_result",
118134
"user_define_outcome",
@@ -135,4 +151,8 @@
135151
"WebhookDisabledReason",
136152
"WebhookDeliveryStatus",
137153
"WebhookEventType",
154+
"SSEEventType",
155+
"SessionStatus",
156+
"StopReasonType",
157+
"PermissionPolicyType",
138158
]

‎dashscope/agentstudio/client.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
AsyncEnvironments,
2222
)
2323
from dashscope.agentstudio.resources.files import Files, AsyncFiles
24+
from dashscope.agentstudio.resources.security import Security, AsyncSecurity
2425
from dashscope.agentstudio.resources.sessions import Sessions, AsyncSessions
2526
from dashscope.agentstudio.resources.skills import Skills, AsyncSkills
2627
from dashscope.agentstudio.resources.vaults import Vaults, AsyncVaults
@@ -134,6 +135,7 @@ def __init__(
134135
self.skills = Skills(self)
135136
self.vaults = Vaults(self)
136137
self.webhook_endpoints = WebhookEndpoints(self)
138+
self.security = Security(self)
137139

138140
def close(self) -> None:
139141
self.transport.close()
@@ -207,6 +209,7 @@ def __init__(
207209
self.skills = AsyncSkills(self)
208210
self.vaults = AsyncVaults(self)
209211
self.webhook_endpoints = AsyncWebhookEndpoints(self)
212+
self.security = AsyncSecurity(self)
210213

211214
async def aclose(self) -> None:
212215
await self.transport.aclose()

‎dashscope/agentstudio/constants.py‎

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ class StrEnum(str, enum.Enum): # type: ignore[no-redef]
3434
class SSEEventType(StrEnum):
3535
"""Server-sent event types (the value of ``event.type`` in SSE payloads).
3636
37-
Client-sendable: MESSAGE, INTERRUPT, TOOL_CONFIRMATION,
37+
Client-sendable: MESSAGE, INTERRUPT, TOOL_APPROVAL_RESPONSE,
3838
FUNCTION_CALL_OUTPUT, TOOL_CALL_OUTPUT, DEFINE_OUTCOME.
39-
Server-emitted: all types.
39+
Server-emitted: all types (23 total).
4040
"""
4141

4242
# Client-sendable
4343
MESSAGE = "message"
4444
INTERRUPT = "interrupt"
45-
TOOL_CONFIRMATION = "tool_confirmation"
45+
TOOL_APPROVAL_RESPONSE = "tool_approval_response"
4646
FUNCTION_CALL_OUTPUT = "function_call_output"
4747
TOOL_CALL_OUTPUT = "tool_call_output"
4848
DEFINE_OUTCOME = "define_outcome"
@@ -53,6 +53,7 @@ class SSEEventType(StrEnum):
5353
REASONING = "reasoning"
5454
MCP_CALL = "mcp_call"
5555
MCP_CALL_OUTPUT = "mcp_call_output"
56+
TOOL_APPROVAL_REQUEST = "tool_approval_request"
5657
THREAD_MESSAGE_SENT = "thread_message_sent"
5758
THREAD_MESSAGE_RECEIVED = "thread_message_received"
5859
THREAD_CONTEXT_COMPACTED = "thread_context_compacted"
@@ -87,12 +88,44 @@ class BlockType(StrEnum):
8788

8889

8990
class SessionStatus(StrEnum):
90-
"""Session run-status values (``session_status``)."""
91+
"""Session run-status values (``session_status``).
92+
93+
Top-level ``Session.status`` ∈ {idle, running, terminated, rescheduled};
94+
the ``session_status`` event ``data.session_status`` value set is the
95+
same plus ``deleted`` (a delete returns a tombstone, not a Session with
96+
``status=deleted``). ``idle`` also covers waiting for tool approval,
97+
signalled by ``stop_reason.type=requires_action``.
98+
"""
9199

92100
IDLE = "idle"
93101
RUNNING = "running"
94-
RESCHEDULING = "rescheduling"
102+
RESCHEDULED = "rescheduled"
95103
TERMINATED = "terminated"
104+
DELETED = "deleted"
105+
106+
107+
class StopReasonType(StrEnum):
108+
"""``stop_reason.type`` carried by ``session_status`` idle events.
109+
110+
Only ``idle`` carries ``stop_reason``; a running Session's
111+
``stop_reason`` is ``null``.
112+
"""
113+
114+
END_TURN = "end_turn"
115+
REQUIRES_ACTION = "requires_action"
116+
RETRIES_EXHAUSTED = "retries_exhausted"
117+
118+
119+
class PermissionPolicyType(StrEnum):
120+
"""Tool approval policy (``permission_policy.type``).
121+
122+
``always_allow`` (default) executes the tool without asking;
123+
``always_ask`` produces a ``tool_approval_request`` per call.
124+
Subagent/subthread ``always_ask`` degrades to ``always_allow``.
125+
"""
126+
127+
ALWAYS_ALLOW = "always_allow"
128+
ALWAYS_ASK = "always_ask"
96129

97130

98131
class WebhookStatus(StrEnum):

‎dashscope/agentstudio/resources/_helpers.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
File,
1717
ServerEvent,
1818
Session,
19+
SessionResource,
20+
SessionThread,
1921
Skill,
2022
SkillVersion,
2123
Vault,
@@ -63,6 +65,14 @@ def _coerce_session(payload: Mapping[str, Any]) -> Session:
6365
return Session(**dict(payload))
6466

6567

68+
def _coerce_session_resource(payload: Mapping[str, Any]) -> SessionResource:
69+
return SessionResource(**dict(payload))
70+
71+
72+
def _coerce_session_thread(payload: Mapping[str, Any]) -> SessionThread:
73+
return SessionThread(**dict(payload))
74+
75+
6676
def _coerce_vault(payload: Mapping[str, Any]) -> Vault:
6777
return Vault(**dict(payload))
6878

@@ -104,3 +114,27 @@ def _events_path(session_id: str) -> str:
104114

105115
def _stream_path(session_id: str) -> str:
106116
return f"/sessions/{session_id}/events/stream"
117+
118+
119+
def _session_resources_path(session_id: str) -> str:
120+
return f"/sessions/{session_id}/resources"
121+
122+
123+
def _session_resource_item_path(session_id: str, resource_id: str) -> str:
124+
return f"/sessions/{session_id}/resources/{resource_id}"
125+
126+
127+
def _session_threads_path(session_id: str) -> str:
128+
return f"/sessions/{session_id}/threads"
129+
130+
131+
def _session_thread_item_path(session_id: str, thread_id: str) -> str:
132+
return f"/sessions/{session_id}/threads/{thread_id}"
133+
134+
135+
def _session_thread_events_path(session_id: str, thread_id: str) -> str:
136+
return f"/sessions/{session_id}/threads/{thread_id}/events"
137+
138+
139+
def _session_thread_archive_path(session_id: str, thread_id: str) -> str:
140+
return f"/sessions/{session_id}/threads/{thread_id}/archive"
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) Alibaba, Inc. and its affiliates.
3+
"""Security center read interface.
4+
5+
Exposes the two highest-value read endpoints: ``GET /security/overview``
6+
(24h dashboard) and ``GET /security/agent_logs`` (alert list). Other
7+
security endpoints (asset summary, policies, alert detail, export,
8+
authorization, activation) are not surfaced here.
9+
"""
10+
11+
from __future__ import annotations
12+
13+
from typing import Any, Mapping, Optional, Sequence
14+
15+
from dashscope.agentstudio.types import SecurityAlertList, SecurityOverview
16+
from dashscope.agentstudio.types.params import SecurityListAgentLogsParams
17+
18+
_PATH_SECURITY_OVERVIEW = "/security/overview"
19+
_PATH_SECURITY_AGENT_LOGS = "/security/agent_logs"
20+
21+
22+
def _overview_from_payload(payload: Mapping[str, Any]) -> SecurityOverview:
23+
return SecurityOverview(**dict(payload))
24+
25+
26+
def _alert_list_from_payload(payload: Mapping[str, Any]) -> SecurityAlertList:
27+
return SecurityAlertList(**dict(payload))
28+
29+
30+
class Security:
31+
"""Security center read interface (overview + agent logs)."""
32+
33+
def __init__(self, client) -> None:
34+
self._client = client
35+
36+
def overview(self) -> SecurityOverview:
37+
"""24h protection dashboard (``GET /security/overview``)."""
38+
resp = self._client.transport.request(
39+
"GET",
40+
_PATH_SECURITY_OVERVIEW,
41+
)
42+
return _overview_from_payload(resp.data)
43+
44+
def list_agent_logs(
45+
self,
46+
*,
47+
current_page: Optional[int] = None,
48+
page_size: Optional[int] = None,
49+
risk_level: Optional[str] = None,
50+
status: Optional[str] = None,
51+
risk_name: Optional[str] = None,
52+
app_name: Optional[str] = None,
53+
asset_type: Optional[str] = None,
54+
vendor: Optional[str] = None,
55+
order_by: Optional[str] = None,
56+
order: Optional[str] = None,
57+
lang: Optional[str] = None,
58+
status_list: Optional[Sequence[str]] = None,
59+
) -> SecurityAlertList:
60+
"""Alert list (``GET /security/agent_logs``).
61+
62+
Page-number pagination: pass ``current_page + 1`` for the next
63+
page, or use the returned ``next_page`` cursor if non-null.
64+
``check_time`` / ``handle_time`` on rows are millisecond strings.
65+
"""
66+
params = SecurityListAgentLogsParams(
67+
current_page=current_page,
68+
page_size=page_size,
69+
risk_level=risk_level,
70+
status=status,
71+
risk_name=risk_name,
72+
app_name=app_name,
73+
asset_type=asset_type,
74+
vendor=vendor,
75+
order_by=order_by,
76+
order=order,
77+
lang=lang,
78+
status_list=status_list,
79+
).to_dict()
80+
resp = self._client.transport.request(
81+
"GET",
82+
_PATH_SECURITY_AGENT_LOGS,
83+
params=params,
84+
)
85+
return _alert_list_from_payload(resp.data)
86+
87+
88+
class AsyncSecurity:
89+
"""Async security center read interface."""
90+
91+
def __init__(self, client) -> None:
92+
self._client = client
93+
94+
async def overview(self) -> SecurityOverview:
95+
resp = await self._client.transport.request(
96+
"GET",
97+
_PATH_SECURITY_OVERVIEW,
98+
)
99+
return _overview_from_payload(resp.data)
100+
101+
async def list_agent_logs(
102+
self,
103+
*,
104+
current_page: Optional[int] = None,
105+
page_size: Optional[int] = None,
106+
risk_level: Optional[str] = None,
107+
status: Optional[str] = None,
108+
risk_name: Optional[str] = None,
109+
app_name: Optional[str] = None,
110+
asset_type: Optional[str] = None,
111+
vendor: Optional[str] = None,
112+
order_by: Optional[str] = None,
113+
order: Optional[str] = None,
114+
lang: Optional[str] = None,
115+
status_list: Optional[Sequence[str]] = None,
116+
) -> SecurityAlertList:
117+
params = SecurityListAgentLogsParams(
118+
current_page=current_page,
119+
page_size=page_size,
120+
risk_level=risk_level,
121+
status=status,
122+
risk_name=risk_name,
123+
app_name=app_name,
124+
asset_type=asset_type,
125+
vendor=vendor,
126+
order_by=order_by,
127+
order=order,
128+
lang=lang,
129+
status_list=status_list,
130+
).to_dict()
131+
resp = await self._client.transport.request(
132+
"GET",
133+
_PATH_SECURITY_AGENT_LOGS,
134+
params=params,
135+
)
136+
return _alert_list_from_payload(resp.data)

0 commit comments

Comments
 (0)