agent-relay 3.2.13 → 3.2.14
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.
- package/README.md +1 -0
- package/dist/index.cjs +21 -8
- package/package.json +12 -11
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/sdk/dist/workflows/runner.js +1 -1
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/sdk/src/workflows/runner.ts +1 -1
- package/packages/sdk-py/pyproject.toml +1 -1
- package/packages/sdk-swift/Package.swift +26 -0
- package/packages/sdk-swift/README.md +39 -0
- package/packages/sdk-swift/Sources/AgentRelaySDK/RelayCast.swift +405 -0
- package/packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift +220 -0
- package/packages/sdk-swift/Sources/AgentRelaySDK/RelayTypes.swift +435 -0
- package/packages/sdk-swift/Tests/AgentRelaySDKTests/AgentRelaySDKTests.swift +15 -0
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/sdk",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"typescript": "^5.7.3"
|
|
103
103
|
},
|
|
104
104
|
"dependencies": {
|
|
105
|
-
"@agent-relay/config": "3.2.
|
|
105
|
+
"@agent-relay/config": "3.2.14",
|
|
106
106
|
"@relaycast/sdk": "^1.0.0",
|
|
107
107
|
"@sinclair/typebox": "^0.34.48",
|
|
108
108
|
"chalk": "^4.1.2",
|
|
@@ -5089,7 +5089,7 @@ export class WorkflowRunner {
|
|
|
5089
5089
|
channels: agentChannels,
|
|
5090
5090
|
task: taskWithExit,
|
|
5091
5091
|
idleThresholdSecs: agentDef.constraints?.idleThresholdSecs,
|
|
5092
|
-
cwd: agentCwd
|
|
5092
|
+
cwd: agentCwd,
|
|
5093
5093
|
});
|
|
5094
5094
|
|
|
5095
5095
|
// Re-key PTY maps if broker assigned a different name than requested
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "AgentRelaySDK",
|
|
6
|
+
platforms: [
|
|
7
|
+
.macOS(.v13),
|
|
8
|
+
.iOS(.v16),
|
|
9
|
+
.watchOS(.v9),
|
|
10
|
+
.tvOS(.v16)
|
|
11
|
+
],
|
|
12
|
+
products: [
|
|
13
|
+
.library(name: "AgentRelaySDK", targets: ["AgentRelaySDK"])
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "AgentRelaySDK",
|
|
18
|
+
path: "Sources/AgentRelaySDK"
|
|
19
|
+
),
|
|
20
|
+
.testTarget(
|
|
21
|
+
name: "AgentRelaySDKTests",
|
|
22
|
+
dependencies: ["AgentRelaySDK"],
|
|
23
|
+
path: "Tests/AgentRelaySDKTests"
|
|
24
|
+
)
|
|
25
|
+
]
|
|
26
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# AgentRelaySDK
|
|
2
|
+
|
|
3
|
+
Native Swift SDK for the Agent Relay broker.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add the package in Swift Package Manager:
|
|
8
|
+
|
|
9
|
+
```swift
|
|
10
|
+
.package(url: "https://github.com/AgentWorkforce/relay.git", revision: "0a2c878748dc34af8b617c8da5ce70af447dfa37")
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> Temporary until the SDK is released under a stable tag.
|
|
14
|
+
|
|
15
|
+
Then depend on `AgentRelaySDK`.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```swift
|
|
20
|
+
import AgentRelaySDK
|
|
21
|
+
|
|
22
|
+
let relay = RelayCast(apiKey: "rk_live_...")
|
|
23
|
+
let channel = relay.channel("wf-my-workflow")
|
|
24
|
+
try await channel.subscribe()
|
|
25
|
+
try await channel.post("Hello from Swift")
|
|
26
|
+
|
|
27
|
+
for await event in channel.events {
|
|
28
|
+
print("\(event.from): \(event.body)")
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
|
|
34
|
+
- `RelayCast(apiKey:baseURL:)`
|
|
35
|
+
- `channel(_:) -> Channel`
|
|
36
|
+
- `registerOrRotate(name:)`
|
|
37
|
+
- `AgentRegistration.asClient()`
|
|
38
|
+
- `AgentClient.post(to:message:)`
|
|
39
|
+
- `AgentClient.dm(to:message:)`
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public enum RelayError: Error, Sendable {
|
|
4
|
+
case invalidBaseURL(String)
|
|
5
|
+
case connectionFailed(String)
|
|
6
|
+
case handshakeFailed(String)
|
|
7
|
+
case protocolError(code: String, message: String, retryable: Bool)
|
|
8
|
+
case encodingFailed(String)
|
|
9
|
+
case decodingFailed(String)
|
|
10
|
+
case notConnected
|
|
11
|
+
case unsupported(String)
|
|
12
|
+
case timeout(String)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Connection state changes emitted by the SDK.
|
|
16
|
+
public enum ConnectionStateChange: Sendable {
|
|
17
|
+
case connected
|
|
18
|
+
case disconnected
|
|
19
|
+
case reconnecting(attempt: Int)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public struct RelayChannelEvent: Sendable {
|
|
23
|
+
public let from: String
|
|
24
|
+
public let body: String
|
|
25
|
+
public let threadId: String?
|
|
26
|
+
public let timestamp: Date
|
|
27
|
+
|
|
28
|
+
public init(from: String, body: String, threadId: String?, timestamp: Date = Date()) {
|
|
29
|
+
self.from = from
|
|
30
|
+
self.body = body
|
|
31
|
+
self.threadId = threadId
|
|
32
|
+
self.timestamp = timestamp
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public struct AgentRegistration: Sendable {
|
|
37
|
+
public let agentName: String
|
|
38
|
+
public let token: String
|
|
39
|
+
private let factory: @Sendable (String, String) -> AgentClient
|
|
40
|
+
|
|
41
|
+
public init(agentName: String, token: String, factory: @escaping @Sendable (String, String) -> AgentClient) {
|
|
42
|
+
self.agentName = agentName
|
|
43
|
+
self.token = token
|
|
44
|
+
self.factory = factory
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public func asClient() -> AgentClient {
|
|
48
|
+
factory(agentName, token)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
actor RelayCore {
|
|
53
|
+
let apiKey: String
|
|
54
|
+
let transport: RelayTransport
|
|
55
|
+
let encoder = JSONEncoder()
|
|
56
|
+
let decoder = JSONDecoder()
|
|
57
|
+
|
|
58
|
+
private var handshakeInFlight = false
|
|
59
|
+
private var handshakeGeneration = 0
|
|
60
|
+
private var handshakeContinuations: [CheckedContinuation<Void, Error>] = []
|
|
61
|
+
private var routerTask: Task<Void, Never>?
|
|
62
|
+
private var channelContinuations: [String: [AsyncStream<RelayChannelEvent>.Continuation]] = [:]
|
|
63
|
+
private var brokerEventContinuations: [AsyncStream<BrokerEvent>.Continuation] = []
|
|
64
|
+
private var inboundMessageContinuations: [AsyncStream<InboundMessage>.Continuation] = []
|
|
65
|
+
private var connectionStateContinuations: [AsyncStream<ConnectionStateChange>.Continuation] = []
|
|
66
|
+
|
|
67
|
+
init(apiKey: String, transport: RelayTransport) {
|
|
68
|
+
self.apiKey = apiKey
|
|
69
|
+
self.transport = transport
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func configureTransportCallbacks() async {
|
|
73
|
+
await transport.setOnConnect { [weak self] in
|
|
74
|
+
await self?.transportDidConnect()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func ensureConnected() async throws {
|
|
79
|
+
if routerTask == nil || routerTask?.isCancelled == true {
|
|
80
|
+
routerTask = Task { [weak self] in await self?.routeFrames() }
|
|
81
|
+
}
|
|
82
|
+
if handshakeInFlight {
|
|
83
|
+
return try await waitForHandshake()
|
|
84
|
+
}
|
|
85
|
+
handshakeInFlight = true
|
|
86
|
+
handshakeGeneration &+= 1
|
|
87
|
+
try await transport.connect()
|
|
88
|
+
try await send(.hello(HelloPayload(clientName: "AgentRelaySDK.Swift", clientVersion: "0.1.0", apiKey: apiKey)))
|
|
89
|
+
try await waitForHandshake()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func transportDidConnect() async {
|
|
93
|
+
if handshakeInFlight {
|
|
94
|
+
finishHandshake(with: RelayError.connectionFailed("Transport reconnected before previous handshake completed"))
|
|
95
|
+
}
|
|
96
|
+
handshakeInFlight = true
|
|
97
|
+
handshakeGeneration &+= 1
|
|
98
|
+
notifyConnectionState(.connected)
|
|
99
|
+
do {
|
|
100
|
+
try await send(.hello(HelloPayload(clientName: "AgentRelaySDK.Swift", clientVersion: "0.1.0", apiKey: apiKey)))
|
|
101
|
+
} catch {
|
|
102
|
+
finishHandshake(with: error)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func registerChannelContinuation(_ continuation: AsyncStream<RelayChannelEvent>.Continuation, for channel: String) {
|
|
107
|
+
channelContinuations[channel, default: []].append(continuation)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func registerBrokerEventContinuation(_ continuation: AsyncStream<BrokerEvent>.Continuation) {
|
|
111
|
+
brokerEventContinuations.append(continuation)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func registerInboundMessageContinuation(_ continuation: AsyncStream<InboundMessage>.Continuation) {
|
|
115
|
+
inboundMessageContinuations.append(continuation)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func registerConnectionStateContinuation(_ continuation: AsyncStream<ConnectionStateChange>.Continuation) {
|
|
119
|
+
connectionStateContinuations.append(continuation)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func sendChannelPost(channel: String, text: String) async throws {
|
|
123
|
+
try await ensureConnected()
|
|
124
|
+
try await send(.sendMessage(SendMessagePayload(to: channel, text: text, from: nil, threadId: nil, workspaceId: nil, workspaceAlias: nil, priority: nil, data: nil)))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func sendAgentMessage(from agentName: String, to target: String, text: String) async throws {
|
|
128
|
+
try await ensureConnected()
|
|
129
|
+
try await send(.sendMessage(SendMessagePayload(to: target, text: text, from: agentName, threadId: nil, workspaceId: nil, workspaceAlias: nil, priority: nil, data: nil)))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
func spawnAgent(_ spec: AgentSpec, initialTask: String? = nil, skipRelayPrompt: Bool? = nil) async throws {
|
|
133
|
+
try await ensureConnected()
|
|
134
|
+
try await send(.spawnAgent(SpawnAgentPayload(agent: spec, initialTask: initialTask, skipRelayPrompt: skipRelayPrompt)))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func releaseAgent(name: String, reason: String? = nil) async throws {
|
|
138
|
+
try await ensureConnected()
|
|
139
|
+
try await send(.releaseAgent(ReleaseAgentPayload(name: name, reason: reason)))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
func registerOrRotate(name: String) async throws -> AgentRegistration {
|
|
143
|
+
try await ensureConnected()
|
|
144
|
+
return AgentRegistration(agentName: name, token: name) { agentName, token in
|
|
145
|
+
AgentClient(core: self, agentName: agentName, token: token)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func disconnect() async {
|
|
150
|
+
routerTask?.cancel()
|
|
151
|
+
routerTask = nil
|
|
152
|
+
handshakeInFlight = false
|
|
153
|
+
let pendingHandshakes = handshakeContinuations
|
|
154
|
+
handshakeContinuations.removeAll()
|
|
155
|
+
for continuation in pendingHandshakes {
|
|
156
|
+
continuation.resume(throwing: RelayError.notConnected)
|
|
157
|
+
}
|
|
158
|
+
await transport.disconnect()
|
|
159
|
+
notifyConnectionState(.disconnected)
|
|
160
|
+
// Finish all event stream continuations
|
|
161
|
+
for continuation in brokerEventContinuations { continuation.finish() }
|
|
162
|
+
brokerEventContinuations.removeAll()
|
|
163
|
+
for continuation in inboundMessageContinuations { continuation.finish() }
|
|
164
|
+
inboundMessageContinuations.removeAll()
|
|
165
|
+
for continuations in channelContinuations.values {
|
|
166
|
+
for continuation in continuations { continuation.finish() }
|
|
167
|
+
}
|
|
168
|
+
channelContinuations.removeAll()
|
|
169
|
+
for continuation in connectionStateContinuations { continuation.finish() }
|
|
170
|
+
connectionStateContinuations.removeAll()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private func send(_ message: OutboundMessage) async throws {
|
|
174
|
+
do {
|
|
175
|
+
let data = try encoder.encode(message)
|
|
176
|
+
try await transport.send(data)
|
|
177
|
+
} catch let error as RelayTransport.TransportError {
|
|
178
|
+
switch error {
|
|
179
|
+
case .notConnected: throw RelayError.notConnected
|
|
180
|
+
case .connectionFailed(let message), .sendFailed(let message): throw RelayError.connectionFailed(message)
|
|
181
|
+
case .invalidResponse: throw RelayError.connectionFailed("Invalid response")
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
throw RelayError.encodingFailed(String(describing: error))
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private func waitForHandshake() async throws {
|
|
189
|
+
let generation = handshakeGeneration
|
|
190
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
191
|
+
handshakeContinuations.append(continuation)
|
|
192
|
+
Task { [weak self] in
|
|
193
|
+
try? await Task.sleep(for: .seconds(10))
|
|
194
|
+
await self?.failHandshakeIfPending(generation: generation, with: RelayError.timeout("Timed out waiting for hello_ack"))
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private func finishHandshake() {
|
|
200
|
+
handshakeInFlight = false
|
|
201
|
+
let continuations = handshakeContinuations
|
|
202
|
+
handshakeContinuations.removeAll()
|
|
203
|
+
for continuation in continuations {
|
|
204
|
+
continuation.resume(returning: ())
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private func finishHandshake(with error: Error) {
|
|
209
|
+
handshakeInFlight = false
|
|
210
|
+
let continuations = handshakeContinuations
|
|
211
|
+
handshakeContinuations.removeAll()
|
|
212
|
+
for continuation in continuations {
|
|
213
|
+
continuation.resume(throwing: error)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private func failHandshakeIfPending(generation: Int, with error: Error) {
|
|
218
|
+
guard handshakeInFlight, handshakeGeneration == generation else { return }
|
|
219
|
+
finishHandshake(with: error)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private func notifyConnectionState(_ state: ConnectionStateChange) {
|
|
223
|
+
for continuation in connectionStateContinuations {
|
|
224
|
+
continuation.yield(state)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private func routeFrames() async {
|
|
229
|
+
for await data in transport.inbound {
|
|
230
|
+
do {
|
|
231
|
+
let inbound = try decoder.decode(InboundMessage.self, from: data)
|
|
232
|
+
|
|
233
|
+
// Notify all raw inbound message subscribers
|
|
234
|
+
for continuation in inboundMessageContinuations {
|
|
235
|
+
continuation.yield(inbound)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
switch inbound {
|
|
239
|
+
case .helloAck:
|
|
240
|
+
finishHandshake()
|
|
241
|
+
case .event(let event):
|
|
242
|
+
// Notify all broker event subscribers
|
|
243
|
+
for continuation in brokerEventContinuations {
|
|
244
|
+
continuation.yield(event)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Route relay_inbound events to channel subscribers
|
|
248
|
+
if case .relayInbound(let relayEvent) = event {
|
|
249
|
+
let message = RelayChannelEvent(from: relayEvent.from, body: relayEvent.body, threadId: relayEvent.threadId)
|
|
250
|
+
for continuation in channelContinuations[relayEvent.target] ?? [] {
|
|
251
|
+
continuation.yield(message)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
case .deliverRelay(let delivery):
|
|
255
|
+
// Route relay deliveries to channel subscribers as RelayChannelEvents
|
|
256
|
+
let message = RelayChannelEvent(from: delivery.from, body: delivery.body, threadId: delivery.threadId)
|
|
257
|
+
for continuation in channelContinuations[delivery.target] ?? [] {
|
|
258
|
+
continuation.yield(message)
|
|
259
|
+
}
|
|
260
|
+
case .error(let error):
|
|
261
|
+
finishHandshake(with: RelayError.protocolError(code: error.code, message: error.message, retryable: error.retryable))
|
|
262
|
+
default:
|
|
263
|
+
break
|
|
264
|
+
}
|
|
265
|
+
} catch {
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Transport stream ended (disconnection)
|
|
270
|
+
notifyConnectionState(.disconnected)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
public final class RelayCast: @unchecked Sendable {
|
|
275
|
+
private let core: RelayCore
|
|
276
|
+
public let apiKey: String
|
|
277
|
+
public let baseURL: URL
|
|
278
|
+
|
|
279
|
+
public init(apiKey: String, baseURL: URL? = nil) {
|
|
280
|
+
self.apiKey = apiKey
|
|
281
|
+
let resolved = Self.resolveBaseURL(from: baseURL)
|
|
282
|
+
self.baseURL = resolved
|
|
283
|
+
let transport = RelayTransport(baseURL: resolved, authToken: apiKey)
|
|
284
|
+
self.core = RelayCore(apiKey: apiKey, transport: transport)
|
|
285
|
+
Task {
|
|
286
|
+
await self.core.configureTransportCallbacks()
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// Create a channel handle for subscribing and posting.
|
|
291
|
+
public func channel(_ name: String) -> Channel {
|
|
292
|
+
Channel(name: name, core: core)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/// Register (or re-register) an agent identity with the broker.
|
|
296
|
+
public func registerOrRotate(name: String) async throws -> AgentRegistration {
|
|
297
|
+
try await core.registerOrRotate(name: name)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// Create an agent client from an existing agent name and token.
|
|
301
|
+
public func `as`(agentName: String, token: String) -> AgentClient {
|
|
302
|
+
AgentClient(core: core, agentName: agentName, token: token)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/// Spawn a new agent process on the broker.
|
|
306
|
+
public func spawnAgent(_ spec: AgentSpec, initialTask: String? = nil, skipRelayPrompt: Bool? = nil) async throws {
|
|
307
|
+
try await core.spawnAgent(spec, initialTask: initialTask, skipRelayPrompt: skipRelayPrompt)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// Release (stop) a named agent on the broker.
|
|
311
|
+
public func releaseAgent(name: String, reason: String? = nil) async throws {
|
|
312
|
+
try await core.releaseAgent(name: name, reason: reason)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/// Disconnect from the broker and cancel all event streams.
|
|
316
|
+
public func disconnect() async {
|
|
317
|
+
await core.disconnect()
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/// Stream of all broker events (agent_spawned, worker_stream, delivery_*, etc.).
|
|
321
|
+
///
|
|
322
|
+
/// This provides full visibility into broker activity, suitable for building
|
|
323
|
+
/// agent dashboards, monitoring tools, or custom event routing.
|
|
324
|
+
///
|
|
325
|
+
/// Call `ensureConnected()` on a channel or register an agent first to start
|
|
326
|
+
/// receiving events.
|
|
327
|
+
public var brokerEvents: AsyncStream<BrokerEvent> {
|
|
328
|
+
AsyncStream<BrokerEvent> { continuation in
|
|
329
|
+
Task { await core.registerBrokerEventContinuation(continuation) }
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/// Stream of all raw inbound protocol messages.
|
|
334
|
+
///
|
|
335
|
+
/// This is the lowest-level event stream, including hello_ack, ok, error,
|
|
336
|
+
/// event, deliver_relay, worker_stream, worker_exited, and pong frames.
|
|
337
|
+
/// Use this when you need full protocol visibility.
|
|
338
|
+
public var inboundMessages: AsyncStream<InboundMessage> {
|
|
339
|
+
AsyncStream<InboundMessage> { continuation in
|
|
340
|
+
Task { await core.registerInboundMessageContinuation(continuation) }
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/// Stream of connection state changes (connected, disconnected, reconnecting).
|
|
345
|
+
public var connectionState: AsyncStream<ConnectionStateChange> {
|
|
346
|
+
AsyncStream<ConnectionStateChange> { continuation in
|
|
347
|
+
Task { await core.registerConnectionStateContinuation(continuation) }
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private static func resolveBaseURL(from baseURL: URL?) -> URL {
|
|
352
|
+
if let baseURL {
|
|
353
|
+
return baseURL
|
|
354
|
+
}
|
|
355
|
+
return URL(string: "http://localhost:3889")!
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
public final class Channel: @unchecked Sendable {
|
|
360
|
+
public let name: String
|
|
361
|
+
private let core: RelayCore
|
|
362
|
+
private let continuationRef: AsyncStream<RelayChannelEvent>.Continuation?
|
|
363
|
+
public let events: AsyncStream<RelayChannelEvent>
|
|
364
|
+
|
|
365
|
+
init(name: String, core: RelayCore) {
|
|
366
|
+
self.name = name
|
|
367
|
+
self.core = core
|
|
368
|
+
var continuation: AsyncStream<RelayChannelEvent>.Continuation?
|
|
369
|
+
self.events = AsyncStream<RelayChannelEvent> { incoming in
|
|
370
|
+
continuation = incoming
|
|
371
|
+
}
|
|
372
|
+
self.continuationRef = continuation
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
public func subscribe() async throws {
|
|
376
|
+
if let continuationRef {
|
|
377
|
+
await core.registerChannelContinuation(continuationRef, for: name)
|
|
378
|
+
}
|
|
379
|
+
try await core.ensureConnected()
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
public func post(_ text: String) async throws {
|
|
383
|
+
try await core.sendChannelPost(channel: name, text: text)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
public final class AgentClient: @unchecked Sendable {
|
|
388
|
+
private let core: RelayCore
|
|
389
|
+
public let agentName: String
|
|
390
|
+
public let token: String
|
|
391
|
+
|
|
392
|
+
init(core: RelayCore, agentName: String, token: String) {
|
|
393
|
+
self.core = core
|
|
394
|
+
self.agentName = agentName
|
|
395
|
+
self.token = token
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
public func post(to channel: String, message: String) async throws {
|
|
399
|
+
try await core.sendAgentMessage(from: agentName, to: channel, text: message)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
public func dm(to agentName: String, message: String) async throws {
|
|
403
|
+
try await core.sendAgentMessage(from: self.agentName, to: agentName, text: message)
|
|
404
|
+
}
|
|
405
|
+
}
|