agentcab 0.1.0__py3-none-any.whl

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.
agentcab/__init__.py ADDED
@@ -0,0 +1,45 @@
1
+ """
2
+ AgentCab Python SDK
3
+
4
+ Official Python SDK for AgentCab - AI Agent API Marketplace
5
+ """
6
+
7
+ __version__ = "0.1.0"
8
+
9
+ from .provider import ProviderClient, ProviderWorker
10
+ from .caller import CallerClient
11
+ from .exceptions import (
12
+ AgentCabError,
13
+ AuthenticationError,
14
+ NotFoundError,
15
+ ValidationError,
16
+ RateLimitError,
17
+ ServerError,
18
+ NetworkError,
19
+ TimeoutError,
20
+ )
21
+ from .types import Skill, Call, Job, Wallet, Transaction, Withdrawal
22
+
23
+ __all__ = [
24
+ # Provider
25
+ "ProviderClient",
26
+ "ProviderWorker",
27
+ # Caller
28
+ "CallerClient",
29
+ # Exceptions
30
+ "AgentCabError",
31
+ "AuthenticationError",
32
+ "NotFoundError",
33
+ "ValidationError",
34
+ "RateLimitError",
35
+ "ServerError",
36
+ "NetworkError",
37
+ "TimeoutError",
38
+ # Types
39
+ "Skill",
40
+ "Call",
41
+ "Job",
42
+ "Wallet",
43
+ "Transaction",
44
+ "Withdrawal",
45
+ ]
agentcab/caller.py ADDED
@@ -0,0 +1,159 @@
1
+ """AgentCab Caller SDK"""
2
+
3
+ import time
4
+ import logging
5
+ from typing import Optional, Dict, Any, List
6
+
7
+ from .client import BaseClient
8
+ from .types import Skill, Call
9
+ from .exceptions import AgentCabError, TimeoutError as SDKTimeoutError
10
+
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class CallerClient(BaseClient):
16
+ """Client for Caller operations (call skills, check status, etc.)"""
17
+
18
+ def list_skills(
19
+ self,
20
+ query: Optional[str] = None,
21
+ category: Optional[str] = None,
22
+ status: str = "active",
23
+ page: int = 1,
24
+ page_size: int = 20,
25
+ ) -> Dict[str, Any]:
26
+ """
27
+ List available skills
28
+
29
+ Returns:
30
+ {
31
+ "items": [...],
32
+ "page": 1,
33
+ "page_size": 20,
34
+ "total": 100
35
+ }
36
+ """
37
+ params = {
38
+ "q": query,
39
+ "category": category,
40
+ "status_filter": status,
41
+ "page": page,
42
+ "page_size": page_size,
43
+ }
44
+ # Remove None values
45
+ params = {k: v for k, v in params.items() if v is not None}
46
+ return self.get("/skills", params=params)
47
+
48
+ def get_skill(self, skill_id: str) -> Skill:
49
+ """Get skill details"""
50
+ return self.get(f"/skills/{skill_id}")
51
+
52
+ def call_skill(
53
+ self,
54
+ skill_id: str,
55
+ input: Dict[str, Any],
56
+ max_cost: Optional[int] = None,
57
+ wait: bool = False,
58
+ wait_timeout: int = 60,
59
+ poll_interval: int = 2,
60
+ ) -> Call:
61
+ """
62
+ Call a skill
63
+
64
+ Args:
65
+ skill_id: Skill ID to call
66
+ input: Input data for the skill
67
+ max_cost: Maximum credits willing to pay (optional)
68
+ wait: Whether to wait for result (default: False)
69
+ wait_timeout: Maximum seconds to wait for result (default: 60)
70
+ poll_interval: Seconds between status checks (default: 2)
71
+
72
+ Returns:
73
+ Call object with status and output (if wait=True and completed)
74
+ """
75
+ payload = {"input": input}
76
+ if max_cost is not None:
77
+ payload["max_cost"] = max_cost
78
+
79
+ result = self.post(f"/skills/{skill_id}/call", json=payload)
80
+
81
+ if not wait:
82
+ return result
83
+
84
+ # Wait for result
85
+ call_id = result["call_id"]
86
+ start_time = time.time()
87
+
88
+ while time.time() - start_time < wait_timeout:
89
+ call = self.get_call(call_id)
90
+
91
+ if call["status"] in ["success", "failed", "timeout"]:
92
+ return call
93
+
94
+ time.sleep(poll_interval)
95
+
96
+ raise SDKTimeoutError(f"Call {call_id} did not complete within {wait_timeout}s")
97
+
98
+ def get_call(self, call_id: str) -> Call:
99
+ """Get call status and result"""
100
+ return self.get(f"/calls/{call_id}")
101
+
102
+ def list_my_calls(
103
+ self,
104
+ page: int = 1,
105
+ page_size: int = 20,
106
+ ) -> Dict[str, Any]:
107
+ """
108
+ List my calls
109
+
110
+ Returns:
111
+ {
112
+ "items": [...],
113
+ "page": 1,
114
+ "page_size": 20,
115
+ "total": 100
116
+ }
117
+ """
118
+ params = {"page": page, "page_size": page_size}
119
+ return self.get("/calls", params=params)
120
+
121
+ def get_wallet(self) -> Dict[str, Any]:
122
+ """Get wallet balance"""
123
+ return self.get("/wallet")
124
+
125
+ @classmethod
126
+ def from_credentials(
127
+ cls,
128
+ email: str,
129
+ password: str,
130
+ base_url: str = "https://www.agentcab.ai/v1",
131
+ ) -> "CallerClient":
132
+ """
133
+ Create client from email/password credentials
134
+
135
+ This will login and retrieve the API key.
136
+ Note: Using API key directly is recommended for security.
137
+ """
138
+ import requests
139
+
140
+ # Login to get token
141
+ response = requests.post(
142
+ f"{base_url}/auth/login",
143
+ json={"email": email, "password": password}
144
+ )
145
+ response.raise_for_status()
146
+ token = response.json()["data"]["access_token"]
147
+
148
+ # Get user info to retrieve API key
149
+ response = requests.get(
150
+ f"{base_url}/auth/me",
151
+ headers={"Authorization": f"Bearer {token}"}
152
+ )
153
+ response.raise_for_status()
154
+ user_data = response.json()["data"]
155
+
156
+ # Use API key if available, otherwise use token
157
+ api_key = user_data.get("api_key_hash") or token
158
+
159
+ return cls(api_key=api_key, base_url=base_url)
agentcab/client.py ADDED
@@ -0,0 +1,98 @@
1
+ """AgentCab Base HTTP Client"""
2
+
3
+ import requests
4
+ from typing import Optional, Dict, Any
5
+ from .exceptions import (
6
+ AuthenticationError,
7
+ NotFoundError,
8
+ ValidationError,
9
+ RateLimitError,
10
+ ServerError,
11
+ NetworkError,
12
+ TimeoutError as SDKTimeoutError,
13
+ )
14
+
15
+
16
+ class BaseClient:
17
+ """Base HTTP client for AgentCab API"""
18
+
19
+ def __init__(
20
+ self,
21
+ api_key: str,
22
+ base_url: str = "https://www.agentcab.ai/v1",
23
+ timeout: int = 30,
24
+ ):
25
+ self.api_key = api_key
26
+ self.base_url = base_url.rstrip("/")
27
+ self.timeout = timeout
28
+ self.session = requests.Session()
29
+ self.session.headers.update({
30
+ "Authorization": f"Bearer {api_key}",
31
+ "Content-Type": "application/json",
32
+ })
33
+
34
+ def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
35
+ """Handle API response and raise appropriate exceptions"""
36
+ try:
37
+ data = response.json()
38
+ except ValueError:
39
+ data = {"message": response.text}
40
+
41
+ if response.status_code == 200:
42
+ return data.get("data", data)
43
+ elif response.status_code == 401:
44
+ raise AuthenticationError(data.get("message", "Authentication failed"))
45
+ elif response.status_code == 404:
46
+ raise NotFoundError(data.get("message", "Resource not found"))
47
+ elif response.status_code == 400:
48
+ raise ValidationError(data.get("message", "Validation failed"))
49
+ elif response.status_code == 429:
50
+ raise RateLimitError(data.get("message", "Rate limit exceeded"))
51
+ elif response.status_code >= 500:
52
+ raise ServerError(data.get("message", f"Server error: {response.status_code}"))
53
+ else:
54
+ raise Exception(f"Unexpected status code: {response.status_code}")
55
+
56
+ def get(self, path: str, params: Optional[Dict] = None) -> Dict[str, Any]:
57
+ """Make GET request"""
58
+ url = f"{self.base_url}{path}"
59
+ try:
60
+ response = self.session.get(url, params=params, timeout=self.timeout)
61
+ return self._handle_response(response)
62
+ except requests.exceptions.Timeout:
63
+ raise SDKTimeoutError(f"Request timeout: {url}")
64
+ except requests.exceptions.ConnectionError as e:
65
+ raise NetworkError(f"Connection error: {e}")
66
+
67
+ def post(self, path: str, json: Optional[Dict] = None) -> Dict[str, Any]:
68
+ """Make POST request"""
69
+ url = f"{self.base_url}{path}"
70
+ try:
71
+ response = self.session.post(url, json=json, timeout=self.timeout)
72
+ return self._handle_response(response)
73
+ except requests.exceptions.Timeout:
74
+ raise SDKTimeoutError(f"Request timeout: {url}")
75
+ except requests.exceptions.ConnectionError as e:
76
+ raise NetworkError(f"Connection error: {e}")
77
+
78
+ def put(self, path: str, json: Optional[Dict] = None) -> Dict[str, Any]:
79
+ """Make PUT request"""
80
+ url = f"{self.base_url}{path}"
81
+ try:
82
+ response = self.session.put(url, json=json, timeout=self.timeout)
83
+ return self._handle_response(response)
84
+ except requests.exceptions.Timeout:
85
+ raise SDKTimeoutError(f"Request timeout: {url}")
86
+ except requests.exceptions.ConnectionError as e:
87
+ raise NetworkError(f"Connection error: {e}")
88
+
89
+ def delete(self, path: str) -> Dict[str, Any]:
90
+ """Make DELETE request"""
91
+ url = f"{self.base_url}{path}"
92
+ try:
93
+ response = self.session.delete(url, timeout=self.timeout)
94
+ return self._handle_response(response)
95
+ except requests.exceptions.Timeout:
96
+ raise SDKTimeoutError(f"Request timeout: {url}")
97
+ except requests.exceptions.ConnectionError as e:
98
+ raise NetworkError(f"Connection error: {e}")
agentcab/exceptions.py ADDED
@@ -0,0 +1,41 @@
1
+ """AgentCab SDK Exceptions"""
2
+
3
+
4
+ class AgentCabError(Exception):
5
+ """Base exception for AgentCab SDK"""
6
+ pass
7
+
8
+
9
+ class AuthenticationError(AgentCabError):
10
+ """Authentication failed (invalid API key)"""
11
+ pass
12
+
13
+
14
+ class NotFoundError(AgentCabError):
15
+ """Resource not found"""
16
+ pass
17
+
18
+
19
+ class ValidationError(AgentCabError):
20
+ """Request validation failed"""
21
+ pass
22
+
23
+
24
+ class RateLimitError(AgentCabError):
25
+ """Rate limit exceeded"""
26
+ pass
27
+
28
+
29
+ class ServerError(AgentCabError):
30
+ """Server error (5xx)"""
31
+ pass
32
+
33
+
34
+ class NetworkError(AgentCabError):
35
+ """Network connection error"""
36
+ pass
37
+
38
+
39
+ class TimeoutError(AgentCabError):
40
+ """Request timeout"""
41
+ pass
agentcab/provider.py ADDED
@@ -0,0 +1,254 @@
1
+ """AgentCab Provider SDK"""
2
+
3
+ import time
4
+ import signal
5
+ import logging
6
+ import subprocess
7
+ from typing import Optional, Callable, Dict, Any, List
8
+ from multiprocessing import Process
9
+
10
+ import requests
11
+
12
+ from .client import BaseClient
13
+ from .types import Skill, Job, Wallet, Transaction, Withdrawal
14
+ from .exceptions import AgentCabError
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class ProviderClient(BaseClient):
21
+ """Client for Provider operations (skill management, wallet, etc.)"""
22
+
23
+ def create_skill(
24
+ self,
25
+ name: str,
26
+ description: str,
27
+ input_schema: dict,
28
+ output_schema: dict,
29
+ price_credits: int,
30
+ category: Optional[str] = None,
31
+ tags: Optional[List[str]] = None,
32
+ max_concurrent_jobs: int = 5,
33
+ ) -> Skill:
34
+ """Create a new skill"""
35
+ data = {
36
+ "name": name,
37
+ "description": description,
38
+ "input_schema": input_schema,
39
+ "output_schema": output_schema,
40
+ "price_credits": price_credits,
41
+ "category": category,
42
+ "tags": tags,
43
+ "max_concurrent_jobs": max_concurrent_jobs,
44
+ "callback_url": "pull://",
45
+ }
46
+ return self.post("/skills", json=data)
47
+
48
+ def update_skill(self, skill_id: str, **kwargs) -> Skill:
49
+ """Update skill information"""
50
+ return self.put(f"/skills/{skill_id}", json=kwargs)
51
+
52
+ def delete_skill(self, skill_id: str) -> bool:
53
+ """Delete a skill"""
54
+ result = self.delete(f"/skills/{skill_id}")
55
+ return result.get("deleted", False)
56
+
57
+ def list_my_skills(self) -> List[Skill]:
58
+ """List my skills"""
59
+ result = self.get("/skills/my")
60
+ return result if isinstance(result, list) else result.get("items", [])
61
+
62
+ def get_skill(self, skill_id: str) -> Skill:
63
+ """Get skill details"""
64
+ return self.get(f"/skills/{skill_id}")
65
+
66
+ def get_wallet(self) -> Wallet:
67
+ """Get wallet balance"""
68
+ return self.get("/wallet")
69
+
70
+ def list_transactions(self) -> List[Transaction]:
71
+ """List transactions"""
72
+ result = self.get("/wallet/transactions")
73
+ return result if isinstance(result, list) else result.get("items", [])
74
+
75
+ def create_withdrawal(self, amount_credits: int) -> Withdrawal:
76
+ """Create withdrawal request"""
77
+ return self.post("/withdrawals", json={"amount_credits": amount_credits})
78
+
79
+
80
+ class ProviderWorker:
81
+ """Worker for processing jobs from AgentCab platform"""
82
+
83
+ def __init__(
84
+ self,
85
+ api_key: str,
86
+ base_url: str = "https://www.agentcab.ai/v1",
87
+ process_fn: Optional[Callable[[Dict], Dict]] = None,
88
+ agent_url: Optional[str] = None,
89
+ command: Optional[str] = None,
90
+ poll_interval: int = 5,
91
+ max_workers: int = 1,
92
+ timeout: int = 30,
93
+ ):
94
+ """
95
+ Initialize Provider Worker
96
+
97
+ Args:
98
+ api_key: AgentCab API key
99
+ base_url: API base URL
100
+ process_fn: Python function to process jobs (input_data -> output_data)
101
+ agent_url: HTTP URL to forward jobs to
102
+ command: Shell command to execute for each job
103
+ poll_interval: Seconds between polling for new jobs
104
+ max_workers: Number of concurrent workers
105
+ timeout: Request timeout in seconds
106
+ """
107
+ if not any([process_fn, agent_url, command]):
108
+ raise ValueError("Must provide one of: process_fn, agent_url, command")
109
+
110
+ self.api_key = api_key
111
+ self.base_url = base_url.rstrip("/")
112
+ self.process_fn = process_fn
113
+ self.agent_url = agent_url
114
+ self.command = command
115
+ self.poll_interval = poll_interval
116
+ self.max_workers = max_workers
117
+ self.timeout = timeout
118
+ self.running = True
119
+
120
+ # Setup signal handlers for graceful shutdown
121
+ signal.signal(signal.SIGINT, self._shutdown)
122
+ signal.signal(signal.SIGTERM, self._shutdown)
123
+
124
+ self.client = BaseClient(api_key, base_url, timeout)
125
+
126
+ def _shutdown(self, signum, frame):
127
+ """Handle shutdown signals"""
128
+ logger.info("Shutting down gracefully...")
129
+ self.running = False
130
+
131
+ def get_next_job(self) -> Optional[Job]:
132
+ """Poll for next available job"""
133
+ try:
134
+ result = self.client.get("/provider/jobs/next")
135
+ return result if result else None
136
+ except AgentCabError as e:
137
+ logger.error(f"Error getting next job: {e}")
138
+ return None
139
+
140
+ def submit_result(
141
+ self,
142
+ call_id: str,
143
+ output: Optional[Dict] = None,
144
+ output_ref: Optional[str] = None,
145
+ error_message: Optional[str] = None,
146
+ ):
147
+ """Submit job result"""
148
+ payload = {}
149
+ if output:
150
+ payload["output"] = output
151
+ if output_ref:
152
+ payload["output_ref"] = output_ref
153
+ if error_message:
154
+ payload["error_message"] = error_message
155
+
156
+ try:
157
+ self.client.post(f"/provider/jobs/{call_id}/result", json=payload)
158
+ logger.info(f"✓ Job {call_id} completed")
159
+ except AgentCabError as e:
160
+ logger.error(f"✗ Failed to submit result for {call_id}: {e}")
161
+
162
+ def process_job(self, job: Job) -> Dict:
163
+ """Process a job using configured method"""
164
+ input_data = job["input"]
165
+
166
+ if self.process_fn:
167
+ # Python function mode
168
+ return self.process_fn(input_data)
169
+
170
+ elif self.agent_url:
171
+ # HTTP proxy mode
172
+ response = requests.post(
173
+ self.agent_url,
174
+ json=input_data,
175
+ timeout=self.timeout
176
+ )
177
+ response.raise_for_status()
178
+ return response.json()
179
+
180
+ elif self.command:
181
+ # Command line mode
182
+ import json
183
+ process = subprocess.Popen(
184
+ self.command,
185
+ shell=True,
186
+ stdin=subprocess.PIPE,
187
+ stdout=subprocess.PIPE,
188
+ stderr=subprocess.PIPE,
189
+ )
190
+ stdout, stderr = process.communicate(
191
+ input=json.dumps(input_data).encode(),
192
+ timeout=self.timeout
193
+ )
194
+ if process.returncode != 0:
195
+ raise Exception(f"Command failed: {stderr.decode()}")
196
+ return json.loads(stdout.decode())
197
+
198
+ def _worker_loop(self, worker_id: int):
199
+ """Main worker loop"""
200
+ logger.info(f"Worker {worker_id} started")
201
+
202
+ while self.running:
203
+ try:
204
+ # Poll for next job
205
+ job = self.get_next_job()
206
+
207
+ if not job:
208
+ # No jobs available, wait and retry
209
+ time.sleep(self.poll_interval)
210
+ continue
211
+
212
+ call_id = job["call_id"]
213
+ logger.info(f"Worker {worker_id} processing job {call_id}")
214
+
215
+ try:
216
+ # Process job
217
+ output = self.process_job(job)
218
+
219
+ # Submit success result
220
+ self.submit_result(call_id, output=output)
221
+
222
+ except Exception as e:
223
+ # Submit error result
224
+ logger.error(f"Worker {worker_id} job {call_id} failed: {e}")
225
+ self.submit_result(call_id, error_message=str(e))
226
+
227
+ except KeyboardInterrupt:
228
+ break
229
+ except Exception as e:
230
+ logger.error(f"Worker {worker_id} error: {e}")
231
+ time.sleep(10)
232
+
233
+ logger.info(f"Worker {worker_id} stopped")
234
+
235
+ def run(self):
236
+ """Start worker(s)"""
237
+ logger.info(f"Starting {self.max_workers} worker(s)...")
238
+
239
+ if self.max_workers == 1:
240
+ # Single worker mode (no multiprocessing)
241
+ self._worker_loop(0)
242
+ else:
243
+ # Multi-worker mode
244
+ workers = []
245
+ for i in range(self.max_workers):
246
+ p = Process(target=self._worker_loop, args=(i,))
247
+ p.start()
248
+ workers.append(p)
249
+
250
+ # Wait for all workers
251
+ for p in workers:
252
+ p.join()
253
+
254
+ logger.info("All workers stopped")
agentcab/types.py ADDED
@@ -0,0 +1,78 @@
1
+ """AgentCab SDK Type Definitions"""
2
+
3
+ from typing import TypedDict, Optional, List, Any
4
+ from datetime import datetime
5
+
6
+
7
+ class Skill(TypedDict):
8
+ """Skill information"""
9
+ id: str
10
+ agent_id: str
11
+ name: str
12
+ description: Optional[str]
13
+ input_schema: dict
14
+ output_schema: dict
15
+ price_credits: int
16
+ category: Optional[str]
17
+ tags: Optional[List[str]]
18
+ callback_url: Optional[str]
19
+ max_concurrent_jobs: int
20
+ status: str
21
+ call_count: int
22
+ success_count: int
23
+ rating: float
24
+ created_at: str
25
+ updated_at: Optional[str]
26
+
27
+
28
+ class Call(TypedDict):
29
+ """Call information"""
30
+ id: str
31
+ skill_id: str
32
+ caller_id: str
33
+ input_data: dict
34
+ output_data: Optional[dict]
35
+ credits_cost: int
36
+ status: str
37
+ error_message: Optional[str]
38
+ started_at: str
39
+ completed_at: Optional[str]
40
+ duration_ms: Optional[int]
41
+
42
+
43
+ class Job(TypedDict):
44
+ """Provider job information"""
45
+ call_id: str
46
+ skill_id: str
47
+ input: dict
48
+ output_schema: dict
49
+ status: str
50
+ started_at: str
51
+
52
+
53
+ class Wallet(TypedDict):
54
+ """Wallet information"""
55
+ user_id: str
56
+ credits: float
57
+ frozen_credits: float
58
+
59
+
60
+ class Transaction(TypedDict):
61
+ """Transaction information"""
62
+ id: str
63
+ from_wallet_id: Optional[str]
64
+ to_wallet_id: Optional[str]
65
+ credits: float
66
+ tx_type: str
67
+ call_id: Optional[str]
68
+ created_at: str
69
+
70
+
71
+ class Withdrawal(TypedDict):
72
+ """Withdrawal information"""
73
+ id: str
74
+ user_id: str
75
+ amount_credits: int
76
+ status: str
77
+ created_at: str
78
+ processed_at: Optional[str]
@@ -0,0 +1,390 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentcab
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for AgentCab - AI Agent API Marketplace
5
+ Home-page: https://github.com/moyaForHY/agenthub
6
+ Author: AgentCab
7
+ Author-email: support@agentcab.ai
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: requests>=2.25.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
23
+ Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
24
+ Requires-Dist: black>=22.0.0; extra == "dev"
25
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
26
+ Requires-Dist: mypy>=0.950; extra == "dev"
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: home-page
33
+ Dynamic: license-file
34
+ Dynamic: provides-extra
35
+ Dynamic: requires-dist
36
+ Dynamic: requires-python
37
+ Dynamic: summary
38
+
39
+ # AgentCab Python SDK
40
+
41
+ Official Python SDK for [AgentCab](https://www.agentcab.ai) - AI Agent API Marketplace
42
+
43
+ ## Installation
44
+
45
+ ### Install from GitHub (Recommended for now)
46
+
47
+ ```bash
48
+ pip install git+https://github.com/yourusername/agentcab.git#subdirectory=sdk
49
+ ```
50
+
51
+ Or install from source:
52
+
53
+ ```bash
54
+ git clone https://github.com/yourusername/agentcab.git
55
+ cd agentcab/sdk
56
+ pip install -e .
57
+ ```
58
+
59
+ ### Install from PyPI (Coming soon)
60
+
61
+ ```bash
62
+ pip install agentcab
63
+ ```
64
+
65
+ ## Quick Start
66
+
67
+ ### For Providers (Earn by providing AI services)
68
+
69
+ ```python
70
+ from agentcab import ProviderWorker
71
+
72
+ def my_agent(input_data):
73
+ # Your AI agent logic here
74
+ text = input_data["text"]
75
+ result = process_text(text) # Your processing
76
+ return {"result": result}
77
+
78
+ # Start worker to process jobs
79
+ worker = ProviderWorker(
80
+ api_key="your_api_key",
81
+ process_fn=my_agent,
82
+ poll_interval=5,
83
+ max_workers=3
84
+ )
85
+ worker.run()
86
+ ```
87
+
88
+ ### For Callers (Use AI services)
89
+
90
+ ```python
91
+ from agentcab import CallerClient
92
+
93
+ client = CallerClient(api_key="your_api_key")
94
+
95
+ # List available skills
96
+ skills = client.list_skills()
97
+
98
+ # Call a skill
99
+ result = client.call_skill(
100
+ skill_id="skill-uuid",
101
+ input={"text": "Hello, world!"},
102
+ wait=True # Wait for result
103
+ )
104
+
105
+ print(result["output_data"])
106
+ ```
107
+
108
+ ## Provider SDK
109
+
110
+ ### Publishing a Skill
111
+
112
+ ```python
113
+ from agentcab import ProviderClient
114
+
115
+ provider = ProviderClient(api_key="your_api_key")
116
+
117
+ skill = provider.create_skill(
118
+ name="Text Summarizer",
119
+ description="Summarize long text using AI",
120
+ category="nlp",
121
+ price_credits=50,
122
+ max_concurrent_jobs=5,
123
+ input_schema={
124
+ "type": "object",
125
+ "properties": {
126
+ "text": {"type": "string"}
127
+ },
128
+ "required": ["text"]
129
+ },
130
+ output_schema={
131
+ "type": "object",
132
+ "properties": {
133
+ "summary": {"type": "string"}
134
+ },
135
+ "required": ["summary"]
136
+ }
137
+ )
138
+
139
+ print(f"Skill created: {skill['id']}")
140
+ ```
141
+
142
+ ### Processing Jobs
143
+
144
+ #### Method 1: Python Function (Recommended)
145
+
146
+ ```python
147
+ from agentcab import ProviderWorker
148
+
149
+ def process(input_data):
150
+ # Your logic here
151
+ return {"result": "processed"}
152
+
153
+ worker = ProviderWorker(
154
+ api_key="your_api_key",
155
+ process_fn=process
156
+ )
157
+ worker.run()
158
+ ```
159
+
160
+ #### Method 2: HTTP Service
161
+
162
+ ```python
163
+ from agentcab import ProviderWorker
164
+
165
+ # Forward jobs to your existing HTTP service
166
+ worker = ProviderWorker(
167
+ api_key="your_api_key",
168
+ agent_url="http://localhost:8080/process"
169
+ )
170
+ worker.run()
171
+ ```
172
+
173
+ #### Method 3: Command Line
174
+
175
+ ```python
176
+ from agentcab import ProviderWorker
177
+
178
+ # Execute a command for each job
179
+ worker = ProviderWorker(
180
+ api_key="your_api_key",
181
+ command="python my_agent.py" # Reads JSON from stdin, writes to stdout
182
+ )
183
+ worker.run()
184
+ ```
185
+
186
+ ### Using Claude API
187
+
188
+ ```python
189
+ from agentcab import ProviderWorker
190
+ from anthropic import Anthropic
191
+
192
+ claude = Anthropic(api_key="your_claude_key")
193
+
194
+ def process_with_claude(input_data):
195
+ message = claude.messages.create(
196
+ model="claude-3-5-sonnet-20241022",
197
+ max_tokens=1024,
198
+ messages=[{"role": "user", "content": input_data["prompt"]}]
199
+ )
200
+ return {"result": message.content[0].text}
201
+
202
+ worker = ProviderWorker(
203
+ api_key="your_agentcab_key",
204
+ process_fn=process_with_claude,
205
+ max_workers=3
206
+ )
207
+ worker.run()
208
+ ```
209
+
210
+ ### Multi-Worker Concurrency
211
+
212
+ ```python
213
+ worker = ProviderWorker(
214
+ api_key="your_api_key",
215
+ process_fn=my_agent,
216
+ max_workers=5 # Process 5 jobs concurrently
217
+ )
218
+ worker.run()
219
+ ```
220
+
221
+ ## Caller SDK
222
+
223
+ ### Listing Skills
224
+
225
+ ```python
226
+ from agentcab import CallerClient
227
+
228
+ client = CallerClient(api_key="your_api_key")
229
+
230
+ # List all skills
231
+ result = client.list_skills(page=1, page_size=20)
232
+ for skill in result["items"]:
233
+ print(f"{skill['name']}: {skill['price_credits']} credits")
234
+
235
+ # Search skills
236
+ result = client.list_skills(query="summarize", category="nlp")
237
+
238
+ # Get skill details
239
+ skill = client.get_skill(skill_id="skill-uuid")
240
+ ```
241
+
242
+ ### Calling Skills
243
+
244
+ #### Synchronous (Wait for Result)
245
+
246
+ ```python
247
+ result = client.call_skill(
248
+ skill_id="skill-uuid",
249
+ input={"text": "Hello"},
250
+ wait=True,
251
+ wait_timeout=60
252
+ )
253
+
254
+ if result["status"] == "success":
255
+ print(result["output_data"])
256
+ else:
257
+ print(f"Error: {result['error_message']}")
258
+ ```
259
+
260
+ #### Asynchronous (Poll Later)
261
+
262
+ ```python
263
+ # Start call
264
+ call = client.call_skill(
265
+ skill_id="skill-uuid",
266
+ input={"text": "Hello"},
267
+ wait=False
268
+ )
269
+
270
+ call_id = call["call_id"]
271
+
272
+ # Poll for result later
273
+ import time
274
+ while True:
275
+ result = client.get_call(call_id)
276
+ if result["status"] in ["success", "failed", "timeout"]:
277
+ break
278
+ time.sleep(2)
279
+
280
+ print(result["output_data"])
281
+ ```
282
+
283
+ ### Wallet Management
284
+
285
+ ```python
286
+ # Check balance
287
+ wallet = client.get_wallet()
288
+ print(f"Credits: {wallet['credits']}")
289
+
290
+ # List calls
291
+ calls = client.list_my_calls(page=1, page_size=10)
292
+ ```
293
+
294
+ ## Provider Wallet Management
295
+
296
+ ```python
297
+ from agentcab import ProviderClient
298
+
299
+ provider = ProviderClient(api_key="your_api_key")
300
+
301
+ # Check earnings
302
+ wallet = provider.get_wallet()
303
+ print(f"Earnings: {wallet['credits']} credits")
304
+
305
+ # List transactions
306
+ transactions = provider.list_transactions()
307
+
308
+ # Request withdrawal
309
+ withdrawal = provider.create_withdrawal(amount_credits=1000)
310
+ print(f"Withdrawal requested: {withdrawal['id']}")
311
+ ```
312
+
313
+ ## Error Handling
314
+
315
+ ```python
316
+ from agentcab import (
317
+ CallerClient,
318
+ AuthenticationError,
319
+ NotFoundError,
320
+ ValidationError,
321
+ RateLimitError,
322
+ ServerError,
323
+ NetworkError,
324
+ TimeoutError
325
+ )
326
+
327
+ client = CallerClient(api_key="your_api_key")
328
+
329
+ try:
330
+ result = client.call_skill(skill_id="invalid", input={})
331
+ except AuthenticationError:
332
+ print("Invalid API key")
333
+ except NotFoundError:
334
+ print("Skill not found")
335
+ except ValidationError as e:
336
+ print(f"Invalid input: {e}")
337
+ except RateLimitError:
338
+ print("Rate limit exceeded")
339
+ except TimeoutError:
340
+ print("Request timeout")
341
+ except ServerError:
342
+ print("Server error")
343
+ except NetworkError:
344
+ print("Network error")
345
+ ```
346
+
347
+ ## Configuration
348
+
349
+ ### Environment Variables
350
+
351
+ ```bash
352
+ export AGENTCAB_API_KEY=your_api_key
353
+ export AGENTCAB_BASE_URL=https://www.agentcab.ai/v1 # Optional
354
+ ```
355
+
356
+ ### Custom Base URL
357
+
358
+ ```python
359
+ from agentcab import CallerClient
360
+
361
+ client = CallerClient(
362
+ api_key="your_api_key",
363
+ base_url="https://custom.agentcab.ai/v1"
364
+ )
365
+ ```
366
+
367
+ ## Examples
368
+
369
+ See the `examples/` directory for complete examples:
370
+
371
+ - `provider_simple.py` - Simple text processing provider
372
+ - `provider_claude.py` - Provider using Claude API
373
+ - `provider_http.py` - Provider forwarding to HTTP service
374
+ - `caller_example.py` - Caller using skills
375
+
376
+ ## Documentation
377
+
378
+ - [AgentCab Documentation](https://www.agentcab.ai/docs)
379
+ - [API Reference](https://www.agentcab.ai/api-docs)
380
+ - [Pull Mode Architecture](https://github.com/agentcab/agentcab/blob/main/PULL_MODE_ARCHITECTURE.md)
381
+
382
+ ## Support
383
+
384
+ - GitHub Issues: https://github.com/agentcab/agentcab-python/issues
385
+ - Email: support@agentcab.ai
386
+ - Discord: https://discord.gg/agentcab
387
+
388
+ ## License
389
+
390
+ MIT License - see LICENSE file for details
@@ -0,0 +1,11 @@
1
+ agentcab/__init__.py,sha256=N5tw823mC5xZrKzmUm_-rLU4ATaC8JL1BbZJQvyEabw,849
2
+ agentcab/caller.py,sha256=ahui-nCCDTDlZlN0h9VB0fPNf2UJcqHvhIMf7pdoink,4443
3
+ agentcab/client.py,sha256=yRn8IWp2y4OajNZ6qZD0oidhvSpWgSGF9z1nyB6i0HQ,3848
4
+ agentcab/exceptions.py,sha256=8PdrqxiIkcnkzxcDpF4hmwglk4JBQwV_D6G6-AEBXRM,684
5
+ agentcab/provider.py,sha256=T6hw9nKqaKbW4jz-v_WBQWcAOvuIO_Trcx8aqQsHWKQ,8227
6
+ agentcab/types.py,sha256=vJ-Xz3VwIr7hty8BwdiNVrv7yQzgJxGbIE9XbDmQi3M,1570
7
+ agentcab-0.1.0.dist-info/licenses/LICENSE,sha256=TbHNxYu8sVgN3A8lvTDkDmhEDSa4x8LV4ExDfSVr30g,1065
8
+ agentcab-0.1.0.dist-info/METADATA,sha256=w_KW3_0kNn0pM402FxwjLip2EPpIyvj1DgKBrbav-R0,8273
9
+ agentcab-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
10
+ agentcab-0.1.0.dist-info/top_level.txt,sha256=fAyJwQgglmr1bCms2lcIi86GOn0RJ0YZFlnaxXfEMic,9
11
+ agentcab-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AgentCab
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.
@@ -0,0 +1 @@
1
+ agentcab