conversational-agent-client 0.1.0__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.
- conversational_agent_client-0.1.0/LICENSE +21 -0
- conversational_agent_client-0.1.0/PKG-INFO +146 -0
- conversational_agent_client-0.1.0/README.md +132 -0
- conversational_agent_client-0.1.0/pyproject.toml +24 -0
- conversational_agent_client-0.1.0/setup.cfg +4 -0
- conversational_agent_client-0.1.0/src/conversational_agent_client.egg-info/PKG-INFO +146 -0
- conversational_agent_client-0.1.0/src/conversational_agent_client.egg-info/SOURCES.txt +11 -0
- conversational_agent_client-0.1.0/src/conversational_agent_client.egg-info/dependency_links.txt +1 -0
- conversational_agent_client-0.1.0/src/conversational_agent_client.egg-info/requires.txt +1 -0
- conversational_agent_client-0.1.0/src/conversational_agent_client.egg-info/top_level.txt +1 -0
- conversational_agent_client-0.1.0/src/dust_sdk/__init__.py +0 -0
- conversational_agent_client-0.1.0/src/dust_sdk/client.py +226 -0
- conversational_agent_client-0.1.0/tests/test_client.py +216 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zaymerstone
|
|
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,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: conversational-agent-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python client for the Dust (dust.tt) conversational AI agent platform API
|
|
5
|
+
Author: Egor Nesterov
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Zaymerstone/dust-python-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/Zaymerstone/dust-python-sdk
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# dust-sdk (unofficial)
|
|
16
|
+
|
|
17
|
+
An unofficial Python client for the [Dust](https://dust.tt) API.
|
|
18
|
+
|
|
19
|
+
Dust ships an official [JavaScript/TypeScript SDK](https://docs.dust.tt/reference/javascript-sdk),
|
|
20
|
+
but has no official Python client - despite Python being the dominant
|
|
21
|
+
language for the data science, ML engineering, and automation teams
|
|
22
|
+
that make up a large part of Dust's target audience (their own
|
|
23
|
+
marketing highlights Data & Analytics as a core use case).
|
|
24
|
+
|
|
25
|
+
This project closes that gap.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install dust-sdk
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
_(not yet published to PyPI — see [Status](#status) below)_
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from dust_sdk.client import DustClient
|
|
39
|
+
|
|
40
|
+
client = DustClient(
|
|
41
|
+
api_key="your-dust-api-key",
|
|
42
|
+
workspace_id="your-workspace-id",
|
|
43
|
+
base_url="https://eu.dust.tt", # or https://dust.tt — see note below
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# List agents available in your workspace
|
|
47
|
+
agents = client.list_agents()
|
|
48
|
+
for agent in agents:
|
|
49
|
+
print(agent["sId"], "-", agent["name"])
|
|
50
|
+
|
|
51
|
+
# Talk to an agent
|
|
52
|
+
conversation = client.create_conversation(
|
|
53
|
+
message_content="What can you help me with?",
|
|
54
|
+
agent_sid="dust",
|
|
55
|
+
)
|
|
56
|
+
answer = client.get_last_agent_message_text(conversation)
|
|
57
|
+
print(answer)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### ⚠️ `base_url` is required, no default
|
|
61
|
+
|
|
62
|
+
Dust hosts separate regional infrastructure (`https://dust.tt` for US,
|
|
63
|
+
`https://eu.dust.tt` for EU). Using the wrong one doesn't 404 — it
|
|
64
|
+
returns a misleading `invalid_api_key_error`, making it look like your
|
|
65
|
+
key is wrong when it's actually a region mismatch. Check which region
|
|
66
|
+
your workspace lives in (visible in your workspace URL) before making
|
|
67
|
+
your first call.
|
|
68
|
+
|
|
69
|
+
## What's implemented
|
|
70
|
+
|
|
71
|
+
| Method | Operation | Verified against |
|
|
72
|
+
| --------------------------------- | --------------------- | ------------------------ |
|
|
73
|
+
| `list_agents()` | GET agent list | ✅ Live API call |
|
|
74
|
+
| `get_agent(sid)` | GET single agent | ✅ Live API call |
|
|
75
|
+
| `list_spaces()` | GET spaces | ✅ Live API call |
|
|
76
|
+
| `list_data_sources(space_id)` | GET data sources | ✅ Live API call |
|
|
77
|
+
| `list_documents(space_id, ds_id)` | GET documents | 📄 Official OpenAPI spec |
|
|
78
|
+
| `get_tables(space_id, ds_id)` | GET tables | 📄 Official OpenAPI spec |
|
|
79
|
+
| `create_conversation(...)` | POST new conversation | ✅ Live API call |
|
|
80
|
+
| `get_conversation(cid)` | GET conversation | ✅ Live API call |
|
|
81
|
+
| `import_agent(...)` | POST create agent | ✅ Live API call |
|
|
82
|
+
| `archive_agent(sid)` | DELETE (soft) agent | ✅ Live API call |
|
|
83
|
+
|
|
84
|
+
_"Live API call" means the response schema was confirmed against a
|
|
85
|
+
real request during development, not just documentation. "Official
|
|
86
|
+
OpenAPI spec" means it's based on Dust's published spec but hasn't
|
|
87
|
+
been round-tripped against a live response yet (usually because
|
|
88
|
+
testing it live requires resources — like a connected data source —
|
|
89
|
+
that weren't available in the development workspace)._
|
|
90
|
+
|
|
91
|
+
## Known limitations
|
|
92
|
+
|
|
93
|
+
- **Message-sending is gated on Dust's Free plan.** Any endpoint that
|
|
94
|
+
invokes a model (`create_conversation` with an agent mention) returns
|
|
95
|
+
`429 rate_limit_error` on workspaces without a paid seat —
|
|
96
|
+
`Programmatic usage` is entirely disabled (`No access`) on Free,
|
|
97
|
+
independent of the regular in-app usage credits shown in the UI.
|
|
98
|
+
Write operations that _don't_ invoke a model (`import_agent`,
|
|
99
|
+
`archive_agent`) work fine on Free.
|
|
100
|
+
- **Some `Private` API endpoints aren't accessible via API key at all**,
|
|
101
|
+
regardless of plan — e.g. `POST /spaces` (creating a space) returns
|
|
102
|
+
`401 not_authenticated` even with a valid Bearer token, because it's
|
|
103
|
+
a session-only, web-app-internal endpoint despite appearing in the
|
|
104
|
+
public API reference.
|
|
105
|
+
- **The documentation contains at least one broken example URL.**
|
|
106
|
+
`GET /spaces` is shown at `https://dust.tt/api/w/{wId}/spaces`
|
|
107
|
+
(missing `/v1/`) — using that exact path returns a misleading
|
|
108
|
+
`401 not_authenticated` instead of a 404, making it look like an
|
|
109
|
+
auth problem. The correct path is `/api/v1/w/{wId}/spaces`.
|
|
110
|
+
- **Response shapes aren't consistent across endpoints.** Most list
|
|
111
|
+
endpoints wrap results in an object (e.g. `{"data_sources": [...]}`),
|
|
112
|
+
but `GET .../tables` returns a bare JSON array. This SDK normalizes
|
|
113
|
+
both into consistent Python return types, but it's worth knowing if
|
|
114
|
+
you're calling the raw API directly.
|
|
115
|
+
- **Dust's official OpenAPI spec has several inaccuracies**, found
|
|
116
|
+
through live testing:
|
|
117
|
+
- `agent.avatar_url` is required in practice, marked optional in the spec
|
|
118
|
+
- `editors` must be an array of email strings, not objects as the spec shows
|
|
119
|
+
- `generation_settings.reasoning_effort` is required but easy to miss
|
|
120
|
+
- Message `type` example values in the spec show `"human"`, but the
|
|
121
|
+
real API returns `"user_message"` / `"agent_message"`
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
git clone https://github.com/Zaymerstone/dust-python-sdk.git
|
|
127
|
+
cd dust-python-sdk
|
|
128
|
+
python -m venv venv
|
|
129
|
+
venv\Scripts\Activate.ps1 # Windows
|
|
130
|
+
pip install -e .
|
|
131
|
+
pip install pytest requests-mock
|
|
132
|
+
pytest -v
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Tests run entirely against recorded fixtures (`tests/fixtures/`) —
|
|
136
|
+
no live API calls or credits are required to run the test suite.
|
|
137
|
+
|
|
138
|
+
## Status
|
|
139
|
+
|
|
140
|
+
This is an early-stage, unofficial project built to explore a gap in
|
|
141
|
+
Dust's SDK coverage. 10 methods are implemented and tested; the full
|
|
142
|
+
Dust API surface is 40+ endpoints. Contributions and feedback welcome.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# dust-sdk (unofficial)
|
|
2
|
+
|
|
3
|
+
An unofficial Python client for the [Dust](https://dust.tt) API.
|
|
4
|
+
|
|
5
|
+
Dust ships an official [JavaScript/TypeScript SDK](https://docs.dust.tt/reference/javascript-sdk),
|
|
6
|
+
but has no official Python client - despite Python being the dominant
|
|
7
|
+
language for the data science, ML engineering, and automation teams
|
|
8
|
+
that make up a large part of Dust's target audience (their own
|
|
9
|
+
marketing highlights Data & Analytics as a core use case).
|
|
10
|
+
|
|
11
|
+
This project closes that gap.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install dust-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
_(not yet published to PyPI — see [Status](#status) below)_
|
|
20
|
+
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from dust_sdk.client import DustClient
|
|
25
|
+
|
|
26
|
+
client = DustClient(
|
|
27
|
+
api_key="your-dust-api-key",
|
|
28
|
+
workspace_id="your-workspace-id",
|
|
29
|
+
base_url="https://eu.dust.tt", # or https://dust.tt — see note below
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# List agents available in your workspace
|
|
33
|
+
agents = client.list_agents()
|
|
34
|
+
for agent in agents:
|
|
35
|
+
print(agent["sId"], "-", agent["name"])
|
|
36
|
+
|
|
37
|
+
# Talk to an agent
|
|
38
|
+
conversation = client.create_conversation(
|
|
39
|
+
message_content="What can you help me with?",
|
|
40
|
+
agent_sid="dust",
|
|
41
|
+
)
|
|
42
|
+
answer = client.get_last_agent_message_text(conversation)
|
|
43
|
+
print(answer)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### ⚠️ `base_url` is required, no default
|
|
47
|
+
|
|
48
|
+
Dust hosts separate regional infrastructure (`https://dust.tt` for US,
|
|
49
|
+
`https://eu.dust.tt` for EU). Using the wrong one doesn't 404 — it
|
|
50
|
+
returns a misleading `invalid_api_key_error`, making it look like your
|
|
51
|
+
key is wrong when it's actually a region mismatch. Check which region
|
|
52
|
+
your workspace lives in (visible in your workspace URL) before making
|
|
53
|
+
your first call.
|
|
54
|
+
|
|
55
|
+
## What's implemented
|
|
56
|
+
|
|
57
|
+
| Method | Operation | Verified against |
|
|
58
|
+
| --------------------------------- | --------------------- | ------------------------ |
|
|
59
|
+
| `list_agents()` | GET agent list | ✅ Live API call |
|
|
60
|
+
| `get_agent(sid)` | GET single agent | ✅ Live API call |
|
|
61
|
+
| `list_spaces()` | GET spaces | ✅ Live API call |
|
|
62
|
+
| `list_data_sources(space_id)` | GET data sources | ✅ Live API call |
|
|
63
|
+
| `list_documents(space_id, ds_id)` | GET documents | 📄 Official OpenAPI spec |
|
|
64
|
+
| `get_tables(space_id, ds_id)` | GET tables | 📄 Official OpenAPI spec |
|
|
65
|
+
| `create_conversation(...)` | POST new conversation | ✅ Live API call |
|
|
66
|
+
| `get_conversation(cid)` | GET conversation | ✅ Live API call |
|
|
67
|
+
| `import_agent(...)` | POST create agent | ✅ Live API call |
|
|
68
|
+
| `archive_agent(sid)` | DELETE (soft) agent | ✅ Live API call |
|
|
69
|
+
|
|
70
|
+
_"Live API call" means the response schema was confirmed against a
|
|
71
|
+
real request during development, not just documentation. "Official
|
|
72
|
+
OpenAPI spec" means it's based on Dust's published spec but hasn't
|
|
73
|
+
been round-tripped against a live response yet (usually because
|
|
74
|
+
testing it live requires resources — like a connected data source —
|
|
75
|
+
that weren't available in the development workspace)._
|
|
76
|
+
|
|
77
|
+
## Known limitations
|
|
78
|
+
|
|
79
|
+
- **Message-sending is gated on Dust's Free plan.** Any endpoint that
|
|
80
|
+
invokes a model (`create_conversation` with an agent mention) returns
|
|
81
|
+
`429 rate_limit_error` on workspaces without a paid seat —
|
|
82
|
+
`Programmatic usage` is entirely disabled (`No access`) on Free,
|
|
83
|
+
independent of the regular in-app usage credits shown in the UI.
|
|
84
|
+
Write operations that _don't_ invoke a model (`import_agent`,
|
|
85
|
+
`archive_agent`) work fine on Free.
|
|
86
|
+
- **Some `Private` API endpoints aren't accessible via API key at all**,
|
|
87
|
+
regardless of plan — e.g. `POST /spaces` (creating a space) returns
|
|
88
|
+
`401 not_authenticated` even with a valid Bearer token, because it's
|
|
89
|
+
a session-only, web-app-internal endpoint despite appearing in the
|
|
90
|
+
public API reference.
|
|
91
|
+
- **The documentation contains at least one broken example URL.**
|
|
92
|
+
`GET /spaces` is shown at `https://dust.tt/api/w/{wId}/spaces`
|
|
93
|
+
(missing `/v1/`) — using that exact path returns a misleading
|
|
94
|
+
`401 not_authenticated` instead of a 404, making it look like an
|
|
95
|
+
auth problem. The correct path is `/api/v1/w/{wId}/spaces`.
|
|
96
|
+
- **Response shapes aren't consistent across endpoints.** Most list
|
|
97
|
+
endpoints wrap results in an object (e.g. `{"data_sources": [...]}`),
|
|
98
|
+
but `GET .../tables` returns a bare JSON array. This SDK normalizes
|
|
99
|
+
both into consistent Python return types, but it's worth knowing if
|
|
100
|
+
you're calling the raw API directly.
|
|
101
|
+
- **Dust's official OpenAPI spec has several inaccuracies**, found
|
|
102
|
+
through live testing:
|
|
103
|
+
- `agent.avatar_url` is required in practice, marked optional in the spec
|
|
104
|
+
- `editors` must be an array of email strings, not objects as the spec shows
|
|
105
|
+
- `generation_settings.reasoning_effort` is required but easy to miss
|
|
106
|
+
- Message `type` example values in the spec show `"human"`, but the
|
|
107
|
+
real API returns `"user_message"` / `"agent_message"`
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git clone https://github.com/Zaymerstone/dust-python-sdk.git
|
|
113
|
+
cd dust-python-sdk
|
|
114
|
+
python -m venv venv
|
|
115
|
+
venv\Scripts\Activate.ps1 # Windows
|
|
116
|
+
pip install -e .
|
|
117
|
+
pip install pytest requests-mock
|
|
118
|
+
pytest -v
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Tests run entirely against recorded fixtures (`tests/fixtures/`) —
|
|
122
|
+
no live API calls or credits are required to run the test suite.
|
|
123
|
+
|
|
124
|
+
## Status
|
|
125
|
+
|
|
126
|
+
This is an early-stage, unofficial project built to explore a gap in
|
|
127
|
+
Dust's SDK coverage. 10 methods are implemented and tested; the full
|
|
128
|
+
Dust API surface is 40+ endpoints. Contributions and feedback welcome.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "conversational-agent-client"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Unofficial Python client for the Dust (dust.tt) conversational AI agent platform API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Egor Nesterov"}
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"requests",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/Zaymerstone/dust-python-sdk"
|
|
21
|
+
Repository = "https://github.com/Zaymerstone/dust-python-sdk"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: conversational-agent-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python client for the Dust (dust.tt) conversational AI agent platform API
|
|
5
|
+
Author: Egor Nesterov
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Zaymerstone/dust-python-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/Zaymerstone/dust-python-sdk
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# dust-sdk (unofficial)
|
|
16
|
+
|
|
17
|
+
An unofficial Python client for the [Dust](https://dust.tt) API.
|
|
18
|
+
|
|
19
|
+
Dust ships an official [JavaScript/TypeScript SDK](https://docs.dust.tt/reference/javascript-sdk),
|
|
20
|
+
but has no official Python client - despite Python being the dominant
|
|
21
|
+
language for the data science, ML engineering, and automation teams
|
|
22
|
+
that make up a large part of Dust's target audience (their own
|
|
23
|
+
marketing highlights Data & Analytics as a core use case).
|
|
24
|
+
|
|
25
|
+
This project closes that gap.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install dust-sdk
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
_(not yet published to PyPI — see [Status](#status) below)_
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from dust_sdk.client import DustClient
|
|
39
|
+
|
|
40
|
+
client = DustClient(
|
|
41
|
+
api_key="your-dust-api-key",
|
|
42
|
+
workspace_id="your-workspace-id",
|
|
43
|
+
base_url="https://eu.dust.tt", # or https://dust.tt — see note below
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# List agents available in your workspace
|
|
47
|
+
agents = client.list_agents()
|
|
48
|
+
for agent in agents:
|
|
49
|
+
print(agent["sId"], "-", agent["name"])
|
|
50
|
+
|
|
51
|
+
# Talk to an agent
|
|
52
|
+
conversation = client.create_conversation(
|
|
53
|
+
message_content="What can you help me with?",
|
|
54
|
+
agent_sid="dust",
|
|
55
|
+
)
|
|
56
|
+
answer = client.get_last_agent_message_text(conversation)
|
|
57
|
+
print(answer)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### ⚠️ `base_url` is required, no default
|
|
61
|
+
|
|
62
|
+
Dust hosts separate regional infrastructure (`https://dust.tt` for US,
|
|
63
|
+
`https://eu.dust.tt` for EU). Using the wrong one doesn't 404 — it
|
|
64
|
+
returns a misleading `invalid_api_key_error`, making it look like your
|
|
65
|
+
key is wrong when it's actually a region mismatch. Check which region
|
|
66
|
+
your workspace lives in (visible in your workspace URL) before making
|
|
67
|
+
your first call.
|
|
68
|
+
|
|
69
|
+
## What's implemented
|
|
70
|
+
|
|
71
|
+
| Method | Operation | Verified against |
|
|
72
|
+
| --------------------------------- | --------------------- | ------------------------ |
|
|
73
|
+
| `list_agents()` | GET agent list | ✅ Live API call |
|
|
74
|
+
| `get_agent(sid)` | GET single agent | ✅ Live API call |
|
|
75
|
+
| `list_spaces()` | GET spaces | ✅ Live API call |
|
|
76
|
+
| `list_data_sources(space_id)` | GET data sources | ✅ Live API call |
|
|
77
|
+
| `list_documents(space_id, ds_id)` | GET documents | 📄 Official OpenAPI spec |
|
|
78
|
+
| `get_tables(space_id, ds_id)` | GET tables | 📄 Official OpenAPI spec |
|
|
79
|
+
| `create_conversation(...)` | POST new conversation | ✅ Live API call |
|
|
80
|
+
| `get_conversation(cid)` | GET conversation | ✅ Live API call |
|
|
81
|
+
| `import_agent(...)` | POST create agent | ✅ Live API call |
|
|
82
|
+
| `archive_agent(sid)` | DELETE (soft) agent | ✅ Live API call |
|
|
83
|
+
|
|
84
|
+
_"Live API call" means the response schema was confirmed against a
|
|
85
|
+
real request during development, not just documentation. "Official
|
|
86
|
+
OpenAPI spec" means it's based on Dust's published spec but hasn't
|
|
87
|
+
been round-tripped against a live response yet (usually because
|
|
88
|
+
testing it live requires resources — like a connected data source —
|
|
89
|
+
that weren't available in the development workspace)._
|
|
90
|
+
|
|
91
|
+
## Known limitations
|
|
92
|
+
|
|
93
|
+
- **Message-sending is gated on Dust's Free plan.** Any endpoint that
|
|
94
|
+
invokes a model (`create_conversation` with an agent mention) returns
|
|
95
|
+
`429 rate_limit_error` on workspaces without a paid seat —
|
|
96
|
+
`Programmatic usage` is entirely disabled (`No access`) on Free,
|
|
97
|
+
independent of the regular in-app usage credits shown in the UI.
|
|
98
|
+
Write operations that _don't_ invoke a model (`import_agent`,
|
|
99
|
+
`archive_agent`) work fine on Free.
|
|
100
|
+
- **Some `Private` API endpoints aren't accessible via API key at all**,
|
|
101
|
+
regardless of plan — e.g. `POST /spaces` (creating a space) returns
|
|
102
|
+
`401 not_authenticated` even with a valid Bearer token, because it's
|
|
103
|
+
a session-only, web-app-internal endpoint despite appearing in the
|
|
104
|
+
public API reference.
|
|
105
|
+
- **The documentation contains at least one broken example URL.**
|
|
106
|
+
`GET /spaces` is shown at `https://dust.tt/api/w/{wId}/spaces`
|
|
107
|
+
(missing `/v1/`) — using that exact path returns a misleading
|
|
108
|
+
`401 not_authenticated` instead of a 404, making it look like an
|
|
109
|
+
auth problem. The correct path is `/api/v1/w/{wId}/spaces`.
|
|
110
|
+
- **Response shapes aren't consistent across endpoints.** Most list
|
|
111
|
+
endpoints wrap results in an object (e.g. `{"data_sources": [...]}`),
|
|
112
|
+
but `GET .../tables` returns a bare JSON array. This SDK normalizes
|
|
113
|
+
both into consistent Python return types, but it's worth knowing if
|
|
114
|
+
you're calling the raw API directly.
|
|
115
|
+
- **Dust's official OpenAPI spec has several inaccuracies**, found
|
|
116
|
+
through live testing:
|
|
117
|
+
- `agent.avatar_url` is required in practice, marked optional in the spec
|
|
118
|
+
- `editors` must be an array of email strings, not objects as the spec shows
|
|
119
|
+
- `generation_settings.reasoning_effort` is required but easy to miss
|
|
120
|
+
- Message `type` example values in the spec show `"human"`, but the
|
|
121
|
+
real API returns `"user_message"` / `"agent_message"`
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
git clone https://github.com/Zaymerstone/dust-python-sdk.git
|
|
127
|
+
cd dust-python-sdk
|
|
128
|
+
python -m venv venv
|
|
129
|
+
venv\Scripts\Activate.ps1 # Windows
|
|
130
|
+
pip install -e .
|
|
131
|
+
pip install pytest requests-mock
|
|
132
|
+
pytest -v
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Tests run entirely against recorded fixtures (`tests/fixtures/`) —
|
|
136
|
+
no live API calls or credits are required to run the test suite.
|
|
137
|
+
|
|
138
|
+
## Status
|
|
139
|
+
|
|
140
|
+
This is an early-stage, unofficial project built to explore a gap in
|
|
141
|
+
Dust's SDK coverage. 10 methods are implemented and tested; the full
|
|
142
|
+
Dust API surface is 40+ endpoints. Contributions and feedback welcome.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/conversational_agent_client.egg-info/PKG-INFO
|
|
5
|
+
src/conversational_agent_client.egg-info/SOURCES.txt
|
|
6
|
+
src/conversational_agent_client.egg-info/dependency_links.txt
|
|
7
|
+
src/conversational_agent_client.egg-info/requires.txt
|
|
8
|
+
src/conversational_agent_client.egg-info/top_level.txt
|
|
9
|
+
src/dust_sdk/__init__.py
|
|
10
|
+
src/dust_sdk/client.py
|
|
11
|
+
tests/test_client.py
|
conversational_agent_client-0.1.0/src/conversational_agent_client.egg-info/dependency_links.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dust_sdk
|
|
File without changes
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DustAPIError(Exception):
|
|
5
|
+
"""Base exception for all Dust API errors."""
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DustClient:
|
|
10
|
+
def __init__(self, api_key: str, workspace_id: str, base_url: str):
|
|
11
|
+
"""
|
|
12
|
+
base_url is required with no default on purpose: Dust hosts
|
|
13
|
+
separate regional infrastructure (e.g. https://eu.dust.tt
|
|
14
|
+
and https://dust.tt), and passing the wrong region produces
|
|
15
|
+
a confusing 'invalid_api_key_error' rather than a
|
|
16
|
+
'wrong region' error. So we force the SDK user to specify
|
|
17
|
+
it explicitly.
|
|
18
|
+
"""
|
|
19
|
+
self.api_key = api_key
|
|
20
|
+
self.workspace_id = workspace_id
|
|
21
|
+
self.base_url = base_url.rstrip("/")
|
|
22
|
+
|
|
23
|
+
def _headers(self) -> dict:
|
|
24
|
+
return {
|
|
25
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def _handle_response(self, response: requests.Response) -> dict:
|
|
30
|
+
"""
|
|
31
|
+
Shared response handling for all methods: status check and
|
|
32
|
+
JSON parsing. Extracted here after this same check started
|
|
33
|
+
being duplicated across 4 methods in a row (list_agents,
|
|
34
|
+
create_conversation, list_spaces, list_data_sources).
|
|
35
|
+
"""
|
|
36
|
+
if response.status_code != 200:
|
|
37
|
+
raise DustAPIError(
|
|
38
|
+
f"Dust API returned {response.status_code}: {response.text}"
|
|
39
|
+
)
|
|
40
|
+
return response.json()
|
|
41
|
+
|
|
42
|
+
def list_agents(self) -> list[dict]:
|
|
43
|
+
"""Returns the list of agent configurations in the workspace."""
|
|
44
|
+
url = f"{self.base_url}/api/v1/w/{self.workspace_id}/assistant/agent_configurations"
|
|
45
|
+
response = requests.get(url, headers=self._headers())
|
|
46
|
+
return self._handle_response(response)["agentConfigurations"]
|
|
47
|
+
|
|
48
|
+
def create_conversation(
|
|
49
|
+
self,
|
|
50
|
+
message_content: str,
|
|
51
|
+
agent_sid: str,
|
|
52
|
+
username: str = "sdk-user",
|
|
53
|
+
timezone: str = "UTC",
|
|
54
|
+
blocking: bool = True,
|
|
55
|
+
) -> dict:
|
|
56
|
+
"""
|
|
57
|
+
Creates a conversation and sends the first message to an agent.
|
|
58
|
+
Response schema confirmed against live data on 2026-07-09
|
|
59
|
+
(see NOTES.md).
|
|
60
|
+
"""
|
|
61
|
+
url = f"{self.base_url}/api/v1/w/{self.workspace_id}/assistant/conversations"
|
|
62
|
+
|
|
63
|
+
payload = {
|
|
64
|
+
"message": {
|
|
65
|
+
"content": message_content,
|
|
66
|
+
"mentions": [{"configurationId": agent_sid}],
|
|
67
|
+
"context": {
|
|
68
|
+
"timezone": timezone,
|
|
69
|
+
"username": username,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
"blocking": blocking,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
response = requests.post(url, headers=self._headers(), json=payload)
|
|
76
|
+
return self._handle_response(response)["conversation"]
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def get_last_agent_message_text(conversation: dict) -> str | None:
|
|
80
|
+
"""
|
|
81
|
+
Extracts the text of the last agent message.
|
|
82
|
+
conversation['content'] is a 2D array content[rank][version]:
|
|
83
|
+
rank = the message's position in the conversation, version =
|
|
84
|
+
edit/revision at that position. We take the latest version at
|
|
85
|
+
each rank and look for the last message of type agent_message.
|
|
86
|
+
"""
|
|
87
|
+
for rank_group in reversed(conversation["content"]):
|
|
88
|
+
latest_version = rank_group[-1]
|
|
89
|
+
if latest_version.get("type") == "agent_message":
|
|
90
|
+
return latest_version.get("content")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def list_spaces(self) -> list[dict]:
|
|
94
|
+
"""Returns the list of spaces in the workspace."""
|
|
95
|
+
url = f"{self.base_url}/api/v1/w/{self.workspace_id}/spaces"
|
|
96
|
+
response = requests.get(url, headers=self._headers())
|
|
97
|
+
return self._handle_response(response)["spaces"]
|
|
98
|
+
|
|
99
|
+
def list_data_sources(self, space_id: str) -> list[dict]:
|
|
100
|
+
"""Returns the list of data sources in the given space."""
|
|
101
|
+
url = f"{self.base_url}/api/v1/w/{self.workspace_id}/spaces/{space_id}/data_sources"
|
|
102
|
+
response = requests.get(url, headers=self._headers())
|
|
103
|
+
return self._handle_response(response)["data_sources"]
|
|
104
|
+
|
|
105
|
+
def get_agent(self, agent_sid: str) -> dict:
|
|
106
|
+
"""Returns a single agent's configuration by its sId."""
|
|
107
|
+
url = f"{self.base_url}/api/v1/w/{self.workspace_id}/assistant/agent_configurations/{agent_sid}"
|
|
108
|
+
response = requests.get(url, headers=self._headers())
|
|
109
|
+
return self._handle_response(response)["agentConfiguration"]
|
|
110
|
+
|
|
111
|
+
def get_tables(self, space_id: str, data_source_id: str) -> list[dict]:
|
|
112
|
+
"""
|
|
113
|
+
Returns the list of tables in the given data source.
|
|
114
|
+
|
|
115
|
+
Note: unlike the other methods, this endpoint returns a bare
|
|
116
|
+
JSON array ([...]) rather than a wrapper object (e.g.
|
|
117
|
+
{"data_sources": [...]}). Because of that, _handle_response()
|
|
118
|
+
can't be used as-is here — it assumes response.json() is a
|
|
119
|
+
dict, but here it's a list.
|
|
120
|
+
"""
|
|
121
|
+
url = (
|
|
122
|
+
f"{self.base_url}/api/v1/w/{self.workspace_id}"
|
|
123
|
+
f"/spaces/{space_id}/data_sources/{data_source_id}/tables"
|
|
124
|
+
)
|
|
125
|
+
response = requests.get(url, headers=self._headers())
|
|
126
|
+
|
|
127
|
+
if response.status_code != 200:
|
|
128
|
+
raise DustAPIError(
|
|
129
|
+
f"Dust API returned {response.status_code}: {response.text}"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return response.json()
|
|
133
|
+
|
|
134
|
+
def list_documents(self, space_id: str, data_source_id: str) -> list[dict]:
|
|
135
|
+
"""Returns the list of documents in the given data source."""
|
|
136
|
+
url = (
|
|
137
|
+
f"{self.base_url}/api/v1/w/{self.workspace_id}"
|
|
138
|
+
f"/spaces/{space_id}/data_sources/{data_source_id}/documents"
|
|
139
|
+
)
|
|
140
|
+
response = requests.get(url, headers=self._headers())
|
|
141
|
+
return self._handle_response(response)["documents"]
|
|
142
|
+
|
|
143
|
+
def get_conversation(self, conversation_id: str) -> dict:
|
|
144
|
+
"""Returns a conversation by its id, including its full message history."""
|
|
145
|
+
url = (
|
|
146
|
+
f"{self.base_url}/api/v1/w/{self.workspace_id}"
|
|
147
|
+
f"/assistant/conversations/{conversation_id}"
|
|
148
|
+
)
|
|
149
|
+
response = requests.get(url, headers=self._headers())
|
|
150
|
+
return self._handle_response(response)["conversation"]
|
|
151
|
+
|
|
152
|
+
def import_agent(
|
|
153
|
+
self,
|
|
154
|
+
handle: str,
|
|
155
|
+
description: str,
|
|
156
|
+
instructions: str,
|
|
157
|
+
editors: list[str],
|
|
158
|
+
avatar_url: str,
|
|
159
|
+
model_id: str = "claude-sonnet-5",
|
|
160
|
+
provider_id: str = "anthropic",
|
|
161
|
+
temperature: float = 0.7,
|
|
162
|
+
reasoning_effort: str = "medium",
|
|
163
|
+
max_steps_per_run: int = 5,
|
|
164
|
+
scope: str = "hidden",
|
|
165
|
+
visualization_enabled: bool = False,
|
|
166
|
+
) -> dict:
|
|
167
|
+
"""
|
|
168
|
+
Creates a new agent in the workspace.
|
|
169
|
+
|
|
170
|
+
Field requirements were discovered empirically on 2026-07-09
|
|
171
|
+
through a series of live 400 errors (see NOTES.md) — the
|
|
172
|
+
official OpenAPI spec is inaccurate in several places:
|
|
173
|
+
- avatar_url is required, though marked optional in the spec
|
|
174
|
+
- editors is a list of strings (emails), not objects as shown
|
|
175
|
+
in the spec
|
|
176
|
+
- editors requires at least 1 element
|
|
177
|
+
- generation_settings.reasoning_effort is required
|
|
178
|
+
|
|
179
|
+
Note: unlike create_conversation, this method does NOT invoke
|
|
180
|
+
a model — it's a pure write operation, so it works even on
|
|
181
|
+
the Free plan despite "Programmatic access: No access".
|
|
182
|
+
"""
|
|
183
|
+
url = (
|
|
184
|
+
f"{self.base_url}/api/v1/w/{self.workspace_id}"
|
|
185
|
+
f"/assistant/agent_configurations/import"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
payload = {
|
|
189
|
+
"agent": {
|
|
190
|
+
"handle": handle,
|
|
191
|
+
"description": description,
|
|
192
|
+
"scope": scope,
|
|
193
|
+
"avatar_url": avatar_url,
|
|
194
|
+
"max_steps_per_run": max_steps_per_run,
|
|
195
|
+
"visualization_enabled": visualization_enabled,
|
|
196
|
+
},
|
|
197
|
+
"instructions": instructions,
|
|
198
|
+
"generation_settings": {
|
|
199
|
+
"model_id": model_id,
|
|
200
|
+
"provider_id": provider_id,
|
|
201
|
+
"temperature": temperature,
|
|
202
|
+
"reasoning_effort": reasoning_effort,
|
|
203
|
+
},
|
|
204
|
+
"tags": [],
|
|
205
|
+
"editors": editors,
|
|
206
|
+
"toolset": [],
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
response = requests.post(url, headers=self._headers(), json=payload)
|
|
210
|
+
return self._handle_response(response)["agentConfiguration"]
|
|
211
|
+
|
|
212
|
+
def archive_agent(self, agent_sid: str) -> dict:
|
|
213
|
+
"""
|
|
214
|
+
Archives (soft-deletes) an agent by its sId.
|
|
215
|
+
|
|
216
|
+
Like import_agent, this operation does not invoke a model, so
|
|
217
|
+
it works on the Free plan despite "Programmatic access: No
|
|
218
|
+
access". Confirmed with a live call on 2026-07-09 — it worked
|
|
219
|
+
on the first try, with no validation errors.
|
|
220
|
+
"""
|
|
221
|
+
url = (
|
|
222
|
+
f"{self.base_url}/api/v1/w/{self.workspace_id}"
|
|
223
|
+
f"/assistant/agent_configurations/{agent_sid}"
|
|
224
|
+
)
|
|
225
|
+
response = requests.delete(url, headers=self._headers())
|
|
226
|
+
return self._handle_response(response)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests_mock
|
|
3
|
+
from dust_sdk.client import DustClient, DustAPIError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load_fixture(name: str):
|
|
7
|
+
with open(f"tests/fixtures/{name}", encoding="utf-8") as f:
|
|
8
|
+
return json.load(f)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_list_agents_returns_agent_list():
|
|
12
|
+
fixture = load_fixture("agent_configurations.json")
|
|
13
|
+
|
|
14
|
+
client = DustClient(
|
|
15
|
+
api_key="fake-key",
|
|
16
|
+
workspace_id="fake-workspace",
|
|
17
|
+
base_url="https://eu.dust.tt",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
with requests_mock.Mocker() as m:
|
|
21
|
+
m.get(
|
|
22
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/assistant/agent_configurations",
|
|
23
|
+
json=fixture,
|
|
24
|
+
)
|
|
25
|
+
agents = client.list_agents()
|
|
26
|
+
|
|
27
|
+
assert len(agents) == 10
|
|
28
|
+
assert agents[0]["sId"] == "helper"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_create_conversation_returns_agent_answer():
|
|
32
|
+
fixture = load_fixture("conversation_response.json")
|
|
33
|
+
|
|
34
|
+
client = DustClient(
|
|
35
|
+
api_key="fake-key",
|
|
36
|
+
workspace_id="fake-workspace",
|
|
37
|
+
base_url="https://eu.dust.tt",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
with requests_mock.Mocker() as m:
|
|
41
|
+
m.post(
|
|
42
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/assistant/conversations",
|
|
43
|
+
json=fixture,
|
|
44
|
+
)
|
|
45
|
+
conversation = client.create_conversation(
|
|
46
|
+
message_content="Hello!",
|
|
47
|
+
agent_sid="claude-5-sonnet",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
answer = client.get_last_agent_message_text(conversation)
|
|
51
|
+
assert answer is not None
|
|
52
|
+
assert "how can i help" in answer.lower()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_list_spaces_returns_spaces():
|
|
56
|
+
fixture = load_fixture("spaces_response.json")
|
|
57
|
+
|
|
58
|
+
client = DustClient(
|
|
59
|
+
api_key="fake-key",
|
|
60
|
+
workspace_id="fake-workspace",
|
|
61
|
+
base_url="https://eu.dust.tt",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
with requests_mock.Mocker() as m:
|
|
65
|
+
m.get(
|
|
66
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/spaces",
|
|
67
|
+
json=fixture,
|
|
68
|
+
)
|
|
69
|
+
spaces = client.list_spaces()
|
|
70
|
+
|
|
71
|
+
assert len(spaces) == 1
|
|
72
|
+
assert spaces[0]["name"] == "Company Data"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_list_data_sources_returns_data_sources():
|
|
76
|
+
fixture = load_fixture("data_sources_response.json")
|
|
77
|
+
|
|
78
|
+
client = DustClient(
|
|
79
|
+
api_key="fake-key",
|
|
80
|
+
workspace_id="fake-workspace",
|
|
81
|
+
base_url="https://eu.dust.tt",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
with requests_mock.Mocker() as m:
|
|
85
|
+
m.get(
|
|
86
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/spaces/fake-space/data_sources",
|
|
87
|
+
json=fixture,
|
|
88
|
+
)
|
|
89
|
+
data_sources = client.list_data_sources(space_id="fake-space")
|
|
90
|
+
|
|
91
|
+
assert data_sources == []
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_get_agent_returns_single_agent():
|
|
95
|
+
fixture = load_fixture("single_agent_response.json")
|
|
96
|
+
|
|
97
|
+
client = DustClient(
|
|
98
|
+
api_key="fake-key",
|
|
99
|
+
workspace_id="fake-workspace",
|
|
100
|
+
base_url="https://eu.dust.tt",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
with requests_mock.Mocker() as m:
|
|
104
|
+
m.get(
|
|
105
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/assistant/agent_configurations/claude-5-sonnet",
|
|
106
|
+
json=fixture,
|
|
107
|
+
)
|
|
108
|
+
agent = client.get_agent("claude-5-sonnet")
|
|
109
|
+
|
|
110
|
+
assert agent["sId"] == "claude-5-sonnet"
|
|
111
|
+
assert agent["model"]["providerId"] == "anthropic"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_get_tables_returns_table_list():
|
|
115
|
+
fixture = load_fixture("tables_response.json")
|
|
116
|
+
|
|
117
|
+
client = DustClient(
|
|
118
|
+
api_key="fake-key",
|
|
119
|
+
workspace_id="fake-workspace",
|
|
120
|
+
base_url="https://eu.dust.tt",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
with requests_mock.Mocker() as m:
|
|
124
|
+
m.get(
|
|
125
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/spaces/fake-space/data_sources/fake-ds/tables",
|
|
126
|
+
json=fixture,
|
|
127
|
+
)
|
|
128
|
+
tables = client.get_tables(space_id="fake-space", data_source_id="fake-ds")
|
|
129
|
+
|
|
130
|
+
assert len(tables) == 1
|
|
131
|
+
assert tables[0]["title"] == "ROI Data"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_list_documents_returns_document_list():
|
|
135
|
+
fixture = load_fixture("documents_response.json")
|
|
136
|
+
|
|
137
|
+
client = DustClient(
|
|
138
|
+
api_key="fake-key",
|
|
139
|
+
workspace_id="fake-workspace",
|
|
140
|
+
base_url="https://eu.dust.tt",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
with requests_mock.Mocker() as m:
|
|
144
|
+
m.get(
|
|
145
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/spaces/fake-space/data_sources/fake-ds/documents",
|
|
146
|
+
json=fixture,
|
|
147
|
+
)
|
|
148
|
+
documents = client.list_documents(space_id="fake-space", data_source_id="fake-ds")
|
|
149
|
+
|
|
150
|
+
assert len(documents) == 1
|
|
151
|
+
assert documents[0]["title"] == "Customer Support FAQ"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_get_conversation_returns_conversation():
|
|
155
|
+
fixture = load_fixture("get_conversation_response.json")
|
|
156
|
+
|
|
157
|
+
client = DustClient(
|
|
158
|
+
api_key="fake-key",
|
|
159
|
+
workspace_id="fake-workspace",
|
|
160
|
+
base_url="https://eu.dust.tt",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
with requests_mock.Mocker() as m:
|
|
164
|
+
m.get(
|
|
165
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/assistant/conversations/fake-cid",
|
|
166
|
+
json=fixture,
|
|
167
|
+
)
|
|
168
|
+
conversation = client.get_conversation(conversation_id="fake-cid")
|
|
169
|
+
|
|
170
|
+
assert conversation["sId"] == "3U61h9tf0Y"
|
|
171
|
+
assert conversation["title"] == "Test greeting message"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_import_agent_creates_agent():
|
|
175
|
+
fixture = load_fixture("import_agent_response.json")
|
|
176
|
+
|
|
177
|
+
client = DustClient(
|
|
178
|
+
api_key="fake-key",
|
|
179
|
+
workspace_id="fake-workspace",
|
|
180
|
+
base_url="https://eu.dust.tt",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
with requests_mock.Mocker() as m:
|
|
184
|
+
m.post(
|
|
185
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/assistant/agent_configurations/import",
|
|
186
|
+
json=fixture,
|
|
187
|
+
)
|
|
188
|
+
agent = client.import_agent(
|
|
189
|
+
handle="sdk-test-agent",
|
|
190
|
+
description="Test agent",
|
|
191
|
+
instructions="You are a test agent.",
|
|
192
|
+
editors=["test@example.com"],
|
|
193
|
+
avatar_url="https://dust.tt/static/systemavatar/dust_avatar_full.png",
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
assert agent["sId"] == "hKKykKCnRI"
|
|
197
|
+
assert agent["name"] == "sdk-test-agent"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_archive_agent_returns_success():
|
|
201
|
+
fixture = load_fixture("archive_agent_response.json")
|
|
202
|
+
|
|
203
|
+
client = DustClient(
|
|
204
|
+
api_key="fake-key",
|
|
205
|
+
workspace_id="fake-workspace",
|
|
206
|
+
base_url="https://eu.dust.tt",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
with requests_mock.Mocker() as m:
|
|
210
|
+
m.delete(
|
|
211
|
+
"https://eu.dust.tt/api/v1/w/fake-workspace/assistant/agent_configurations/fake-sid",
|
|
212
|
+
json=fixture,
|
|
213
|
+
)
|
|
214
|
+
result = client.archive_agent(agent_sid="fake-sid")
|
|
215
|
+
|
|
216
|
+
assert result["success"] is True
|