qodercn-agent-sdk 1.0.12__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.
- qodercn_agent_sdk-1.0.12/.gitignore +59 -0
- qodercn_agent_sdk-1.0.12/LICENSE +7 -0
- qodercn_agent_sdk-1.0.12/PKG-INFO +467 -0
- qodercn_agent_sdk-1.0.12/README.md +425 -0
- qodercn_agent_sdk-1.0.12/pyproject.toml +123 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/__init__.py +1056 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_brand.py +18 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_bundled/.gitignore +3 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_cli_version.py +3 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_errors.py +130 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/__init__.py +1 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/auth_helpers.py +53 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/client.py +228 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/cloud_agent.py +1406 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/mcp_handler.py +145 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/mcp_serialize.py +58 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/message_parser.py +287 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/query.py +1996 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/sdk_mcp_transport.py +63 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/session_mutations.py +683 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/session_storage.py +525 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/sessions.py +1255 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/transport/__init__.py +68 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/transport/subprocess_cli.py +1000 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_version.py +3 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/auth.py +266 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/client.py +990 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/plugins.py +180 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/py.typed +0 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/query.py +142 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/session_import.py +162 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/session_store.py +100 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/settings.py +57 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/store_sessions.py +445 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/testing/__init__.py +5 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/testing/session_store_conformance.py +223 -0
- qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/types.py +2646 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
MANIFEST
|
|
23
|
+
|
|
24
|
+
# Virtual environments
|
|
25
|
+
venv/
|
|
26
|
+
ENV/
|
|
27
|
+
env/
|
|
28
|
+
.venv
|
|
29
|
+
.venv-release/
|
|
30
|
+
.tmp-brand-packages/
|
|
31
|
+
uv.lock
|
|
32
|
+
|
|
33
|
+
# IDEs
|
|
34
|
+
.vscode/
|
|
35
|
+
.idea/
|
|
36
|
+
*.swp
|
|
37
|
+
*.swo
|
|
38
|
+
*~
|
|
39
|
+
**/.DS_Store
|
|
40
|
+
|
|
41
|
+
# Local test credentials
|
|
42
|
+
.env
|
|
43
|
+
.env.local
|
|
44
|
+
|
|
45
|
+
# Testing
|
|
46
|
+
.tox/
|
|
47
|
+
.coverage
|
|
48
|
+
.coverage.*
|
|
49
|
+
.cache
|
|
50
|
+
.pytest_cache/
|
|
51
|
+
htmlcov/
|
|
52
|
+
|
|
53
|
+
# Type checking
|
|
54
|
+
.mypy_cache/
|
|
55
|
+
.dmypy.json
|
|
56
|
+
dmypy.json
|
|
57
|
+
.pyre/
|
|
58
|
+
.qoder/worktrees/
|
|
59
|
+
test-reports/
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qodercn-agent-sdk
|
|
3
|
+
Version: 1.0.12
|
|
4
|
+
Summary: Python SDK for Qoder Agent
|
|
5
|
+
Author: Qoder
|
|
6
|
+
License: Copyright (c) 2026 Qoder
|
|
7
|
+
|
|
8
|
+
Use of this software is governed by the Qoder Product Service Terms:
|
|
9
|
+
|
|
10
|
+
https://qoder.com/product-service
|
|
11
|
+
|
|
12
|
+
By installing or using this package, you agree to those terms.
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: agent,ai,qoder,sdk
|
|
15
|
+
Classifier: Development Status :: 3 - Alpha
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: Other/Proprietary License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: anyio>=4.0.0
|
|
26
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0
|
|
27
|
+
Requires-Dist: typing-extensions>=4.0.0; python_version < '3.11'
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: anyio[trio]>=4.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.20.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-timeout>=2.0.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: tomli>=2.0.0; (python_version < '3.11') and extra == 'dev'
|
|
38
|
+
Provides-Extra: examples
|
|
39
|
+
Requires-Dist: asyncpg<1,>=0.27.0; extra == 'examples'
|
|
40
|
+
Requires-Dist: redis<9,>=5.0.0; extra == 'examples'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# Qoder Agent SDK for Python
|
|
44
|
+
|
|
45
|
+
Python SDK for building applications on top of Qoder Agent.
|
|
46
|
+
|
|
47
|
+
The SDK starts `qodercn` for you, streams agent messages back to Python, and
|
|
48
|
+
lets your application configure tools, permissions, working directories, MCP
|
|
49
|
+
servers, hooks, and interactive sessions.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install qodercn-agent-sdk
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Prerequisites:
|
|
58
|
+
|
|
59
|
+
- Python 3.10+
|
|
60
|
+
- A Qoder account or another authentication method supported by your host
|
|
61
|
+
application
|
|
62
|
+
|
|
63
|
+
## CLI Behavior
|
|
64
|
+
|
|
65
|
+
Published platform wheels include a bundled `qodercn`, so a separate CLI
|
|
66
|
+
installation is not required for normal SDK use. If you prefer to use a
|
|
67
|
+
system-wide CLI or a pinned local build, pass `QoderAgentOptions(cli_path=...)`.
|
|
68
|
+
|
|
69
|
+
## Authentication
|
|
70
|
+
|
|
71
|
+
Every SDK query needs an explicit authentication option.
|
|
72
|
+
|
|
73
|
+
| Authentication method | Identity | Use case |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| Personal Access Token (PAT) | A Qoder user | Automation that needs the user's permissions and data |
|
|
76
|
+
| Service Account | An organization workload | Services and jobs that should not depend on a personal account |
|
|
77
|
+
| Local `qodercn` session | The signed-in user | Interactive development on a workstation |
|
|
78
|
+
|
|
79
|
+
For a PAT, generate a token at
|
|
80
|
+
[qoder.cn/account/integrations](https://qoder.cn/account/integrations), store
|
|
81
|
+
it in a secret manager, and expose it through the default environment variable:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
export QODERCN_PERSONAL_ACCESS_TOKEN=your-token
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from qodercn_agent_sdk import QoderAgentOptions, access_token_from_env
|
|
89
|
+
|
|
90
|
+
options = QoderAgentOptions(auth=access_token_from_env())
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For a Service Account, read the key from your secret manager and pass it
|
|
94
|
+
directly to the SDK:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from qodercn_agent_sdk import QoderAgentOptions, service_account
|
|
98
|
+
|
|
99
|
+
# Get the Service Account key from the host's secret manager adapter.
|
|
100
|
+
service_account_key = read_secret("qoder-service-account-key")
|
|
101
|
+
options = QoderAgentOptions(
|
|
102
|
+
auth=service_account(service_account_key=service_account_key)
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The SDK and CLI obtain and refresh short-lived Service Account tokens for this
|
|
107
|
+
authentication method. A host can retain the Service Account key and use
|
|
108
|
+
`service_account(fetch_service_account_token=...)` to obtain and refresh
|
|
109
|
+
short-lived SATs for qodercn. See the
|
|
110
|
+
[host callback example](examples/service_account_token.py) for a complete Token
|
|
111
|
+
exchange and query. To reuse a signed-in developer
|
|
112
|
+
workstation, use `qodercli_auth()`. See the
|
|
113
|
+
[SDK authentication guide](docs/public/en/cli/sdk/python/authentication.mdx) for
|
|
114
|
+
complete setup instructions and security guidance.
|
|
115
|
+
|
|
116
|
+
## Quick Start
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import anyio
|
|
120
|
+
from qodercn_agent_sdk import QoderAgentOptions, qodercli_auth, query
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def main() -> None:
|
|
124
|
+
options = QoderAgentOptions(auth=qodercli_auth())
|
|
125
|
+
|
|
126
|
+
async for message in query(
|
|
127
|
+
prompt="What is 2 + 2?",
|
|
128
|
+
options=options,
|
|
129
|
+
):
|
|
130
|
+
print(message)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
anyio.run(main)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Basic Usage
|
|
137
|
+
|
|
138
|
+
`query()` runs a single SDK query and returns an async iterator of response
|
|
139
|
+
messages.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from qodercn_agent_sdk import (
|
|
143
|
+
AssistantMessage,
|
|
144
|
+
QoderAgentOptions,
|
|
145
|
+
TextBlock,
|
|
146
|
+
qodercli_auth,
|
|
147
|
+
query,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
options = QoderAgentOptions(
|
|
151
|
+
auth=qodercli_auth(),
|
|
152
|
+
system_prompt="You are a helpful assistant.",
|
|
153
|
+
max_turns=1,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
async for message in query(prompt="Explain this repository", options=options):
|
|
157
|
+
if isinstance(message, AssistantMessage):
|
|
158
|
+
for block in message.content:
|
|
159
|
+
if isinstance(block, TextBlock):
|
|
160
|
+
print(block.text)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Tools and Permissions
|
|
164
|
+
|
|
165
|
+
Qoder Agent can use tools such as file reads, file edits, shell commands, and
|
|
166
|
+
MCP tools. `allowed_tools` is an approval allowlist: listed tools are
|
|
167
|
+
auto-approved, while unlisted tools continue through `permission_mode` and
|
|
168
|
+
`can_use_tool` for a decision. It does not remove tools from the agent's
|
|
169
|
+
available toolset. To block tools, use `disallowed_tools`.
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from qodercn_agent_sdk import QoderAgentOptions, qodercli_auth, query
|
|
173
|
+
|
|
174
|
+
options = QoderAgentOptions(
|
|
175
|
+
auth=qodercli_auth(),
|
|
176
|
+
allowed_tools=["Read", "Edit"],
|
|
177
|
+
disallowed_tools=["Bash"],
|
|
178
|
+
permission_mode="acceptEdits",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
async for message in query(
|
|
182
|
+
prompt="Update the README introduction.",
|
|
183
|
+
options=options,
|
|
184
|
+
):
|
|
185
|
+
print(message)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
For application-specific approval flows, provide `can_use_tool`:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from qodercn_agent_sdk import (
|
|
192
|
+
PermissionResultAllow,
|
|
193
|
+
PermissionResultDeny,
|
|
194
|
+
QoderAgentOptions,
|
|
195
|
+
ToolPermissionContext,
|
|
196
|
+
qodercli_auth,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
async def can_use_tool(
|
|
201
|
+
tool_name: str,
|
|
202
|
+
tool_input: dict,
|
|
203
|
+
context: ToolPermissionContext,
|
|
204
|
+
):
|
|
205
|
+
if tool_name == "Bash":
|
|
206
|
+
return PermissionResultDeny(message="Shell commands are disabled here.")
|
|
207
|
+
return PermissionResultAllow()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
options = QoderAgentOptions(
|
|
211
|
+
auth=qodercli_auth(),
|
|
212
|
+
can_use_tool=can_use_tool,
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Working Directory
|
|
217
|
+
|
|
218
|
+
Use `cwd` to run the agent in a specific project directory:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from pathlib import Path
|
|
222
|
+
|
|
223
|
+
from qodercn_agent_sdk import QoderAgentOptions, qodercli_auth
|
|
224
|
+
|
|
225
|
+
options = QoderAgentOptions(
|
|
226
|
+
auth=qodercli_auth(),
|
|
227
|
+
cwd=Path("/path/to/project"),
|
|
228
|
+
)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Interactive Sessions
|
|
232
|
+
|
|
233
|
+
Use `QoderSDKClient` when you need a long-lived, bidirectional session instead
|
|
234
|
+
of a single `query()` call.
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from qodercn_agent_sdk import QoderAgentOptions, QoderSDKClient, qodercli_auth
|
|
238
|
+
|
|
239
|
+
options = QoderAgentOptions(auth=qodercli_auth())
|
|
240
|
+
|
|
241
|
+
async with QoderSDKClient(options=options) as client:
|
|
242
|
+
await client.query("Inspect this project and summarize the main modules.")
|
|
243
|
+
|
|
244
|
+
async for message in client.receive_response():
|
|
245
|
+
print(message)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`QoderSDKClient` is useful for chat interfaces, follow-up prompts, interrupts,
|
|
249
|
+
runtime permission changes, MCP server management, and other workflows that need
|
|
250
|
+
state across multiple turns.
|
|
251
|
+
|
|
252
|
+
Use message priority to steer a turn that is already running:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
await client.query(
|
|
256
|
+
"Stop the current direction and inspect the failing tests first.",
|
|
257
|
+
priority="now",
|
|
258
|
+
)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`priority="now"` stops the current response and handles the message
|
|
262
|
+
immediately. `priority="next"` is the default and uses the next suitable
|
|
263
|
+
point. `priority="later"` waits until the current response finishes.
|
|
264
|
+
`should_query=False` adds the message to the conversation without starting a
|
|
265
|
+
response by itself; its processing time still follows `priority`.
|
|
266
|
+
|
|
267
|
+
Assign a session-unique `message_uuid` to messages that need tracking or
|
|
268
|
+
cancellation, and do not reuse UUIDs within a session.
|
|
269
|
+
`await client.interrupt()` stops the current response and returns `None`.
|
|
270
|
+
`await client.cancel_async_message(message_uuid)` returns `True` when the
|
|
271
|
+
queued message is cancelled and `False` when it can no longer be cancelled.
|
|
272
|
+
|
|
273
|
+
## External Session Storage
|
|
274
|
+
|
|
275
|
+
Use `session_store` when a host needs durable transcripts outside the local
|
|
276
|
+
machine. The SDK mirrors entries after qodercn commits them locally. A later
|
|
277
|
+
process can restore the same session before qodercn starts:
|
|
278
|
+
|
|
279
|
+
```text
|
|
280
|
+
qodercn commit -> SDK append(key, entries) -> external store
|
|
281
|
+
external store -> SDK load(key) -> temporary QODERCN_CONFIG_DIR -> qodercn resume
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
from qodercn_agent_sdk import (
|
|
286
|
+
InMemorySessionStore,
|
|
287
|
+
QoderAgentOptions,
|
|
288
|
+
qodercli_auth,
|
|
289
|
+
query,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
session_store = InMemorySessionStore()
|
|
293
|
+
|
|
294
|
+
options = QoderAgentOptions(
|
|
295
|
+
auth=qodercli_auth(),
|
|
296
|
+
cwd="/path/to/project",
|
|
297
|
+
session_store=session_store,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
async for message in query(prompt="Inspect this project.", options=options):
|
|
301
|
+
print(message)
|
|
302
|
+
|
|
303
|
+
resume_options = QoderAgentOptions(
|
|
304
|
+
auth=qodercli_auth(),
|
|
305
|
+
cwd="/path/to/project",
|
|
306
|
+
resume="11111111-1111-4111-8111-111111111111",
|
|
307
|
+
session_store=session_store,
|
|
308
|
+
)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Every store implements async `append(key, entries)` and `load(key)`. Implement
|
|
312
|
+
`list_sessions(project_key)` for `continue_conversation=True` and session
|
|
313
|
+
listing, `list_subkeys(key)` to restore child-agent transcripts, and
|
|
314
|
+
`delete(key)` for deletion. Entries are opaque JSON dictionaries and must remain
|
|
315
|
+
in append order. A child transcript uses an opaque `subpath` such as
|
|
316
|
+
`subagents/agent-<id>`; the key does not include the on-disk `.jsonl`
|
|
317
|
+
extension.
|
|
318
|
+
|
|
319
|
+
When `load()` returns `None` or an empty list for an explicit `resume`, the SDK
|
|
320
|
+
falls back to the same local session ID. Missing or empty child transcripts do
|
|
321
|
+
not prevent restoration of the main session, and unsafe subpaths are ignored.
|
|
322
|
+
|
|
323
|
+
`session_store_flush="batched"` is the default. `"eager"` starts each append
|
|
324
|
+
without waiting for the result boundary. Final append failures are emitted as
|
|
325
|
+
non-fatal `SDKMirrorErrorMessage` values. `load_timeout_ms` defaults to 60,000
|
|
326
|
+
ms. Session storage cannot be combined with file checkpointing, a custom
|
|
327
|
+
transport. It requires the built-in subprocess
|
|
328
|
+
transport.
|
|
329
|
+
|
|
330
|
+
The existing local session helpers remain synchronous. External stores use the
|
|
331
|
+
async helpers `list_sessions_from_store`, `get_session_info_from_store`,
|
|
332
|
+
`get_session_messages_from_store`, `rename_session_via_store`,
|
|
333
|
+
`tag_session_via_store`, `fork_session_via_store`, and
|
|
334
|
+
`delete_session_via_store`. Local and external child-agent transcripts are
|
|
335
|
+
available through `list_subagents` / `get_subagent_messages` and
|
|
336
|
+
`list_subagents_from_store` / `get_subagent_messages_from_store`. Use
|
|
337
|
+
`import_session_to_store` to copy an existing local main transcript,
|
|
338
|
+
child-agent transcripts, and metadata into a store.
|
|
339
|
+
|
|
340
|
+
### Production stores
|
|
341
|
+
|
|
342
|
+
The SDK exports the `SessionStore` protocol but does not ship a
|
|
343
|
+
production-ready external storage implementation. Implement the protocol
|
|
344
|
+
against shared storage operated by your application, then validate its
|
|
345
|
+
append/load ordering, project isolation, subkey handling, and deletion behavior
|
|
346
|
+
with `run_session_store_conformance`.
|
|
347
|
+
|
|
348
|
+
## Custom Tools
|
|
349
|
+
|
|
350
|
+
You can expose Python functions to Qoder Agent as in-process SDK MCP servers.
|
|
351
|
+
This avoids managing a separate MCP subprocess for simple application-local
|
|
352
|
+
tools.
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
from qodercn_agent_sdk import (
|
|
356
|
+
QoderAgentOptions,
|
|
357
|
+
QoderSDKClient,
|
|
358
|
+
create_sdk_mcp_server,
|
|
359
|
+
qodercli_auth,
|
|
360
|
+
tool,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@tool("greet", "Greet a user", {"name": str})
|
|
365
|
+
async def greet_user(args):
|
|
366
|
+
return {
|
|
367
|
+
"content": [
|
|
368
|
+
{"type": "text", "text": f"Hello, {args['name']}!"}
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
server = create_sdk_mcp_server(
|
|
374
|
+
name="my-tools",
|
|
375
|
+
version="1.0.0",
|
|
376
|
+
tools=[greet_user],
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
options = QoderAgentOptions(
|
|
380
|
+
auth=qodercli_auth(),
|
|
381
|
+
mcp_servers={"tools": server},
|
|
382
|
+
allowed_tools=["mcp__tools__greet"],
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
async with QoderSDKClient(options=options) as client:
|
|
386
|
+
await client.query("Greet Alice.")
|
|
387
|
+
async for message in client.receive_response():
|
|
388
|
+
print(message)
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Hooks
|
|
392
|
+
|
|
393
|
+
Hooks are deterministic Python callbacks invoked at specific points in the
|
|
394
|
+
agent loop. They are useful for validation, policy checks, logging, and
|
|
395
|
+
application-specific feedback.
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
from qodercn_agent_sdk import HookMatcher, QoderAgentOptions, qodercli_auth
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
async def block_script(input_data, tool_use_id, context):
|
|
402
|
+
if input_data["tool_name"] != "Bash":
|
|
403
|
+
return {}
|
|
404
|
+
|
|
405
|
+
command = input_data["tool_input"].get("command", "")
|
|
406
|
+
if "./deploy.sh" in command:
|
|
407
|
+
return {
|
|
408
|
+
"hookSpecificOutput": {
|
|
409
|
+
"hookEventName": "PreToolUse",
|
|
410
|
+
"permissionDecision": "deny",
|
|
411
|
+
"permissionDecisionReason": "Deployment scripts require review.",
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return {}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
options = QoderAgentOptions(
|
|
418
|
+
auth=qodercli_auth(),
|
|
419
|
+
hooks={
|
|
420
|
+
"PreToolUse": [
|
|
421
|
+
HookMatcher(matcher="Bash", hooks=[block_script]),
|
|
422
|
+
],
|
|
423
|
+
},
|
|
424
|
+
)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## Error Handling
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
from qodercn_agent_sdk import (
|
|
431
|
+
CLIConnectionError,
|
|
432
|
+
CLIJSONDecodeError,
|
|
433
|
+
CLINotFoundError,
|
|
434
|
+
ProcessError,
|
|
435
|
+
QoderAgentOptions,
|
|
436
|
+
QoderSDKError,
|
|
437
|
+
qodercli_auth,
|
|
438
|
+
query,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
async for message in query(
|
|
443
|
+
prompt="Hello Qoder",
|
|
444
|
+
options=QoderAgentOptions(auth=qodercli_auth()),
|
|
445
|
+
):
|
|
446
|
+
print(message)
|
|
447
|
+
except CLINotFoundError:
|
|
448
|
+
print("qodercn was not found. Install a platform wheel or set cli_path.")
|
|
449
|
+
except CLIConnectionError as exc:
|
|
450
|
+
print(f"Connection failed: {exc}")
|
|
451
|
+
except ProcessError as exc:
|
|
452
|
+
print(f"qodercn exited with code {exc.exit_code}")
|
|
453
|
+
except CLIJSONDecodeError as exc:
|
|
454
|
+
print(f"Could not parse qodercn output: {exc}")
|
|
455
|
+
except QoderSDKError as exc:
|
|
456
|
+
print(f"SDK error: {exc}")
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## License and Terms
|
|
460
|
+
|
|
461
|
+
Copyright (c) 2026 Qoder
|
|
462
|
+
|
|
463
|
+
Use of this software is governed by the Qoder Product Service Terms:
|
|
464
|
+
|
|
465
|
+
https://qoder.com/product-service
|
|
466
|
+
|
|
467
|
+
By installing or using this package, you agree to those terms.
|