qoni 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.
- qoni-0.1.0/.gitignore +8 -0
- qoni-0.1.0/.gitlab-ci.yml +84 -0
- qoni-0.1.0/LICENSE +21 -0
- qoni-0.1.0/PKG-INFO +342 -0
- qoni-0.1.0/README.md +298 -0
- qoni-0.1.0/pyproject.toml +41 -0
- qoni-0.1.0/qoni/__init__.py +101 -0
- qoni-0.1.0/qoni/auth.py +45 -0
- qoni-0.1.0/qoni/client.py +1073 -0
- qoni-0.1.0/qoni/deep_research.py +290 -0
- qoni-0.1.0/qoni/do_anything.py +333 -0
- qoni-0.1.0/qoni/errors.py +252 -0
- qoni-0.1.0/qoni/genauth.py +221 -0
- qoni-0.1.0/qoni/gumem.py +154 -0
- qoni-0.1.0/qoni/interactions.py +316 -0
- qoni-0.1.0/qoni/run_events.py +369 -0
- qoni-0.1.0/qoni/run_handle.py +370 -0
- qoni-0.1.0/qoni/scopes.py +68 -0
- qoni-0.1.0/qoni/signature.py +87 -0
- qoni-0.1.0/qoni/track.py +324 -0
- qoni-0.1.0/qoni/types.py +34 -0
- qoni-0.1.0/qoni/web_search.py +196 -0
- qoni-0.1.0/tests/test_client.py +327 -0
- qoni-0.1.0/tests/test_errors.py +87 -0
- qoni-0.1.0/tests/test_events.py +201 -0
- qoni-0.1.0/tests/test_local_http_server.py +192 -0
- qoni-0.1.0/tests/test_signature.py +54 -0
qoni-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
- test
|
|
3
|
+
- package
|
|
4
|
+
- sync
|
|
5
|
+
- publish
|
|
6
|
+
|
|
7
|
+
# ---------- 测试:多 Python 版本矩阵 ----------
|
|
8
|
+
test:
|
|
9
|
+
stage: test
|
|
10
|
+
image: python:${PYTHON_VERSION}-slim
|
|
11
|
+
parallel:
|
|
12
|
+
matrix:
|
|
13
|
+
- PYTHON_VERSION: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
14
|
+
script:
|
|
15
|
+
- pip install --upgrade pip
|
|
16
|
+
- pip install -e ".[dev]"
|
|
17
|
+
- pytest -v
|
|
18
|
+
rules:
|
|
19
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
20
|
+
- if: $CI_COMMIT_BRANCH == "main"
|
|
21
|
+
|
|
22
|
+
# ---------- 包内容检查:构建 + 元数据 + wheel 内容 + 安装验证 ----------
|
|
23
|
+
package-check:
|
|
24
|
+
stage: package
|
|
25
|
+
image: python:3.12-slim
|
|
26
|
+
script:
|
|
27
|
+
- pip install --upgrade pip
|
|
28
|
+
- pip install build twine check-wheel-contents
|
|
29
|
+
# 版本一致性检查:pyproject.toml 与 qoni/__init__.py
|
|
30
|
+
- |
|
|
31
|
+
PKG_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
32
|
+
INIT_VERSION=$(python -c "import re; print(re.search(r'__version__ = \"(.+?)\"', open('qoni/__init__.py').read()).group(1))")
|
|
33
|
+
echo "pyproject.toml: $PKG_VERSION / qoni.__version__: $INIT_VERSION"
|
|
34
|
+
if [ "$PKG_VERSION" != "$INIT_VERSION" ]; then
|
|
35
|
+
echo "ERROR: 版本号不一致" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
- python -m build
|
|
39
|
+
- twine check --strict dist/*
|
|
40
|
+
- check-wheel-contents dist/*.whl
|
|
41
|
+
- pip install dist/*.whl
|
|
42
|
+
- python -c "import qoni; print('qoni', qoni.__version__, 'imported OK')"
|
|
43
|
+
artifacts:
|
|
44
|
+
paths:
|
|
45
|
+
- dist/
|
|
46
|
+
expire_in: 1 week
|
|
47
|
+
rules:
|
|
48
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
49
|
+
- if: $CI_COMMIT_BRANCH == "main"
|
|
50
|
+
|
|
51
|
+
# ---------- 同步到 GitHub(镜像推送) ----------
|
|
52
|
+
sync-github:
|
|
53
|
+
stage: sync
|
|
54
|
+
image: alpine:3.20
|
|
55
|
+
variables:
|
|
56
|
+
GIT_DEPTH: 0
|
|
57
|
+
before_script:
|
|
58
|
+
- apk add --no-cache git
|
|
59
|
+
script:
|
|
60
|
+
- git push --force
|
|
61
|
+
"https://${GITHUB_SYNC_USER}:${GITHUB_SYNC_TOKEN}@github.com/QoniAI/qoni-sdk-python.git"
|
|
62
|
+
"HEAD:main" --tags
|
|
63
|
+
rules:
|
|
64
|
+
- if: $CI_COMMIT_BRANCH == "main" && $GITHUB_SYNC_TOKEN
|
|
65
|
+
needs:
|
|
66
|
+
- test
|
|
67
|
+
- package-check
|
|
68
|
+
|
|
69
|
+
# ---------- 发布到 PyPI(仅手动触发) ----------
|
|
70
|
+
publish-pypi:
|
|
71
|
+
stage: publish
|
|
72
|
+
image: python:3.12-slim
|
|
73
|
+
script:
|
|
74
|
+
- pip install --upgrade pip twine
|
|
75
|
+
- twine upload --non-interactive dist/*
|
|
76
|
+
variables:
|
|
77
|
+
TWINE_USERNAME: __token__
|
|
78
|
+
TWINE_PASSWORD: $PYPI_API_TOKEN
|
|
79
|
+
rules:
|
|
80
|
+
- if: $CI_COMMIT_BRANCH == "main"
|
|
81
|
+
when: manual
|
|
82
|
+
needs:
|
|
83
|
+
- test
|
|
84
|
+
- package-check
|
qoni-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eazo
|
|
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.
|
qoni-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: qoni
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unified Python SDK for Qoni Agent delegation, GUMem memory, and WebAgent automation.
|
|
5
|
+
Project-URL: Homepage, https://github.com/QONIAI/qoni-sdk-python
|
|
6
|
+
Project-URL: Issues, https://github.com/QONIAI/qoni-sdk-python/issues
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 Eazo
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Keywords: agent,delegation,gumem,qoni,sdk,webagent
|
|
30
|
+
Classifier: Development Status :: 4 - Beta
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Requires-Python: >=3.9
|
|
40
|
+
Requires-Dist: httpx>=0.24
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# qoni
|
|
46
|
+
|
|
47
|
+
Unified Python SDK for Qoni Agent delegation, GenAuth user management, GUMem memory, WebAgent automation, web search, and monitoring.
|
|
48
|
+
|
|
49
|
+
`qoni` gives a trusted server one compact way to use Qoni AK/SK. For runtime product calls, your service verifies the GenAuth user, requests short-lived delegation with `delegate_token`, and then uses the returned token in GUMem, Do Anything, Web Search, Deep Research, and Track calls. For GenAuth user management, the SDK exchanges AK/SK for a standard GenAuth management token internally and calls GenAuth v3 users APIs with that token.
|
|
50
|
+
|
|
51
|
+
AK/SK credentials must stay on a trusted server. Do not ship them to browsers, mobile apps, public CLI config, or untrusted Agent runtimes.
|
|
52
|
+
|
|
53
|
+
> This is the Python counterpart of [`@qoniai/qoni`](https://github.com/QONIAI/qoni-sdk-node) (the Node.js SDK). The API surface mirrors it, adapted to Python conventions: snake_case naming, keyword arguments, exceptions, generators, and dataclasses.
|
|
54
|
+
|
|
55
|
+
## Why Qoni
|
|
56
|
+
|
|
57
|
+
Agents that perform real work need more than a backend API key. They need a user boundary, explicit scopes, expiry, observable execution, and audit metadata that explains what happened later.
|
|
58
|
+
|
|
59
|
+
Qoni keeps that model small:
|
|
60
|
+
|
|
61
|
+
- One SDK entry: `Qoni(access_key=..., secret_key=...)`.
|
|
62
|
+
- One discovery path: `host` can override the Qoni Console/SDK gateway for private or local deployments, and the SDK reads downstream runtime URLs from `/api/v3/eak/runtime-config`.
|
|
63
|
+
- One delegation entry: call `delegate_token`; silent mode returns `data["token"]`, while interactive mode returns an authorization URL and later completes on your server.
|
|
64
|
+
- One management path: call `genauth.users.*`, and the SDK exchanges AK/SK for a GenAuth management token before calling GenAuth v3 users APIs.
|
|
65
|
+
- Capability-first namespaces: `genauth`, `gumem`, `do_anything`, `web_search`, `deep_research`, and `track`.
|
|
66
|
+
- Readable scope strings for least-privilege authorization.
|
|
67
|
+
- Typed exceptions with `request_id`, `trace_id`, `audit_id`, and `retryable`.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install qoni
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Requirements:
|
|
76
|
+
|
|
77
|
+
- Python 3.9 or later.
|
|
78
|
+
- Qoni `access_key` and `secret_key` created in Qoni Console.
|
|
79
|
+
- For silent runtime product calls, a real GenAuth user id from the userpool bound to the Qoni credential. For smoke tests, call `qoni.resolve_any_bound_user()` to grab the first bound user; in application code, resolve it with `current_user` or your existing server-side user session.
|
|
80
|
+
- For `genauth.users.*` management calls, no user id is required. The SDK uses AK/SK to request a GenAuth management token from Qoni.
|
|
81
|
+
- Optional `host` for private or local Qoni deployments. Leave it unset for hosted Qoni.
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import os
|
|
87
|
+
|
|
88
|
+
from qoni import Qoni
|
|
89
|
+
|
|
90
|
+
qoni = Qoni(
|
|
91
|
+
access_key=os.environ["QONI_ACCESS_KEY"],
|
|
92
|
+
secret_key=os.environ["QONI_SECRET_KEY"],
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### GenAuth User Management
|
|
97
|
+
|
|
98
|
+
`genauth.users.*` is a management-plane capability. It does not need a user id or `delegate_token`; the SDK exchanges AK/SK for a GenAuth management token internally.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
users = qoni.genauth.users.list(page=1, limit=20)
|
|
102
|
+
print("GenAuth users:", users.data)
|
|
103
|
+
|
|
104
|
+
created = qoni.genauth.users.create(
|
|
105
|
+
username="sdk-demo",
|
|
106
|
+
password=os.environ["GENAUTH_DEMO_USER_PASSWORD"],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
profile = qoni.genauth.users.get(user_id=created.data["userId"])
|
|
110
|
+
|
|
111
|
+
qoni.genauth.users.update(
|
|
112
|
+
user_id=profile.data["userId"],
|
|
113
|
+
nickname="SDK demo user",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Optional smoke-test cleanup:
|
|
117
|
+
# qoni.genauth.users.delete_batch(user_ids=[profile.data["userId"]])
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Runtime Product Delegation
|
|
121
|
+
|
|
122
|
+
GUMem, Do Anything, Web Search, Deep Research, and Track act for an end user. Silent delegation calls need a real GenAuth user id, then a `delegate_token` result:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
user_id = os.environ["QONI_USER_ID"]
|
|
126
|
+
|
|
127
|
+
# `products` is per-product authorization sugar; `agent` defaults to "sdk".
|
|
128
|
+
delegation = qoni.delegate_token(user_id=user_id, products=["do_anything"])
|
|
129
|
+
token = delegation.data["token"]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Interactive delegation returns an authorization URL instead; complete it on your server after the user authorizes:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
started = qoni.delegate_token(
|
|
136
|
+
mode="interactive",
|
|
137
|
+
redirect_uri="https://example.com/callback",
|
|
138
|
+
state="opaque-state",
|
|
139
|
+
products=["do_anything"],
|
|
140
|
+
)
|
|
141
|
+
print("send the user to:", started.data["authorizationUrl"])
|
|
142
|
+
|
|
143
|
+
# later, in the redirect handler:
|
|
144
|
+
completed = qoni.complete_delegate_token(
|
|
145
|
+
grant_id=started.data["grantId"],
|
|
146
|
+
code=code_from_callback,
|
|
147
|
+
state="opaque-state",
|
|
148
|
+
)
|
|
149
|
+
token = completed.data["token"]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Do Anything
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from qoni import CaptureOptions, QoniEventTypes
|
|
156
|
+
|
|
157
|
+
run = qoni.do_anything.run(
|
|
158
|
+
token=token, # passed once — the handle holds it; handle methods never take a token
|
|
159
|
+
prompt="Open https://en.wikipedia.org/wiki/Singapore and summarize the country's key facts.",
|
|
160
|
+
capture=CaptureOptions(screenshots=True),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Stream semantic events (a generator; ends at the terminal event):
|
|
164
|
+
for event in run.events():
|
|
165
|
+
if event.type == QoniEventTypes.PROGRESS:
|
|
166
|
+
print("progress:", event.data)
|
|
167
|
+
elif event.type == QoniEventTypes.MESSAGE:
|
|
168
|
+
print(f"[{event.data['role']}] {event.data['text']}")
|
|
169
|
+
elif event.type == QoniEventTypes.SCREENSHOT:
|
|
170
|
+
open(f"step-{event.data.get('step', 0)}.png", "wb").write(event.image.data)
|
|
171
|
+
elif event.type == QoniEventTypes.DONE:
|
|
172
|
+
print("done:", event.data["terminal_reason"])
|
|
173
|
+
|
|
174
|
+
# Or drive the run to a settled result in one call:
|
|
175
|
+
result = run.wait(timeout=600)
|
|
176
|
+
print(result.status, result.output)
|
|
177
|
+
|
|
178
|
+
# Reuse the same browser session for a follow-up run:
|
|
179
|
+
follow_up = qoni.do_anything.run(
|
|
180
|
+
token=token,
|
|
181
|
+
prompt="Now open the History section and summarize it.",
|
|
182
|
+
session=run.session_ref,
|
|
183
|
+
)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Reconnect to a run later from anywhere:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
run = qoni.do_anything.attach(run_id, token=token)
|
|
190
|
+
print(run.status().status)
|
|
191
|
+
run.cancel("no longer needed") # idempotent — terminal runs return their state
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Human-in-the-loop interactions
|
|
195
|
+
|
|
196
|
+
When a run needs the user (site login, clarification, confirmation, take-control, wait), it emits an `interaction` event carrying a typed `Interaction`. Act on it via the handle's declared-action methods:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
def on_interaction(handle, event):
|
|
200
|
+
if handle.type == "clarification":
|
|
201
|
+
print("agent asks:", handle.interaction.payload["question"])
|
|
202
|
+
handle.answer("Use the first option.")
|
|
203
|
+
elif handle.type == "site_login":
|
|
204
|
+
for site in handle.interaction.payload["sites"]:
|
|
205
|
+
print("sign in at:", site["login_url"])
|
|
206
|
+
handle.confirm_signed_in()
|
|
207
|
+
|
|
208
|
+
result = run.wait(on_interaction=on_interaction)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Calling a method the backend did not declare raises `QoniValidationError` — check `handle.can(kind)` first if unsure.
|
|
212
|
+
|
|
213
|
+
### Web Search
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
search = qoni.web_search.run(
|
|
217
|
+
token=qoni.delegate_token(user_id=user_id, products=["web_search"]).data["token"],
|
|
218
|
+
prompt=["latest Qoni SDK release", "Qoni delegation model"],
|
|
219
|
+
max_results_per_query=5,
|
|
220
|
+
)
|
|
221
|
+
result = search.wait(timeout=300)
|
|
222
|
+
print(result.output)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Deep Research
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
research = qoni.deep_research.run(
|
|
229
|
+
token=qoni.delegate_token(user_id=user_id, products=["deep_research"]).data["token"],
|
|
230
|
+
prompt="The state of server-side agent authorization in 2026",
|
|
231
|
+
depth="standard",
|
|
232
|
+
)
|
|
233
|
+
result = research.wait(timeout=3600)
|
|
234
|
+
for artifact in result.artifacts:
|
|
235
|
+
open(artifact.name or f"{artifact.id}.md", "wb").write(artifact.content())
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Track (monitors)
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
monitor = qoni.track.create(
|
|
242
|
+
token=qoni.delegate_token(user_id=user_id, products=["track"]).data["token"],
|
|
243
|
+
prompt="Watch the pricing page of example.com and alert me on changes.",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
monitor.run_now()
|
|
247
|
+
for event in monitor.events(): # resident stream — break when done observing
|
|
248
|
+
if event.type == QoniEventTypes.TRIGGERED:
|
|
249
|
+
print("change detected:", event.data)
|
|
250
|
+
break
|
|
251
|
+
|
|
252
|
+
monitor.pause()
|
|
253
|
+
monitor.refine(schedule={"kind": "interval", "interval_seconds": 3600})
|
|
254
|
+
monitor.resume()
|
|
255
|
+
print(monitor.runs(limit=10))
|
|
256
|
+
monitor.delete()
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### GUMem memory
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
gumem_token = qoni.delegate_token(
|
|
263
|
+
user_id=user_id,
|
|
264
|
+
scopes=["gumem.memory:read", "gumem.memory:write"],
|
|
265
|
+
).data["token"]
|
|
266
|
+
|
|
267
|
+
qoni.gumem.create_session(token=gumem_token, session_id="demo", title="Demo")
|
|
268
|
+
qoni.gumem.add_messages(
|
|
269
|
+
token=gumem_token,
|
|
270
|
+
session_id="demo",
|
|
271
|
+
messages=[{"role": "user", "content": "I prefer aisle seats."}],
|
|
272
|
+
)
|
|
273
|
+
context = qoni.gumem.recall(token=gumem_token, session_id="demo", query="seating preference")
|
|
274
|
+
print(context.data)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Error handling
|
|
278
|
+
|
|
279
|
+
Every failure raises a typed exception from one hierarchy:
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from qoni import (
|
|
283
|
+
QoniError, # base — code / status / request_id / trace_id / audit_id / retryable / body
|
|
284
|
+
QoniAuthError, # 401
|
|
285
|
+
QoniPermissionDeniedError, # 403 / missing scopes (message lists known scopes)
|
|
286
|
+
QoniValidationError, # 400 / 422 / local pre-validation
|
|
287
|
+
QoniRateLimitError, # 429 (retryable)
|
|
288
|
+
QoniUpstreamError, # 5xx / network (retryable)
|
|
289
|
+
QoniTimeoutError, # request or wait() timeout (retryable)
|
|
290
|
+
QoniTokenExpiredError,
|
|
291
|
+
QoniDelegationRequiredError,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
qoni.do_anything.run(token=token, prompt="...")
|
|
296
|
+
except QoniPermissionDeniedError as err:
|
|
297
|
+
print(err.code, err.status, err.request_id)
|
|
298
|
+
except QoniError as err:
|
|
299
|
+
if err.retryable:
|
|
300
|
+
... # safe to retry with backoff
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Local pre-validation fails fast with actionable messages: unknown products, malformed scopes, missing user ids, and unsupported run options are raised before any request is made.
|
|
304
|
+
|
|
305
|
+
## Scopes
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from qoni import QoniScopes, QONI_PRODUCT_SCOPES, QONI_SCOPE_BUNDLES
|
|
309
|
+
|
|
310
|
+
QoniScopes.DO_ANYTHING_MANAGE # "webagent.do_anything:manage"
|
|
311
|
+
QONI_PRODUCT_SCOPES["do_anything"] # ("webagent.do_anything:read", "webagent.do_anything:manage")
|
|
312
|
+
QONI_SCOPE_BUNDLES["GUMEM_READONLY"]
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`delegate_token(products=[...])` accepts `"do_anything"`, `"web_search"`, `"deep_research"`, `"track"` and expands each to its read + manage pair.
|
|
316
|
+
|
|
317
|
+
## Event model
|
|
318
|
+
|
|
319
|
+
`run.events()` and `run.wait(on_event=...)` deliver semantic `RunEvent` objects. Match on `event.type` against `QoniEventTypes` constants:
|
|
320
|
+
|
|
321
|
+
| type | `event.data` |
|
|
322
|
+
| --- | --- |
|
|
323
|
+
| `PROGRESS` | human-readable line (str) |
|
|
324
|
+
| `MESSAGE` | `{"text": str, "role": str}` |
|
|
325
|
+
| `INTERACTION` | typed `Interaction` (act via `run.interaction_handle(...)`) |
|
|
326
|
+
| `SCREENSHOT` | `{"page_url", "step"}` — decoded image on `event.image` |
|
|
327
|
+
| `DONE` | `{"output", "succeeded", "terminal_reason"}` (terminal) |
|
|
328
|
+
| `RESULTS_READY` | result count (int, web search) |
|
|
329
|
+
| `PHASE` / `SECTION_READY` | phase name / section title (deep research) |
|
|
330
|
+
| `MONITOR_CREATED` / `TRIGGERED` / `CHECK_COMPLETED` | monitor id / change summary / bool (track) |
|
|
331
|
+
|
|
332
|
+
All internal wire churn folds into `PROGRESS`; the original wire frame stays on `event.raw`. Dropped SSE connections reconnect automatically with `Last-Event-ID` catch-up (`sse_max_retries`, default 5).
|
|
333
|
+
|
|
334
|
+
## Notes
|
|
335
|
+
|
|
336
|
+
- The public package and API use the Qoni brand. The deployed server protocol has not migrated yet, so internal signed routes remain under `/api/v3/eak/*`, the delegation token claim remains `eak_delegation_token`, and existing `eak.*` backend error codes are surfaced unchanged. Applications should use the public Qoni API and must not construct these internal wire values themselves.
|
|
337
|
+
- The client is synchronous and thread-safe for independent calls; construct it once per process. It is also a context manager (`with Qoni(...) as qoni:`).
|
|
338
|
+
- The client honors proxy environment variables (`HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` / `NO_PROXY`) by default. Behind a local SOCKS proxy, either add your gateway to `NO_PROXY`, install `httpx[socks]`, or pass `Qoni(..., trust_env=False)` to bypass system proxies entirely.
|
|
339
|
+
|
|
340
|
+
## License
|
|
341
|
+
|
|
342
|
+
MIT
|