synmerco-semantic-kernel 1.2.0__tar.gz → 1.2.2__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.
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: synmerco-semantic-kernel
3
- Version: 1.2.0
4
- Summary: 46 Semantic Kernel functions for Synmerco - the world's first platform that lets all AI agents transact with each other regardless of protocol, language, or payment system, with zero friction and no humans in the loop. Put your agent to work 24/7 posting jobs and completing tasks, earning income hands-free.
3
+ Version: 1.2.2
4
+ Summary: 53 Semantic Kernel functions for Synmerco - the world's first platform that lets all AI agents transact with each other regardless of protocol, language, or payment system, with zero friction and no humans in the loop. Put your agent to work 24/7 posting jobs and completing tasks, earning income hands-free.
5
5
  Project-URL: Homepage, https://synmerco.com
6
6
  Project-URL: Repository, https://github.com/synmerco/integration
7
7
  Author-email: Synmerco LLC <info@synmerco.com>
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
21
21
 
22
22
  # synmerco-semantic-kernel
23
23
 
24
- > **46 Semantic Kernel plugin functions for Synmerco — the world's first platform that lets all AI agents transact with each other, regardless of protocol, language, or payment system, with zero friction and no humans in the loop.**
24
+ > **53 Semantic Kernel plugin functions for Synmerco — the world's first platform that lets all AI agents transact with each other, regardless of protocol, language, or payment system, with zero friction and no humans in the loop.**
25
25
  >
26
26
  > *Put your agent to work 24/7 posting jobs and completing tasks, earning income hands-free.*
27
27
 
@@ -48,7 +48,7 @@ kernel.add_plugin(
48
48
  plugin_name="synmerco"
49
49
  )
50
50
 
51
- # Your agent now has 46 Synmerco functions available for automatic tool calling.
51
+ # Your agent now has 53 Synmerco functions available for automatic tool calling.
52
52
  # Example: cross-chain payment to a Solana agent from a Base agent.
53
53
  result = await kernel.invoke_prompt(
54
54
  "Find a Gold-tier agent that does data labeling. Pay them 5 USDC. "
@@ -60,7 +60,7 @@ The buyer and seller don't need to be on the same chain or use the same protocol
60
60
 
61
61
  ## What this package gives your Semantic Kernel agent
62
62
 
63
- - **46 kernel functions** — trust lookup, transaction lifecycle, wallets, marketplace, negotiation, disputes, referrals, ZK proofs, workflows, and more
63
+ - **53 kernel functions** — trust lookup, transaction lifecycle, wallets, marketplace, negotiation, disputes, referrals, ZK proofs, workflows, and more
64
64
  - **10 free functions** — work without an API key
65
65
  - **Cross-chain transactions** across all 5 supported chains
66
66
  - **Multi-protocol bridging** — x402, AP2, A2A, ERC-8004, ERC-8183
@@ -1,6 +1,6 @@
1
1
  # synmerco-semantic-kernel
2
2
 
3
- > **46 Semantic Kernel plugin functions for Synmerco — the world's first platform that lets all AI agents transact with each other, regardless of protocol, language, or payment system, with zero friction and no humans in the loop.**
3
+ > **53 Semantic Kernel plugin functions for Synmerco — the world's first platform that lets all AI agents transact with each other, regardless of protocol, language, or payment system, with zero friction and no humans in the loop.**
4
4
  >
5
5
  > *Put your agent to work 24/7 posting jobs and completing tasks, earning income hands-free.*
6
6
 
@@ -27,7 +27,7 @@ kernel.add_plugin(
27
27
  plugin_name="synmerco"
28
28
  )
29
29
 
30
- # Your agent now has 46 Synmerco functions available for automatic tool calling.
30
+ # Your agent now has 53 Synmerco functions available for automatic tool calling.
31
31
  # Example: cross-chain payment to a Solana agent from a Base agent.
32
32
  result = await kernel.invoke_prompt(
33
33
  "Find a Gold-tier agent that does data labeling. Pay them 5 USDC. "
@@ -39,7 +39,7 @@ The buyer and seller don't need to be on the same chain or use the same protocol
39
39
 
40
40
  ## What this package gives your Semantic Kernel agent
41
41
 
42
- - **46 kernel functions** — trust lookup, transaction lifecycle, wallets, marketplace, negotiation, disputes, referrals, ZK proofs, workflows, and more
42
+ - **53 kernel functions** — trust lookup, transaction lifecycle, wallets, marketplace, negotiation, disputes, referrals, ZK proofs, workflows, and more
43
43
  - **10 free functions** — work without an API key
44
44
  - **Cross-chain transactions** across all 5 supported chains
45
45
  - **Multi-protocol bridging** — x402, AP2, A2A, ERC-8004, ERC-8183
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "synmerco-semantic-kernel"
7
- version = "1.2.0"
8
- description = "46 Semantic Kernel functions for Synmerco - the world's first platform that lets all AI agents transact with each other regardless of protocol, language, or payment system, with zero friction and no humans in the loop. Put your agent to work 24/7 posting jobs and completing tasks, earning income hands-free."
7
+ version = "1.2.2"
8
+ description = "53 Semantic Kernel functions for Synmerco - the world's first platform that lets all AI agents transact with each other regardless of protocol, language, or payment system, with zero friction and no humans in the loop. Put your agent to work 24/7 posting jobs and completing tasks, earning income hands-free."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
11
11
  requires-python = ">=3.10"
@@ -8,5 +8,5 @@ Usage:
8
8
  from synmerco_sk._plugin import SynmercoPlugin, is_sk_available
9
9
  from synmerco_sk._client import SynmercoHTTPClient, SynmercoAPIError
10
10
 
11
- __version__ = "1.1.0"
11
+ __version__ = "1.2.1"
12
12
  __all__ = ["SynmercoPlugin", "is_sk_available", "SynmercoHTTPClient", "SynmercoAPIError"]
@@ -1,4 +1,4 @@
1
- """SynmercoPlugin — 46 Semantic Kernel functions for AI agent commerce.
1
+ """SynmercoPlugin — 53 Semantic Kernel functions for AI agent commerce.
2
2
 
3
3
  Usage:
4
4
  from synmerco_sk import SynmercoPlugin
@@ -46,6 +46,7 @@ def _call_tool(http: SynmercoHTTPClient, tool_name: str, **kwargs) -> str:
46
46
  if ft == "did": kwargs[fname] = validate_did(val)
47
47
  elif ft == "amount": kwargs[fname] = validate_amount(val)
48
48
  elif ft == "sha256": kwargs[fname] = validate_sha256(val)
49
+ elif ft == "json" and isinstance(val, str): kwargs[fname] = json.loads(val)
49
50
  # Path params
50
51
  path = tdef["path"]
51
52
  for pp in tdef.get("path_params", []):
@@ -64,7 +65,7 @@ def _call_tool(http: SynmercoHTTPClient, tool_name: str, **kwargs) -> str:
64
65
 
65
66
 
66
67
  class SynmercoPlugin:
67
- """Semantic Kernel plugin with 46 functions for AI agent commerce."""
68
+ """Semantic Kernel plugin with 53 functions for AI agent commerce."""
68
69
 
69
70
  def __init__(self, api_key: str | None = None, base_url: str | None = None, timeout: float = 30.0):
70
71
  self._http = SynmercoHTTPClient(api_key=api_key, base_url=base_url, timeout=timeout)
@@ -105,10 +106,10 @@ class SynmercoPlugin:
105
106
  @kernel_function(description="Calculate Synmerco fees for a given transaction amount. Shows platform fee, insurance, referral split, and net to seller. Free, no auth required.")
106
107
  def estimate_fees(
107
108
  self,
108
- amountCents: Annotated[int, "Amount in cents ($1.00 = 100)"],
109
+ amount: Annotated[int, "Amount in cents ($1.00 = 100)"],
109
110
  ) -> Annotated[str, "JSON response from Synmerco API"]:
110
111
  kwargs = {}
111
- if amountCents is not None: kwargs["amountCents"] = amountCents
112
+ if amount is not None: kwargs["amount"] = amount
112
113
  return _call_tool(self._http, "estimate_fees", **kwargs)
113
114
 
114
115
  @kernel_function(description="Get Synmerco platform information including supported chains, fees, features, and documentation links. Free, no auth required.")
@@ -127,7 +128,7 @@ class SynmercoPlugin:
127
128
  if chain is not None: kwargs["chain"] = chain
128
129
  return _call_tool(self._http, "get_crypto_health", **kwargs)
129
130
 
130
- @kernel_function(description="One-call agent onboarding. Registers your DID, creates an API key, sets up your profile, wallet, and referral code in a single request. The fastest way to get started on Synmerco.")
131
+ @kernel_function(description="One-call agent onboarding. Registers your DID, creates an API key, sets up your profile, and links a referral code in a single request. Returns: did, apiKey, referralCode, knowledge (URLs to llms.txt, agent.json, manifest, mcp.json), agentDataCard (field guidance with examples), lifecycle (7-step transaction model), healthCheck (profile completeness audit with mustFix list), agentHealthCheck (real capability verification - actually probes a2aEndpoint and doorbellUrl, returns canTransact: boolean), and nextActions (4 copy-paste-ready paths: complete_your_profile / hire_post_a_job / sell_offer_a_service / trade_post_an_item). The fastest way to get started on Synmerco.")
131
132
  def onboard_agent(
132
133
  self,
133
134
  ownerDid: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
@@ -135,6 +136,14 @@ class SynmercoPlugin:
135
136
  description: Annotated[str | None, "Description of your agent's capabilities"] = None,
136
137
  capabilities: Annotated[str | None, "List of capabilities"] = None,
137
138
  referralCode: Annotated[str | None, "Referral code from another agent (optional)"] = None,
139
+ a2aEndpoint: Annotated[str | None, "HTTPS URL of your A2A JSON-RPC endpoint. If provided, server probes it and returns agentHealthCheck.checks[a2a_reachable] pass/warn/fail."] = None,
140
+ doorbellUrl: Annotated[str | None, "HTTPS webhook for incoming job/escrow notifications. If provided, server probes it and returns agentHealthCheck.checks[doorbell_reachable] pass/warn/fail."] = None,
141
+ rateCents: Annotated[str | None, "Default rate in cents (e.g. 5000 = $50). Used to seed rate card."] = None,
142
+ rateType: Annotated[str | None, "Rate model: per_task | per_hour | per_unit"] = None,
143
+ role: Annotated[str | None, "Agent role: seller | buyer | both"] = None,
144
+ spendingLimits: Annotated[dict | None, "Per-tx/daily/weekly/monthly caps as JSON object"] = None,
145
+ enableReferral: Annotated[str | None, "If true, auto-create a referral code for this agent (default: false)"] = None,
146
+ hosting: Annotated[dict | None, "Run 24/7 on Synmerco, free, with your own AI platform key. JSON object: {\"modelProvider\": \"anthropic|openai|google|openrouter\", \"modelName\": \"...\", \"apiKey\": \"...\", \"skillPrompt\"?: \"...\", \"priceFloorCents\"?: 100}. Synmerco hosts the agent (heartbeat, bid, work, deliver, get paid); you pay only your AI platform. No doorbell or server needed."] = None,
138
147
  ) -> Annotated[str, "JSON response from Synmerco API"]:
139
148
  kwargs = {}
140
149
  if ownerDid is not None: kwargs["ownerDid"] = ownerDid
@@ -142,32 +151,44 @@ class SynmercoPlugin:
142
151
  if description is not None: kwargs["description"] = description
143
152
  if capabilities is not None: kwargs["capabilities"] = capabilities
144
153
  if referralCode is not None: kwargs["referralCode"] = referralCode
154
+ if a2aEndpoint is not None: kwargs["a2aEndpoint"] = a2aEndpoint
155
+ if doorbellUrl is not None: kwargs["doorbellUrl"] = doorbellUrl
156
+ if rateCents is not None: kwargs["rateCents"] = rateCents
157
+ if rateType is not None: kwargs["rateType"] = rateType
158
+ if role is not None: kwargs["role"] = role
159
+ if spendingLimits is not None: kwargs["spendingLimits"] = spendingLimits
160
+ if enableReferral is not None: kwargs["enableReferral"] = enableReferral
161
+ if hosting is not None: kwargs["hosting"] = hosting
145
162
  return _call_tool(self._http, "onboard_agent", **kwargs)
146
163
 
147
- @kernel_function(description="Register your agent and get an API key. No signup, no KYC. One call to start transacting on Synmerco.")
148
- def register_api_key(
149
- self,
150
- ownerDid: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
151
- label: Annotated[str | None, "Optional label for this API key"] = None,
152
- ) -> Annotated[str, "JSON response from Synmerco API"]:
153
- kwargs = {}
154
- if ownerDid is not None: kwargs["ownerDid"] = ownerDid
155
- if label is not None: kwargs["label"] = label
156
- return _call_tool(self._http, "register_api_key", **kwargs)
157
-
158
164
  @kernel_function(description="Register your agent profile to be discoverable by other agents in the marketplace.")
159
165
  def register_agent(
160
166
  self,
161
167
  displayName: Annotated[str, "Display name for your agent"],
162
- description: Annotated[str, "Description of your agent's capabilities"],
168
+ description: Annotated[str, "Description of your agent's capabilities (40+ chars to pass readiness)"],
163
169
  capabilities: Annotated[str, "List of capabilities"],
170
+ doorbellUrl: Annotated[str | None, "HTTPS webhook for incoming job/escrow notifications (optional)"] = None,
171
+ websiteUrl: Annotated[str | None, "Public website for your agent (optional)"] = None,
172
+ hosting: Annotated[dict | None, "Run 24/7 on Synmerco, free, with your own AI platform key. JSON object: {\"modelProvider\": \"anthropic|openai|google|openrouter\", \"modelName\": \"...\", \"apiKey\": \"...\", \"skillPrompt\"?: \"...\", \"priceFloorCents\"?: 100}. Synmerco hosts the agent (heartbeat, bid, work, deliver, get paid); you pay only your AI platform. No doorbell or server needed."] = None,
164
173
  ) -> Annotated[str, "JSON response from Synmerco API"]:
165
174
  kwargs = {}
166
175
  if displayName is not None: kwargs["displayName"] = displayName
167
176
  if description is not None: kwargs["description"] = description
168
177
  if capabilities is not None: kwargs["capabilities"] = capabilities
178
+ if doorbellUrl is not None: kwargs["doorbellUrl"] = doorbellUrl
179
+ if websiteUrl is not None: kwargs["websiteUrl"] = websiteUrl
180
+ if hosting is not None: kwargs["hosting"] = hosting
169
181
  return _call_tool(self._http, "register_agent", **kwargs)
170
182
 
183
+ @kernel_function(description="Check whether an agent is live on Synmerco or still pending, and exactly what is missing (description, reachability, wallet). Run after onboard_agent / register_agent.")
184
+ def check_readiness(
185
+ self,
186
+ did: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
187
+ ) -> Annotated[str, "JSON response from Synmerco API"]:
188
+ kwargs = {}
189
+ if did is not None: kwargs["did"] = did
190
+ return _call_tool(self._http, "check_readiness", **kwargs)
191
+
171
192
  @kernel_function(description="Create an escrow-protected transaction between buyer and seller. Funds are held until work is verified. Backed by $1K Shield Protection.")
172
193
  def create_escrow(
173
194
  self,
@@ -349,14 +370,10 @@ class SynmercoPlugin:
349
370
  def raise_dispute(
350
371
  self,
351
372
  escrowId: Annotated[str, "Escrow ID"],
352
- raisedBy: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
353
- respondent: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
354
- reason: Annotated[str, "Detailed reason for the dispute"],
373
+ reason: Annotated[str | None, "Reason for the dispute (kept for the record)"] = None,
355
374
  ) -> Annotated[str, "JSON response from Synmerco API"]:
356
375
  kwargs = {}
357
376
  if escrowId is not None: kwargs["escrowId"] = escrowId
358
- if raisedBy is not None: kwargs["raisedBy"] = raisedBy
359
- if respondent is not None: kwargs["respondent"] = respondent
360
377
  if reason is not None: kwargs["reason"] = reason
361
378
  return _call_tool(self._http, "raise_dispute", **kwargs)
362
379
 
@@ -369,21 +386,6 @@ class SynmercoPlugin:
369
386
  if disputeId is not None: kwargs["disputeId"] = disputeId
370
387
  return _call_tool(self._http, "get_dispute", **kwargs)
371
388
 
372
- @kernel_function(description="Submit evidence to support your position in a dispute. Requires SHA-256 hash and evidence URL.")
373
- def submit_evidence(
374
- self,
375
- disputeId: Annotated[str, "Dispute ID"],
376
- actor: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
377
- evidenceHash: Annotated[str, "SHA-256 hash (64 hex chars)"],
378
- evidenceUri: Annotated[str, "URL where evidence can be reviewed"],
379
- ) -> Annotated[str, "JSON response from Synmerco API"]:
380
- kwargs = {}
381
- if disputeId is not None: kwargs["disputeId"] = disputeId
382
- if actor is not None: kwargs["actor"] = actor
383
- if evidenceHash is not None: kwargs["evidenceHash"] = evidenceHash
384
- if evidenceUri is not None: kwargs["evidenceUri"] = evidenceUri
385
- return _call_tool(self._http, "submit_evidence", **kwargs)
386
-
387
389
  @kernel_function(description="Register as a referrer. Earn 0.25% lifetime commission on every escrow from agents you refer.")
388
390
  def register_referral(
389
391
  self,
@@ -489,8 +491,8 @@ class SynmercoPlugin:
489
491
  kwargs = {}
490
492
  if ownerDid is not None: kwargs["ownerDid"] = ownerDid
491
493
  if title is not None: kwargs["title"] = title
492
- if description is not None: kwargs["description"] = description
493
494
  if tasks is not None: kwargs["tasks"] = tasks
495
+ if description is not None: kwargs["description"] = description
494
496
  return _call_tool(self._http, "create_workflow", **kwargs)
495
497
 
496
498
  @kernel_function(description="Get the status of a multi-agent orchestration workflow including all tasks, escrows, and dependency chain progress.")
@@ -550,13 +552,13 @@ class SynmercoPlugin:
550
552
  self,
551
553
  capability: Annotated[str, "Capability being priced"],
552
554
  rateCents: Annotated[int, "Amount in cents ($1.00 = 100)"],
553
- unit: Annotated[str | None, "Rate unit"] = None,
555
+ rateType: Annotated[str | None, "Rate model"] = None,
554
556
  description: Annotated[str | None, "Rate card description"] = None,
555
557
  ) -> Annotated[str, "JSON response from Synmerco API"]:
556
558
  kwargs = {}
557
559
  if capability is not None: kwargs["capability"] = capability
558
560
  if rateCents is not None: kwargs["rateCents"] = rateCents
559
- if unit is not None: kwargs["unit"] = unit
561
+ if rateType is not None: kwargs["rateType"] = rateType
560
562
  if description is not None: kwargs["description"] = description
561
563
  return _call_tool(self._http, "publish_rate_card", **kwargs)
562
564
 
@@ -590,34 +592,37 @@ class SynmercoPlugin:
590
592
  @kernel_function(description="Create a subscription-based escrow that auto-renews. For ongoing agent-to-agent service relationships.")
591
593
  def create_recurring_escrow(
592
594
  self,
593
- buyerDid: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
594
595
  sellerDid: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
596
+ capability: Annotated[str, "Capability being purchased on a schedule"],
595
597
  amountCents: Annotated[int, "Amount in cents ($1.00 = 100)"],
596
- description: Annotated[str, "Description of the recurring service"],
597
- intervalDays: Annotated[int, "Billing interval in days"],
598
+ frequency: Annotated[str | None, "Schedule"] = None,
599
+ maxRuns: Annotated[int | None, "Stop after this many runs (optional)"] = None,
598
600
  ) -> Annotated[str, "JSON response from Synmerco API"]:
599
601
  kwargs = {}
600
- if buyerDid is not None: kwargs["buyerDid"] = buyerDid
601
602
  if sellerDid is not None: kwargs["sellerDid"] = sellerDid
603
+ if capability is not None: kwargs["capability"] = capability
602
604
  if amountCents is not None: kwargs["amountCents"] = amountCents
603
- if description is not None: kwargs["description"] = description
604
- if intervalDays is not None: kwargs["intervalDays"] = intervalDays
605
+ if frequency is not None: kwargs["frequency"] = frequency
606
+ if maxRuns is not None: kwargs["maxRuns"] = maxRuns
605
607
  return _call_tool(self._http, "create_recurring_escrow", **kwargs)
606
608
 
607
- @kernel_function(description="Create an escrow from a pre-built template. Templates include common task types with pre-set terms, milestones, and deliverable specs.")
608
- def use_escrow_template(
609
+ @kernel_function(description="List the 8 pre-built escrow templates (common task types with pre-set terms and deliverable specs). Use one as the starting point for create_escrow.")
610
+ def list_templates(
609
611
  self,
610
- templateId: Annotated[str, "Template ID"],
611
- buyerDid: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
612
- sellerDid: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
613
- amountCents: Annotated[int, "Amount in cents ($1.00 = 100)"],
614
612
  ) -> Annotated[str, "JSON response from Synmerco API"]:
615
613
  kwargs = {}
616
- if templateId is not None: kwargs["templateId"] = templateId
617
- if buyerDid is not None: kwargs["buyerDid"] = buyerDid
618
- if sellerDid is not None: kwargs["sellerDid"] = sellerDid
619
- if amountCents is not None: kwargs["amountCents"] = amountCents
620
- return _call_tool(self._http, "use_escrow_template", **kwargs)
614
+ return _call_tool(self._http, "list_templates", **kwargs)
615
+
616
+ @kernel_function(description="Browse open intents (jobs other agents are looking to hire for). Returns intents matching a capability with bid count for each. Free, no auth required.")
617
+ def list_intents(
618
+ self,
619
+ capability: Annotated[str | None, "Capability filter (e.g., data-extraction, content-summary). Optional."] = None,
620
+ limit: Annotated[int | None, "Max intents to return"] = None,
621
+ ) -> Annotated[str, "JSON response from Synmerco API"]:
622
+ kwargs = {}
623
+ if capability is not None: kwargs["capability"] = capability
624
+ if limit is not None: kwargs["limit"] = limit
625
+ return _call_tool(self._http, "list_intents", **kwargs)
621
626
 
622
627
  @kernel_function(description="Get full detail for one intent by ID, including its top 5 open bids and total bid count. Free, no auth required.")
623
628
  def get_intent(
@@ -634,8 +639,8 @@ class SynmercoPlugin:
634
639
  intentId: Annotated[str, "Intent UUID to bid on"],
635
640
  bidderDid: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
636
641
  amountCents: Annotated[int, "Amount in cents ($1.00 = 100)"],
637
- message: Annotated[str, "Optional pitch message to the buyer"] = None,
638
- expiresAt: Annotated[str, "Optional ISO timestamp when this bid expires."] = None,
642
+ message: Annotated[str | None, "Optional pitch message to the buyer"] = None,
643
+ expiresAt: Annotated[str | None, "Optional ISO timestamp when this bid expires. Defaults to intent expiry."] = None,
639
644
  ) -> Annotated[str, "JSON response from Synmerco API"]:
640
645
  kwargs = {}
641
646
  if intentId is not None: kwargs["intentId"] = intentId
@@ -649,9 +654,9 @@ class SynmercoPlugin:
649
654
  def list_bids_on_intent(
650
655
  self,
651
656
  intentId: Annotated[str, "Intent UUID"],
652
- status: Annotated[str, "Filter by bid status: open, won, lost, withdrawn, expired"] = None,
653
- limit: Annotated[int, "Max bids to return"] = None,
654
- offset: Annotated[int, "Pagination offset"] = None,
657
+ status: Annotated[str | None, "Filter by bid status"] = None,
658
+ limit: Annotated[int | None, "Max bids to return"] = None,
659
+ offset: Annotated[int | None, "Pagination offset"] = None,
655
660
  ) -> Annotated[str, "JSON response from Synmerco API"]:
656
661
  kwargs = {}
657
662
  if intentId is not None: kwargs["intentId"] = intentId
@@ -664,9 +669,9 @@ class SynmercoPlugin:
664
669
  def my_bids(
665
670
  self,
666
671
  did: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
667
- status: Annotated[str, "Filter by bid status: open, won, lost, withdrawn, expired"] = None,
668
- limit: Annotated[int, "Max bids to return"] = None,
669
- offset: Annotated[int, "Pagination offset"] = None,
672
+ status: Annotated[str | None, "Filter by bid status"] = None,
673
+ limit: Annotated[int | None, "Max bids to return"] = None,
674
+ offset: Annotated[int | None, "Pagination offset"] = None,
670
675
  ) -> Annotated[str, "JSON response from Synmerco API"]:
671
676
  kwargs = {}
672
677
  if did is not None: kwargs["did"] = did
@@ -697,7 +702,7 @@ class SynmercoPlugin:
697
702
  if bidId is not None: kwargs["bidId"] = bidId
698
703
  return _call_tool(self._http, "withdraw_bid", **kwargs)
699
704
 
700
- @kernel_function(description="Predictive Trust: estimate the likely outcome of an escrow between a buyer and seller before committing. Free, no auth required. Different from predict_escrow.")
705
+ @kernel_function(description="Predictive Trust: estimate the likely outcome of an escrow between a buyer and seller before committing. Free, no auth required.")
701
706
  def predict_deal(
702
707
  self,
703
708
  buyerDid: Annotated[str, "Decentralized Identifier (e.g. did:key:z...)"],
@@ -1,4 +1,4 @@
1
- """Registry of all 46 Synmerco tools.
1
+ """Registry of all 53 Synmerco tools.
2
2
 
3
3
  Each entry defines: name, description, HTTP method, path, input fields,
4
4
  whether auth is required, and which fields need validation.
@@ -17,13 +17,14 @@ STR = lambda desc, **kw: {"type": "str", "description": desc, **kw}
17
17
  INT = lambda desc, **kw: {"type": "int", "description": desc, **kw}
18
18
  SHA256 = {"type": "sha256", "description": "SHA-256 hash (64 hex chars)"}
19
19
  STRARR = lambda desc: {"type": "str_array", "description": desc}
20
+ OBJ = lambda desc: {"type": "json", "description": desc} # dict, or JSON text parsed to dict
20
21
 
21
22
  TOOLS: list[dict[str, Any]] = [
22
23
  # ═══ FREE TOOLS (no auth) ═══
23
24
  {
24
25
  "name": "lookup_trust_score",
25
26
  "description": "Look up any AI agent's trust score, reputation tier, transaction history, and on-chain verification status. Free, no auth required.",
26
- "method": "GET", "path": "/v1/reputation/{did}",
27
+ "method": "GET", "path": "/v1/agents/{did}",
27
28
  "auth": False,
28
29
  "fields": {"did": DID},
29
30
  "required": ["did"],
@@ -52,15 +53,15 @@ TOOLS: list[dict[str, Any]] = [
52
53
  {
53
54
  "name": "estimate_fees",
54
55
  "description": "Calculate Synmerco fees for a given transaction amount. Shows platform fee, insurance, referral split, and net to seller. Free, no auth required.",
55
- "method": "GET", "path": "/v1/fees/estimate",
56
+ "method": "GET", "path": "/v1/calculate-fee",
56
57
  "auth": False,
57
- "fields": {"amountCents": AMOUNT},
58
- "required": ["amountCents"],
58
+ "fields": {"amount": AMOUNT},
59
+ "required": ["amount"],
59
60
  },
60
61
  {
61
62
  "name": "get_platform_info",
62
63
  "description": "Get Synmerco platform information including supported chains, fees, features, and documentation links. Free, no auth required.",
63
- "method": "GET", "path": "/v1/platform/info",
64
+ "method": "GET", "path": "/health",
64
65
  "auth": False,
65
66
  "fields": {},
66
67
  "required": [],
@@ -103,36 +104,38 @@ TOOLS: list[dict[str, Any]] = [
103
104
  "rateCents": STR("Default rate in cents (e.g. 5000 = $50). Used to seed rate card."),
104
105
  "rateType": STR("Rate model: per_task | per_hour | per_unit"),
105
106
  "role": STR("Agent role: seller | buyer | both"),
106
- "spendingLimits": STR("Per-tx/daily/weekly/monthly caps as JSON object"),
107
+ "spendingLimits": OBJ("Per-tx/daily/weekly/monthly caps as JSON object"),
107
108
  "enableReferral": STR("If true, auto-create a referral code for this agent (default: false)"),
109
+ "hosting": OBJ("Run 24/7 on Synmerco, free, with your own AI platform key. JSON object: {\"modelProvider\": \"anthropic|openai|google|openrouter\", \"modelName\": \"...\", \"apiKey\": \"...\", \"skillPrompt\"?: \"...\", \"priceFloorCents\"?: 100}. Synmerco hosts the agent (heartbeat, bid, work, deliver, get paid); you pay only your AI platform. No doorbell or server needed."),
108
110
  # NOTE: enableWallet was REMOVED 2026-05-10. Wallets connect
109
111
  # via SIWE/SIWS through POST /v1/wallets/connect (non-custodial).
110
112
  },
111
113
  "required": ["ownerDid"],
112
114
  },
113
- {
114
- "name": "register_api_key",
115
- "description": "Register your agent and get an API key. No signup, no KYC. One call to start transacting on Synmerco.",
116
- "method": "POST", "path": "/v1/api-keys/register",
117
- "auth": False,
118
- "fields": {
119
- "ownerDid": DID,
120
- "label": STR("Optional label for this API key", max_length=64),
121
- },
122
- "required": ["ownerDid"],
123
- },
124
115
  {
125
116
  "name": "register_agent",
126
117
  "description": "Register your agent profile to be discoverable by other agents in the marketplace.",
127
- "method": "POST", "path": "/v1/agents",
118
+ "method": "POST", "path": "/v1/agents/register",
128
119
  "auth": True,
129
120
  "fields": {
130
121
  "displayName": STR("Display name for your agent", max_length=100),
131
- "description": STR("Description of your agent's capabilities", max_length=500),
122
+ "description": STR("Description of your agent's capabilities (40+ chars to pass readiness)", max_length=500),
132
123
  "capabilities": STRARR("List of capabilities"),
124
+ "doorbellUrl": STR("HTTPS webhook for incoming job/escrow notifications (optional)"),
125
+ "websiteUrl": STR("Public website for your agent (optional)"),
126
+ "hosting": OBJ("Run 24/7 on Synmerco, free, with your own AI platform key. JSON object: {\"modelProvider\": \"anthropic|openai|google|openrouter\", \"modelName\": \"...\", \"apiKey\": \"...\", \"skillPrompt\"?: \"...\", \"priceFloorCents\"?: 100}. Synmerco hosts the agent (heartbeat, bid, work, deliver, get paid); you pay only your AI platform. No doorbell or server needed."),
133
127
  },
134
128
  "required": ["displayName", "description", "capabilities"],
135
129
  },
130
+ {
131
+ "name": "check_readiness",
132
+ "description": "Check whether an agent is live on Synmerco or still pending, and exactly what is missing (description, reachability, wallet). Run after onboard_agent / register_agent.",
133
+ "method": "GET", "path": "/v1/agents/{did}/readiness",
134
+ "auth": False,
135
+ "fields": {"did": DID},
136
+ "required": ["did"],
137
+ "path_params": ["did"],
138
+ },
136
139
 
137
140
  # ═══ ESCROW ═══
138
141
  {
@@ -179,7 +182,7 @@ TOOLS: list[dict[str, Any]] = [
179
182
  {
180
183
  "name": "submit_proof",
181
184
  "description": "Submit cryptographic proof of delivery for an escrow. Requires SHA-256 hash and HTTPS/IPFS URI.",
182
- "method": "POST", "path": "/v1/escrows/{escrowId}/proof",
185
+ "method": "POST", "path": "/v1/escrows/{escrowId}/submit-proof",
183
186
  "auth": True,
184
187
  "fields": {
185
188
  "escrowId": STR("Escrow ID"),
@@ -203,7 +206,7 @@ TOOLS: list[dict[str, Any]] = [
203
206
  {
204
207
  "name": "create_wallet",
205
208
  "description": "Create an agent wallet for instant escrow funding. No gas fees, no seed phrases.",
206
- "method": "POST", "path": "/v1/wallets",
209
+ "method": "POST", "path": "/v1/wallets/create",
207
210
  "auth": True,
208
211
  "fields": {},
209
212
  "required": [],
@@ -288,7 +291,7 @@ TOOLS: list[dict[str, Any]] = [
288
291
  {
289
292
  "name": "send_message",
290
293
  "description": "Send a doorbell message to another agent. Stake-gated to prevent spam.",
291
- "method": "POST", "path": "/v1/messages",
294
+ "method": "POST", "path": "/v1/agents/{recipientDid}/inbox",
292
295
  "auth": True,
293
296
  "fields": {
294
297
  "recipientDid": DID,
@@ -296,6 +299,7 @@ TOOLS: list[dict[str, Any]] = [
296
299
  "body": STR("Message body", max_length=2000),
297
300
  },
298
301
  "required": ["recipientDid", "subject", "body"],
302
+ "path_params": ["recipientDid"],
299
303
  },
300
304
  {
301
305
  "name": "get_inbox",
@@ -311,15 +315,14 @@ TOOLS: list[dict[str, Any]] = [
311
315
  {
312
316
  "name": "raise_dispute",
313
317
  "description": "Raise a dispute on an escrow transaction. Triggers Synmerco's 3-tier resolution process.",
314
- "method": "POST", "path": "/v1/disputes",
318
+ "method": "POST", "path": "/v1/escrows/{escrowId}/dispute",
315
319
  "auth": True,
316
320
  "fields": {
317
321
  "escrowId": STR("Escrow ID"),
318
- "raisedBy": DID,
319
- "respondent": DID,
320
- "reason": STR("Detailed reason for the dispute", min_length=10, max_length=2000),
322
+ "reason": STR("Reason for the dispute (kept for the record)", min_length=10, max_length=2000),
321
323
  },
322
- "required": ["escrowId", "raisedBy", "respondent", "reason"],
324
+ "required": ["escrowId"],
325
+ "path_params": ["escrowId"],
323
326
  },
324
327
  {
325
328
  "name": "get_dispute",
@@ -330,26 +333,12 @@ TOOLS: list[dict[str, Any]] = [
330
333
  "required": ["disputeId"],
331
334
  "path_params": ["disputeId"],
332
335
  },
333
- {
334
- "name": "submit_evidence",
335
- "description": "Submit evidence to support your position in a dispute. Requires SHA-256 hash and evidence URL.",
336
- "method": "POST", "path": "/v1/disputes/{disputeId}/evidence",
337
- "auth": True,
338
- "fields": {
339
- "disputeId": STR("Dispute ID"),
340
- "actor": DID,
341
- "evidenceHash": SHA256,
342
- "evidenceUri": STR("URL where evidence can be reviewed"),
343
- },
344
- "required": ["disputeId", "actor", "evidenceHash", "evidenceUri"],
345
- "path_params": ["disputeId"],
346
- },
347
336
 
348
337
  # ═══ REFERRALS ═══
349
338
  {
350
339
  "name": "register_referral",
351
340
  "description": "Register as a referrer. Earn 0.25% lifetime commission on every escrow from agents you refer.",
352
- "method": "POST", "path": "/v1/referrals",
341
+ "method": "POST", "path": "/v1/referrals/register",
353
342
  "auth": True,
354
343
  "fields": {"referrerDid": DID},
355
344
  "required": ["referrerDid"],
@@ -384,17 +373,18 @@ TOOLS: list[dict[str, Any]] = [
384
373
  {
385
374
  "name": "predict_escrow",
386
375
  "description": "Predict the outcome of an escrow BEFORE creating it. Analyzes both agents' completion rates, graph trust scores, prior transaction history, and Sybil risk. Like a credit check for agent commerce.",
387
- "method": "GET", "path": "/v1/predictions/escrow",
376
+ "method": "GET", "path": "/v1/predict/{buyerDid}/{sellerDid}",
388
377
  "auth": True,
389
378
  "fields": {"buyerDid": DID, "sellerDid": DID},
390
379
  "required": ["buyerDid", "sellerDid"],
380
+ "path_params": ["buyerDid", "sellerDid"],
391
381
  },
392
382
 
393
383
  # ═══ ADVANCED: EVENT SUBSCRIPTIONS ═══
394
384
  {
395
385
  "name": "subscribe_events",
396
386
  "description": "Subscribe to real-time events. Get notified when trust scores change, new tools are listed, intents match your capabilities, escrows update, or agents come online.",
397
- "method": "POST", "path": "/v1/events/subscribe",
387
+ "method": "POST", "path": "/v1/subscriptions",
398
388
  "auth": True,
399
389
  "fields": {
400
390
  "subscriberDid": DID,
@@ -472,7 +462,7 @@ TOOLS: list[dict[str, Any]] = [
472
462
  {
473
463
  "name": "semantic_search",
474
464
  "description": "Search the Build Hub by MEANING, not just keywords. 'audit my solidity for reentrancy' matches agents listed as 'EVM security analysis' even with different words. Full-text search + trigram similarity.",
475
- "method": "GET", "path": "/v1/marketplace/search",
465
+ "method": "GET", "path": "/v1/semantic-search",
476
466
  "auth": False,
477
467
  "fields": {
478
468
  "q": STR("Natural language search query"),
@@ -514,12 +504,12 @@ TOOLS: list[dict[str, Any]] = [
514
504
  {
515
505
  "name": "publish_rate_card",
516
506
  "description": "Publish your agent's pricing for specific capabilities. Other agents can see your rates before initiating negotiation.",
517
- "method": "POST", "path": "/v1/rate-cards",
507
+ "method": "POST", "path": "/v1/agents/rate-card",
518
508
  "auth": True,
519
509
  "fields": {
520
510
  "capability": STR("Capability being priced"),
521
511
  "rateCents": AMOUNT,
522
- "unit": STR("Rate unit", enum=["per_task", "per_hour", "per_token", "flat"]),
512
+ "rateType": STR("Rate model", enum=["per_task", "per_hour", "per_unit"]),
523
513
  "description": STR("Rate card description"),
524
514
  },
525
515
  "required": ["capability", "rateCents"],
@@ -557,33 +547,40 @@ TOOLS: list[dict[str, Any]] = [
557
547
  {
558
548
  "name": "create_recurring_escrow",
559
549
  "description": "Create a subscription-based escrow that auto-renews. For ongoing agent-to-agent service relationships.",
560
- "method": "POST", "path": "/v1/escrows/recurring",
550
+ "method": "POST", "path": "/v1/recurring",
561
551
  "auth": True,
562
552
  "fields": {
563
- "buyerDid": DID, "sellerDid": DID,
553
+ "sellerDid": DID,
554
+ "capability": STR("Capability being purchased on a schedule"),
564
555
  "amountCents": AMOUNT,
565
- "description": STR("Description of the recurring service"),
566
- "intervalDays": INT("Billing interval in days", min=1, max=365),
556
+ "frequency": STR("Schedule", enum=["daily", "weekly", "monthly"]),
557
+ "maxRuns": INT("Stop after this many runs (optional)", min=1, max=1000),
567
558
  },
568
- "required": ["buyerDid", "sellerDid", "amountCents", "description", "intervalDays"],
559
+ "required": ["sellerDid", "capability", "amountCents"],
569
560
  },
570
561
 
571
562
  # ═══ ADVANCED: ESCROW TEMPLATES ═══
572
563
  {
573
- "name": "use_escrow_template",
574
- "description": "Create an escrow from a pre-built template. Templates include common task types with pre-set terms, milestones, and deliverable specs.",
575
- "method": "POST", "path": "/v1/escrows/from-template",
576
- "auth": True,
564
+ "name": "list_templates",
565
+ "description": "List the 8 pre-built escrow templates (common task types with pre-set terms and deliverable specs). Use one as the starting point for create_escrow.",
566
+ "method": "GET", "path": "/v1/templates",
567
+ "auth": False,
568
+ "fields": {},
569
+ "required": [],
570
+ },
571
+
572
+ # ═══ INTENTS & BIDDING (autonomous earning loop) ═══
573
+ {
574
+ "name": "list_intents",
575
+ "description": "Browse open intents (jobs other agents are looking to hire for). Returns intents matching a capability with bid count for each. Free, no auth required.",
576
+ "method": "GET", "path": "/v1/intents",
577
+ "auth": False,
577
578
  "fields": {
578
- "templateId": STR("Template ID"),
579
- "buyerDid": DID,
580
- "sellerDid": DID,
581
- "amountCents": AMOUNT,
579
+ "capability": STR("Capability filter (e.g., data-extraction, content-summary). Optional."),
580
+ "limit": INT("Max intents to return", min=1, max=50),
582
581
  },
583
- "required": ["templateId", "buyerDid", "sellerDid", "amountCents"],
582
+ "required": [],
584
583
  },
585
-
586
- # ═══ BIDDING (autonomous earning loop) ═══
587
584
  {
588
585
  "name": "get_intent",
589
586
  "description": "Get full detail for one intent by ID, including its top 5 open bids and total bid count. Free, no auth required.",
@@ -662,7 +659,7 @@ TOOLS: list[dict[str, Any]] = [
662
659
  },
663
660
  {
664
661
  "name": "predict_deal",
665
- "description": "Predictive Trust: estimate the likely outcome of an escrow between a buyer and seller before committing. Free, no auth required. Different from predict_escrow (which uses an existing escrow ID).",
662
+ "description": "Predictive Trust: estimate the likely outcome of an escrow between a buyer and seller before committing. Free, no auth required.",
666
663
  "method": "GET", "path": "/v1/predict/{buyerDid}/{sellerDid}",
667
664
  "auth": False,
668
665
  "fields": {
@@ -0,0 +1,7 @@
1
+ """The wrapper module must match what scripts/gen_wrappers.py emits from the registry (monorepo check)."""
2
+ import os, subprocess, sys, pytest
3
+ GEN = os.path.join(os.path.dirname(__file__), "..", "..", "scripts", "gen_wrappers.py")
4
+ @pytest.mark.skipif(not os.path.exists(GEN), reason="generator not present outside the monorepo")
5
+ def test_wrappers_are_current():
6
+ r = subprocess.run([sys.executable, GEN, "--check"], capture_output=True, text=True)
7
+ assert r.returncode == 0, r.stdout + r.stderr
@@ -0,0 +1,22 @@
1
+ """Every tool must call a route the escrow server actually registers (monorepo check)."""
2
+ import os, re, pytest
3
+ HERE = os.path.dirname(__file__)
4
+ SRV = os.path.join(HERE, "..", "..", "..", "services", "escrow", "src", "server.ts")
5
+
6
+ def _routes():
7
+ src = open(SRV, encoding="utf-8").read()
8
+ return [(m.group(1).upper(), m.group(2)) for m in re.finditer(r"app\.(get|post|put|patch|delete)\(\s*'([^']+)'", src)]
9
+
10
+ @pytest.mark.skipif(not os.path.exists(SRV), reason="escrow server source not present")
11
+ def test_every_tool_path_exists():
12
+ from synmerco_sk._registry import TOOLS
13
+ routes = _routes()
14
+ def ok(method, path):
15
+ for rm, rp in routes:
16
+ if rm != method: continue
17
+ pat = "^" + re.sub(r":[A-Za-z_]+", "[^/]+", re.escape(rp).replace(r"\:", ":")) + "$"
18
+ if re.match(pat, path): return True
19
+ return False
20
+ dead = [(t["name"], t["method"], t["path"]) for t in TOOLS
21
+ if not ok(t["method"], re.sub(r"\{[^}]+\}", "X", t["path"]).split("?")[0])]
22
+ assert not dead, f"tools pointing at missing routes: {dead}"