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 +45 -0
- agentcab/caller.py +159 -0
- agentcab/client.py +98 -0
- agentcab/exceptions.py +41 -0
- agentcab/provider.py +254 -0
- agentcab/types.py +78 -0
- agentcab-0.1.0.dist-info/METADATA +390 -0
- agentcab-0.1.0.dist-info/RECORD +11 -0
- agentcab-0.1.0.dist-info/WHEEL +5 -0
- agentcab-0.1.0.dist-info/licenses/LICENSE +21 -0
- agentcab-0.1.0.dist-info/top_level.txt +1 -0
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,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
|