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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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