managed-deepagents 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- managed_deepagents-0.1.1/.gitignore +21 -0
- managed_deepagents-0.1.1/LICENSE +21 -0
- managed_deepagents-0.1.1/PKG-INFO +140 -0
- managed_deepagents-0.1.1/README.md +110 -0
- managed_deepagents-0.1.1/managed_deepagents/__init__.py +25 -0
- managed_deepagents-0.1.1/managed_deepagents/_utils.py +90 -0
- managed_deepagents-0.1.1/managed_deepagents/client.py +236 -0
- managed_deepagents-0.1.1/managed_deepagents/errors.py +30 -0
- managed_deepagents-0.1.1/managed_deepagents/py.typed +1 -0
- managed_deepagents-0.1.1/managed_deepagents/resources.py +613 -0
- managed_deepagents-0.1.1/managed_deepagents/streaming.py +139 -0
- managed_deepagents-0.1.1/managed_deepagents/types.py +119 -0
- managed_deepagents-0.1.1/pyproject.toml +69 -0
- managed_deepagents-0.1.1/tests/test_client.py +405 -0
- managed_deepagents-0.1.1/tests/test_streaming.py +25 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
*.crt
|
|
5
|
+
*.key
|
|
6
|
+
*.pem
|
|
7
|
+
credentials.json
|
|
8
|
+
token.json
|
|
9
|
+
|
|
10
|
+
node_modules/
|
|
11
|
+
dist/
|
|
12
|
+
coverage/
|
|
13
|
+
|
|
14
|
+
__pycache__/
|
|
15
|
+
*.py[cod]
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
.venv/
|
|
20
|
+
build/
|
|
21
|
+
*.egg-info/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LangChain, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: managed-deepagents
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python SDK for LangSmith Managed Deep Agents.
|
|
5
|
+
Project-URL: Changelog, https://github.com/langchain-ai/managed-deepagents-sdk/blob/main/CHANGELOG.md
|
|
6
|
+
Project-URL: Issues, https://github.com/langchain-ai/managed-deepagents-sdk/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/langchain-ai/managed-deepagents-sdk
|
|
8
|
+
Author: LangChain
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,langchain,langsmith,managed-deep-agents,sdk
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: httpx<1.0,>=0.28.1
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy<2.0,>=1.11; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest<10.0,>=8.4; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff<1.0,>=0.12; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# managed-deepagents
|
|
32
|
+
|
|
33
|
+
Python SDK for the LangSmith Managed Deep Agents API.
|
|
34
|
+
|
|
35
|
+
Managed Deep Agents is a hosted runtime for creating, running, and operating
|
|
36
|
+
Deep Agents through LangSmith. This package is currently public beta software.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install managed-deepagents
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Requirements:
|
|
45
|
+
|
|
46
|
+
- Python 3.10 or newer
|
|
47
|
+
- A LangSmith API key with access to Managed Deep Agents
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
The client reads `LANGSMITH_API_KEY` by default.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
export LANGSMITH_API_KEY="..."
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The default API URL is `https://api.smith.langchain.com/v1/deepagents`.
|
|
58
|
+
You can override it with `LANGSMITH_ENDPOINT` or the `api_url` client option.
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from managed_deepagents import Client
|
|
64
|
+
|
|
65
|
+
with Client() as client:
|
|
66
|
+
agent = client.agents.create(
|
|
67
|
+
name="research-assistant",
|
|
68
|
+
model="anthropic:claude-sonnet-4-6",
|
|
69
|
+
instructions="You are a careful research assistant.",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
thread = client.threads.create(agent_id=agent["id"])
|
|
73
|
+
|
|
74
|
+
for event in client.threads.stream(
|
|
75
|
+
thread["id"],
|
|
76
|
+
agent_id=agent["id"],
|
|
77
|
+
messages=[{"role": "user", "content": "Summarize the latest notes."}],
|
|
78
|
+
stream_mode=["values", "updates", "messages-tuple"],
|
|
79
|
+
):
|
|
80
|
+
print(event.event, event.data)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Async Usage
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from managed_deepagents import AsyncClient
|
|
87
|
+
|
|
88
|
+
async with AsyncClient() as client:
|
|
89
|
+
agent = await client.agents.create(
|
|
90
|
+
name="research-assistant",
|
|
91
|
+
model="anthropic:claude-sonnet-4-6",
|
|
92
|
+
instructions="You are a careful research assistant.",
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Agent Files
|
|
97
|
+
|
|
98
|
+
Top-level `files` entries may be passed as raw strings; the SDK normalizes them
|
|
99
|
+
to file entries before sending the request. Use either `instructions` or
|
|
100
|
+
`files["AGENTS.md"]`, not both, because the API maps the typed prompt field to
|
|
101
|
+
`AGENTS.md`.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
agent = client.agents.create(
|
|
105
|
+
name="research-assistant",
|
|
106
|
+
files={
|
|
107
|
+
"AGENTS.md": "You are a careful research assistant.",
|
|
108
|
+
"skills/research/SKILL.md": "# Research\n\nGather context before answering.",
|
|
109
|
+
},
|
|
110
|
+
include_files=True,
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## API Surface
|
|
115
|
+
|
|
116
|
+
Resources exposed by the client:
|
|
117
|
+
|
|
118
|
+
- `client.agents`: list, create, get, update, delete, clone, health
|
|
119
|
+
- `client.threads`: create, search, count, get, update, delete, create run,
|
|
120
|
+
invoke, stream, bulk update, resolve interrupt
|
|
121
|
+
- `client.mcp_servers`: create, list, get, update, delete, register OAuth
|
|
122
|
+
provider
|
|
123
|
+
- `client.auth_sessions`: create and get
|
|
124
|
+
|
|
125
|
+
## Errors
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from managed_deepagents import ManagedDeepAgentsAPIError
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
client.agents.get("missing-agent")
|
|
132
|
+
except ManagedDeepAgentsAPIError as error:
|
|
133
|
+
print(error.status_code, error.code, error.detail)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Links
|
|
137
|
+
|
|
138
|
+
- Repository: https://github.com/langchain-ai/managed-deepagents-sdk
|
|
139
|
+
- Issues: https://github.com/langchain-ai/managed-deepagents-sdk/issues
|
|
140
|
+
- Changelog: https://github.com/langchain-ai/managed-deepagents-sdk/blob/main/CHANGELOG.md
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# managed-deepagents
|
|
2
|
+
|
|
3
|
+
Python SDK for the LangSmith Managed Deep Agents API.
|
|
4
|
+
|
|
5
|
+
Managed Deep Agents is a hosted runtime for creating, running, and operating
|
|
6
|
+
Deep Agents through LangSmith. This package is currently public beta software.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install managed-deepagents
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Requirements:
|
|
15
|
+
|
|
16
|
+
- Python 3.10 or newer
|
|
17
|
+
- A LangSmith API key with access to Managed Deep Agents
|
|
18
|
+
|
|
19
|
+
## Configuration
|
|
20
|
+
|
|
21
|
+
The client reads `LANGSMITH_API_KEY` by default.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
export LANGSMITH_API_KEY="..."
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The default API URL is `https://api.smith.langchain.com/v1/deepagents`.
|
|
28
|
+
You can override it with `LANGSMITH_ENDPOINT` or the `api_url` client option.
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from managed_deepagents import Client
|
|
34
|
+
|
|
35
|
+
with Client() as client:
|
|
36
|
+
agent = client.agents.create(
|
|
37
|
+
name="research-assistant",
|
|
38
|
+
model="anthropic:claude-sonnet-4-6",
|
|
39
|
+
instructions="You are a careful research assistant.",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
thread = client.threads.create(agent_id=agent["id"])
|
|
43
|
+
|
|
44
|
+
for event in client.threads.stream(
|
|
45
|
+
thread["id"],
|
|
46
|
+
agent_id=agent["id"],
|
|
47
|
+
messages=[{"role": "user", "content": "Summarize the latest notes."}],
|
|
48
|
+
stream_mode=["values", "updates", "messages-tuple"],
|
|
49
|
+
):
|
|
50
|
+
print(event.event, event.data)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Async Usage
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from managed_deepagents import AsyncClient
|
|
57
|
+
|
|
58
|
+
async with AsyncClient() as client:
|
|
59
|
+
agent = await client.agents.create(
|
|
60
|
+
name="research-assistant",
|
|
61
|
+
model="anthropic:claude-sonnet-4-6",
|
|
62
|
+
instructions="You are a careful research assistant.",
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Agent Files
|
|
67
|
+
|
|
68
|
+
Top-level `files` entries may be passed as raw strings; the SDK normalizes them
|
|
69
|
+
to file entries before sending the request. Use either `instructions` or
|
|
70
|
+
`files["AGENTS.md"]`, not both, because the API maps the typed prompt field to
|
|
71
|
+
`AGENTS.md`.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
agent = client.agents.create(
|
|
75
|
+
name="research-assistant",
|
|
76
|
+
files={
|
|
77
|
+
"AGENTS.md": "You are a careful research assistant.",
|
|
78
|
+
"skills/research/SKILL.md": "# Research\n\nGather context before answering.",
|
|
79
|
+
},
|
|
80
|
+
include_files=True,
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## API Surface
|
|
85
|
+
|
|
86
|
+
Resources exposed by the client:
|
|
87
|
+
|
|
88
|
+
- `client.agents`: list, create, get, update, delete, clone, health
|
|
89
|
+
- `client.threads`: create, search, count, get, update, delete, create run,
|
|
90
|
+
invoke, stream, bulk update, resolve interrupt
|
|
91
|
+
- `client.mcp_servers`: create, list, get, update, delete, register OAuth
|
|
92
|
+
provider
|
|
93
|
+
- `client.auth_sessions`: create and get
|
|
94
|
+
|
|
95
|
+
## Errors
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from managed_deepagents import ManagedDeepAgentsAPIError
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
client.agents.get("missing-agent")
|
|
102
|
+
except ManagedDeepAgentsAPIError as error:
|
|
103
|
+
print(error.status_code, error.code, error.detail)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Links
|
|
107
|
+
|
|
108
|
+
- Repository: https://github.com/langchain-ai/managed-deepagents-sdk
|
|
109
|
+
- Issues: https://github.com/langchain-ai/managed-deepagents-sdk/issues
|
|
110
|
+
- Changelog: https://github.com/langchain-ai/managed-deepagents-sdk/blob/main/CHANGELOG.md
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Python SDK for LangSmith Managed Deep Agents."""
|
|
2
|
+
|
|
3
|
+
from managed_deepagents.client import (
|
|
4
|
+
AsyncClient,
|
|
5
|
+
AsyncManagedDeepAgentsClient,
|
|
6
|
+
Client,
|
|
7
|
+
ManagedDeepAgentsClient,
|
|
8
|
+
)
|
|
9
|
+
from managed_deepagents.errors import (
|
|
10
|
+
ManagedDeepAgentsAPIError,
|
|
11
|
+
ManagedDeepAgentsConfigError,
|
|
12
|
+
ManagedDeepAgentsError,
|
|
13
|
+
)
|
|
14
|
+
from managed_deepagents.streaming import SSEEvent
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AsyncClient",
|
|
18
|
+
"AsyncManagedDeepAgentsClient",
|
|
19
|
+
"Client",
|
|
20
|
+
"ManagedDeepAgentsAPIError",
|
|
21
|
+
"ManagedDeepAgentsClient",
|
|
22
|
+
"ManagedDeepAgentsConfigError",
|
|
23
|
+
"ManagedDeepAgentsError",
|
|
24
|
+
"SSEEvent",
|
|
25
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
DEFAULT_API_URL = "https://api.smith.langchain.com"
|
|
8
|
+
MANAGED_DEEPAGENTS_PATH = "/v1/deepagents"
|
|
9
|
+
FLEET_PATH = "/v1/fleet"
|
|
10
|
+
MANAGED_AGENT_BASE_PATHS = (
|
|
11
|
+
MANAGED_DEEPAGENTS_PATH,
|
|
12
|
+
FLEET_PATH,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_default_api_key() -> str | None:
|
|
17
|
+
return os.getenv("LANGSMITH_API_KEY")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_default_api_url() -> str:
|
|
21
|
+
return os.getenv("LANGSMITH_ENDPOINT") or DEFAULT_API_URL
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def normalize_api_url(api_url: str) -> str:
|
|
25
|
+
base_url = api_url.rstrip("/")
|
|
26
|
+
if base_url.endswith(MANAGED_AGENT_BASE_PATHS):
|
|
27
|
+
return base_url
|
|
28
|
+
if base_url.endswith("/v1"):
|
|
29
|
+
return f"{base_url}/deepagents"
|
|
30
|
+
return f"{base_url}{MANAGED_DEEPAGENTS_PATH}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def clean_mapping(values: Mapping[str, Any]) -> dict[str, Any]:
|
|
34
|
+
return {key: value for key, value in values.items() if value is not None}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def merge_body_fields(
|
|
38
|
+
body: Mapping[str, Any] | None,
|
|
39
|
+
fields: Mapping[str, Any],
|
|
40
|
+
) -> dict[str, Any]:
|
|
41
|
+
payload = dict(body or {})
|
|
42
|
+
payload.update(clean_mapping(fields))
|
|
43
|
+
return payload
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def prepare_agent_payload(payload: Mapping[str, Any]) -> dict[str, Any]:
|
|
47
|
+
next_payload = dict(payload)
|
|
48
|
+
files = next_payload.get("files")
|
|
49
|
+
if isinstance(files, Mapping):
|
|
50
|
+
next_payload["files"] = {
|
|
51
|
+
path: {"content": value} if isinstance(value, str) else value
|
|
52
|
+
for path, value in files.items()
|
|
53
|
+
}
|
|
54
|
+
return next_payload
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_stream_body(
|
|
58
|
+
*,
|
|
59
|
+
agent_id: str,
|
|
60
|
+
messages: list[Mapping[str, Any]],
|
|
61
|
+
stream_mode: list[str] | None = None,
|
|
62
|
+
stream_subgraphs: bool | None = None,
|
|
63
|
+
user_timezone: str | None = None,
|
|
64
|
+
extra: Mapping[str, Any] | None = None,
|
|
65
|
+
) -> dict[str, Any]:
|
|
66
|
+
body = dict(extra or {})
|
|
67
|
+
body.update(
|
|
68
|
+
clean_mapping(
|
|
69
|
+
{
|
|
70
|
+
"agent_id": agent_id,
|
|
71
|
+
"messages": messages,
|
|
72
|
+
"stream_mode": stream_mode,
|
|
73
|
+
"stream_subgraphs": stream_subgraphs,
|
|
74
|
+
"user_timezone": user_timezone,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
return body
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def build_invoke_body(
|
|
82
|
+
*,
|
|
83
|
+
agent_id: str,
|
|
84
|
+
messages: list[Mapping[str, Any]],
|
|
85
|
+
extra: Mapping[str, Any] | None = None,
|
|
86
|
+
) -> dict[str, Any]:
|
|
87
|
+
body = dict(extra or {})
|
|
88
|
+
body["assistant_id"] = agent_id
|
|
89
|
+
body["input"] = {"messages": messages}
|
|
90
|
+
return body
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterable, Iterable, Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from managed_deepagents._utils import (
|
|
9
|
+
get_default_api_key,
|
|
10
|
+
get_default_api_url,
|
|
11
|
+
normalize_api_url,
|
|
12
|
+
)
|
|
13
|
+
from managed_deepagents.errors import ManagedDeepAgentsAPIError
|
|
14
|
+
from managed_deepagents.resources import (
|
|
15
|
+
AgentsResource,
|
|
16
|
+
AsyncAgentsResource,
|
|
17
|
+
AsyncAuthSessionsResource,
|
|
18
|
+
AsyncMcpServersResource,
|
|
19
|
+
AsyncThreadsResource,
|
|
20
|
+
AuthSessionsResource,
|
|
21
|
+
McpServersResource,
|
|
22
|
+
ThreadsResource,
|
|
23
|
+
)
|
|
24
|
+
from managed_deepagents.streaming import SSEEvent, aiter_sse_events, iter_sse_events
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Client:
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
api_url: str | None = None,
|
|
32
|
+
api_key: str | None = None,
|
|
33
|
+
workspace_id: str | None = None,
|
|
34
|
+
timeout: float | httpx.Timeout = 30.0,
|
|
35
|
+
headers: Mapping[str, str] | None = None,
|
|
36
|
+
http_client: httpx.Client | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.api_url = normalize_api_url(api_url or get_default_api_url())
|
|
39
|
+
self.api_key = api_key if api_key is not None else get_default_api_key()
|
|
40
|
+
self.workspace_id = workspace_id
|
|
41
|
+
self._headers = dict(headers or {})
|
|
42
|
+
self._owns_client = http_client is None
|
|
43
|
+
self._client = http_client or httpx.Client(timeout=timeout)
|
|
44
|
+
|
|
45
|
+
self.agents = AgentsResource(self)
|
|
46
|
+
self.threads = ThreadsResource(self)
|
|
47
|
+
self.mcp_servers = McpServersResource(self)
|
|
48
|
+
self.auth_sessions = AuthSessionsResource(self)
|
|
49
|
+
|
|
50
|
+
def close(self) -> None:
|
|
51
|
+
if self._owns_client:
|
|
52
|
+
self._client.close()
|
|
53
|
+
|
|
54
|
+
def __enter__(self) -> Client:
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def __exit__(self, *_exc: object) -> None:
|
|
58
|
+
self.close()
|
|
59
|
+
|
|
60
|
+
def request(
|
|
61
|
+
self,
|
|
62
|
+
method: str,
|
|
63
|
+
path: str,
|
|
64
|
+
*,
|
|
65
|
+
json: Mapping[str, Any] | None = None,
|
|
66
|
+
params: Mapping[str, Any] | None = None,
|
|
67
|
+
headers: Mapping[str, str] | None = None,
|
|
68
|
+
) -> Any:
|
|
69
|
+
response = self._client.request(
|
|
70
|
+
method,
|
|
71
|
+
self._url(path),
|
|
72
|
+
json=json,
|
|
73
|
+
params=params,
|
|
74
|
+
headers=self._request_headers(headers),
|
|
75
|
+
)
|
|
76
|
+
return _decode_response(response)
|
|
77
|
+
|
|
78
|
+
def stream(
|
|
79
|
+
self,
|
|
80
|
+
path: str,
|
|
81
|
+
*,
|
|
82
|
+
json: Mapping[str, Any],
|
|
83
|
+
headers: Mapping[str, str] | None = None,
|
|
84
|
+
) -> Iterable[SSEEvent]:
|
|
85
|
+
with self._client.stream(
|
|
86
|
+
"POST",
|
|
87
|
+
self._url(path),
|
|
88
|
+
json=json,
|
|
89
|
+
headers=self._request_headers(
|
|
90
|
+
{"Accept": "text/event-stream", **dict(headers or {})}
|
|
91
|
+
),
|
|
92
|
+
) as response:
|
|
93
|
+
if response.status_code >= 400:
|
|
94
|
+
response.read()
|
|
95
|
+
_raise_for_status(response)
|
|
96
|
+
yield from iter_sse_events(response.iter_bytes())
|
|
97
|
+
|
|
98
|
+
def _url(self, path: str) -> str:
|
|
99
|
+
return f"{self.api_url}/{path.lstrip('/')}"
|
|
100
|
+
|
|
101
|
+
def _request_headers(
|
|
102
|
+
self,
|
|
103
|
+
headers: Mapping[str, str] | None = None,
|
|
104
|
+
) -> dict[str, str]:
|
|
105
|
+
request_headers = {"Accept": "application/json", **self._headers}
|
|
106
|
+
if self.api_key:
|
|
107
|
+
request_headers["X-Api-Key"] = self.api_key
|
|
108
|
+
if self.workspace_id:
|
|
109
|
+
request_headers["X-Tenant-Id"] = self.workspace_id
|
|
110
|
+
request_headers.update(headers or {})
|
|
111
|
+
return request_headers
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AsyncClient:
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
*,
|
|
118
|
+
api_url: str | None = None,
|
|
119
|
+
api_key: str | None = None,
|
|
120
|
+
workspace_id: str | None = None,
|
|
121
|
+
timeout: float | httpx.Timeout = 30.0,
|
|
122
|
+
headers: Mapping[str, str] | None = None,
|
|
123
|
+
http_client: httpx.AsyncClient | None = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
self.api_url = normalize_api_url(api_url or get_default_api_url())
|
|
126
|
+
self.api_key = api_key if api_key is not None else get_default_api_key()
|
|
127
|
+
self.workspace_id = workspace_id
|
|
128
|
+
self._headers = dict(headers or {})
|
|
129
|
+
self._owns_client = http_client is None
|
|
130
|
+
self._client = http_client or httpx.AsyncClient(timeout=timeout)
|
|
131
|
+
|
|
132
|
+
self.agents = AsyncAgentsResource(self)
|
|
133
|
+
self.threads = AsyncThreadsResource(self)
|
|
134
|
+
self.mcp_servers = AsyncMcpServersResource(self)
|
|
135
|
+
self.auth_sessions = AsyncAuthSessionsResource(self)
|
|
136
|
+
|
|
137
|
+
async def close(self) -> None:
|
|
138
|
+
if self._owns_client:
|
|
139
|
+
await self._client.aclose()
|
|
140
|
+
|
|
141
|
+
async def __aenter__(self) -> AsyncClient:
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
async def __aexit__(self, *_exc: object) -> None:
|
|
145
|
+
await self.close()
|
|
146
|
+
|
|
147
|
+
async def request(
|
|
148
|
+
self,
|
|
149
|
+
method: str,
|
|
150
|
+
path: str,
|
|
151
|
+
*,
|
|
152
|
+
json: Mapping[str, Any] | None = None,
|
|
153
|
+
params: Mapping[str, Any] | None = None,
|
|
154
|
+
headers: Mapping[str, str] | None = None,
|
|
155
|
+
) -> Any:
|
|
156
|
+
response = await self._client.request(
|
|
157
|
+
method,
|
|
158
|
+
self._url(path),
|
|
159
|
+
json=json,
|
|
160
|
+
params=params,
|
|
161
|
+
headers=self._request_headers(headers),
|
|
162
|
+
)
|
|
163
|
+
return _decode_response(response)
|
|
164
|
+
|
|
165
|
+
async def stream(
|
|
166
|
+
self,
|
|
167
|
+
path: str,
|
|
168
|
+
*,
|
|
169
|
+
json: Mapping[str, Any],
|
|
170
|
+
headers: Mapping[str, str] | None = None,
|
|
171
|
+
) -> AsyncIterable[SSEEvent]:
|
|
172
|
+
async with self._client.stream(
|
|
173
|
+
"POST",
|
|
174
|
+
self._url(path),
|
|
175
|
+
json=json,
|
|
176
|
+
headers=self._request_headers(
|
|
177
|
+
{"Accept": "text/event-stream", **dict(headers or {})}
|
|
178
|
+
),
|
|
179
|
+
) as response:
|
|
180
|
+
if response.status_code >= 400:
|
|
181
|
+
await response.aread()
|
|
182
|
+
_raise_for_status(response)
|
|
183
|
+
async for event in aiter_sse_events(response.aiter_bytes()):
|
|
184
|
+
yield event
|
|
185
|
+
|
|
186
|
+
def _url(self, path: str) -> str:
|
|
187
|
+
return f"{self.api_url}/{path.lstrip('/')}"
|
|
188
|
+
|
|
189
|
+
def _request_headers(
|
|
190
|
+
self,
|
|
191
|
+
headers: Mapping[str, str] | None = None,
|
|
192
|
+
) -> dict[str, str]:
|
|
193
|
+
request_headers = {"Accept": "application/json", **self._headers}
|
|
194
|
+
if self.api_key:
|
|
195
|
+
request_headers["X-Api-Key"] = self.api_key
|
|
196
|
+
if self.workspace_id:
|
|
197
|
+
request_headers["X-Tenant-Id"] = self.workspace_id
|
|
198
|
+
request_headers.update(headers or {})
|
|
199
|
+
return request_headers
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _decode_response(response: httpx.Response) -> Any:
|
|
203
|
+
_raise_for_status(response)
|
|
204
|
+
if response.status_code == 204 or not response.content:
|
|
205
|
+
return None
|
|
206
|
+
content_type = response.headers.get("content-type", "")
|
|
207
|
+
if "application/json" not in content_type:
|
|
208
|
+
return response.text
|
|
209
|
+
return response.json()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _raise_for_status(response: httpx.Response) -> None:
|
|
213
|
+
if response.status_code < 400:
|
|
214
|
+
return
|
|
215
|
+
body = _response_body(response)
|
|
216
|
+
detail = body.get("detail") if isinstance(body, dict) else None
|
|
217
|
+
code = body.get("code") if isinstance(body, dict) else None
|
|
218
|
+
message = detail or response.reason_phrase or "Managed Deep Agents API error"
|
|
219
|
+
raise ManagedDeepAgentsAPIError(
|
|
220
|
+
message,
|
|
221
|
+
status_code=response.status_code,
|
|
222
|
+
code=code,
|
|
223
|
+
detail=detail,
|
|
224
|
+
body=body,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _response_body(response: httpx.Response) -> Any:
|
|
229
|
+
try:
|
|
230
|
+
return response.json()
|
|
231
|
+
except ValueError:
|
|
232
|
+
return response.text
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
ManagedDeepAgentsClient = Client
|
|
236
|
+
AsyncManagedDeepAgentsClient = AsyncClient
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ManagedDeepAgentsError(Exception):
|
|
7
|
+
"""Base exception raised by the Managed Deep Agents SDK."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ManagedDeepAgentsConfigError(ManagedDeepAgentsError):
|
|
11
|
+
"""Raised when client configuration is invalid."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ManagedDeepAgentsAPIError(ManagedDeepAgentsError):
|
|
15
|
+
"""Raised when the API returns a non-2xx response."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
message: str,
|
|
20
|
+
*,
|
|
21
|
+
status_code: int,
|
|
22
|
+
code: str | None = None,
|
|
23
|
+
detail: str | None = None,
|
|
24
|
+
body: Any | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
self.status_code = status_code
|
|
28
|
+
self.code = code
|
|
29
|
+
self.detail = detail
|
|
30
|
+
self.body = body
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|