vedika-agent-sdk 0.2.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.
- vedika_agent_sdk-0.2.0/LICENSE +17 -0
- vedika_agent_sdk-0.2.0/PKG-INFO +161 -0
- vedika_agent_sdk-0.2.0/README.md +133 -0
- vedika_agent_sdk-0.2.0/pyproject.toml +45 -0
- vedika_agent_sdk-0.2.0/setup.cfg +4 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent/__init__.py +65 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent/_min_sdk.py +138 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent/client.py +401 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent/errors.py +155 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent/py.typed +0 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent/stream.py +391 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent/types.py +230 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent_sdk.egg-info/PKG-INFO +161 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent_sdk.egg-info/SOURCES.txt +19 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent_sdk.egg-info/dependency_links.txt +1 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent_sdk.egg-info/requires.txt +5 -0
- vedika_agent_sdk-0.2.0/src/vedika_agent_sdk.egg-info/top_level.txt +1 -0
- vedika_agent_sdk-0.2.0/tests/test_client.py +403 -0
- vedika_agent_sdk-0.2.0/tests/test_http_transport.py +111 -0
- vedika_agent_sdk-0.2.0/tests/test_min_sdk.py +324 -0
- vedika_agent_sdk-0.2.0/tests/test_stream.py +95 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 XALEN Technology Private Limited
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vedika-agent-sdk
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python client for the Vedika agent harness.
|
|
5
|
+
Author: XALEN Technology Private Limited
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/vedika-io/vedika-kit
|
|
8
|
+
Project-URL: Source, https://github.com/vedika-io/vedika-kit/tree/main/packages/agent-sdk-py
|
|
9
|
+
Project-URL: Issues, https://github.com/vedika-io/vedika-kit/issues
|
|
10
|
+
Keywords: vedika,agent,sdk,streaming,ui-message-stream
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: httpx<1,>=0.27
|
|
24
|
+
Provides-Extra: test
|
|
25
|
+
Requires-Dist: pytest>=7.4; extra == "test"
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# vedika-agent-sdk
|
|
30
|
+
|
|
31
|
+
Python client for the Vedika agent harness. Streams turns over the UI Message
|
|
32
|
+
Stream v1 wire, resumes with `Last-Event-ID`, and surfaces typed errors.
|
|
33
|
+
|
|
34
|
+
This client is **server-side only**: it authenticates with your tenant key
|
|
35
|
+
(`vk_live_...` or `vk_ent_...`), which must never reach a browser or app.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install vedika-agent-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Python 3.10 or newer. `httpx` is installed with it and backs the default
|
|
44
|
+
transport.
|
|
45
|
+
|
|
46
|
+
## Quickstart (10 lines)
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import asyncio
|
|
50
|
+
from vedika_agent import create_agent
|
|
51
|
+
|
|
52
|
+
async def main():
|
|
53
|
+
agent = create_agent("https://harness.example.com", api_key="...")
|
|
54
|
+
thread = await agent.threads.create()
|
|
55
|
+
turn = await agent.threads.send_turn(thread.id, {"kind": "text", "text": "Show my kundli for 1990-06-15."})
|
|
56
|
+
async for chunk in turn:
|
|
57
|
+
if chunk.type == "text-delta":
|
|
58
|
+
print(chunk.delta, end="")
|
|
59
|
+
if chunk.type == "data-vedika-artifact" and chunk.data.kind == "kundli":
|
|
60
|
+
print("kundli ready:", chunk.data.id)
|
|
61
|
+
await turn.done()
|
|
62
|
+
|
|
63
|
+
asyncio.run(main())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Exchanging your key for a browser token
|
|
67
|
+
|
|
68
|
+
A browser gets a short-lived `vt_` token from your backend, never your key.
|
|
69
|
+
Register the page's exact origin first (`PUT /api/v1/admin/embed/origins`, with an admin session token, not your
|
|
70
|
+
tenant key).
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import os
|
|
74
|
+
import httpx
|
|
75
|
+
|
|
76
|
+
# Your backend only. Never run this in a browser.
|
|
77
|
+
response = httpx.post(
|
|
78
|
+
f"{os.environ['VEDIKA_HARNESS_URL']}/api/v1/embed/tokens",
|
|
79
|
+
headers={"authorization": f"Bearer {os.environ['VEDIKA_TENANT_KEY']}"},
|
|
80
|
+
json={"end_user": "visitor:abc", "origin": "https://shop.example", "ttl_s": 600},
|
|
81
|
+
)
|
|
82
|
+
response.raise_for_status() # 201
|
|
83
|
+
minted = response.json() # token, tenant_id, expires_in_seconds
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The token lives at most 15 minutes. To put the chat on a page, see
|
|
87
|
+
`mountWidget` in `@vedika-io/embed-loader`.
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
### `create_agent(base_url, api_key, *, default_headers=None, transport=None)`
|
|
92
|
+
|
|
93
|
+
Returns a `VedikaAgent` with a `.threads` namespace.
|
|
94
|
+
|
|
95
|
+
- `await agent.threads.create(opts=None)` — `POST /api/v1/agent/threads`. Returns a `Thread`.
|
|
96
|
+
- `await agent.threads.send_turn(thread_id, input, opts=None)` — `POST /threads/{id}/turns` with `Accept: text/event-stream`. Returns a `Turn` you can iterate.
|
|
97
|
+
- `await agent.threads.resume_turn(turn_id, *, last_event_id)` — `GET /turns/{id}/events` with `Last-Event-ID`.
|
|
98
|
+
- `await agent.threads.cancel_turn(turn_id)` — `POST /turns/{id}/cancel`.
|
|
99
|
+
|
|
100
|
+
### `Turn`
|
|
101
|
+
|
|
102
|
+
- `async for chunk in turn` — async iterator over every chunk.
|
|
103
|
+
- `await turn.done()` — resolves on `finish` / `abort`, raises on `error`.
|
|
104
|
+
- `await turn.cancel()` — calls the cancel endpoint.
|
|
105
|
+
- `await turn.message_id()` — resolves to the `start.messageId` when seen.
|
|
106
|
+
|
|
107
|
+
### Chunk allowlist
|
|
108
|
+
|
|
109
|
+
`start`, `start-step`, `finish-step`, `reset-step`, `text-start` / `-delta` /
|
|
110
|
+
`-end`, `data-vedika-artifact`, `data-vedika-stage`, `data-vedika-tool-call`,
|
|
111
|
+
`data-vedika-tool-result`, `message-metadata`, `source-document`, `finish`,
|
|
112
|
+
`abort`, `error`. Anything else raises `ProtocolError`.
|
|
113
|
+
|
|
114
|
+
### Errors
|
|
115
|
+
|
|
116
|
+
`VedikaAgentError`, `AuthenticationError`, `NotFoundError`, `RateLimitError`
|
|
117
|
+
(with `retry_after_seconds`), `InvalidRequestError`, `ServerError`,
|
|
118
|
+
`TurnCancelledError`, `StreamError`, `ProtocolError`. Use
|
|
119
|
+
`is_vedika_agent_error(e)` to discriminate.
|
|
120
|
+
|
|
121
|
+
## Memory toggles
|
|
122
|
+
|
|
123
|
+
Pass `memory=MemoryToggles(old_memory=..., sensitive_info=...)` to
|
|
124
|
+
`send_turn`. They are sent as `x-vedika-memory-old` and
|
|
125
|
+
`x-vedika-memory-sensitive` request headers.
|
|
126
|
+
|
|
127
|
+
## Idempotency
|
|
128
|
+
|
|
129
|
+
Every `create` and `send_turn` carries an `Idempotency-Key` header. A fresh
|
|
130
|
+
UUID v4 is generated when you don't pass one; pass your own to retry safely.
|
|
131
|
+
|
|
132
|
+
## Version warnings
|
|
133
|
+
|
|
134
|
+
The server can tell the SDK that it is older than the minimum supported
|
|
135
|
+
version (the `X-Harness-Min-Sdk` response header). When that happens the SDK
|
|
136
|
+
emits one `UserWarning` per process through the standard `warnings` module,
|
|
137
|
+
naming the version to upgrade to, and keeps working. Nothing breaks: calls,
|
|
138
|
+
retries and errors behave exactly as before.
|
|
139
|
+
|
|
140
|
+
## Testing
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install -e ".[test]"
|
|
144
|
+
pytest
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Most tests inject a `FakeTransport` that speaks the documented wire. A few
|
|
148
|
+
run the default `httpx` transport against a local server to prove that a turn
|
|
149
|
+
streams chunk by chunk.
|
|
150
|
+
|
|
151
|
+
## Changelog
|
|
152
|
+
|
|
153
|
+
- **0.2.0**: fixes the default transport, which failed with `httpx.StreamConsumed`
|
|
154
|
+
and buffered the whole turn before yielding anything. `httpx` is now a declared
|
|
155
|
+
dependency (0.1.0 declared none, so the default transport could not run after
|
|
156
|
+
`pip install`). License metadata uses an SPDX expression.
|
|
157
|
+
- **0.1.0**: first version.
|
|
158
|
+
|
|
159
|
+
## Licence
|
|
160
|
+
|
|
161
|
+
Apache-2.0. See `LICENSE`.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# vedika-agent-sdk
|
|
2
|
+
|
|
3
|
+
Python client for the Vedika agent harness. Streams turns over the UI Message
|
|
4
|
+
Stream v1 wire, resumes with `Last-Event-ID`, and surfaces typed errors.
|
|
5
|
+
|
|
6
|
+
This client is **server-side only**: it authenticates with your tenant key
|
|
7
|
+
(`vk_live_...` or `vk_ent_...`), which must never reach a browser or app.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install vedika-agent-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Python 3.10 or newer. `httpx` is installed with it and backs the default
|
|
16
|
+
transport.
|
|
17
|
+
|
|
18
|
+
## Quickstart (10 lines)
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import asyncio
|
|
22
|
+
from vedika_agent import create_agent
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
agent = create_agent("https://harness.example.com", api_key="...")
|
|
26
|
+
thread = await agent.threads.create()
|
|
27
|
+
turn = await agent.threads.send_turn(thread.id, {"kind": "text", "text": "Show my kundli for 1990-06-15."})
|
|
28
|
+
async for chunk in turn:
|
|
29
|
+
if chunk.type == "text-delta":
|
|
30
|
+
print(chunk.delta, end="")
|
|
31
|
+
if chunk.type == "data-vedika-artifact" and chunk.data.kind == "kundli":
|
|
32
|
+
print("kundli ready:", chunk.data.id)
|
|
33
|
+
await turn.done()
|
|
34
|
+
|
|
35
|
+
asyncio.run(main())
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Exchanging your key for a browser token
|
|
39
|
+
|
|
40
|
+
A browser gets a short-lived `vt_` token from your backend, never your key.
|
|
41
|
+
Register the page's exact origin first (`PUT /api/v1/admin/embed/origins`, with an admin session token, not your
|
|
42
|
+
tenant key).
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import os
|
|
46
|
+
import httpx
|
|
47
|
+
|
|
48
|
+
# Your backend only. Never run this in a browser.
|
|
49
|
+
response = httpx.post(
|
|
50
|
+
f"{os.environ['VEDIKA_HARNESS_URL']}/api/v1/embed/tokens",
|
|
51
|
+
headers={"authorization": f"Bearer {os.environ['VEDIKA_TENANT_KEY']}"},
|
|
52
|
+
json={"end_user": "visitor:abc", "origin": "https://shop.example", "ttl_s": 600},
|
|
53
|
+
)
|
|
54
|
+
response.raise_for_status() # 201
|
|
55
|
+
minted = response.json() # token, tenant_id, expires_in_seconds
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The token lives at most 15 minutes. To put the chat on a page, see
|
|
59
|
+
`mountWidget` in `@vedika-io/embed-loader`.
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
### `create_agent(base_url, api_key, *, default_headers=None, transport=None)`
|
|
64
|
+
|
|
65
|
+
Returns a `VedikaAgent` with a `.threads` namespace.
|
|
66
|
+
|
|
67
|
+
- `await agent.threads.create(opts=None)` — `POST /api/v1/agent/threads`. Returns a `Thread`.
|
|
68
|
+
- `await agent.threads.send_turn(thread_id, input, opts=None)` — `POST /threads/{id}/turns` with `Accept: text/event-stream`. Returns a `Turn` you can iterate.
|
|
69
|
+
- `await agent.threads.resume_turn(turn_id, *, last_event_id)` — `GET /turns/{id}/events` with `Last-Event-ID`.
|
|
70
|
+
- `await agent.threads.cancel_turn(turn_id)` — `POST /turns/{id}/cancel`.
|
|
71
|
+
|
|
72
|
+
### `Turn`
|
|
73
|
+
|
|
74
|
+
- `async for chunk in turn` — async iterator over every chunk.
|
|
75
|
+
- `await turn.done()` — resolves on `finish` / `abort`, raises on `error`.
|
|
76
|
+
- `await turn.cancel()` — calls the cancel endpoint.
|
|
77
|
+
- `await turn.message_id()` — resolves to the `start.messageId` when seen.
|
|
78
|
+
|
|
79
|
+
### Chunk allowlist
|
|
80
|
+
|
|
81
|
+
`start`, `start-step`, `finish-step`, `reset-step`, `text-start` / `-delta` /
|
|
82
|
+
`-end`, `data-vedika-artifact`, `data-vedika-stage`, `data-vedika-tool-call`,
|
|
83
|
+
`data-vedika-tool-result`, `message-metadata`, `source-document`, `finish`,
|
|
84
|
+
`abort`, `error`. Anything else raises `ProtocolError`.
|
|
85
|
+
|
|
86
|
+
### Errors
|
|
87
|
+
|
|
88
|
+
`VedikaAgentError`, `AuthenticationError`, `NotFoundError`, `RateLimitError`
|
|
89
|
+
(with `retry_after_seconds`), `InvalidRequestError`, `ServerError`,
|
|
90
|
+
`TurnCancelledError`, `StreamError`, `ProtocolError`. Use
|
|
91
|
+
`is_vedika_agent_error(e)` to discriminate.
|
|
92
|
+
|
|
93
|
+
## Memory toggles
|
|
94
|
+
|
|
95
|
+
Pass `memory=MemoryToggles(old_memory=..., sensitive_info=...)` to
|
|
96
|
+
`send_turn`. They are sent as `x-vedika-memory-old` and
|
|
97
|
+
`x-vedika-memory-sensitive` request headers.
|
|
98
|
+
|
|
99
|
+
## Idempotency
|
|
100
|
+
|
|
101
|
+
Every `create` and `send_turn` carries an `Idempotency-Key` header. A fresh
|
|
102
|
+
UUID v4 is generated when you don't pass one; pass your own to retry safely.
|
|
103
|
+
|
|
104
|
+
## Version warnings
|
|
105
|
+
|
|
106
|
+
The server can tell the SDK that it is older than the minimum supported
|
|
107
|
+
version (the `X-Harness-Min-Sdk` response header). When that happens the SDK
|
|
108
|
+
emits one `UserWarning` per process through the standard `warnings` module,
|
|
109
|
+
naming the version to upgrade to, and keeps working. Nothing breaks: calls,
|
|
110
|
+
retries and errors behave exactly as before.
|
|
111
|
+
|
|
112
|
+
## Testing
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install -e ".[test]"
|
|
116
|
+
pytest
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Most tests inject a `FakeTransport` that speaks the documented wire. A few
|
|
120
|
+
run the default `httpx` transport against a local server to prove that a turn
|
|
121
|
+
streams chunk by chunk.
|
|
122
|
+
|
|
123
|
+
## Changelog
|
|
124
|
+
|
|
125
|
+
- **0.2.0**: fixes the default transport, which failed with `httpx.StreamConsumed`
|
|
126
|
+
and buffered the whole turn before yielding anything. `httpx` is now a declared
|
|
127
|
+
dependency (0.1.0 declared none, so the default transport could not run after
|
|
128
|
+
`pip install`). License metadata uses an SPDX expression.
|
|
129
|
+
- **0.1.0**: first version.
|
|
130
|
+
|
|
131
|
+
## Licence
|
|
132
|
+
|
|
133
|
+
Apache-2.0. See `LICENSE`.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vedika-agent-sdk"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Python client for the Vedika agent harness."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "XALEN Technology Private Limited" }]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
]
|
|
25
|
+
keywords = ["vedika", "agent", "sdk", "streaming", "ui-message-stream"]
|
|
26
|
+
dependencies = ["httpx>=0.27,<1"]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/vedika-io/vedika-kit"
|
|
30
|
+
Source = "https://github.com/vedika-io/vedika-kit/tree/main/packages/agent-sdk-py"
|
|
31
|
+
Issues = "https://github.com/vedika-io/vedika-kit/issues"
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
test = ["pytest>=7.4", "pytest-asyncio>=0.23"]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.package-data]
|
|
40
|
+
"vedika_agent" = ["py.typed"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
addopts = "-ra"
|
|
45
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Public entry point for the Vedika agent SDK."""
|
|
2
|
+
|
|
3
|
+
from .client import Transport, VedikaAgent, create_agent
|
|
4
|
+
from .errors import (
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
InvalidRequestError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
ProtocolError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
ServerError,
|
|
11
|
+
StreamError,
|
|
12
|
+
VedikaAgentError,
|
|
13
|
+
is_vedika_agent_error,
|
|
14
|
+
)
|
|
15
|
+
from .stream import (
|
|
16
|
+
Turn,
|
|
17
|
+
format_last_event_id,
|
|
18
|
+
parse_chunk,
|
|
19
|
+
parse_sse_chunk,
|
|
20
|
+
UI_MESSAGE_STREAM_HEADER,
|
|
21
|
+
)
|
|
22
|
+
from .types import (
|
|
23
|
+
Artifact,
|
|
24
|
+
CancelTurnOptions,
|
|
25
|
+
CreateThreadOptions,
|
|
26
|
+
Json,
|
|
27
|
+
MemoryToggles,
|
|
28
|
+
ResumeTurnOptions,
|
|
29
|
+
SendTurnOptions,
|
|
30
|
+
Thread,
|
|
31
|
+
TurnInput,
|
|
32
|
+
TurnOutcome,
|
|
33
|
+
UIMessageStreamChunk,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"VedikaAgent",
|
|
38
|
+
"create_agent",
|
|
39
|
+
"Transport",
|
|
40
|
+
"VedikaAgentError",
|
|
41
|
+
"AuthenticationError",
|
|
42
|
+
"NotFoundError",
|
|
43
|
+
"RateLimitError",
|
|
44
|
+
"InvalidRequestError",
|
|
45
|
+
"ServerError",
|
|
46
|
+
"StreamError",
|
|
47
|
+
"ProtocolError",
|
|
48
|
+
"is_vedika_agent_error",
|
|
49
|
+
"parse_chunk",
|
|
50
|
+
"parse_sse_chunk",
|
|
51
|
+
"format_last_event_id",
|
|
52
|
+
"UI_MESSAGE_STREAM_HEADER",
|
|
53
|
+
"Artifact",
|
|
54
|
+
"CancelTurnOptions",
|
|
55
|
+
"CreateThreadOptions",
|
|
56
|
+
"Json",
|
|
57
|
+
"MemoryToggles",
|
|
58
|
+
"ResumeTurnOptions",
|
|
59
|
+
"SendTurnOptions",
|
|
60
|
+
"Thread",
|
|
61
|
+
"Turn",
|
|
62
|
+
"TurnInput",
|
|
63
|
+
"TurnOutcome",
|
|
64
|
+
"UIMessageStreamChunk",
|
|
65
|
+
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""The "server says your SDK is too old" warning.
|
|
2
|
+
|
|
3
|
+
Every harness response may carry ``X-Harness-Min-Sdk: <major.minor.patch>``.
|
|
4
|
+
When this SDK's own version is lower, one ``SdkVersionWarning`` is emitted per
|
|
5
|
+
process and the SDK keeps working. The check only ever warns: it never raises,
|
|
6
|
+
never delays a call and never changes retry or error behaviour.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import functools
|
|
12
|
+
import re
|
|
13
|
+
import threading
|
|
14
|
+
import warnings
|
|
15
|
+
from typing import Any, Mapping, Optional, Tuple
|
|
16
|
+
|
|
17
|
+
#: Response header (lower-case; header names are case-insensitive).
|
|
18
|
+
MIN_SDK_HEADER = "x-harness-min-sdk"
|
|
19
|
+
|
|
20
|
+
#: Distribution name from ``pyproject.toml``.
|
|
21
|
+
PACKAGE_NAME = "vedika-agent-sdk"
|
|
22
|
+
|
|
23
|
+
# Three dot-separated groups of 1 to 9 ASCII digits. Written as [0-9], not \d,
|
|
24
|
+
# because \d also matches non-ASCII digits in Python.
|
|
25
|
+
_VERSION_CORE = re.compile(r"[0-9]{1,9}\.[0-9]{1,9}\.[0-9]{1,9}")
|
|
26
|
+
|
|
27
|
+
Version = Tuple[int, int, int]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SdkVersionWarning(UserWarning):
|
|
31
|
+
"""This SDK is older than the minimum version the server supports."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_version(value: Any) -> Optional[Version]:
|
|
35
|
+
"""Parse ``major.minor.patch``, ignoring a prerelease or build suffix.
|
|
36
|
+
|
|
37
|
+
Returns ``None`` for anything that is not exactly three dot-separated
|
|
38
|
+
digit groups of at most nine digits each.
|
|
39
|
+
"""
|
|
40
|
+
if not isinstance(value, str):
|
|
41
|
+
return None
|
|
42
|
+
core = re.split(r"[-+]", value.strip(), maxsplit=1)[0]
|
|
43
|
+
if _VERSION_CORE.fullmatch(core) is None:
|
|
44
|
+
return None
|
|
45
|
+
major, minor, patch = core.split(".")
|
|
46
|
+
return int(major), int(minor), int(patch)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def compare_versions(own: Any, minimum: Any) -> Optional[int]:
|
|
50
|
+
"""Compare two versions numerically.
|
|
51
|
+
|
|
52
|
+
Returns -1 when ``own`` is lower than ``minimum``, 0 when equal, 1 when
|
|
53
|
+
higher, and ``None`` when either side cannot be compared.
|
|
54
|
+
"""
|
|
55
|
+
a = parse_version(own)
|
|
56
|
+
b = parse_version(minimum)
|
|
57
|
+
if a is None or b is None:
|
|
58
|
+
return None
|
|
59
|
+
return (a > b) - (a < b)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def warning_message(package_name: str, own_version: str, min_version: str) -> str:
|
|
63
|
+
return (
|
|
64
|
+
f"{package_name} {own_version} is older than the minimum supported version "
|
|
65
|
+
f"{min_version}. Upgrade to {min_version} or later."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@functools.lru_cache(maxsize=1)
|
|
70
|
+
def _own_version() -> Optional[str]:
|
|
71
|
+
try:
|
|
72
|
+
from importlib.metadata import version
|
|
73
|
+
|
|
74
|
+
return version(PACKAGE_NAME)
|
|
75
|
+
except Exception:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _header_value(headers: Optional[Mapping[str, Any]]) -> Optional[str]:
|
|
80
|
+
if not headers:
|
|
81
|
+
return None
|
|
82
|
+
value = headers.get(MIN_SDK_HEADER)
|
|
83
|
+
if value is None:
|
|
84
|
+
for key, candidate in headers.items():
|
|
85
|
+
if isinstance(key, str) and key.lower() == MIN_SDK_HEADER:
|
|
86
|
+
value = candidate
|
|
87
|
+
break
|
|
88
|
+
if isinstance(value, bytes):
|
|
89
|
+
value = value.decode("ascii", errors="replace")
|
|
90
|
+
return value if isinstance(value, str) else None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
_lock = threading.Lock()
|
|
94
|
+
_warned = False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _reset_min_sdk_warning_for_tests() -> None:
|
|
98
|
+
"""Test-only: allow the once-per-process warning to fire again."""
|
|
99
|
+
global _warned
|
|
100
|
+
with _lock:
|
|
101
|
+
_warned = False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def check_min_sdk_header(
|
|
105
|
+
headers: Optional[Mapping[str, Any]],
|
|
106
|
+
*,
|
|
107
|
+
own_version: Optional[str] = None,
|
|
108
|
+
stacklevel: int = 4,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Warn once per process when the server's minimum SDK is above ours.
|
|
111
|
+
|
|
112
|
+
``stacklevel`` counts from this function: 2 is the caller, so the default
|
|
113
|
+
of 4 points at the code that called ``agent.threads.<method>()``.
|
|
114
|
+
Swallows every failure, including a warnings filter set to ``error``.
|
|
115
|
+
"""
|
|
116
|
+
global _warned
|
|
117
|
+
if _warned:
|
|
118
|
+
return
|
|
119
|
+
try:
|
|
120
|
+
minimum = parse_version(_header_value(headers))
|
|
121
|
+
if minimum is None:
|
|
122
|
+
return
|
|
123
|
+
own_text = own_version if own_version is not None else _own_version()
|
|
124
|
+
own = parse_version(own_text)
|
|
125
|
+
if own is None or own >= minimum:
|
|
126
|
+
return
|
|
127
|
+
with _lock:
|
|
128
|
+
if _warned:
|
|
129
|
+
return
|
|
130
|
+
_warned = True
|
|
131
|
+
warnings.warn(
|
|
132
|
+
warning_message(PACKAGE_NAME, str(own_text), ".".join(map(str, minimum))),
|
|
133
|
+
SdkVersionWarning,
|
|
134
|
+
stacklevel=stacklevel,
|
|
135
|
+
)
|
|
136
|
+
except Exception:
|
|
137
|
+
# The check must never affect the call that triggered it.
|
|
138
|
+
pass
|