Skip to content

Commit e0de654

Browse files
gcmsgclaude
andcommitted
feat: add multi-platform adapter interface with OpenClaw, IronClaw, and Bridge adapters
Refactor OpenClaw-specific integration into a platform-agnostic adapter interface (platform.Adapter) supporting multiple AI orchestration platforms. - Add platform.Adapter interface: Connect, SendChat, InjectNotification, SetOutboundHandler - Move agent/openclaw/ → agent/platform/openclaw/ implementing Adapter - Add agent/platform/ironclaw/ adapter (HTTP/SSE for Axum gateway) - Add agent/platform/bridge/ adapter (local WebSocket for nanobot/PicoClaw) - Add shared helpers: SessionKeyForPeer, ParsePeerFromSessionKey, FormatNotification - Update agent.go to use platform.Adapter instead of openclaw.Client Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 38c201a commit e0de654

File tree

11 files changed

+793
-170
lines changed

11 files changed

+793
-170
lines changed

agent.go

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ import (
1313
"github.com/peerclaw/peerclaw-agent/conn"
1414
"github.com/peerclaw/peerclaw-agent/discovery"
1515
"github.com/peerclaw/peerclaw-agent/filetransfer"
16-
"github.com/peerclaw/peerclaw-agent/openclaw"
1716
"github.com/peerclaw/peerclaw-agent/peer"
18-
"github.com/peerclaw/peerclaw-agent/sdkversion"
17+
"github.com/peerclaw/peerclaw-agent/platform"
1918
"github.com/peerclaw/peerclaw-agent/security"
2019
pcsignaling "github.com/peerclaw/peerclaw-agent/signaling"
2120
"github.com/peerclaw/peerclaw-agent/transport"
@@ -119,10 +118,10 @@ type Options struct {
119118
// (done, failed, or cancelled).
120119
OnFileTransferComplete func(info filetransfer.TransferInfo)
121120

122-
// OpenClaw holds optional OpenClaw gateway integration settings.
123-
// When set, the agent connects to OpenClaw and forwards P2P messages
124-
// and server notifications to OpenClaw conversations.
125-
OpenClaw *openclaw.Config
121+
// Platform is an optional AI orchestration platform adapter.
122+
// When set, the agent forwards P2P messages and server notifications
123+
// to the platform and routes AI responses back via P2P.
124+
Platform platform.Adapter
126125

127126
// SkipRegistration skips server registration on Start(). Use when the agent
128127
// is already registered and you only need P2P connectivity.
@@ -155,7 +154,7 @@ type Agent struct {
155154
handler MessageHandler
156155
connRequestHandler ConnectionRequestHandler
157156
notificationHandler func(n *NotificationPayload)
158-
openclawClient *openclaw.Client
157+
platformAdapter platform.Adapter
159158
connManager *conn.Manager
160159
mailbox *transport.Mailbox
161160
fileTransfer *filetransfer.Manager
@@ -536,37 +535,37 @@ func (a *Agent) Start(ctx context.Context) error {
536535
}
537536
}
538537

539-
// Initialize OpenClaw gateway integration.
540-
if a.opts.OpenClaw != nil {
541-
oc := openclaw.NewClient(*a.opts.OpenClaw, a.agentID, a.opts.Name, sdkversion.Version, a.logger)
542-
oc.SetOutboundHandler(func(sessionKey, text string) {
543-
peerID := openclaw.ParsePeerFromSessionKey(sessionKey)
538+
// Initialize platform adapter integration.
539+
if a.opts.Platform != nil {
540+
pa := a.opts.Platform
541+
pa.SetOutboundHandler(func(sessionKey, text string) {
542+
peerID := platform.ParsePeerFromSessionKey(sessionKey)
544543
if peerID == "" {
545544
return
546545
}
547546
env := envelope.New(a.agentID, peerID, "peerclaw", []byte(text))
548547
_ = a.Send(context.Background(), env)
549548
})
550-
if err := oc.Connect(ctx); err != nil {
551-
a.logger.Warn("openclaw connect failed", "error", err)
549+
if err := pa.Connect(ctx); err != nil {
550+
a.logger.Warn("platform connect failed", "platform", pa.Name(), "error", err)
552551
} else {
553-
a.openclawClient = oc
552+
a.platformAdapter = pa
554553
}
555554

556-
// Forward notifications to OpenClaw.
557-
if a.openclawClient != nil {
555+
// Forward notifications to platform.
556+
if a.platformAdapter != nil {
558557
a.OnNotification(func(n *NotificationPayload) {
559-
text := openclaw.FormatNotification(n.Severity, n.Title, n.Body)
560-
_ = a.openclawClient.ChatInject(ctx, "peerclaw:notifications", text, "peerclaw-notification")
558+
text := platform.FormatNotification(n.Severity, n.Title, n.Body)
559+
_ = a.platformAdapter.InjectNotification(ctx, platform.NotificationSessionKey, text, "peerclaw-notification")
561560
})
562561
}
563562

564-
// Forward P2P messages to OpenClaw.
565-
if a.openclawClient != nil {
563+
// Forward P2P messages to platform.
564+
if a.platformAdapter != nil {
566565
prevHandler := a.handler
567566
a.OnMessage(func(msgCtx context.Context, env *envelope.Envelope) {
568-
sessionKey := openclaw.SessionKeyForPeer(env.Source)
569-
_ = a.openclawClient.ChatSend(msgCtx, sessionKey, string(env.Payload))
567+
sessionKey := platform.SessionKeyForPeer(env.Source)
568+
_ = a.platformAdapter.SendChat(msgCtx, sessionKey, string(env.Payload))
570569
if prevHandler != nil {
571570
prevHandler(msgCtx, env)
572571
}
@@ -630,9 +629,9 @@ func (a *Agent) Stop(ctx context.Context) error {
630629
}
631630
}
632631

633-
// Close OpenClaw gateway.
634-
if a.openclawClient != nil {
635-
a.openclawClient.Close()
632+
// Close platform adapter.
633+
if a.platformAdapter != nil {
634+
a.platformAdapter.Close()
636635
}
637636

638637
// Close signaling and peers.

openclaw/format.go

Lines changed: 0 additions & 38 deletions
This file was deleted.

platform/adapter.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Package platform defines the adapter interface for integrating the PeerClaw
2+
// agent with AI orchestration platforms (OpenClaw, IronClaw, etc.).
3+
package platform
4+
5+
import "context"
6+
7+
// OutboundHandler is called when the platform produces a final AI response.
8+
// sessionKey identifies the conversation; text is the response content.
9+
type OutboundHandler func(sessionKey, text string)
10+
11+
// Adapter is the interface that all platform integrations must implement.
12+
// It abstracts the bidirectional bridge between PeerClaw P2P messaging
13+
// and an AI orchestration platform's conversation system.
14+
type Adapter interface {
15+
// Name returns the platform identifier (e.g., "openclaw", "ironclaw", "bridge").
16+
Name() string
17+
18+
// Connect establishes the connection to the platform.
19+
Connect(ctx context.Context) error
20+
21+
// Close shuts down the platform connection.
22+
Close() error
23+
24+
// SendChat forwards a P2P message into the platform for AI processing.
25+
// The response arrives asynchronously via the OutboundHandler.
26+
SendChat(ctx context.Context, sessionKey, message string) error
27+
28+
// InjectNotification inserts a notification into a platform conversation
29+
// without triggering AI processing.
30+
InjectNotification(ctx context.Context, sessionKey, message, label string) error
31+
32+
// SetOutboundHandler registers the callback for AI responses from the platform.
33+
SetOutboundHandler(handler OutboundHandler)
34+
}
35+
36+
// SessionKeyForPeer returns the platform session key for a DM with the given peer.
37+
func SessionKeyForPeer(peerAgentID string) string {
38+
return "peerclaw:dm:" + peerAgentID
39+
}
40+
41+
// ParsePeerFromSessionKey extracts the peer agent ID from a session key.
42+
// Returns empty string if the key doesn't match the expected prefix.
43+
func ParsePeerFromSessionKey(key string) string {
44+
const prefix = "peerclaw:dm:"
45+
if len(key) > len(prefix) && key[:len(prefix)] == prefix {
46+
return key[len(prefix):]
47+
}
48+
return ""
49+
}
50+
51+
// NotificationSessionKey is the session key used for server notification messages.
52+
const NotificationSessionKey = "peerclaw:notifications"
53+
54+
// FormatNotification formats a notification for display in a platform conversation.
55+
func FormatNotification(severity, title, body string) string {
56+
tag := severity
57+
if tag == "" {
58+
tag = "INFO"
59+
}
60+
// Use uppercase for the tag.
61+
result := make([]byte, 0, len(tag)+len(title)+len(body)+6)
62+
result = append(result, '[')
63+
for _, b := range []byte(tag) {
64+
if b >= 'a' && b <= 'z' {
65+
b -= 'a' - 'A'
66+
}
67+
result = append(result, b)
68+
}
69+
result = append(result, "] "...)
70+
result = append(result, title...)
71+
result = append(result, ": "...)
72+
result = append(result, body...)
73+
return string(result)
74+
}

0 commit comments

Comments
 (0)