anva 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.
anva-0.1.0/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .npmrc
2
+ dist/
3
+ node_modules/
4
+ __pycache__/
5
+ *.egg-info/
6
+ .venv/
anva-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: anva
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for Anva — live AI avatars for your product.
5
+ Project-URL: Homepage, https://anva.ai
6
+ Project-URL: Documentation, https://anva.ai/docs
7
+ Project-URL: Source, https://github.com/C-L-2013/anva-sdk
8
+ Author-email: Anva <anva.ai.2026@gmail.com>
9
+ License: MIT
10
+ Keywords: ai,anva,avatar,voice,webrtc
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.9
17
+ Provides-Extra: ws
18
+ Requires-Dist: websockets>=12; extra == 'ws'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # anva — Python SDK
22
+
23
+ Official Python SDK for [Anva](https://anva.ai) — live AI avatars.
24
+
25
+ ```bash
26
+ pip install anva # REST client, zero dependencies
27
+ pip install "anva[ws]" # + live event stream (WebSocket)
28
+ ```
29
+
30
+ ```python
31
+ from anva import Anva
32
+
33
+ client = Anva("anva_key_...")
34
+
35
+ session = client.create_session(character_id="char_...")
36
+ print(session["embed_url"])
37
+
38
+ client.send_message(session["session_id"], "Welcome!")
39
+ client.interrupt(session["session_id"])
40
+
41
+ for event in client.events(session["session_id"]):
42
+ print(event)
43
+
44
+ chars = client.list_characters()
45
+ ```
46
+
47
+ Errors raise `anva.AnvaError` with `.status`, `.code` and `.message`.
48
+ MIT © Penguin Robotics
anva-0.1.0/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # anva — Python SDK
2
+
3
+ Official Python SDK for [Anva](https://anva.ai) — live AI avatars.
4
+
5
+ ```bash
6
+ pip install anva # REST client, zero dependencies
7
+ pip install "anva[ws]" # + live event stream (WebSocket)
8
+ ```
9
+
10
+ ```python
11
+ from anva import Anva
12
+
13
+ client = Anva("anva_key_...")
14
+
15
+ session = client.create_session(character_id="char_...")
16
+ print(session["embed_url"])
17
+
18
+ client.send_message(session["session_id"], "Welcome!")
19
+ client.interrupt(session["session_id"])
20
+
21
+ for event in client.events(session["session_id"]):
22
+ print(event)
23
+
24
+ chars = client.list_characters()
25
+ ```
26
+
27
+ Errors raise `anva.AnvaError` with `.status`, `.code` and `.message`.
28
+ MIT © Penguin Robotics
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "anva"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for Anva — live AI avatars for your product."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Anva", email = "anva.ai.2026@gmail.com" }]
13
+ keywords = ["avatar", "ai", "webrtc", "voice", "anva"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Libraries",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ ws = ["websockets>=12"]
24
+
25
+ [project.urls]
26
+ Homepage = "https://anva.ai"
27
+ Documentation = "https://anva.ai/docs"
28
+ Source = "https://github.com/C-L-2013/anva-sdk"
29
+
30
+ [tool.hatch.build.targets.wheel]
31
+ packages = ["src/anva"]
@@ -0,0 +1,17 @@
1
+ """Official Python SDK for Anva (https://anva.ai) — live AI avatars.
2
+
3
+ Quickstart:
4
+
5
+ from anva import Anva
6
+
7
+ client = Anva("anva_key_...")
8
+ session = client.create_session(character_id="char_...")
9
+ print(session["embed_url"])
10
+
11
+ for event in client.events(session["session_id"]): # pip install anva[ws]
12
+ print(event)
13
+ """
14
+ from .client import Anva, AnvaError
15
+
16
+ __all__ = ["Anva", "AnvaError"]
17
+ __version__ = "0.1.0"
@@ -0,0 +1,164 @@
1
+ """Thin, dependency-free client for the Anva REST API.
2
+
3
+ Every method mirrors one endpoint and returns the decoded JSON body.
4
+ Errors raise AnvaError carrying the API's machine-readable code.
5
+ The events stream (WebSocket) needs the optional extra: pip install anva[ws].
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import urllib.error
11
+ import urllib.parse
12
+ import urllib.request
13
+ from typing import Any, Dict, Iterator, Optional
14
+
15
+ DEFAULT_BASE_URL = "https://anva.ai"
16
+
17
+
18
+ class AnvaError(Exception):
19
+ """API error with the server's machine-readable code and HTTP status."""
20
+
21
+ def __init__(self, status: int, code: str, message: str):
22
+ super().__init__(f"{code}: {message} (HTTP {status})")
23
+ self.status = status
24
+ self.code = code
25
+ self.message = message
26
+
27
+
28
+ class Anva:
29
+ """Client for the Anva API.
30
+
31
+ Args:
32
+ api_key: an API key from the dashboard (anva_key_...).
33
+ base_url: override for self-hosted / testing setups.
34
+ timeout: per-request timeout in seconds.
35
+ """
36
+
37
+ def __init__(self, api_key: str, *, base_url: str = DEFAULT_BASE_URL,
38
+ timeout: float = 30.0):
39
+ if not api_key or not api_key.strip():
40
+ raise ValueError("api_key is required")
41
+ self.api_key = api_key.strip()
42
+ self.base_url = base_url.rstrip("/")
43
+ self.timeout = timeout
44
+
45
+ # -- sessions -----------------------------------------------------------
46
+
47
+ def create_session(self, character_id: str, *, llm_mode: Optional[str] = None,
48
+ webhook_url: Optional[str] = None,
49
+ webhook_secret: Optional[str] = None) -> Dict[str, Any]:
50
+ """Create a live session. Returns session_id, session_token,
51
+ embed_url (iframe-ready) and events_ws_url."""
52
+ body: Dict[str, Any] = {"character_id": character_id}
53
+ if llm_mode:
54
+ body["llm_mode"] = llm_mode
55
+ if webhook_url:
56
+ body["webhook_url"] = webhook_url
57
+ if webhook_secret:
58
+ body["webhook_secret"] = webhook_secret
59
+ return self._request("POST", "/api/v1/sessions", body)
60
+
61
+ def get_session(self, session_id: str) -> Dict[str, Any]:
62
+ return self._request("GET", f"/api/v1/sessions/{_esc(session_id)}")
63
+
64
+ def end_session(self, session_id: str) -> Dict[str, Any]:
65
+ return self._request("DELETE", f"/api/v1/sessions/{_esc(session_id)}")
66
+
67
+ def send_message(self, session_id: str, text: str) -> Dict[str, Any]:
68
+ """Have the avatar speak `text` to the user."""
69
+ return self._request(
70
+ "POST", f"/api/v1/sessions/{_esc(session_id)}/messages",
71
+ {"text": text})
72
+
73
+ def interrupt(self, session_id: str) -> Dict[str, Any]:
74
+ """Stop the avatar mid-sentence."""
75
+ return self._request(
76
+ "POST", f"/api/v1/sessions/{_esc(session_id)}/interrupt", {})
77
+
78
+ def trigger_action(self, session_id: str, name: str) -> Dict[str, Any]:
79
+ return self._request(
80
+ "POST", f"/api/v1/sessions/{_esc(session_id)}/actions",
81
+ {"name": name})
82
+
83
+ def events_url(self, session_id: str) -> str:
84
+ """The authenticated WebSocket URL for the session's event stream."""
85
+ ws_base = self.base_url.replace("http", "ws", 1)
86
+ return (f"{ws_base}/api/v1/sessions/{_esc(session_id)}/events"
87
+ f"?api_key={urllib.parse.quote(self.api_key)}")
88
+
89
+ def events(self, session_id: str) -> Iterator[Dict[str, Any]]:
90
+ """Yield event dicts (transcripts, state changes) as they happen.
91
+
92
+ Requires the ws extra: pip install anva[ws]
93
+ """
94
+ try:
95
+ from websockets.sync.client import connect
96
+ except ImportError as e: # pragma: no cover
97
+ raise RuntimeError(
98
+ "the events stream needs the websockets package: "
99
+ "pip install anva[ws]") from e
100
+ with connect(self.events_url(session_id)) as ws:
101
+ for raw in ws:
102
+ try:
103
+ yield json.loads(raw)
104
+ except (TypeError, ValueError):
105
+ continue
106
+
107
+ # -- characters ---------------------------------------------------------
108
+
109
+ def list_characters(self) -> Dict[str, Any]:
110
+ return self._request("GET", "/api/v1/characters")
111
+
112
+ def create_character(self, name: str, *,
113
+ visual_character_id: Optional[str] = None,
114
+ system_prompt: Optional[str] = None,
115
+ voice_id: Optional[str] = None,
116
+ language_code: Optional[str] = None) -> Dict[str, Any]:
117
+ body: Dict[str, Any] = {"name": name}
118
+ if visual_character_id:
119
+ body["visual_character_id"] = visual_character_id
120
+ if system_prompt:
121
+ body["system_prompt"] = system_prompt
122
+ if voice_id:
123
+ body["voice_id"] = voice_id
124
+ if language_code:
125
+ body["language_code"] = language_code
126
+ return self._request("POST", "/api/v1/characters", body)
127
+
128
+ def get_character(self, character_id: str) -> Dict[str, Any]:
129
+ return self._request("GET", f"/api/v1/characters/{_esc(character_id)}")
130
+
131
+ def delete_character(self, character_id: str) -> Dict[str, Any]:
132
+ return self._request("DELETE", f"/api/v1/characters/{_esc(character_id)}")
133
+
134
+ # -- plumbing -----------------------------------------------------------
135
+
136
+ def _request(self, method: str, path: str,
137
+ body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
138
+ data = json.dumps(body).encode() if body is not None else None
139
+ req = urllib.request.Request(
140
+ self.base_url + path, data=data, method=method,
141
+ headers={
142
+ "Authorization": f"Bearer {self.api_key}",
143
+ "Content-Type": "application/json",
144
+ "User-Agent": "anva-python/0.1.0",
145
+ })
146
+ try:
147
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
148
+ raw = resp.read()
149
+ return json.loads(raw) if raw else {}
150
+ except urllib.error.HTTPError as e:
151
+ raw = e.read()
152
+ code, message = "request_failed", raw.decode(errors="replace")[:300]
153
+ try:
154
+ payload = json.loads(raw)
155
+ err = payload.get("error") or payload
156
+ code = err.get("code", code)
157
+ message = err.get("message", message)
158
+ except (TypeError, ValueError):
159
+ pass
160
+ raise AnvaError(e.code, code, message) from None
161
+
162
+
163
+ def _esc(part: str) -> str:
164
+ return urllib.parse.quote(str(part), safe="")