roar-sdk 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- roar_sdk-0.2.0/.gitignore +7 -0
- roar_sdk-0.2.0/PKG-INFO +74 -0
- roar_sdk-0.2.0/README.md +34 -0
- roar_sdk-0.2.0/pyproject.toml +45 -0
- roar_sdk-0.2.0/src/roar_sdk/__init__.py +129 -0
- roar_sdk-0.2.0/src/roar_sdk/_compat.py +11 -0
- roar_sdk-0.2.0/src/roar_sdk/adapters/__init__.py +12 -0
- roar_sdk-0.2.0/src/roar_sdk/adapters/acp.py +192 -0
- roar_sdk-0.2.0/src/roar_sdk/adapters/detect.py +96 -0
- roar_sdk-0.2.0/src/roar_sdk/autonomy.py +224 -0
- roar_sdk-0.2.0/src/roar_sdk/client.py +161 -0
- roar_sdk-0.2.0/src/roar_sdk/dedup.py +92 -0
- roar_sdk-0.2.0/src/roar_sdk/delegation.py +179 -0
- roar_sdk-0.2.0/src/roar_sdk/did_document.py +159 -0
- roar_sdk-0.2.0/src/roar_sdk/did_key.py +99 -0
- roar_sdk-0.2.0/src/roar_sdk/did_web.py +119 -0
- roar_sdk-0.2.0/src/roar_sdk/discovery_cache.py +126 -0
- roar_sdk-0.2.0/src/roar_sdk/hub.py +215 -0
- roar_sdk-0.2.0/src/roar_sdk/router.py +285 -0
- roar_sdk-0.2.0/src/roar_sdk/server.py +190 -0
- roar_sdk-0.2.0/src/roar_sdk/signing.py +163 -0
- roar_sdk-0.2.0/src/roar_sdk/sqlite_directory.py +140 -0
- roar_sdk-0.2.0/src/roar_sdk/streaming.py +178 -0
- roar_sdk-0.2.0/src/roar_sdk/transports/__init__.py +41 -0
- roar_sdk-0.2.0/src/roar_sdk/transports/http.py +105 -0
- roar_sdk-0.2.0/src/roar_sdk/transports/stdio.py +54 -0
- roar_sdk-0.2.0/src/roar_sdk/transports/websocket.py +103 -0
- roar_sdk-0.2.0/src/roar_sdk/types.py +314 -0
roar_sdk-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: roar-sdk
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: ROAR Protocol — Python SDK. Standalone implementation of the 5-layer agent communication standard.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ProwlrBot/roar-protocol
|
|
6
|
+
Project-URL: Repository, https://github.com/ProwlrBot/roar-protocol
|
|
7
|
+
Project-URL: Specification, https://github.com/ProwlrBot/roar-protocol/blob/main/ROAR-SPEC.md
|
|
8
|
+
Author-email: kdairatchi <kdairatchi@users.noreply.github.com>
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
Keywords: a2a,agents,ai,did,mcp,protocol,roar
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: cryptography>=41.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: httpx>=0.25; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: websockets>=12.0; extra == 'dev'
|
|
29
|
+
Provides-Extra: ed25519
|
|
30
|
+
Requires-Dist: cryptography>=41.0; extra == 'ed25519'
|
|
31
|
+
Provides-Extra: http
|
|
32
|
+
Requires-Dist: httpx>=0.25; extra == 'http'
|
|
33
|
+
Provides-Extra: server
|
|
34
|
+
Requires-Dist: fastapi>=0.104; extra == 'server'
|
|
35
|
+
Requires-Dist: httpx>=0.25; extra == 'server'
|
|
36
|
+
Requires-Dist: uvicorn[standard]>=0.24; extra == 'server'
|
|
37
|
+
Provides-Extra: websocket
|
|
38
|
+
Requires-Dist: websockets>=12.0; extra == 'websocket'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# roar-sdk
|
|
42
|
+
|
|
43
|
+
**ROAR Protocol** — standalone Python SDK. 5-layer agent communication standard.
|
|
44
|
+
|
|
45
|
+
Design by [@kdairatchi](https://github.com/kdairatchi) — [ProwlrBot/roar-protocol](https://github.com/ProwlrBot/roar-protocol)
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/ProwlrBot/roar-protocol.git
|
|
51
|
+
pip install -e ./python # types + client + server (pydantic only)
|
|
52
|
+
pip install -e './python[http]' # + httpx for HTTP transport
|
|
53
|
+
pip install -e './python[server]' # + fastapi + uvicorn for serving
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from roar_sdk import AgentIdentity, ROARMessage, MessageIntent, ROARClient, ROARServer
|
|
60
|
+
|
|
61
|
+
# Layer 1: identity
|
|
62
|
+
agent = AgentIdentity(display_name="my-agent", capabilities=["code"])
|
|
63
|
+
|
|
64
|
+
# Layer 4: message
|
|
65
|
+
msg = ROARMessage(
|
|
66
|
+
**{"from": agent, "to": other},
|
|
67
|
+
intent=MessageIntent.DELEGATE,
|
|
68
|
+
payload={"task": "review"},
|
|
69
|
+
)
|
|
70
|
+
msg.sign("shared-secret")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See [examples/python/](https://github.com/ProwlrBot/roar-protocol/tree/main/examples/python) for runnable server + client.
|
|
74
|
+
See [ROAR-SPEC.md](https://github.com/ProwlrBot/roar-protocol/blob/main/ROAR-SPEC.md) for the full protocol specification.
|
roar_sdk-0.2.0/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# roar-sdk
|
|
2
|
+
|
|
3
|
+
**ROAR Protocol** — standalone Python SDK. 5-layer agent communication standard.
|
|
4
|
+
|
|
5
|
+
Design by [@kdairatchi](https://github.com/kdairatchi) — [ProwlrBot/roar-protocol](https://github.com/ProwlrBot/roar-protocol)
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/ProwlrBot/roar-protocol.git
|
|
11
|
+
pip install -e ./python # types + client + server (pydantic only)
|
|
12
|
+
pip install -e './python[http]' # + httpx for HTTP transport
|
|
13
|
+
pip install -e './python[server]' # + fastapi + uvicorn for serving
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from roar_sdk import AgentIdentity, ROARMessage, MessageIntent, ROARClient, ROARServer
|
|
20
|
+
|
|
21
|
+
# Layer 1: identity
|
|
22
|
+
agent = AgentIdentity(display_name="my-agent", capabilities=["code"])
|
|
23
|
+
|
|
24
|
+
# Layer 4: message
|
|
25
|
+
msg = ROARMessage(
|
|
26
|
+
**{"from": agent, "to": other},
|
|
27
|
+
intent=MessageIntent.DELEGATE,
|
|
28
|
+
payload={"task": "review"},
|
|
29
|
+
)
|
|
30
|
+
msg.sign("shared-secret")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
See [examples/python/](https://github.com/ProwlrBot/roar-protocol/tree/main/examples/python) for runnable server + client.
|
|
34
|
+
See [ROAR-SPEC.md](https://github.com/ProwlrBot/roar-protocol/blob/main/ROAR-SPEC.md) for the full protocol specification.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "roar-sdk"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "ROAR Protocol — Python SDK. Standalone implementation of the 5-layer agent communication standard."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "Apache-2.0" }
|
|
11
|
+
authors = [{ name = "kdairatchi", email = "kdairatchi@users.noreply.github.com" }]
|
|
12
|
+
keywords = ["agents", "ai", "protocol", "roar", "did", "a2a", "mcp"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: Apache Software License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Software Development :: Libraries",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
]
|
|
25
|
+
requires-python = ">=3.10"
|
|
26
|
+
dependencies = ["pydantic>=2.0"]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
http = ["httpx>=0.25"]
|
|
30
|
+
websocket = ["websockets>=12.0"]
|
|
31
|
+
ed25519 = ["cryptography>=41.0"]
|
|
32
|
+
server = ["fastapi>=0.104", "uvicorn[standard]>=0.24", "httpx>=0.25"]
|
|
33
|
+
dev = ["pytest>=7.0", "pytest-asyncio>=0.23", "httpx>=0.25", "websockets>=12.0", "cryptography>=41.0"]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/ProwlrBot/roar-protocol"
|
|
37
|
+
Repository = "https://github.com/ProwlrBot/roar-protocol"
|
|
38
|
+
Specification = "https://github.com/ProwlrBot/roar-protocol/blob/main/ROAR-SPEC.md"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/roar_sdk"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
asyncio_mode = "auto"
|
|
45
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ROAR Protocol — Python SDK.
|
|
3
|
+
|
|
4
|
+
Standalone implementation of the 5-layer agent communication standard.
|
|
5
|
+
Design: @kdairatchi — https://github.com/ProwlrBot/roar-protocol
|
|
6
|
+
|
|
7
|
+
Quick start::
|
|
8
|
+
|
|
9
|
+
from roar_sdk import AgentIdentity, ROARMessage, MessageIntent, ROARClient, ROARServer
|
|
10
|
+
|
|
11
|
+
# Layer 1: Identity
|
|
12
|
+
identity = AgentIdentity(display_name="my-agent", capabilities=["code"])
|
|
13
|
+
print(identity.did) # did:roar:agent:my-agent-a1b2c3d4...
|
|
14
|
+
|
|
15
|
+
# Layer 4: Exchange — build and sign a message
|
|
16
|
+
msg = ROARMessage(
|
|
17
|
+
**{"from": identity, "to": other_identity},
|
|
18
|
+
intent=MessageIntent.DELEGATE,
|
|
19
|
+
payload={"task": "review"},
|
|
20
|
+
)
|
|
21
|
+
msg.sign("shared-secret")
|
|
22
|
+
|
|
23
|
+
# Layer 3: Connect — send over HTTP
|
|
24
|
+
client = ROARClient(identity, signing_secret="shared-secret")
|
|
25
|
+
response = await client.send_remote(
|
|
26
|
+
to_agent_id=other_identity.did,
|
|
27
|
+
intent=MessageIntent.DELEGATE,
|
|
28
|
+
content={"task": "review"},
|
|
29
|
+
)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
__version__ = "0.2.0"
|
|
33
|
+
__author__ = "kdairatchi"
|
|
34
|
+
__spec_version__ = "0.2.0"
|
|
35
|
+
|
|
36
|
+
from .types import (
|
|
37
|
+
# Layer 1
|
|
38
|
+
AgentIdentity,
|
|
39
|
+
AgentCapability,
|
|
40
|
+
AgentCard,
|
|
41
|
+
# Layer 2
|
|
42
|
+
DiscoveryEntry,
|
|
43
|
+
AgentDirectory,
|
|
44
|
+
# Layer 3
|
|
45
|
+
TransportType,
|
|
46
|
+
ConnectionConfig,
|
|
47
|
+
# Layer 4
|
|
48
|
+
MessageIntent,
|
|
49
|
+
ROARMessage,
|
|
50
|
+
# Layer 5
|
|
51
|
+
StreamEventType,
|
|
52
|
+
StreamEvent,
|
|
53
|
+
# Adapters
|
|
54
|
+
MCPAdapter,
|
|
55
|
+
A2AAdapter,
|
|
56
|
+
)
|
|
57
|
+
from .client import ROARClient
|
|
58
|
+
from .server import ROARServer
|
|
59
|
+
from .streaming import EventBus, StreamFilter, Subscription
|
|
60
|
+
from .signing import generate_keypair, sign_ed25519, verify_ed25519
|
|
61
|
+
from .delegation import DelegationToken, issue_token, verify_token
|
|
62
|
+
from .adapters import ACPAdapter
|
|
63
|
+
from .adapters.detect import detect_protocol, ProtocolType
|
|
64
|
+
from .hub import ROARHub
|
|
65
|
+
from .did_document import DIDDocument, VerificationMethod, ServiceEndpoint
|
|
66
|
+
from .did_key import DIDKeyMethod, DIDKeyIdentity
|
|
67
|
+
from .did_web import DIDWebMethod, DIDWebIdentity
|
|
68
|
+
from .sqlite_directory import SQLiteAgentDirectory
|
|
69
|
+
from .discovery_cache import DiscoveryCache
|
|
70
|
+
from .dedup import IdempotencyGuard
|
|
71
|
+
from .autonomy import AutonomyLevel, CapabilityDelegation, RuntimeToken
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
# Layer 1
|
|
75
|
+
"AgentIdentity",
|
|
76
|
+
"AgentCapability",
|
|
77
|
+
"AgentCard",
|
|
78
|
+
# Layer 2
|
|
79
|
+
"DiscoveryEntry",
|
|
80
|
+
"AgentDirectory",
|
|
81
|
+
# Layer 3
|
|
82
|
+
"TransportType",
|
|
83
|
+
"ConnectionConfig",
|
|
84
|
+
# Layer 4
|
|
85
|
+
"MessageIntent",
|
|
86
|
+
"ROARMessage",
|
|
87
|
+
# Layer 5
|
|
88
|
+
"StreamEventType",
|
|
89
|
+
"StreamEvent",
|
|
90
|
+
# Adapters
|
|
91
|
+
"MCPAdapter",
|
|
92
|
+
"A2AAdapter",
|
|
93
|
+
"ACPAdapter",
|
|
94
|
+
"detect_protocol",
|
|
95
|
+
"ProtocolType",
|
|
96
|
+
# Client / Server / Hub
|
|
97
|
+
"ROARClient",
|
|
98
|
+
"ROARServer",
|
|
99
|
+
"ROARHub",
|
|
100
|
+
# Streaming
|
|
101
|
+
"EventBus",
|
|
102
|
+
"StreamFilter",
|
|
103
|
+
"Subscription",
|
|
104
|
+
# Ed25519 signing
|
|
105
|
+
"generate_keypair",
|
|
106
|
+
"sign_ed25519",
|
|
107
|
+
"verify_ed25519",
|
|
108
|
+
# Delegation tokens (cryptographic, portable)
|
|
109
|
+
"DelegationToken",
|
|
110
|
+
"issue_token",
|
|
111
|
+
"verify_token",
|
|
112
|
+
# DID methods
|
|
113
|
+
"DIDDocument",
|
|
114
|
+
"VerificationMethod",
|
|
115
|
+
"ServiceEndpoint",
|
|
116
|
+
"DIDKeyMethod",
|
|
117
|
+
"DIDKeyIdentity",
|
|
118
|
+
"DIDWebMethod",
|
|
119
|
+
"DIDWebIdentity",
|
|
120
|
+
# Persistent discovery
|
|
121
|
+
"SQLiteAgentDirectory",
|
|
122
|
+
"DiscoveryCache",
|
|
123
|
+
# Autonomy model (runtime policy enforcement)
|
|
124
|
+
"AutonomyLevel",
|
|
125
|
+
"CapabilityDelegation",
|
|
126
|
+
"RuntimeToken",
|
|
127
|
+
# Deduplication
|
|
128
|
+
"IdempotencyGuard",
|
|
129
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Python 3.10 compatibility shim for StrEnum (added in 3.11)
|
|
2
|
+
try:
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
except ImportError:
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
class StrEnum(str, Enum): # type: ignore[no-redef]
|
|
8
|
+
"""Backport of Python 3.11 StrEnum for Python 3.10."""
|
|
9
|
+
|
|
10
|
+
def __str__(self) -> str:
|
|
11
|
+
return self.value
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ROAR Protocol — protocol adapters for backward compatibility.
|
|
3
|
+
|
|
4
|
+
Available adapters:
|
|
5
|
+
MCPAdapter — translate MCP tool calls ↔ ROARMessage (in roar_sdk.types)
|
|
6
|
+
A2AAdapter — translate A2A tasks ↔ ROARMessage (in roar_sdk.types)
|
|
7
|
+
ACPAdapter — translate ACP sessions ↔ ROARMessage (in roar_sdk.adapters.acp)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .acp import ACPAdapter
|
|
11
|
+
|
|
12
|
+
__all__ = ["ACPAdapter"]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ROAR ACP Adapter — translate between ACP (Agent Communication Protocol) and ROAR.
|
|
3
|
+
|
|
4
|
+
ACP (IBM/BeeAI) is a session-based HTTP protocol for IDE-to-agent communication.
|
|
5
|
+
It defines sessions, messages, and responses but has no identity, signing, or
|
|
6
|
+
federation layer.
|
|
7
|
+
|
|
8
|
+
Mapping:
|
|
9
|
+
ACP session start → ROARMessage(intent=NOTIFY, payload={"event": "session.start"})
|
|
10
|
+
ACP message → ROARMessage(intent=ASK) if awaiting human input
|
|
11
|
+
→ ROARMessage(intent=UPDATE) if reporting progress
|
|
12
|
+
ACP response → ROARMessage(intent=RESPOND)
|
|
13
|
+
ACP session end → ROARMessage(intent=NOTIFY, payload={"event": "session.end"})
|
|
14
|
+
|
|
15
|
+
ACP wire format (simplified):
|
|
16
|
+
POST /sessions → create session, returns session_id
|
|
17
|
+
POST /sessions/{id}/runs → send message, returns run_id + response stream
|
|
18
|
+
GET /sessions/{id} → get session state
|
|
19
|
+
|
|
20
|
+
Usage::
|
|
21
|
+
|
|
22
|
+
from roar_sdk.adapters import ACPAdapter
|
|
23
|
+
from roar_sdk import AgentIdentity, MessageIntent
|
|
24
|
+
|
|
25
|
+
ide = AgentIdentity(display_name="vscode", agent_type="ide")
|
|
26
|
+
agent = AgentIdentity(display_name="my-agent")
|
|
27
|
+
|
|
28
|
+
# Translate an incoming ACP message to a ROARMessage
|
|
29
|
+
acp_msg = {"content": "Explain this function", "role": "user"}
|
|
30
|
+
roar_msg = ACPAdapter.acp_to_roar(acp_msg, from_agent=ide, to_agent=agent)
|
|
31
|
+
|
|
32
|
+
# Translate a ROARMessage back to ACP response format
|
|
33
|
+
acp_response = ACPAdapter.roar_to_acp(roar_msg)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from typing import Any, Dict, List, Optional
|
|
39
|
+
|
|
40
|
+
from ..types import AgentIdentity, MessageIntent, ROARMessage
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ACPAdapter:
|
|
44
|
+
"""Translate between ACP sessions/messages and ROAR messages."""
|
|
45
|
+
|
|
46
|
+
# ── ACP → ROAR ──────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def acp_message_to_roar(
|
|
50
|
+
acp_message: Dict[str, Any],
|
|
51
|
+
from_agent: AgentIdentity,
|
|
52
|
+
to_agent: AgentIdentity,
|
|
53
|
+
session_id: str = "",
|
|
54
|
+
) -> ROARMessage:
|
|
55
|
+
"""Translate an ACP message dict to a ROARMessage.
|
|
56
|
+
|
|
57
|
+
ACP message format::
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
"role": "user" | "assistant",
|
|
61
|
+
"content": str | list,
|
|
62
|
+
"attachments": [...] # optional
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
The intent is derived from role:
|
|
66
|
+
- "user" → ASK (user is requesting something from the agent)
|
|
67
|
+
- "assistant" → RESPOND (agent is replying)
|
|
68
|
+
"""
|
|
69
|
+
role = acp_message.get("role", "user")
|
|
70
|
+
content = acp_message.get("content", "")
|
|
71
|
+
attachments = acp_message.get("attachments", [])
|
|
72
|
+
|
|
73
|
+
intent = MessageIntent.ASK if role == "user" else MessageIntent.RESPOND
|
|
74
|
+
|
|
75
|
+
payload: Dict[str, Any] = {"content": content}
|
|
76
|
+
if attachments:
|
|
77
|
+
payload["attachments"] = attachments
|
|
78
|
+
|
|
79
|
+
context: Dict[str, Any] = {"protocol": "acp"}
|
|
80
|
+
if session_id:
|
|
81
|
+
context["session_id"] = session_id
|
|
82
|
+
|
|
83
|
+
return ROARMessage(
|
|
84
|
+
**{"from": from_agent, "to": to_agent},
|
|
85
|
+
intent=intent,
|
|
86
|
+
payload=payload,
|
|
87
|
+
context=context,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def acp_session_event_to_roar(
|
|
92
|
+
event: str, # "start" | "end"
|
|
93
|
+
from_agent: AgentIdentity,
|
|
94
|
+
to_agent: AgentIdentity,
|
|
95
|
+
session_id: str = "",
|
|
96
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
97
|
+
) -> ROARMessage:
|
|
98
|
+
"""Translate an ACP session lifecycle event to a ROARMessage."""
|
|
99
|
+
return ROARMessage(
|
|
100
|
+
**{"from": from_agent, "to": to_agent},
|
|
101
|
+
intent=MessageIntent.NOTIFY,
|
|
102
|
+
payload={"event": f"session.{event}", **(metadata or {})},
|
|
103
|
+
context={"protocol": "acp", "session_id": session_id},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# ── ROAR → ACP ──────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def roar_to_acp_message(msg: ROARMessage) -> Dict[str, Any]:
|
|
110
|
+
"""Translate a ROARMessage to an ACP message dict.
|
|
111
|
+
|
|
112
|
+
Maps intent to ACP role:
|
|
113
|
+
RESPOND → "assistant"
|
|
114
|
+
ASK → "user" (agent asking human for input)
|
|
115
|
+
UPDATE → "assistant" with status metadata
|
|
116
|
+
NOTIFY → "assistant" with event metadata
|
|
117
|
+
* → "assistant"
|
|
118
|
+
"""
|
|
119
|
+
intent_to_role = {
|
|
120
|
+
MessageIntent.RESPOND: "assistant",
|
|
121
|
+
MessageIntent.ASK: "user",
|
|
122
|
+
MessageIntent.UPDATE: "assistant",
|
|
123
|
+
MessageIntent.NOTIFY: "assistant",
|
|
124
|
+
}
|
|
125
|
+
role = intent_to_role.get(msg.intent, "assistant")
|
|
126
|
+
|
|
127
|
+
# Prefer a "content" field, fall back to full payload as string
|
|
128
|
+
content = msg.payload.get("content") or msg.payload.get("result") or msg.payload
|
|
129
|
+
|
|
130
|
+
acp: Dict[str, Any] = {"role": role, "content": content}
|
|
131
|
+
|
|
132
|
+
# Carry attachments through if present
|
|
133
|
+
if "attachments" in msg.payload:
|
|
134
|
+
acp["attachments"] = msg.payload["attachments"]
|
|
135
|
+
|
|
136
|
+
return acp
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def roar_to_acp_run(msg: ROARMessage, run_id: str = "") -> Dict[str, Any]:
|
|
140
|
+
"""Translate a ROARMessage to an ACP run response (richer format)."""
|
|
141
|
+
import time
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
"run_id": run_id or msg.id,
|
|
145
|
+
"session_id": msg.context.get("session_id", ""),
|
|
146
|
+
"status": "completed" if msg.intent == MessageIntent.RESPOND else "in_progress",
|
|
147
|
+
"output": ACPAdapter.roar_to_acp_message(msg),
|
|
148
|
+
"metadata": {
|
|
149
|
+
"roar_intent": msg.intent,
|
|
150
|
+
"roar_message_id": msg.id,
|
|
151
|
+
"from_did": msg.from_identity.did,
|
|
152
|
+
"timestamp": msg.timestamp,
|
|
153
|
+
},
|
|
154
|
+
"created_at": time.time(),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# ── Agent Card ↔ ACP Agent ───────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def well_known_agent_to_card(
|
|
161
|
+
well_known: Dict[str, Any],
|
|
162
|
+
endpoint: str = "",
|
|
163
|
+
) -> Dict[str, Any]:
|
|
164
|
+
"""Convert an A2A/ACP /.well-known/agent.json to a ROAR AgentCard dict.
|
|
165
|
+
|
|
166
|
+
Returns a dict suitable for constructing an AgentCard + AgentIdentity.
|
|
167
|
+
"""
|
|
168
|
+
name = well_known.get("name", "unknown-agent")
|
|
169
|
+
description = well_known.get("description", "")
|
|
170
|
+
skills: List[str] = [
|
|
171
|
+
s.get("name", "") for s in well_known.get("skills", []) if s.get("name")
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
"identity": {
|
|
176
|
+
"did": "", # will be auto-generated
|
|
177
|
+
"display_name": name,
|
|
178
|
+
"agent_type": "agent",
|
|
179
|
+
"capabilities": skills,
|
|
180
|
+
"version": well_known.get("version", "1.0"),
|
|
181
|
+
"public_key": None,
|
|
182
|
+
},
|
|
183
|
+
"description": description,
|
|
184
|
+
"skills": skills,
|
|
185
|
+
"channels": well_known.get("supportedModes", []),
|
|
186
|
+
"endpoints": {"http": endpoint or well_known.get("url", "")},
|
|
187
|
+
"declared_capabilities": [],
|
|
188
|
+
"metadata": {
|
|
189
|
+
"protocol": "acp",
|
|
190
|
+
"original": well_known,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Protocol auto-detection — sniff incoming messages to identify their format.
|
|
3
|
+
|
|
4
|
+
Examines the structure of an incoming JSON message to determine whether
|
|
5
|
+
it's ROAR native, MCP (JSON-RPC 2.0), A2A, or ACP protocol format, then
|
|
6
|
+
routes to the appropriate adapter.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from roar_sdk.adapters.detect import detect_protocol, ProtocolType
|
|
11
|
+
|
|
12
|
+
msg = json.loads(raw_body)
|
|
13
|
+
protocol = detect_protocol(msg)
|
|
14
|
+
|
|
15
|
+
if protocol == ProtocolType.ROAR:
|
|
16
|
+
roar_msg = ROARMessage.model_validate(msg)
|
|
17
|
+
elif protocol == ProtocolType.MCP:
|
|
18
|
+
roar_msg = MCPAdapter.mcp_to_roar(...)
|
|
19
|
+
elif protocol == ProtocolType.A2A:
|
|
20
|
+
roar_msg = A2AAdapter.a2a_task_to_roar(...)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from enum import Enum
|
|
26
|
+
from typing import Any, Dict
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ProtocolType(str, Enum):
|
|
30
|
+
"""Detected protocol type."""
|
|
31
|
+
|
|
32
|
+
ROAR = "roar"
|
|
33
|
+
MCP = "mcp"
|
|
34
|
+
A2A = "a2a"
|
|
35
|
+
ACP = "acp"
|
|
36
|
+
UNKNOWN = "unknown"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_MCP_METHOD_PREFIXES = (
|
|
40
|
+
"tools/",
|
|
41
|
+
"resources/",
|
|
42
|
+
"prompts/",
|
|
43
|
+
"completion/",
|
|
44
|
+
"initialize",
|
|
45
|
+
"notifications/",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
_A2A_METHOD_PREFIXES = ("tasks/", "agent/")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def detect_protocol(message: Dict[str, Any]) -> ProtocolType:
|
|
52
|
+
"""Detect the protocol of an incoming message.
|
|
53
|
+
|
|
54
|
+
Detection heuristics (in priority order):
|
|
55
|
+
1. ROAR: Has "roar" version field and "intent" field
|
|
56
|
+
2. ACP: Has "role" field and "content" field (ACP message body)
|
|
57
|
+
3. MCP: JSON-RPC 2.0 with MCP method prefix
|
|
58
|
+
4. A2A: JSON-RPC 2.0 with tasks/ or agent/ method prefix, or task envelope
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
message: The raw JSON message dict.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The detected ProtocolType.
|
|
65
|
+
"""
|
|
66
|
+
# ROAR native
|
|
67
|
+
if "roar" in message and "intent" in message:
|
|
68
|
+
return ProtocolType.ROAR
|
|
69
|
+
|
|
70
|
+
# ACP message (session-based, role/content structure)
|
|
71
|
+
if "role" in message and "content" in message:
|
|
72
|
+
return ProtocolType.ACP
|
|
73
|
+
|
|
74
|
+
# JSON-RPC based protocols
|
|
75
|
+
if message.get("jsonrpc") == "2.0":
|
|
76
|
+
method = message.get("method", "")
|
|
77
|
+
|
|
78
|
+
if any(method.startswith(p) for p in _A2A_METHOD_PREFIXES):
|
|
79
|
+
return ProtocolType.A2A
|
|
80
|
+
|
|
81
|
+
if any(method.startswith(p) for p in _MCP_METHOD_PREFIXES):
|
|
82
|
+
return ProtocolType.MCP
|
|
83
|
+
|
|
84
|
+
# Infer from result structure
|
|
85
|
+
result = message.get("result", {})
|
|
86
|
+
if isinstance(result, dict):
|
|
87
|
+
if "status" in result and "id" in result:
|
|
88
|
+
return ProtocolType.A2A
|
|
89
|
+
if "tools" in result or "resources" in result:
|
|
90
|
+
return ProtocolType.MCP
|
|
91
|
+
|
|
92
|
+
# A2A task envelope (no jsonrpc wrapper)
|
|
93
|
+
if "status" in message and "id" in message and "artifacts" in message:
|
|
94
|
+
return ProtocolType.A2A
|
|
95
|
+
|
|
96
|
+
return ProtocolType.UNKNOWN
|