agent-relay 3.2.15 → 3.2.17

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.
Files changed (140) hide show
  1. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  2. package/bin/agent-relay-broker-darwin-x64 +0 -0
  3. package/bin/agent-relay-broker-linux-arm64 +0 -0
  4. package/bin/agent-relay-broker-linux-x64 +0 -0
  5. package/dist/index.cjs +3865 -17179
  6. package/dist/src/cli/commands/setup.d.ts.map +1 -1
  7. package/dist/src/cli/commands/setup.js +2 -0
  8. package/dist/src/cli/commands/setup.js.map +1 -1
  9. package/package.json +8 -8
  10. package/packages/acp-bridge/package.json +2 -2
  11. package/packages/config/package.json +1 -1
  12. package/packages/hooks/package.json +4 -4
  13. package/packages/memory/package.json +2 -2
  14. package/packages/openclaw/package.json +2 -2
  15. package/packages/policy/package.json +2 -2
  16. package/packages/sdk/dist/broker-path.d.ts +19 -0
  17. package/packages/sdk/dist/broker-path.d.ts.map +1 -0
  18. package/packages/sdk/dist/broker-path.js +71 -0
  19. package/packages/sdk/dist/broker-path.js.map +1 -0
  20. package/packages/sdk/dist/cli-registry.d.ts.map +1 -1
  21. package/packages/sdk/dist/cli-registry.js +4 -0
  22. package/packages/sdk/dist/cli-registry.js.map +1 -1
  23. package/packages/sdk/dist/client.d.ts +6 -1
  24. package/packages/sdk/dist/client.d.ts.map +1 -1
  25. package/packages/sdk/dist/client.js +18 -0
  26. package/packages/sdk/dist/client.js.map +1 -1
  27. package/packages/sdk/dist/communicate/adapters/index.d.ts +0 -5
  28. package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -1
  29. package/packages/sdk/dist/communicate/adapters/index.js +0 -5
  30. package/packages/sdk/dist/communicate/adapters/index.js.map +1 -1
  31. package/packages/sdk/dist/communicate/adapters/pi.d.ts +0 -1
  32. package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -1
  33. package/packages/sdk/dist/communicate/adapters/pi.js +0 -4
  34. package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -1
  35. package/packages/sdk/dist/communicate/core.d.ts.map +1 -1
  36. package/packages/sdk/dist/communicate/core.js +2 -3
  37. package/packages/sdk/dist/communicate/core.js.map +1 -1
  38. package/packages/sdk/dist/communicate/index.d.ts +17 -1
  39. package/packages/sdk/dist/communicate/index.d.ts.map +1 -1
  40. package/packages/sdk/dist/communicate/index.js +40 -1
  41. package/packages/sdk/dist/communicate/index.js.map +1 -1
  42. package/packages/sdk/dist/communicate/transport.d.ts +0 -1
  43. package/packages/sdk/dist/communicate/transport.d.ts.map +1 -1
  44. package/packages/sdk/dist/communicate/transport.js +42 -134
  45. package/packages/sdk/dist/communicate/transport.js.map +1 -1
  46. package/packages/sdk/dist/http.d.ts +38 -0
  47. package/packages/sdk/dist/http.d.ts.map +1 -0
  48. package/packages/sdk/dist/http.js +60 -0
  49. package/packages/sdk/dist/http.js.map +1 -0
  50. package/packages/sdk/dist/protocol.d.ts +25 -0
  51. package/packages/sdk/dist/protocol.d.ts.map +1 -1
  52. package/packages/sdk/dist/relay.d.ts +26 -3
  53. package/packages/sdk/dist/relay.d.ts.map +1 -1
  54. package/packages/sdk/dist/relay.js +62 -4
  55. package/packages/sdk/dist/relay.js.map +1 -1
  56. package/packages/sdk/dist/workflows/api-executor.d.ts +16 -0
  57. package/packages/sdk/dist/workflows/api-executor.d.ts.map +1 -0
  58. package/packages/sdk/dist/workflows/api-executor.js +94 -0
  59. package/packages/sdk/dist/workflows/api-executor.js.map +1 -0
  60. package/packages/sdk/dist/workflows/builder.d.ts +14 -0
  61. package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
  62. package/packages/sdk/dist/workflows/builder.js +26 -0
  63. package/packages/sdk/dist/workflows/builder.js.map +1 -1
  64. package/packages/sdk/dist/workflows/cloud-runner.d.ts +15 -0
  65. package/packages/sdk/dist/workflows/cloud-runner.d.ts.map +1 -0
  66. package/packages/sdk/dist/workflows/cloud-runner.js +41 -0
  67. package/packages/sdk/dist/workflows/cloud-runner.js.map +1 -0
  68. package/packages/sdk/dist/workflows/index.d.ts +2 -0
  69. package/packages/sdk/dist/workflows/index.d.ts.map +1 -1
  70. package/packages/sdk/dist/workflows/index.js +1 -0
  71. package/packages/sdk/dist/workflows/index.js.map +1 -1
  72. package/packages/sdk/dist/workflows/run.d.ts.map +1 -1
  73. package/packages/sdk/dist/workflows/run.js +4 -0
  74. package/packages/sdk/dist/workflows/run.js.map +1 -1
  75. package/packages/sdk/dist/workflows/runner.d.ts +14 -0
  76. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  77. package/packages/sdk/dist/workflows/runner.js +169 -28
  78. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  79. package/packages/sdk/dist/workflows/types.d.ts +13 -3
  80. package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
  81. package/packages/sdk/dist/workflows/types.js +5 -1
  82. package/packages/sdk/dist/workflows/types.js.map +1 -1
  83. package/packages/sdk/dist/workflows/validator.d.ts.map +1 -1
  84. package/packages/sdk/dist/workflows/validator.js +12 -0
  85. package/packages/sdk/dist/workflows/validator.js.map +1 -1
  86. package/packages/sdk/package.json +13 -3
  87. package/packages/sdk/src/__tests__/channel-management.test.ts +131 -0
  88. package/packages/sdk/src/__tests__/communicate/core.test.ts +36 -88
  89. package/packages/sdk/src/__tests__/communicate/transport.test.ts +41 -80
  90. package/packages/sdk/src/__tests__/orchestration-upgrades.test.ts +120 -0
  91. package/packages/sdk/src/__tests__/relay-channel-ops.test.ts +121 -0
  92. package/packages/sdk/src/broker-path.ts +74 -0
  93. package/packages/sdk/src/cli-registry.ts +4 -0
  94. package/packages/sdk/src/client.ts +28 -0
  95. package/packages/sdk/src/communicate/adapters/index.ts +0 -5
  96. package/packages/sdk/src/communicate/adapters/pi.ts +1 -5
  97. package/packages/sdk/src/communicate/core.ts +6 -10
  98. package/packages/sdk/src/communicate/index.ts +57 -1
  99. package/packages/sdk/src/communicate/transport.ts +46 -177
  100. package/packages/sdk/src/http.ts +96 -0
  101. package/packages/sdk/src/protocol.ts +24 -0
  102. package/packages/sdk/src/relay.ts +93 -8
  103. package/packages/sdk/src/workflows/README.md +5 -2
  104. package/packages/sdk/src/workflows/api-executor.ts +108 -0
  105. package/packages/sdk/src/workflows/builder.ts +40 -0
  106. package/packages/sdk/src/workflows/cloud-runner.ts +56 -0
  107. package/packages/sdk/src/workflows/index.ts +2 -0
  108. package/packages/sdk/src/workflows/run.ts +5 -0
  109. package/packages/sdk/src/workflows/runner.ts +197 -30
  110. package/packages/sdk/src/workflows/types.ts +19 -4
  111. package/packages/sdk/src/workflows/validator.ts +15 -0
  112. package/packages/sdk-py/README.md +7 -0
  113. package/packages/sdk-py/pyproject.toml +1 -1
  114. package/packages/sdk-py/src/agent_relay/__init__.py +2 -0
  115. package/packages/sdk-py/src/agent_relay/builder.py +64 -7
  116. package/packages/sdk-py/src/agent_relay/client.py +4 -0
  117. package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +0 -9
  118. package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +5 -9
  119. package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +5 -7
  120. package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +3 -13
  121. package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +5 -2
  122. package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +5 -9
  123. package/packages/sdk-py/src/agent_relay/communicate/core.py +7 -24
  124. package/packages/sdk-py/src/agent_relay/communicate/transport.py +35 -212
  125. package/packages/sdk-py/src/agent_relay/communicate/types.py +1 -1
  126. package/packages/sdk-py/src/agent_relay/protocol.py +1 -0
  127. package/packages/sdk-py/src/agent_relay/relay.py +9 -1
  128. package/packages/sdk-py/src/agent_relay/types.py +1 -0
  129. package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +6 -6
  130. package/packages/sdk-py/tests/communicate/conftest.py +86 -233
  131. package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +2 -2
  132. package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +14 -24
  133. package/packages/sdk-py/tests/communicate/test_transport.py +65 -54
  134. package/packages/sdk-py/tests/test_builder.py +58 -0
  135. package/packages/sdk-py/tests/test_dry_run.py +215 -0
  136. package/packages/sdk-py/tests/test_send_message_mode.py +91 -0
  137. package/packages/telemetry/package.json +1 -1
  138. package/packages/trajectory/package.json +2 -2
  139. package/packages/user-directory/package.json +2 -2
  140. package/packages/utils/package.json +2 -2
@@ -1,7 +1,2 @@
1
1
  export { onRelay as onPiRelay } from './pi.js';
2
2
  export { onRelay as onClaudeRelay } from './claude-sdk.js';
3
- export { onRelay as onCrewAIRelay, onCrewRelay } from './crewai.js';
4
- export { onRelay as onOpenAIAgentsRelay } from './openai-agents.js';
5
- export { onRelay as onLangGraphRelay } from './langgraph.js';
6
- export { onRelay as onGoogleAdkRelay } from './google-adk.js';
7
- export { onRelay as onAiSdkRelay } from './ai-sdk.js';
@@ -74,7 +74,7 @@ export function onRelay<TConfig extends PiConfigLike>(
74
74
  name: string,
75
75
  config: TConfig,
76
76
  relay: RelayLike = new Relay(name)
77
- ): TConfig & { customTools: RelayTool[]; onSessionCreated: (session: PiSessionLike) => Promise<void>; cleanup: () => void } {
77
+ ): TConfig & { customTools: RelayTool[]; onSessionCreated: (session: PiSessionLike) => Promise<void> } {
78
78
  const customTools = [...(config.customTools ?? []), ...createRelayTools(relay)];
79
79
  const originalOnSessionCreated = config.onSessionCreated;
80
80
  let unsubscribe: (() => void) | undefined;
@@ -97,9 +97,5 @@ export function onRelay<TConfig extends PiConfigLike>(
97
97
  await session.followUp(prompt);
98
98
  });
99
99
  },
100
- cleanup() {
101
- unsubscribe?.();
102
- unsubscribe = undefined;
103
- },
104
100
  };
105
101
  }
@@ -124,16 +124,12 @@ export class Relay {
124
124
  }
125
125
 
126
126
  if (!this.connectPromise) {
127
- this.connectPromise = this.transport.connect().then(
128
- () => {
129
- this.connected = true;
130
- },
131
- (err) => {
132
- // Clear cached promise so the next call can retry
133
- this.connectPromise = undefined;
134
- throw err;
135
- },
136
- );
127
+ this.connectPromise = this.transport.connect().then(() => {
128
+ this.connected = true;
129
+ }).catch((error) => {
130
+ this.connectPromise = undefined;
131
+ throw error;
132
+ });
137
133
  }
138
134
 
139
135
  await this.connectPromise;
@@ -1,3 +1,59 @@
1
1
  export * from './types.js';
2
2
  export { Relay } from './core.js';
3
- export { onPiRelay, onClaudeRelay, onAiSdkRelay } from './adapters/index.js';
3
+ export { onPiRelay, onClaudeRelay } from './adapters/index.js';
4
+
5
+ import { onRelay as onPiRelay } from './adapters/pi.js';
6
+ import { onRelay as onClaudeRelay } from './adapters/claude-sdk.js';
7
+ import { Relay } from './core.js';
8
+
9
+ /**
10
+ * Auto-detect the agent framework and apply the appropriate relay adapter.
11
+ *
12
+ * Requires a `framework` discriminator to avoid ambiguous detection.
13
+ * If you know which framework you're using, prefer importing the
14
+ * adapter directly: `onPiRelay` or `onClaudeRelay`.
15
+ *
16
+ * @param nameOrAgent - Agent name (string) or the agent/config object directly.
17
+ * @param configOrOptions - Config/options object when first arg is a name.
18
+ * @param maybeRelay - Optional pre-configured Relay instance.
19
+ * @returns The augmented agent config or options.
20
+ */
21
+ export function onRelay(
22
+ nameOrAgent: string | Record<string, unknown>,
23
+ configOrOptions?: Record<string, unknown>,
24
+ maybeRelay?: Relay
25
+ ): Record<string, unknown> {
26
+ const isStringName = typeof nameOrAgent === 'string';
27
+ const name = isStringName ? nameOrAgent : ((nameOrAgent as Record<string, unknown>).name as string) || 'Agent';
28
+ const target: unknown = isStringName ? configOrOptions : nameOrAgent;
29
+ const relay = (isStringName ? maybeRelay : configOrOptions) as Relay | undefined;
30
+
31
+ const relayInstance = relay || new Relay(name);
32
+
33
+ if (typeof target !== 'object' || target === null) {
34
+ throw new Error(
35
+ `onRelay() received a non-object target for ${name}. ` +
36
+ 'Pass the framework config/options object, or use onPiRelay / onClaudeRelay directly.'
37
+ );
38
+ }
39
+
40
+ const obj = target as Record<string, unknown>;
41
+
42
+ // Detect Pi: has customTools or Agent constructor
43
+ if ('customTools' in obj || obj.constructor?.name === 'Agent') {
44
+ return onPiRelay(name, target as Parameters<typeof onPiRelay>[1], relayInstance);
45
+ }
46
+
47
+ // Detect Claude SDK: has mcpServers or hooks, or is a plain empty options object
48
+ if ('mcpServers' in obj || 'hooks' in obj || Object.keys(obj).length === 0) {
49
+ return onClaudeRelay(name, target as Parameters<typeof onClaudeRelay>[1], relayInstance);
50
+ }
51
+
52
+ throw new Error(
53
+ `onRelay() could not auto-detect framework for ${name}. ` +
54
+ 'Use the framework-specific adapter instead: onPiRelay or onClaudeRelay.'
55
+ );
56
+ }
57
+
58
+ /** Alias for onRelay — spec uses both names */
59
+ export const withRelay = onRelay;
@@ -83,113 +83,70 @@ export class RelayTransport {
83
83
  return this.agentId;
84
84
  }
85
85
 
86
- let payload: JsonObject;
87
- try {
88
- payload = await this.sendHttp<JsonObject>(
89
- 'POST',
90
- '/v1/agents',
91
- { name: this.agentName, type: 'agent' },
92
- );
93
- } catch (error) {
94
- if (error instanceof RelayConnectionError && error.statusCode === 409) {
95
- const agentPayload = await this.sendHttp<JsonObject>(
96
- 'GET',
97
- `/v1/agents/${encodeURIComponent(this.agentName)}`,
98
- );
99
- const agentData = (agentPayload as any).data ?? agentPayload;
100
- this.agentId = String(agentData.id);
101
-
102
- const rotatePayload = await this.sendHttp<JsonObject>(
103
- 'POST',
104
- `/v1/agents/${encodeURIComponent(this.agentName)}/rotate-token`,
105
- );
106
- const rotateData = (rotatePayload as any).data ?? rotatePayload;
107
- this.token = String(rotateData.token);
108
- return this.agentId;
86
+ const payload = await this.sendHttp<{ agent_id: string; token: string }>(
87
+ 'POST',
88
+ '/v1/agents/register',
89
+ {
90
+ name: this.agentName,
91
+ workspace: this.config.workspace,
109
92
  }
110
- throw error;
111
- }
93
+ );
112
94
 
113
- const data = (payload as any).data ?? payload;
114
- this.agentId = String(data.id);
115
- this.token = String(data.token);
95
+ this.agentId = payload.agent_id;
96
+ this.token = payload.token;
116
97
  return this.agentId;
117
98
  }
118
99
 
119
100
  async unregisterAgent(): Promise<void> {
120
- if (!this.agentId || !this.token) {
101
+ if (!this.agentId) {
121
102
  return;
122
103
  }
123
104
 
105
+ const agentId = this.agentId;
124
106
  this.agentId = undefined;
125
- const agentToken = this.token;
126
107
  this.token = undefined;
127
- await this.sendHttpAsAgent('POST', '/v1/agents/disconnect', undefined, agentToken);
108
+ await this.sendHttp('DELETE', `/v1/agents/${agentId}`);
128
109
  }
129
110
 
130
111
  async sendDm(to: string, text: string): Promise<string> {
131
112
  await this.ensureRegistered();
132
- const payload = await this.sendHttpAsAgent<JsonObject>('POST', '/v1/dm', { to, text });
133
- const data = (payload as any).data ?? payload;
134
- return String(data.id ?? data.message_id ?? '');
113
+ const payload = await this.sendHttp<{ message_id: string }>('POST', '/v1/messages/dm', {
114
+ to,
115
+ text,
116
+ from: this.agentName,
117
+ });
118
+ return payload.message_id;
135
119
  }
136
120
 
137
121
  async postMessage(channel: string, text: string): Promise<string> {
138
122
  await this.ensureRegistered();
139
- const payload = await this.sendHttpAsAgent<JsonObject>(
140
- 'POST',
141
- `/v1/channels/${encodeURIComponent(channel)}/messages`,
142
- { text },
143
- );
144
- const data = (payload as any).data ?? payload;
145
- return String(data.id ?? data.message_id ?? '');
123
+ const payload = await this.sendHttp<{ message_id: string }>('POST', '/v1/messages/channel', {
124
+ channel,
125
+ text,
126
+ from: this.agentName,
127
+ });
128
+ return payload.message_id;
146
129
  }
147
130
 
148
131
  async reply(messageId: string, text: string): Promise<string> {
149
132
  await this.ensureRegistered();
150
- const payload = await this.sendHttpAsAgent<JsonObject>(
151
- 'POST',
152
- `/v1/messages/${encodeURIComponent(messageId)}/replies`,
153
- { text },
154
- );
155
- const data = (payload as any).data ?? payload;
156
- return String(data.id ?? data.message_id ?? '');
133
+ const payload = await this.sendHttp<{ message_id: string }>('POST', '/v1/messages/reply', {
134
+ message_id: messageId,
135
+ text,
136
+ from: this.agentName,
137
+ });
138
+ return payload.message_id;
157
139
  }
158
140
 
159
141
  async checkInbox(): Promise<Message[]> {
160
142
  await this.ensureRegistered();
161
- const payload = await this.sendHttpAsAgent<JsonObject>('GET', '/v1/inbox');
162
- const data = (payload as any).data ?? payload;
163
- const messages: Message[] = [];
164
-
165
- for (const mention of ((data as any).mentions ?? []) as JsonObject[]) {
166
- messages.push(this.messageFromPayload(mention));
167
- }
168
-
169
- for (const dm of ((data as any).unread_dms ?? []) as JsonObject[]) {
170
- const last = (dm as any).last_message as JsonObject | undefined;
171
- if (last?.text) {
172
- messages.push({
173
- sender: String(dm.from ?? dm.agent_name ?? 'unknown'),
174
- text: String(last.text),
175
- channel: undefined,
176
- threadId: typeof dm.conversation_id === 'string' ? dm.conversation_id : undefined,
177
- timestamp: typeof last.created_at === 'string' ? undefined : (last.created_at as number | undefined),
178
- messageId: typeof last.id === 'string' ? last.id : undefined,
179
- });
180
- }
181
- }
182
-
183
- return messages;
143
+ const payload = await this.sendHttp<{ messages?: JsonObject[] }>('GET', `/v1/inbox/${this.agentId}`);
144
+ return (payload.messages ?? []).map((message) => this.messageFromPayload(message));
184
145
  }
185
146
 
186
147
  async listAgents(): Promise<string[]> {
187
- const payload = await this.sendHttp<JsonObject>('GET', '/v1/agents');
188
- const data = (payload as any).data ?? (payload as any).agents ?? [];
189
- if (Array.isArray(data)) {
190
- return data.map((a: any) => (typeof a === 'string' ? a : String(a.name ?? a)));
191
- }
192
- return [];
148
+ const payload = await this.sendHttp<{ agents?: string[] }>('GET', '/v1/agents');
149
+ return [...(payload.agents ?? [])];
193
150
  }
194
151
 
195
152
  private async ensureRegistered(): Promise<void> {
@@ -213,71 +170,6 @@ export class RelayTransport {
213
170
  }
214
171
  }
215
172
 
216
- private async sendHttpAsAgent<T = unknown>(
217
- method: string,
218
- path: string,
219
- payload?: JsonObject,
220
- overrideToken?: string,
221
- ): Promise<T> {
222
- this.requireConfig();
223
-
224
- const agentToken = overrideToken ?? this.token;
225
- if (!agentToken) {
226
- throw new RelayConfigError('Agent not registered; no agent token available.');
227
- }
228
-
229
- const url = `${this.config.baseUrl}${path}`;
230
- const headers: Record<string, string> = {
231
- authorization: `Bearer ${agentToken}`,
232
- };
233
- if (payload !== undefined) {
234
- headers['content-type'] = 'application/json';
235
- }
236
-
237
- for (let attempt = 1; attempt <= HTTP_RETRY_ATTEMPTS; attempt += 1) {
238
- let response: Response;
239
- try {
240
- response = await fetch(url, {
241
- method,
242
- headers,
243
- body: payload === undefined ? undefined : JSON.stringify(payload),
244
- });
245
- } catch (error) {
246
- if (attempt < HTTP_RETRY_ATTEMPTS) {
247
- await sleep(Math.min(2 ** (attempt - 1) * 1_000, WS_RECONNECT_MAX_DELAY_MS));
248
- continue;
249
- }
250
- throw new RelayConnectionError(0, error instanceof Error ? error.message : String(error));
251
- }
252
-
253
- if (response.status === 401) {
254
- throw new RelayAuthError(await this.errorMessage(response));
255
- }
256
- if (response.status >= 500 && response.status <= 599) {
257
- const message = await this.errorMessage(response);
258
- if (attempt < HTTP_RETRY_ATTEMPTS) {
259
- await sleep(Math.min(2 ** (attempt - 1) * 1_000, WS_RECONNECT_MAX_DELAY_MS));
260
- continue;
261
- }
262
- throw new RelayConnectionError(response.status, message);
263
- }
264
- if (response.status >= 400) {
265
- throw new RelayConnectionError(response.status, await this.errorMessage(response));
266
- }
267
- if (response.status === 204) {
268
- return undefined as T;
269
- }
270
-
271
- const contentType = response.headers.get('content-type') ?? '';
272
- if (contentType.includes('application/json')) {
273
- return (await response.json()) as T;
274
- }
275
- return (await response.text()) as T;
276
- }
277
-
278
- throw new RelayConnectionError(500, 'Unexpected transport retry failure');
279
- }
280
-
281
173
  private async sendHttp<T = unknown>(
282
174
  method: string,
283
175
  path: string,
@@ -354,7 +246,7 @@ export class RelayTransport {
354
246
  return this.wsConnectPromise;
355
247
  }
356
248
 
357
- const url = `${this.wsBaseUrl()}/v1/ws?token=${encodeURIComponent(this.token ?? '')}`;
249
+ const url = `${this.wsBaseUrl()}/v1/ws/${this.agentId}?token=${encodeURIComponent(this.token ?? '')}`;
358
250
  const socket = new WebSocket(url);
359
251
 
360
252
  this.wsConnectPromise = new Promise<void>((resolve, reject) => {
@@ -437,12 +329,7 @@ export class RelayTransport {
437
329
  this.ws?.send(JSON.stringify({ type: 'pong' }));
438
330
  return;
439
331
  }
440
-
441
- const messageEvents = new Set([
442
- 'message.created', 'dm.received', 'direct_message.received',
443
- 'thread.reply', 'message', 'group_dm.received',
444
- ]);
445
- if (!messageEvents.has(payload.type as string) || !this.messageCallback) {
332
+ if (payload.type !== 'message' || !this.messageCallback) {
446
333
  return;
447
334
  }
448
335
 
@@ -450,40 +337,22 @@ export class RelayTransport {
450
337
  }
451
338
 
452
339
  private messageFromPayload(payload: JsonObject): Message {
453
- const m = (typeof payload.message === 'object' && payload.message !== null)
454
- ? (payload.message as JsonObject)
455
- : payload;
456
-
457
- const sender = String(
458
- m.sender ?? m.agent_name ?? m.from ?? m.agentName
459
- ?? payload.agent_name ?? payload.from ?? 'unknown',
460
- );
461
- const text = String(m.text ?? '');
462
- const channel = String(
463
- m.channel ?? m.channel_name ?? m.channelName ?? payload.channel ?? payload.channel_name ?? '',
464
- ) || undefined;
465
- const threadId = String(
466
- m.thread_id ?? m.threadId ?? m.conversation_id ?? m.conversationId ?? payload.thread_id ?? '',
467
- ) || undefined;
468
- const rawTs = m.timestamp ?? m.created_at ?? m.createdAt ?? payload.timestamp;
469
- const timestamp = typeof rawTs === 'number' ? rawTs : undefined;
470
- const messageId = String(
471
- m.id ?? m.message_id ?? m.messageId ?? payload.message_id ?? '',
472
- ) || undefined;
473
-
474
- return { sender, text, channel, threadId, timestamp, messageId };
340
+ return {
341
+ sender: String(payload.sender ?? ''),
342
+ text: String(payload.text ?? ''),
343
+ channel: typeof payload.channel === 'string' ? payload.channel : undefined,
344
+ threadId: typeof payload.thread_id === 'string' ? payload.thread_id : undefined,
345
+ timestamp: typeof payload.timestamp === 'number' ? payload.timestamp : undefined,
346
+ messageId: typeof payload.message_id === 'string' ? payload.message_id : undefined,
347
+ };
475
348
  }
476
349
 
477
350
  private async errorMessage(response: Response): Promise<string> {
478
- const text = await response.text().catch(() => '');
479
351
  try {
480
- const payload = JSON.parse(text) as { message?: string; error?: { message?: string } };
481
- if (typeof payload.error === 'object' && payload.error?.message) {
482
- return payload.error.message;
483
- }
352
+ const payload = (await response.json()) as { message?: string };
484
353
  return payload.message ?? response.statusText ?? 'Request failed';
485
354
  } catch {
486
- return text || response.statusText || 'Request failed';
355
+ return (await response.text()) || response.statusText || 'Request failed';
487
356
  }
488
357
  }
489
358
  }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Lightweight HTTP client for Relaycast API — safe for bundlers (Vite, Rollup, Webpack).
3
+ * No native dependencies, no broker binary, no process spawning.
4
+ *
5
+ * Usage:
6
+ * import { RelayCast, createWorkspace, registerAgent } from '@agent-relay/sdk/http';
7
+ */
8
+
9
+ // Re-export the HTTP-safe parts of @relaycast/sdk
10
+ export { RelayCast, RelayError } from '@relaycast/sdk';
11
+ export type { RelayCastOptions, CreateWorkspaceResponse } from '@relaycast/sdk';
12
+
13
+ // Re-export useful protocol types from the SDK
14
+ export type { AgentSpec, AgentRuntime } from './protocol.js';
15
+
16
+ const DEFAULT_BASE_URL = 'https://api.relaycast.dev';
17
+
18
+ interface CreateWorkspaceResult {
19
+ workspace_id: string;
20
+ api_key: string;
21
+ }
22
+
23
+ interface RegisterAgentResult {
24
+ name: string;
25
+ token: string;
26
+ }
27
+
28
+ /**
29
+ * Create a new Relaycast workspace using plain fetch.
30
+ * This function has zero dependencies beyond the Fetch API.
31
+ *
32
+ * @param name - Human-readable workspace name
33
+ * @param baseUrl - API base URL (defaults to https://api.relaycast.dev)
34
+ * @returns The workspace ID and API key
35
+ */
36
+ export async function createWorkspace(
37
+ name: string,
38
+ baseUrl: string = DEFAULT_BASE_URL,
39
+ ): Promise<CreateWorkspaceResult> {
40
+ const url = `${baseUrl}/v1/workspaces`;
41
+ const response = await fetch(url, {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
44
+ body: JSON.stringify({ name }),
45
+ });
46
+
47
+ if (!response.ok) {
48
+ const body = await response.text().catch(() => '');
49
+ throw new Error(
50
+ `Failed to create workspace (HTTP ${response.status}): ${body}`,
51
+ );
52
+ }
53
+
54
+ const body = (await response.json()) as
55
+ | CreateWorkspaceResult
56
+ | { ok: boolean; data: CreateWorkspaceResult };
57
+ // The API wraps responses in { ok, data } — unwrap transparently
58
+ return 'data' in body && body.data ? body.data : body as CreateWorkspaceResult;
59
+ }
60
+
61
+ /**
62
+ * Register an agent in a workspace using plain fetch.
63
+ * This function has zero dependencies beyond the Fetch API.
64
+ *
65
+ * @param apiKey - Workspace API key
66
+ * @param name - Agent name to register
67
+ * @param baseUrl - API base URL (defaults to https://api.relaycast.dev)
68
+ * @returns The registered agent name and token
69
+ */
70
+ export async function registerAgent(
71
+ apiKey: string,
72
+ name: string,
73
+ baseUrl: string = DEFAULT_BASE_URL,
74
+ ): Promise<RegisterAgentResult> {
75
+ const url = `${baseUrl}/v1/agents`;
76
+ const response = await fetch(url, {
77
+ method: 'POST',
78
+ headers: {
79
+ 'Content-Type': 'application/json',
80
+ Authorization: `Bearer ${apiKey}`,
81
+ },
82
+ body: JSON.stringify({ name }),
83
+ });
84
+
85
+ if (!response.ok) {
86
+ const errBody = await response.text().catch(() => '');
87
+ throw new Error(
88
+ `Failed to register agent (HTTP ${response.status}): ${errBody}`,
89
+ );
90
+ }
91
+
92
+ const body = (await response.json()) as
93
+ | RegisterAgentResult
94
+ | { ok: boolean; data: RegisterAgentResult };
95
+ return 'data' in body && body.data ? body.data : body as RegisterAgentResult;
96
+ }
@@ -25,6 +25,8 @@ export interface AgentSpec {
25
25
  restart_policy?: RestartPolicy;
26
26
  }
27
27
 
28
+ export type MessageInjectionMode = 'wait' | 'steer';
29
+
28
30
  export interface RelayDelivery {
29
31
  delivery_id: string;
30
32
  event_id: string;
@@ -35,6 +37,7 @@ export interface RelayDelivery {
35
37
  body: string;
36
38
  thread_id?: string;
37
39
  priority?: number;
40
+ injection_mode?: MessageInjectionMode;
38
41
  }
39
42
 
40
43
  export interface ProtocolEnvelope<TPayload> {
@@ -64,6 +67,7 @@ export type SdkToBroker =
64
67
  workspace_alias?: string;
65
68
  priority?: number;
66
69
  data?: Record<string, unknown>;
70
+ mode?: MessageInjectionMode;
67
71
  };
68
72
  }
69
73
  | {
@@ -74,6 +78,14 @@ export type SdkToBroker =
74
78
  type: 'send_input';
75
79
  payload: { name: string; data: string };
76
80
  }
81
+ | {
82
+ type: 'subscribe_channels';
83
+ payload: { name: string; channels: string[] };
84
+ }
85
+ | {
86
+ type: 'unsubscribe_channels';
87
+ payload: { name: string; channels: string[] };
88
+ }
77
89
  | {
78
90
  type: 'set_model';
79
91
  payload: { name: string; model: string; timeout_ms?: number };
@@ -221,6 +233,8 @@ export type BrokerEvent =
221
233
  target: string;
222
234
  body: string;
223
235
  thread_id?: string;
236
+ mode?: MessageInjectionMode;
237
+ injection_mode?: MessageInjectionMode;
224
238
  }
225
239
  | {
226
240
  kind: 'worker_stream';
@@ -280,6 +294,16 @@ export type BrokerEvent =
280
294
  delivery_id: string;
281
295
  event_id: string;
282
296
  }
297
+ | {
298
+ kind: 'channel_subscribed';
299
+ name: string;
300
+ channels: string[];
301
+ }
302
+ | {
303
+ kind: 'channel_unsubscribed';
304
+ name: string;
305
+ channels: string[];
306
+ }
283
307
  | {
284
308
  kind: 'worker_ready';
285
309
  name: string;