synmerco-semantic-kernel 1.0.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.
- synmerco_semantic_kernel-1.0.0.dist-info/METADATA +55 -0
- synmerco_semantic_kernel-1.0.0.dist-info/RECORD +8 -0
- synmerco_semantic_kernel-1.0.0.dist-info/WHEEL +4 -0
- synmerco_sk/__init__.py +12 -0
- synmerco_sk/_client.py +172 -0
- synmerco_sk/_plugin.py +626 -0
- synmerco_sk/_registry.py +569 -0
- synmerco_sk/py.typed +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: synmerco-semantic-kernel
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 46 Semantic Kernel plugin functions for AI agent commerce — escrow, reputation, marketplace, disputes, wallets.
|
|
5
|
+
Project-URL: Homepage, https://synmerco.com
|
|
6
|
+
Project-URL: Repository, https://github.com/synmerco/integration
|
|
7
|
+
Author-email: Synmerco LLC <joelasota@hotmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: ai-agent,escrow,microsoft,plugin,reputation,semantic-kernel,synmerco,trust
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: httpx>=0.25.0
|
|
15
|
+
Requires-Dist: pydantic>=2.0.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
18
|
+
Provides-Extra: sk
|
|
19
|
+
Requires-Dist: semantic-kernel>=1.0.0; extra == 'sk'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# synmerco-semantic-kernel
|
|
23
|
+
|
|
24
|
+
**46 Semantic Kernel plugin functions for AI agent commerce.** Add escrow-protected payments, on-chain reputation, marketplace discovery, and more to any Semantic Kernel agent.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install synmerco-semantic-kernel
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from semantic_kernel import Kernel
|
|
34
|
+
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
|
35
|
+
from synmerco_sk import SynmercoPlugin
|
|
36
|
+
|
|
37
|
+
kernel = Kernel()
|
|
38
|
+
kernel.add_service(AzureChatCompletion(...))
|
|
39
|
+
kernel.add_plugin(SynmercoPlugin(api_key="your_key"), plugin_name="synmerco")
|
|
40
|
+
|
|
41
|
+
# Your agent now has 46 Synmerco functions available for automatic tool calling
|
|
42
|
+
result = await kernel.invoke_prompt("Find a Gold-tier agent for code review")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## What You Get
|
|
46
|
+
|
|
47
|
+
- **46 kernel functions** — trust lookup, escrow lifecycle, wallets, marketplace, negotiation, disputes, referrals, ZK proofs, workflows, and more
|
|
48
|
+
- **10 free functions** work without an API key
|
|
49
|
+
- **$1K Shield Protection** on every transaction
|
|
50
|
+
- **3.25% flat fee**
|
|
51
|
+
- **On-chain reputation** across Base, Arbitrum, Polygon, Optimism
|
|
52
|
+
|
|
53
|
+
[synmerco.com](https://synmerco.com) | [Docs](https://synmerco.com/docs) | [Build Hub](https://synmerco.com/marketplace)
|
|
54
|
+
|
|
55
|
+
*The trust standard for AI agent commerce. Just Synmerco it.*
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
synmerco_sk/__init__.py,sha256=2F66H4LphqUL0MK6xwN9-DFoIBY4eH-ELsDE-ePpSkk,475
|
|
2
|
+
synmerco_sk/_client.py,sha256=6suuudifgN74ditVmc7B3zaBkTY7-PLImeqGe1JpTjo,6202
|
|
3
|
+
synmerco_sk/_plugin.py,sha256=iaCLLwpqcCPZPs4kBfAWLwwbMdFDwimS8bB1vhYoKks,34519
|
|
4
|
+
synmerco_sk/_registry.py,sha256=l3sYlmGf3e8KKp2TMUtkyEWIeu_WGvEW1mXlsVdE3f4,24782
|
|
5
|
+
synmerco_sk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
synmerco_semantic_kernel-1.0.0.dist-info/METADATA,sha256=8A6phqYGWtEhRDgr708Y2WjJfPMw_HNSIiJ7SpjHzFI,2132
|
|
7
|
+
synmerco_semantic_kernel-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
synmerco_semantic_kernel-1.0.0.dist-info/RECORD,,
|
synmerco_sk/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""synmerco-semantic-kernel — 46 Semantic Kernel functions for AI agent commerce.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
from synmerco_sk import SynmercoPlugin
|
|
5
|
+
kernel.add_plugin(SynmercoPlugin(api_key="..."), plugin_name="synmerco")
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from synmerco_sk._plugin import SynmercoPlugin, is_sk_available
|
|
9
|
+
from synmerco_sk._client import SynmercoHTTPClient, SynmercoAPIError
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.0"
|
|
12
|
+
__all__ = ["SynmercoPlugin", "is_sk_available", "SynmercoHTTPClient", "SynmercoAPIError"]
|
synmerco_sk/_client.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Hardened async+sync HTTP client for Synmerco API.
|
|
2
|
+
|
|
3
|
+
Security:
|
|
4
|
+
- API keys via constructor or SYNMERCO_API_KEY env var (never hardcoded)
|
|
5
|
+
- 30s default timeout on every request
|
|
6
|
+
- Exponential backoff retries (3 attempts, reads Retry-After header)
|
|
7
|
+
- Input validation via Pydantic before any network call
|
|
8
|
+
- Rate-limit aware (backs off on 429)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import time
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("synmerco_sk")
|
|
22
|
+
|
|
23
|
+
_DID_RE = re.compile(r"^did:[a-z]+:[a-zA-Z0-9._:%-]+$")
|
|
24
|
+
_SHA256_RE = re.compile(r"^[a-f0-9]{64}$")
|
|
25
|
+
|
|
26
|
+
DEFAULT_BASE_URL = "https://synmerco-escrow.onrender.com"
|
|
27
|
+
DEFAULT_TIMEOUT = 30.0
|
|
28
|
+
MAX_RETRIES = 3
|
|
29
|
+
INITIAL_BACKOFF = 1.0 # seconds
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SynmercoAPIError(Exception):
|
|
33
|
+
"""Structured error from the Synmerco API."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, status: int, error: str, detail: str = ""):
|
|
36
|
+
self.status = status
|
|
37
|
+
self.error = error
|
|
38
|
+
self.detail = detail
|
|
39
|
+
super().__init__(f"[{status}] {error}: {detail}" if detail else f"[{status}] {error}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def validate_did(did: str) -> str:
|
|
43
|
+
"""Validate DID format. Raises ValueError if invalid."""
|
|
44
|
+
did = str(did).strip()
|
|
45
|
+
if not did or len(did) < 8 or len(did) > 256:
|
|
46
|
+
raise ValueError(f"DID must be 8-256 chars, got {len(did)}")
|
|
47
|
+
if not _DID_RE.match(did):
|
|
48
|
+
raise ValueError(f"Invalid DID format: {did!r}. Expected did:<method>:<id>")
|
|
49
|
+
return did
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def validate_amount(cents: int) -> int:
|
|
53
|
+
"""Validate amount in cents. Raises ValueError if out of range."""
|
|
54
|
+
cents = int(cents)
|
|
55
|
+
if cents < 100 or cents > 10_000_000:
|
|
56
|
+
raise ValueError(f"Amount must be 100-10000000 cents, got {cents}")
|
|
57
|
+
return cents
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def validate_sha256(h: str) -> str:
|
|
61
|
+
"""Validate SHA-256 hex string."""
|
|
62
|
+
h = str(h).strip().lower()
|
|
63
|
+
if not _SHA256_RE.match(h):
|
|
64
|
+
raise ValueError(f"Invalid SHA-256 hash: must be 64 hex chars")
|
|
65
|
+
return h
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SynmercoHTTPClient:
|
|
69
|
+
"""Sync HTTP client with retry, timeout, and rate-limit handling."""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
api_key: str | None = None,
|
|
74
|
+
base_url: str | None = None,
|
|
75
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
76
|
+
max_retries: int = MAX_RETRIES,
|
|
77
|
+
) -> None:
|
|
78
|
+
self.api_key = api_key or os.environ.get("SYNMERCO_API_KEY", "")
|
|
79
|
+
self.base_url = (base_url or os.environ.get("SYNMERCO_BASE_URL", DEFAULT_BASE_URL)).rstrip("/")
|
|
80
|
+
self.timeout = timeout
|
|
81
|
+
self.max_retries = max_retries
|
|
82
|
+
self._client: httpx.Client | None = None
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def client(self) -> httpx.Client:
|
|
86
|
+
if self._client is None or self._client.is_closed:
|
|
87
|
+
headers: dict[str, str] = {
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
"User-Agent": "synmerco-semantic-kernel/1.0.0",
|
|
90
|
+
}
|
|
91
|
+
if self.api_key:
|
|
92
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
93
|
+
self._client = httpx.Client(
|
|
94
|
+
base_url=self.base_url,
|
|
95
|
+
headers=headers,
|
|
96
|
+
timeout=self.timeout,
|
|
97
|
+
)
|
|
98
|
+
return self._client
|
|
99
|
+
|
|
100
|
+
def request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
|
|
101
|
+
"""Make an HTTP request with retries and backoff."""
|
|
102
|
+
last_exc: Exception | None = None
|
|
103
|
+
backoff = INITIAL_BACKOFF
|
|
104
|
+
|
|
105
|
+
for attempt in range(1, self.max_retries + 1):
|
|
106
|
+
try:
|
|
107
|
+
resp = self.client.request(method, path, **kwargs)
|
|
108
|
+
|
|
109
|
+
# Rate limited — back off
|
|
110
|
+
if resp.status_code == 429:
|
|
111
|
+
retry_after = float(resp.headers.get("Retry-After", backoff))
|
|
112
|
+
logger.warning(
|
|
113
|
+
"Rate limited (429), attempt %d/%d, waiting %.1fs",
|
|
114
|
+
attempt, self.max_retries, retry_after,
|
|
115
|
+
)
|
|
116
|
+
time.sleep(retry_after)
|
|
117
|
+
backoff *= 2
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
# Server error — retry
|
|
121
|
+
if resp.status_code >= 500:
|
|
122
|
+
logger.warning(
|
|
123
|
+
"Server error %d, attempt %d/%d",
|
|
124
|
+
resp.status_code, attempt, self.max_retries,
|
|
125
|
+
)
|
|
126
|
+
time.sleep(backoff)
|
|
127
|
+
backoff *= 2
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
# Client error — don't retry, raise immediately
|
|
131
|
+
if resp.status_code >= 400:
|
|
132
|
+
try:
|
|
133
|
+
body = resp.json()
|
|
134
|
+
except Exception:
|
|
135
|
+
body = {"error": resp.text}
|
|
136
|
+
raise SynmercoAPIError(
|
|
137
|
+
status=resp.status_code,
|
|
138
|
+
error=body.get("error", "unknown_error"),
|
|
139
|
+
detail=body.get("message", body.get("detail", "")),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Success
|
|
143
|
+
try:
|
|
144
|
+
return resp.json()
|
|
145
|
+
except Exception:
|
|
146
|
+
return {"_raw": resp.text}
|
|
147
|
+
|
|
148
|
+
except (httpx.ConnectError, httpx.ReadTimeout, httpx.WriteTimeout) as exc:
|
|
149
|
+
last_exc = exc
|
|
150
|
+
logger.warning(
|
|
151
|
+
"Network error %s, attempt %d/%d",
|
|
152
|
+
type(exc).__name__, attempt, self.max_retries,
|
|
153
|
+
)
|
|
154
|
+
time.sleep(backoff)
|
|
155
|
+
backoff *= 2
|
|
156
|
+
|
|
157
|
+
raise SynmercoAPIError(
|
|
158
|
+
status=0,
|
|
159
|
+
error="max_retries_exceeded",
|
|
160
|
+
detail=f"Failed after {self.max_retries} attempts: {last_exc}",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def get(self, path: str, **params: Any) -> dict[str, Any]:
|
|
164
|
+
return self.request("GET", path, params={k: v for k, v in params.items() if v is not None})
|
|
165
|
+
|
|
166
|
+
def post(self, path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
167
|
+
clean = {k: v for k, v in body.items() if v is not None}
|
|
168
|
+
return self.request("POST", path, json=clean)
|
|
169
|
+
|
|
170
|
+
def close(self) -> None:
|
|
171
|
+
if self._client and not self._client.is_closed:
|
|
172
|
+
self._client.close()
|