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.
Files changed (37) hide show
  1. qodercn_agent_sdk-1.0.12/.gitignore +59 -0
  2. qodercn_agent_sdk-1.0.12/LICENSE +7 -0
  3. qodercn_agent_sdk-1.0.12/PKG-INFO +467 -0
  4. qodercn_agent_sdk-1.0.12/README.md +425 -0
  5. qodercn_agent_sdk-1.0.12/pyproject.toml +123 -0
  6. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/__init__.py +1056 -0
  7. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_brand.py +18 -0
  8. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_bundled/.gitignore +3 -0
  9. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_cli_version.py +3 -0
  10. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_errors.py +130 -0
  11. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/__init__.py +1 -0
  12. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/auth_helpers.py +53 -0
  13. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/client.py +228 -0
  14. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/cloud_agent.py +1406 -0
  15. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/mcp_handler.py +145 -0
  16. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/mcp_serialize.py +58 -0
  17. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/message_parser.py +287 -0
  18. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/query.py +1996 -0
  19. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/sdk_mcp_transport.py +63 -0
  20. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/session_mutations.py +683 -0
  21. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/session_storage.py +525 -0
  22. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/sessions.py +1255 -0
  23. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/transport/__init__.py +68 -0
  24. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_internal/transport/subprocess_cli.py +1000 -0
  25. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/_version.py +3 -0
  26. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/auth.py +266 -0
  27. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/client.py +990 -0
  28. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/plugins.py +180 -0
  29. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/py.typed +0 -0
  30. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/query.py +142 -0
  31. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/session_import.py +162 -0
  32. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/session_store.py +100 -0
  33. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/settings.py +57 -0
  34. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/store_sessions.py +445 -0
  35. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/testing/__init__.py +5 -0
  36. qodercn_agent_sdk-1.0.12/src/qodercn_agent_sdk/testing/session_store_conformance.py +223 -0
  37. 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,7 @@
1
+ Copyright (c) 2026 Qoder
2
+
3
+ Use of this software is governed by the Qoder Product Service Terms:
4
+
5
+ https://qoder.com/product-service
6
+
7
+ By installing or using this package, you agree to those terms.
@@ -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.