roviers 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.
@@ -0,0 +1,6 @@
1
+ /target
2
+ /roviers_data
3
+ *.log
4
+ citizen_id.json
5
+ alice_id.json
6
+
roviers-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.5
2
+ Name: roviers
3
+ Version: 0.1.0
4
+ Summary: Python client SDK for Roviers: High-Throughput Service Intake & Claim-Check Vault
5
+ Author: Sunil K Sundaram
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.9
8
+ Requires-Dist: httpx>=0.25.0
9
+ Provides-Extra: agents
10
+ Requires-Dist: langchain-core>=0.2.0; extra == 'agents'
11
+ Description-Content-Type: text/markdown
12
+
13
+ # Roviers Python SDK 🤖
14
+
15
+ Broker & Agent Asynchronous Task & Claim-Check Vault Client for AI Agents, LLM Orchestrators (LangGraph, CrewAI, AutoGen), and distributed microservices.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install ./clients/python
21
+ # Or in editable mode:
22
+ pip install -e ./clients/python
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```python
28
+ import asyncio
29
+ from roviers import RoviersBrokerClient
30
+
31
+ async def main():
32
+ # 1. Connect to Roviers Broker
33
+ broker = RoviersBrokerClient(base_url="http://127.0.0.1:8080")
34
+
35
+ # 2. Issue or load Agent Token
36
+ await broker.issue_agent_token(
37
+ agent_id="AGENT-RESEARCH-01",
38
+ agent_name="Autonomous Research Agent",
39
+ save_path="agent_token.json"
40
+ )
41
+
42
+ # 3. Submit heavy asynchronous request (returns in ~2ms with Receipt Token)
43
+ receipt = await broker.submit(
44
+ service_name="service.finance.risk_model",
45
+ payload={"portfolio_id": "port_9921", "simulations": 10000}
46
+ )
47
+ print(f"Receipt received: #{receipt.receipt_id} (Status: {receipt.status})")
48
+
49
+ # 4. Wait for real-time SSE signal and redeem outcome from Broker Vault
50
+ outcome = await broker.wait_for_outcome(receipt.receipt_id)
51
+ print("Vault Outcome:", outcome.result)
52
+
53
+ if __name__ == "__main__":
54
+ asyncio.run(main())
55
+ ```
56
+
57
+ ## LangGraph / CrewAI Agent Tool Integration
58
+
59
+ ```python
60
+ from roviers import RoviersBrokerClient, RoviersTool
61
+
62
+ broker = RoviersBrokerClient(base_url="http://127.0.0.1:8080", token="agent_token.json")
63
+
64
+ # Expose as a non-blocking agent tool
65
+ deep_research_tool = RoviersTool(
66
+ broker=broker,
67
+ service_name="service.ai.deep_research",
68
+ name="deep_research",
69
+ description="Runs deep multi-source research asynchronously without blocking agent loops"
70
+ )
71
+ ```
@@ -0,0 +1,59 @@
1
+ # Roviers Python SDK 🤖
2
+
3
+ Broker & Agent Asynchronous Task & Claim-Check Vault Client for AI Agents, LLM Orchestrators (LangGraph, CrewAI, AutoGen), and distributed microservices.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install ./clients/python
9
+ # Or in editable mode:
10
+ pip install -e ./clients/python
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ import asyncio
17
+ from roviers import RoviersBrokerClient
18
+
19
+ async def main():
20
+ # 1. Connect to Roviers Broker
21
+ broker = RoviersBrokerClient(base_url="http://127.0.0.1:8080")
22
+
23
+ # 2. Issue or load Agent Token
24
+ await broker.issue_agent_token(
25
+ agent_id="AGENT-RESEARCH-01",
26
+ agent_name="Autonomous Research Agent",
27
+ save_path="agent_token.json"
28
+ )
29
+
30
+ # 3. Submit heavy asynchronous request (returns in ~2ms with Receipt Token)
31
+ receipt = await broker.submit(
32
+ service_name="service.finance.risk_model",
33
+ payload={"portfolio_id": "port_9921", "simulations": 10000}
34
+ )
35
+ print(f"Receipt received: #{receipt.receipt_id} (Status: {receipt.status})")
36
+
37
+ # 4. Wait for real-time SSE signal and redeem outcome from Broker Vault
38
+ outcome = await broker.wait_for_outcome(receipt.receipt_id)
39
+ print("Vault Outcome:", outcome.result)
40
+
41
+ if __name__ == "__main__":
42
+ asyncio.run(main())
43
+ ```
44
+
45
+ ## LangGraph / CrewAI Agent Tool Integration
46
+
47
+ ```python
48
+ from roviers import RoviersBrokerClient, RoviersTool
49
+
50
+ broker = RoviersBrokerClient(base_url="http://127.0.0.1:8080", token="agent_token.json")
51
+
52
+ # Expose as a non-blocking agent tool
53
+ deep_research_tool = RoviersTool(
54
+ broker=broker,
55
+ service_name="service.ai.deep_research",
56
+ name="deep_research",
57
+ description="Runs deep multi-source research asynchronously without blocking agent loops"
58
+ )
59
+ ```
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "roviers"
7
+ version = "0.1.0"
8
+ description = "Python client SDK for Roviers: High-Throughput Service Intake & Claim-Check Vault"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Sunil K Sundaram" }
14
+ ]
15
+ dependencies = [
16
+ "httpx>=0.25.0",
17
+ ]
18
+
19
+ [project.scripts]
20
+ roviers = "roviers.cli:main"
21
+ roviers-server = "roviers.installer:run_server"
22
+
23
+ [project.optional-dependencies]
24
+ agents = [
25
+ "langchain-core>=0.2.0",
26
+ ]
27
+
@@ -0,0 +1,15 @@
1
+ """
2
+ Roviers Python SDK - Broker & Agent Asynchronous Task & Claim-Check Vault Engine.
3
+ Optimized for Autonomous AI Agents, LLM Orchestrators (LangGraph, CrewAI, AutoGen), and distributed workers.
4
+ """
5
+
6
+ from .client import RoviersBrokerClient, RoviersClient, RoviersReceipt, RoviersOutcome, RoviersTool
7
+
8
+ __all__ = [
9
+ "RoviersBrokerClient",
10
+ "RoviersClient",
11
+ "RoviersReceipt",
12
+ "RoviersOutcome",
13
+ "RoviersTool",
14
+ ]
15
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
5
+
@@ -0,0 +1,28 @@
1
+ """
2
+ Roviers Python CLI Entrypoints
3
+ """
4
+
5
+ import sys
6
+ from .installer import install_roviers, run_server, get_binary_path
7
+
8
+
9
+ def main() -> None:
10
+ if len(sys.argv) > 1 and sys.argv[1] == "install":
11
+ version = sys.argv[2] if len(sys.argv) > 2 else "latest"
12
+ install_roviers(version=version)
13
+ elif len(sys.argv) > 1 and sys.argv[1] == "server":
14
+ run_server(sys.argv[2:])
15
+ elif len(sys.argv) > 1 and sys.argv[1] == "which":
16
+ path = get_binary_path("roviers-server")
17
+ print(f"roviers-server binary: {path} (exists: {path.is_file()})")
18
+ else:
19
+ print("Roviers CLI & Server Manager (Python)")
20
+ print("\nCommands:")
21
+ print(" roviers install [version] Download & install native binaries")
22
+ print(" roviers server [args...] Start native roviers-server")
23
+ print(" roviers which Display installed binary paths")
24
+
25
+
26
+ if __name__ == "__main__":
27
+ main()
28
+
@@ -0,0 +1,250 @@
1
+ """
2
+ Roviers Python Client Library
3
+ Broker & Agent Paradigm for Asynchronous Service Intake & Claim-Check Vault Engine
4
+ """
5
+
6
+ from __future__ import annotations
7
+ import asyncio
8
+ import json
9
+ from dataclasses import dataclass
10
+ from typing import Any, AsyncGenerator, Dict, Optional, Union
11
+ import httpx
12
+
13
+
14
+ @dataclass
15
+ class RoviersReceipt:
16
+ receipt_id: str
17
+ agent_id: str
18
+ service_name: str
19
+ issued_at: int
20
+ status: str
21
+ estimated_ready_secs: Optional[int] = None
22
+ callback_url: Optional[str] = None
23
+
24
+ @classmethod
25
+ def from_dict(cls, data: Dict[str, Any]) -> RoviersReceipt:
26
+ return cls(
27
+ receipt_id=str(data["receipt_id"]),
28
+ agent_id=data.get("agent_id") or data.get("citizen_id") or data.get("client_id", ""),
29
+ service_name=data["service_name"],
30
+ issued_at=data["issued_at"],
31
+ status=data["status"],
32
+ estimated_ready_secs=data.get("estimated_ready_secs"),
33
+ callback_url=data.get("callback_url"),
34
+ )
35
+
36
+
37
+ @dataclass
38
+ class RoviersOutcome:
39
+ receipt_id: str
40
+ agent_id: str
41
+ service_name: str
42
+ completed_at: int
43
+ result: Any
44
+
45
+ @classmethod
46
+ def from_dict(cls, data: Dict[str, Any]) -> RoviersOutcome:
47
+ return cls(
48
+ receipt_id=str(data["receipt_id"]),
49
+ agent_id=data.get("agent_id") or data.get("citizen_id") or data.get("client_id", ""),
50
+ service_name=data["service_name"],
51
+ completed_at=data["completed_at"],
52
+ result=data["result"],
53
+ )
54
+
55
+
56
+ class RoviersBrokerClient:
57
+ """
58
+ Client for interacting with Roviers Broker ingress and Outcome Vault.
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ base_url: str = "http://127.0.0.1:8080",
64
+ token: Optional[Union[Dict[str, Any], str]] = None,
65
+ timeout: float = 10.0,
66
+ ):
67
+ self.base_url = base_url.rstrip("/")
68
+ self.timeout = timeout
69
+ self.token_data: Optional[Dict[str, Any]] = None
70
+
71
+ if isinstance(token, str):
72
+ with open(token, "r", encoding="utf-8") as f:
73
+ self.token_data = json.load(f)
74
+ elif isinstance(token, dict):
75
+ self.token_data = token
76
+
77
+ @property
78
+ def agent_id(self) -> str:
79
+ if not self.token_data:
80
+ raise ValueError("Agent token has not been loaded or configured.")
81
+ creds = self.token_data["credentials"]
82
+ return creds.get("agent_id") or creds.get("citizen_id") or creds.get("client_id", "")
83
+
84
+ async def issue_agent_token(
85
+ self,
86
+ agent_id: str,
87
+ agent_name: str = "Autonomous Agent",
88
+ validity_days: int = 365,
89
+ save_path: Optional[str] = None,
90
+ ) -> Dict[str, Any]:
91
+ """
92
+ Requests an official Ed25519-signed agent credential from the Roviers Broker.
93
+ """
94
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
95
+ resp = await client.post(
96
+ f"{self.base_url}/api/v1/auth/agent",
97
+ json={
98
+ "agent_id": agent_id,
99
+ "agent_name": agent_name,
100
+ "validity_days": validity_days,
101
+ },
102
+ )
103
+ resp.raise_for_status()
104
+ token = resp.json()
105
+
106
+ self.token_data = token
107
+ if save_path:
108
+ with open(save_path, "w", encoding="utf-8") as f:
109
+ json.dump(token, f, indent=2)
110
+
111
+ return token
112
+
113
+ # Backwards compatibility alias
114
+ issue_token = issue_agent_token
115
+
116
+ async def submit(
117
+ self,
118
+ service_name: str,
119
+ payload: Dict[str, Any],
120
+ callback_url: Optional[str] = None,
121
+ ) -> RoviersReceipt:
122
+ """
123
+ Dispatches a service request to the Broker ingress.
124
+ Returns a Claim-Check Receipt immediately (HTTP 202 Accepted).
125
+ """
126
+ if not self.token_data:
127
+ raise ValueError("Agent token must be provided before submitting requests.")
128
+
129
+ body = {
130
+ "service_name": service_name,
131
+ "agent_token": self.token_data,
132
+ "payload": payload,
133
+ "callback_url": callback_url,
134
+ }
135
+
136
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
137
+ resp = await client.post(
138
+ f"{self.base_url}/api/v1/services/{service_name}",
139
+ json=body,
140
+ )
141
+ resp.raise_for_status()
142
+ return RoviersReceipt.from_dict(resp.json())
143
+
144
+ async def get_receipt(self, receipt_id: str) -> RoviersReceipt:
145
+ """
146
+ Inspects the current state of a receipt (Queued, Processing, ReadyForPickup, Fulfilled).
147
+ """
148
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
149
+ resp = await client.get(f"{self.base_url}/api/v1/receipts/{receipt_id}")
150
+ resp.raise_for_status()
151
+ return RoviersReceipt.from_dict(resp.json())
152
+
153
+ async def pickup(self, receipt_id: str) -> Optional[RoviersOutcome]:
154
+ """
155
+ Presents the Receipt ID and cryptographic proof of agent identity to redeem the outcome from the Vault.
156
+ Returns None if outcome is still being processed (HTTP 202).
157
+ """
158
+ if not self.token_data:
159
+ raise ValueError("Token must be loaded before picking up outcomes.")
160
+
161
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
162
+ resp = await client.post(
163
+ f"{self.base_url}/api/v1/receipts/{receipt_id}/pickup",
164
+ json={"agent_token": self.token_data},
165
+ )
166
+ if resp.status_code == 202:
167
+ return None
168
+ resp.raise_for_status()
169
+ return RoviersOutcome.from_dict(resp.json())
170
+
171
+ async def complete(self, receipt_id: str, result: Any) -> None:
172
+ """
173
+ Worker capability: deposits a finished execution outcome into the Vault.
174
+ """
175
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
176
+ resp = await client.post(
177
+ f"{self.base_url}/api/v1/receipts/{receipt_id}/complete",
178
+ json={"result": result},
179
+ )
180
+ resp.raise_for_status()
181
+
182
+ async def listen_signals(
183
+ self,
184
+ timeout_secs: Optional[float] = None,
185
+ ) -> AsyncGenerator[Dict[str, Any], None]:
186
+ """
187
+ Subscribes to the real-time Server-Sent Events (SSE) signal stream.
188
+ Yields completion signals the instant any task for this agent is ready.
189
+ """
190
+ aid = self.agent_id
191
+ url = f"{self.base_url}/api/v1/agents/{aid}/stream"
192
+
193
+ timeout = httpx.Timeout(timeout_secs if timeout_secs else None, connect=10.0)
194
+ async with httpx.AsyncClient(timeout=timeout) as client:
195
+ async with client.stream("GET", url) as stream:
196
+ async for line in stream.aiter_lines():
197
+ if line.startswith("data:"):
198
+ raw = line[5:].strip()
199
+ if raw:
200
+ yield json.loads(raw)
201
+
202
+ async def wait_for_outcome(
203
+ self,
204
+ receipt_id: str,
205
+ timeout_secs: float = 60.0,
206
+ ) -> RoviersOutcome:
207
+ """
208
+ High-level helper: waits for the specific receipt outcome using zero-polling SSE signals,
209
+ then automatically redeems and returns the outcome from the Broker Vault.
210
+ """
211
+ # First check if it's already ready
212
+ immediate = await self.pickup(receipt_id)
213
+ if immediate is not None:
214
+ return immediate
215
+
216
+ loop = asyncio.get_running_loop()
217
+ end_time = loop.time() + timeout_secs
218
+
219
+ async for signal in self.listen_signals(timeout_secs=timeout_secs):
220
+ if signal.get("receipt_id") == receipt_id:
221
+ outcome = await self.pickup(receipt_id)
222
+ if outcome is not None:
223
+ return outcome
224
+
225
+ if loop.time() > end_time:
226
+ break
227
+
228
+ raise TimeoutError(f"Timed out waiting for receipt #{receipt_id} outcome.")
229
+
230
+
231
+ # Backwards compatibility alias
232
+ RoviersClient = RoviersBrokerClient
233
+
234
+
235
+ class RoviersTool:
236
+ """
237
+ Adapter for AI Agent Frameworks (LangGraph, CrewAI, AutoGen).
238
+ Wraps heavy tasks into non-blocking claim-check operations.
239
+ """
240
+
241
+ def __init__(self, broker: RoviersBrokerClient, service_name: str, name: str, description: str):
242
+ self.broker = broker
243
+ self.service_name = service_name
244
+ self.name = name
245
+ self.description = description
246
+
247
+ async def run(self, **kwargs) -> Any:
248
+ receipt = await self.broker.submit(self.service_name, kwargs)
249
+ outcome = await self.broker.wait_for_outcome(receipt.receipt_id)
250
+ return outcome.result
@@ -0,0 +1,115 @@
1
+ """
2
+ Roviers Native Binary Installer & Runner
3
+ Enables `pip install roviers` to download, manage, and launch native `roviers-server` and `roviers-cli` binaries.
4
+ """
5
+
6
+ from __future__ import annotations
7
+ import os
8
+ import platform
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ import urllib.request
13
+ from pathlib import Path
14
+
15
+ GITHUB_REPO = "sunilksundaram/roviers"
16
+ DEFAULT_INSTALL_DIR = Path.home() / ".roviers" / "bin"
17
+
18
+
19
+ def get_platform_asset_name() -> str:
20
+ system = platform.system().lower()
21
+ machine = platform.machine().lower()
22
+
23
+ if "windows" in system:
24
+ return "roviers-windows-x86_64.zip"
25
+ elif "darwin" in system:
26
+ if "arm" in machine or "aarch64" in machine:
27
+ return "roviers-macos-aarch64.tar.gz"
28
+ return "roviers-macos-x86_64.tar.gz"
29
+ elif "linux" in system:
30
+ if "arm" in machine or "aarch64" in machine:
31
+ return "roviers-linux-aarch64.tar.gz"
32
+ return "roviers-linux-x86_64.tar.gz"
33
+ else:
34
+ raise RuntimeError(f"Unsupported operating system: {system} {machine}")
35
+
36
+
37
+ def get_binary_path(name: str = "roviers-server") -> Path:
38
+ ext = ".exe" if platform.system().lower() == "windows" else ""
39
+ binary_name = f"{name}{ext}"
40
+
41
+ # 1. Check system PATH
42
+ which_path = shutil.which(binary_name)
43
+ if which_path:
44
+ return Path(which_path)
45
+
46
+ # 2. Check ~/.roviers/bin
47
+ local_path = DEFAULT_INSTALL_DIR / binary_name
48
+ if local_path.is_file() and os.access(local_path, os.X_OK):
49
+ return local_path
50
+
51
+ # 3. Check workspace target/release (if local repo build)
52
+ target_path = Path.cwd() / "target" / "release" / binary_name
53
+ if target_path.is_file():
54
+ return target_path
55
+
56
+ return local_path
57
+
58
+
59
+ def install_roviers(version: str = "latest", dest_dir: Path = DEFAULT_INSTALL_DIR) -> Path:
60
+ """
61
+ Installs the native roviers-server and roviers-cli binaries into ~/.roviers/bin
62
+ """
63
+ dest_dir.mkdir(parents=True, exist_ok=True)
64
+ asset_name = get_platform_asset_name()
65
+
66
+ if version == "latest":
67
+ url = f"https://github.com/{GITHUB_REPO}/releases/latest/download/{asset_name}"
68
+ else:
69
+ url = f"https://github.com/{GITHUB_REPO}/releases/download/{version}/{asset_name}"
70
+
71
+ archive_path = dest_dir / asset_name
72
+ print(f"📦 Downloading Roviers native binaries from {url}...")
73
+ try:
74
+ urllib.request.urlretrieve(url, archive_path)
75
+ except Exception as e:
76
+ print(f"⚠️ Could not download pre-built binary: {e}")
77
+ # Try cargo install as fallback
78
+ if shutil.which("cargo"):
79
+ print("🔨 Found cargo! Building and installing via cargo...")
80
+ subprocess.check_call(["cargo", "install", "--git", f"https://github.com/{GITHUB_REPO}.git", "roviers-server", "roviers-cli"])
81
+ print("✅ Successfully built and installed via cargo!")
82
+ return get_binary_path("roviers-server")
83
+ raise RuntimeError(f"Failed to install native binaries: {e}")
84
+
85
+ # Extract archive
86
+ print(f"📂 Extracting {archive_path.name} to {dest_dir}...")
87
+ shutil.unpack_archive(archive_path, dest_dir)
88
+ archive_path.unlink(missing_ok=True)
89
+
90
+ # Ensure executable permissions on Unix
91
+ for binary_name in ["roviers-server", "roviers-cli"]:
92
+ p = dest_dir / binary_name
93
+ if p.exists():
94
+ p.chmod(0o755)
95
+
96
+ print(f"✅ Roviers successfully installed to {dest_dir}")
97
+ print(f"💡 Add {dest_dir} to your PATH to run `roviers-server` directly.")
98
+ return get_binary_path("roviers-server")
99
+
100
+
101
+ def run_server(args: list[str] | None = None) -> None:
102
+ """
103
+ Launches the native `roviers-server` binary, installing it first if missing.
104
+ """
105
+ binary = get_binary_path("roviers-server")
106
+ if not binary.is_file():
107
+ print("🔍 roviers-server binary not found locally.")
108
+ binary = install_roviers()
109
+
110
+ cmd = [str(binary)] + (args or sys.argv[1:])
111
+ try:
112
+ subprocess.run(cmd)
113
+ except KeyboardInterrupt:
114
+ pass
115
+