agent-relay 3.2.2 → 3.2.4
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/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/dist/index.cjs +1358 -941
- package/dist/src/cli/commands/agent-management.d.ts +2 -2
- package/dist/src/cli/commands/agent-management.d.ts.map +1 -1
- package/dist/src/cli/commands/agent-management.js +41 -240
- package/dist/src/cli/commands/agent-management.js.map +1 -1
- package/dist/src/cli/commands/messaging.d.ts +1 -1
- package/dist/src/cli/commands/messaging.d.ts.map +1 -1
- package/dist/src/cli/commands/messaging.js +14 -5
- package/dist/src/cli/commands/messaging.js.map +1 -1
- package/dist/src/cli/lib/agent-management-listing.d.ts +4 -1
- package/dist/src/cli/lib/agent-management-listing.d.ts.map +1 -1
- package/dist/src/cli/lib/agent-management-listing.js +27 -2
- package/dist/src/cli/lib/agent-management-listing.js.map +1 -1
- package/package.json +11 -10
- 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/ADAPTER_REVIEW.md +109 -0
- package/packages/sdk/dist/client.d.ts +66 -0
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +230 -0
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts +25 -0
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js +89 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts +31 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.js +220 -0
- package/packages/sdk/dist/communicate/a2a-server.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts +48 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.js +302 -0
- package/packages/sdk/dist/communicate/a2a-transport.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts +107 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.js +209 -0
- package/packages/sdk/dist/communicate/a2a-types.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts +28 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js +47 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts +42 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js +95 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts +53 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js +77 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts +7 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.js +7 -0
- package/packages/sdk/dist/communicate/adapters/index.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts +40 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js +77 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts +25 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js +70 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts +45 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.js +59 -0
- package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -0
- package/packages/sdk/dist/communicate/core.d.ts +58 -0
- package/packages/sdk/dist/communicate/core.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/core.js +128 -0
- package/packages/sdk/dist/communicate/core.js.map +1 -0
- package/packages/sdk/dist/communicate/index.d.ts +4 -0
- package/packages/sdk/dist/communicate/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/index.js +4 -0
- package/packages/sdk/dist/communicate/index.js.map +1 -0
- package/packages/sdk/dist/communicate/transport.d.ts +36 -0
- package/packages/sdk/dist/communicate/transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/transport.js +371 -0
- package/packages/sdk/dist/communicate/transport.js.map +1 -0
- package/packages/sdk/dist/communicate/types.d.ts +58 -0
- package/packages/sdk/dist/communicate/types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/types.js +66 -0
- package/packages/sdk/dist/communicate/types.js.map +1 -0
- package/packages/sdk/dist/workflows/builder.d.ts +35 -5
- package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/builder.js +81 -7
- package/packages/sdk/dist/workflows/builder.js.map +1 -1
- package/packages/sdk/dist/workflows/cli.js +14 -1
- package/packages/sdk/dist/workflows/cli.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +10 -2
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +95 -1
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +11 -0
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/examples/communicate/claude_sdk_example.ts +5 -0
- package/packages/sdk/examples/communicate/pi_example.ts +8 -0
- package/packages/sdk/package.json +48 -2
- package/packages/sdk/src/__tests__/builder-deterministic.test.ts +132 -0
- package/packages/sdk/src/__tests__/communicate/a2a-bridge.test.ts +211 -0
- package/packages/sdk/src/__tests__/communicate/a2a-server.test.ts +359 -0
- package/packages/sdk/src/__tests__/communicate/a2a-transport.test.ts +537 -0
- package/packages/sdk/src/__tests__/communicate/a2a-types.test.ts +297 -0
- package/packages/sdk/src/__tests__/communicate/adapters/claude-sdk.test.ts +163 -0
- package/packages/sdk/src/__tests__/communicate/adapters/crewai.test.ts +219 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-crewai.test.ts +101 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-google-adk.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-langgraph.test.ts +181 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-openai-agents.test.ts +137 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/adapters/google-adk.test.ts +200 -0
- package/packages/sdk/src/__tests__/communicate/adapters/langgraph.test.ts +162 -0
- package/packages/sdk/src/__tests__/communicate/adapters/openai-agents.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/core.test.ts +574 -0
- package/packages/sdk/src/__tests__/communicate/integration/cross-framework.test.ts +353 -0
- package/packages/sdk/src/__tests__/communicate/transport.test.ts +613 -0
- package/packages/sdk/src/__tests__/start-from.test.ts +346 -0
- package/packages/sdk/src/client.ts +301 -0
- package/packages/sdk/src/communicate/a2a-bridge.ts +111 -0
- package/packages/sdk/src/communicate/a2a-server.ts +277 -0
- package/packages/sdk/src/communicate/a2a-transport.ts +395 -0
- package/packages/sdk/src/communicate/a2a-types.ts +338 -0
- package/packages/sdk/src/communicate/adapters/claude-sdk.ts +85 -0
- package/packages/sdk/src/communicate/adapters/crewai.ts +141 -0
- package/packages/sdk/src/communicate/adapters/google-adk.ts +139 -0
- package/packages/sdk/src/communicate/adapters/index.ts +6 -0
- package/packages/sdk/src/communicate/adapters/langgraph.ts +112 -0
- package/packages/sdk/src/communicate/adapters/openai-agents.ts +113 -0
- package/packages/sdk/src/communicate/adapters/pi.ts +105 -0
- package/packages/sdk/src/communicate/core.ts +157 -0
- package/packages/sdk/src/communicate/index.ts +3 -0
- package/packages/sdk/src/communicate/transport.ts +489 -0
- package/packages/sdk/src/communicate/types.ts +106 -0
- package/packages/sdk/src/examples/workflows/fix-dashboard-user-registration.yaml +182 -0
- package/packages/sdk/src/workflows/builder.ts +97 -9
- package/packages/sdk/src/workflows/cli.ts +16 -1
- package/packages/sdk/src/workflows/runner.ts +110 -1
- package/packages/sdk/src/workflows/types.ts +14 -0
- package/packages/sdk/tsconfig.build.json +1 -7
- package/packages/sdk/tsconfig.json +1 -7
- package/packages/sdk-py/README.md +67 -25
- package/packages/sdk-py/examples/communicate/agno_example.py +8 -0
- package/packages/sdk-py/examples/communicate/claude_sdk_example.py +6 -0
- package/packages/sdk-py/examples/communicate/crewai_example.py +7 -0
- package/packages/sdk-py/examples/communicate/google_adk_example.py +7 -0
- package/packages/sdk-py/examples/communicate/openai_agents_example.py +8 -0
- package/packages/sdk-py/examples/communicate/swarms_example.py +7 -0
- package/packages/sdk-py/pyproject.toml +12 -1
- package/packages/sdk-py/src/agent_relay/__init__.py +8 -0
- package/packages/sdk-py/src/agent_relay/builder.py +65 -26
- package/packages/sdk-py/src/agent_relay/communicate/__init__.py +6 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_bridge.py +138 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_server.py +242 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_transport.py +366 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_types.py +294 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +10 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +74 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +78 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +143 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +69 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +86 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/pi.py +175 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/swarms.py +44 -0
- package/packages/sdk-py/src/agent_relay/communicate/core.py +293 -0
- package/packages/sdk-py/src/agent_relay/communicate/transport.py +502 -0
- package/packages/sdk-py/src/agent_relay/communicate/types.py +89 -0
- package/packages/sdk-py/src/agent_relay/types.py +2 -1
- package/packages/sdk-py/tests/communicate/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_agno.py +154 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_claude_sdk.py +428 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_crewai.py +234 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_google_adk.py +182 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_langgraph.py +262 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_openai_agents.py +88 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_pi.py +156 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_swarms.py +239 -0
- package/packages/sdk-py/tests/communicate/adapters/test_agno.py +140 -0
- package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +147 -0
- package/packages/sdk-py/tests/communicate/adapters/test_crewai.py +136 -0
- package/packages/sdk-py/tests/communicate/adapters/test_google_adk.py +125 -0
- package/packages/sdk-py/tests/communicate/adapters/test_openai_agents.py +99 -0
- package/packages/sdk-py/tests/communicate/adapters/test_pi.py +270 -0
- package/packages/sdk-py/tests/communicate/adapters/test_swarms.py +113 -0
- package/packages/sdk-py/tests/communicate/conftest.py +555 -0
- package/packages/sdk-py/tests/communicate/integration/__init__.py +1 -0
- package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +331 -0
- package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +151 -0
- package/packages/sdk-py/tests/communicate/test_a2a_bridge.py +363 -0
- package/packages/sdk-py/tests/communicate/test_a2a_server.py +346 -0
- package/packages/sdk-py/tests/communicate/test_a2a_transport.py +561 -0
- package/packages/sdk-py/tests/communicate/test_a2a_types.py +342 -0
- package/packages/sdk-py/tests/communicate/test_auto_detect.py +67 -0
- package/packages/sdk-py/tests/communicate/test_core.py +331 -0
- package/packages/sdk-py/tests/communicate/test_transport.py +373 -0
- package/packages/sdk-py/tests/communicate/test_types.py +285 -0
- package/packages/sdk-py/tests/test_builder_deterministic.py +118 -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
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts +0 -14
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js +0 -1476
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js +0 -152
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts +0 -16
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +0 -640
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/facade.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.js +0 -305
- package/packages/sdk/dist/__tests__/facade.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/integration.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.js +0 -205
- package/packages/sdk/dist/__tests__/integration.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/pty.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.js +0 -20
- package/packages/sdk/dist/__tests__/pty.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.js +0 -176
- package/packages/sdk/dist/__tests__/quickstart.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js +0 -222
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/unit.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.js +0 -357
- package/packages/sdk/dist/__tests__/unit.test.js.map +0 -1
- package/packages/sdk-py/agent_relay/__init__.py +0 -21
- package/packages/sdk-py/agent_relay/models.py +0 -398
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
|
|
5
|
+
import { onRelay } from '../../../communicate/adapters/pi.js';
|
|
6
|
+
import type { Message, MessageCallback } from '../../../communicate/types.js';
|
|
7
|
+
|
|
8
|
+
const API_KEY = process.env.RELAY_API_KEY!;
|
|
9
|
+
const BASE_URL = (process.env.RELAY_BASE_URL ?? 'https://api.relaycast.dev').replace(/\/+$/, '');
|
|
10
|
+
const AGENT_NAME = `e2e-pi-${randomUUID().slice(0, 8)}`;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimal relay implementation that speaks the real Relaycast API surface.
|
|
14
|
+
* The Pi adapter accepts any RelayLike — this lets us e2e-test the adapter
|
|
15
|
+
* independent of the SDK transport layer.
|
|
16
|
+
*/
|
|
17
|
+
class LiveRelay {
|
|
18
|
+
private agentToken?: string;
|
|
19
|
+
private agentId?: string;
|
|
20
|
+
private callbacks: MessageCallback[] = [];
|
|
21
|
+
|
|
22
|
+
constructor(private readonly name: string) {}
|
|
23
|
+
|
|
24
|
+
async register(): Promise<void> {
|
|
25
|
+
const res = await fetch(`${BASE_URL}/v1/agents`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { authorization: `Bearer ${API_KEY}`, 'content-type': 'application/json' },
|
|
28
|
+
body: JSON.stringify({ name: this.name, type: 'agent' }),
|
|
29
|
+
});
|
|
30
|
+
const body = await res.json() as any;
|
|
31
|
+
if (!body.ok) throw new Error(`Register failed: ${JSON.stringify(body)}`);
|
|
32
|
+
this.agentId = body.data.id;
|
|
33
|
+
this.agentToken = body.data.token;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async send(to: string, text: string): Promise<void> {
|
|
37
|
+
const res = await fetch(`${BASE_URL}/v1/dm`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: { authorization: `Bearer ${this.agentToken}`, 'content-type': 'application/json' },
|
|
40
|
+
body: JSON.stringify({ to, text }),
|
|
41
|
+
});
|
|
42
|
+
const body = await res.json() as any;
|
|
43
|
+
if (!body.ok) throw new Error(`DM failed: ${JSON.stringify(body)}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async post(channel: string, text: string): Promise<void> {
|
|
47
|
+
const res = await fetch(`${BASE_URL}/v1/channels/${encodeURIComponent(channel)}/messages`, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: { authorization: `Bearer ${this.agentToken}`, 'content-type': 'application/json' },
|
|
50
|
+
body: JSON.stringify({ text }),
|
|
51
|
+
});
|
|
52
|
+
const body = await res.json() as any;
|
|
53
|
+
if (!body.ok) throw new Error(`Post failed: ${JSON.stringify(body)}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async inbox(): Promise<Message[]> {
|
|
57
|
+
const res = await fetch(`${BASE_URL}/v1/inbox`, {
|
|
58
|
+
headers: { authorization: `Bearer ${this.agentToken}` },
|
|
59
|
+
});
|
|
60
|
+
const body = await res.json() as any;
|
|
61
|
+
if (!body.ok) throw new Error(`Inbox failed: ${JSON.stringify(body)}`);
|
|
62
|
+
// Map the real inbox shape into Message[]
|
|
63
|
+
const msgs: Message[] = [];
|
|
64
|
+
for (const dm of body.data.unread_dms ?? []) {
|
|
65
|
+
msgs.push({ sender: dm.from ?? 'unknown', text: dm.text ?? '' });
|
|
66
|
+
}
|
|
67
|
+
return msgs;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async agents(): Promise<string[]> {
|
|
71
|
+
const res = await fetch(`${BASE_URL}/v1/agents`, {
|
|
72
|
+
headers: { authorization: `Bearer ${API_KEY}` },
|
|
73
|
+
});
|
|
74
|
+
const body = await res.json() as any;
|
|
75
|
+
if (!body.ok) throw new Error(`Agents failed: ${JSON.stringify(body)}`);
|
|
76
|
+
return (body.data as any[]).map((a) => a.name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onMessage(callback: MessageCallback): () => void {
|
|
80
|
+
this.callbacks.push(callback);
|
|
81
|
+
return () => { this.callbacks = this.callbacks.filter((c) => c !== callback); };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async close(): Promise<void> {
|
|
85
|
+
if (!this.agentId) return;
|
|
86
|
+
// Best-effort unregister
|
|
87
|
+
try {
|
|
88
|
+
await fetch(`${BASE_URL}/v1/agents/${this.agentId}`, {
|
|
89
|
+
method: 'DELETE',
|
|
90
|
+
headers: { authorization: `Bearer ${API_KEY}` },
|
|
91
|
+
});
|
|
92
|
+
} catch { /* ignore */ }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
test('Pi adapter e2e: relay tools work against live Relaycast', async () => {
|
|
97
|
+
const relay = new LiveRelay(AGENT_NAME);
|
|
98
|
+
await relay.register();
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const config = onRelay(AGENT_NAME, {}, relay);
|
|
102
|
+
|
|
103
|
+
const findTool = (name: string) => {
|
|
104
|
+
const tool = config.customTools.find((t: any) => t.name === name);
|
|
105
|
+
assert.ok(tool, `Tool ${name} not found`);
|
|
106
|
+
return tool;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const relayAgents = findTool('relay_agents');
|
|
110
|
+
const relaySend = findTool('relay_send');
|
|
111
|
+
const relayPost = findTool('relay_post');
|
|
112
|
+
const relayInbox = findTool('relay_inbox');
|
|
113
|
+
|
|
114
|
+
// 1. relay_agents — should include our freshly registered agent
|
|
115
|
+
const agentsResult = await relayAgents.execute('call-1', {});
|
|
116
|
+
const agentsText = agentsResult.content[0].text;
|
|
117
|
+
console.log('relay_agents:', agentsText.split('\n').length, 'agents');
|
|
118
|
+
assert.ok(agentsText.includes(AGENT_NAME), `Agent list must include ${AGENT_NAME}`);
|
|
119
|
+
|
|
120
|
+
// 2. relay_send — DM to self
|
|
121
|
+
const sendResult = await relaySend.execute('call-2', { to: AGENT_NAME, text: 'e2e self-ping' });
|
|
122
|
+
console.log('relay_send:', sendResult.content[0].text);
|
|
123
|
+
assert.ok(sendResult.content[0].text.includes('Sent relay message'));
|
|
124
|
+
|
|
125
|
+
// 3. relay_post — post to general channel
|
|
126
|
+
const postResult = await relayPost.execute('call-3', { channel: 'general', text: `e2e from ${AGENT_NAME}` });
|
|
127
|
+
console.log('relay_post:', postResult.content[0].text);
|
|
128
|
+
assert.ok(postResult.content[0].text.includes('Posted relay message'));
|
|
129
|
+
|
|
130
|
+
// 4. relay_inbox — drain inbox
|
|
131
|
+
const inboxResult = await relayInbox.execute('call-4', {});
|
|
132
|
+
console.log('relay_inbox:', inboxResult.content[0].text);
|
|
133
|
+
assert.ok(typeof inboxResult.content[0].text === 'string');
|
|
134
|
+
|
|
135
|
+
console.log('All Pi adapter e2e checks passed.');
|
|
136
|
+
} finally {
|
|
137
|
+
await relay.close();
|
|
138
|
+
console.log('Relay closed.');
|
|
139
|
+
}
|
|
140
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
const adkAdapterModulePath = '../../../communicate/adapters/google-adk.js';
|
|
5
|
+
|
|
6
|
+
async function loadAdkAdapterModule(): Promise<any> {
|
|
7
|
+
return import(adkAdapterModulePath);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class FakeRelay {
|
|
11
|
+
private callbacks: Array<(message: any) => void | Promise<void>> = [];
|
|
12
|
+
|
|
13
|
+
async send(_to: string, _text: string): Promise<void> {}
|
|
14
|
+
|
|
15
|
+
async post(_channel: string, _text: string): Promise<void> {}
|
|
16
|
+
|
|
17
|
+
async inbox(): Promise<any[]> {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async agents(): Promise<string[]> {
|
|
22
|
+
return ['Lead', 'Impl-TS'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onMessage(callback: (message: any) => void | Promise<void>): () => void {
|
|
26
|
+
this.callbacks.push(callback);
|
|
27
|
+
return () => {
|
|
28
|
+
this.callbacks = this.callbacks.filter((entry) => entry !== callback);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async emit(message: any): Promise<void> {
|
|
33
|
+
for (const callback of [...this.callbacks]) {
|
|
34
|
+
await callback(message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createMockAgent() {
|
|
40
|
+
return {
|
|
41
|
+
name: 'test-agent',
|
|
42
|
+
tools: [] as any[],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createMockRunner() {
|
|
47
|
+
const calls: Array<{ userId: string; sessionId: string; newMessage: any }> = [];
|
|
48
|
+
return {
|
|
49
|
+
calls,
|
|
50
|
+
async *runAsync(params: { userId: string; sessionId: string; newMessage: any }) {
|
|
51
|
+
calls.push(params);
|
|
52
|
+
yield { type: 'done' };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test('Google ADK onRelay appends relay tools to agent.tools', async () => {
|
|
58
|
+
const { onRelay } = await loadAdkAdapterModule();
|
|
59
|
+
const relay = new FakeRelay();
|
|
60
|
+
const agent = createMockAgent();
|
|
61
|
+
|
|
62
|
+
// Add an existing tool
|
|
63
|
+
agent.tools.push({ name: 'existing-tool', description: 'Existing tool' });
|
|
64
|
+
|
|
65
|
+
const result = onRelay('AdkTester', { agent }, relay);
|
|
66
|
+
|
|
67
|
+
const toolNames = result.agent.tools.map((tool: any) => tool.name);
|
|
68
|
+
assert.deepEqual(toolNames, [
|
|
69
|
+
'existing-tool',
|
|
70
|
+
'relay_send',
|
|
71
|
+
'relay_inbox',
|
|
72
|
+
'relay_post',
|
|
73
|
+
'relay_agents',
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('Google ADK onRelay tools have correct structure', async () => {
|
|
78
|
+
const { onRelay } = await loadAdkAdapterModule();
|
|
79
|
+
const relay = new FakeRelay();
|
|
80
|
+
const agent = createMockAgent();
|
|
81
|
+
|
|
82
|
+
const result = onRelay('AdkTester', { agent }, relay);
|
|
83
|
+
|
|
84
|
+
for (const toolName of ['relay_send', 'relay_inbox', 'relay_post', 'relay_agents']) {
|
|
85
|
+
const tool = result.tools.find((entry: any) => entry.name === toolName);
|
|
86
|
+
assert.ok(tool, `Expected ${toolName} to be registered`);
|
|
87
|
+
assert.equal(typeof tool.execute, 'function');
|
|
88
|
+
assert.ok(tool.parameters, `Expected ${toolName} to have parameters`);
|
|
89
|
+
assert.ok(tool.description, `Expected ${toolName} to have a description`);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('Google ADK relay_send tool calls relay.send', async () => {
|
|
94
|
+
const { onRelay } = await loadAdkAdapterModule();
|
|
95
|
+
const relay = new FakeRelay();
|
|
96
|
+
const sendCalls: Array<{ to: string; text: string }> = [];
|
|
97
|
+
relay.send = async (to: string, text: string) => {
|
|
98
|
+
sendCalls.push({ to, text });
|
|
99
|
+
};
|
|
100
|
+
const agent = createMockAgent();
|
|
101
|
+
|
|
102
|
+
const result = onRelay('AdkTester', { agent }, relay);
|
|
103
|
+
const sendTool = result.tools.find((t: any) => t.name === 'relay_send');
|
|
104
|
+
|
|
105
|
+
const output = await sendTool.execute({ to: 'Lead', text: 'Hello' });
|
|
106
|
+
assert.equal(sendCalls.length, 1);
|
|
107
|
+
assert.deepEqual(sendCalls[0], { to: 'Lead', text: 'Hello' });
|
|
108
|
+
assert.ok(output.result.includes('Lead'));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('Google ADK relay_inbox tool returns formatted inbox', async () => {
|
|
112
|
+
const { onRelay } = await loadAdkAdapterModule();
|
|
113
|
+
const relay = new FakeRelay();
|
|
114
|
+
const agent = createMockAgent();
|
|
115
|
+
|
|
116
|
+
const result = onRelay('AdkTester', { agent }, relay);
|
|
117
|
+
const inboxTool = result.tools.find((t: any) => t.name === 'relay_inbox');
|
|
118
|
+
|
|
119
|
+
const output = await inboxTool.execute({});
|
|
120
|
+
assert.equal(output.result, 'No new relay messages.');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('Google ADK relay_agents tool returns agent list', async () => {
|
|
124
|
+
const { onRelay } = await loadAdkAdapterModule();
|
|
125
|
+
const relay = new FakeRelay();
|
|
126
|
+
const agent = createMockAgent();
|
|
127
|
+
|
|
128
|
+
const result = onRelay('AdkTester', { agent }, relay);
|
|
129
|
+
const agentsTool = result.tools.find((t: any) => t.name === 'relay_agents');
|
|
130
|
+
|
|
131
|
+
const output = await agentsTool.execute({});
|
|
132
|
+
assert.equal(output.result, 'Lead\nImpl-TS');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('Google ADK onRelay routes incoming messages to the runner', async () => {
|
|
136
|
+
const { onRelay } = await loadAdkAdapterModule();
|
|
137
|
+
const relay = new FakeRelay();
|
|
138
|
+
const agent = createMockAgent();
|
|
139
|
+
const runner = createMockRunner();
|
|
140
|
+
|
|
141
|
+
onRelay('AdkTester', { agent, runner }, relay);
|
|
142
|
+
|
|
143
|
+
await relay.emit({
|
|
144
|
+
sender: 'Lead',
|
|
145
|
+
text: 'Need status update',
|
|
146
|
+
messageId: 'msg-1',
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
assert.equal(runner.calls.length, 1);
|
|
150
|
+
assert.equal(runner.calls[0].userId, 'relay');
|
|
151
|
+
assert.equal(runner.calls[0].sessionId, 'relay-session');
|
|
152
|
+
assert.ok(runner.calls[0].newMessage.parts[0].text.includes('Lead'));
|
|
153
|
+
assert.ok(runner.calls[0].newMessage.parts[0].text.includes('Need status update'));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('Google ADK onRelay uses custom userId and sessionId', async () => {
|
|
157
|
+
const { onRelay } = await loadAdkAdapterModule();
|
|
158
|
+
const relay = new FakeRelay();
|
|
159
|
+
const agent = createMockAgent();
|
|
160
|
+
const runner = createMockRunner();
|
|
161
|
+
|
|
162
|
+
onRelay(
|
|
163
|
+
'AdkTester',
|
|
164
|
+
{ agent, runner, userId: 'custom-user', sessionId: 'custom-session' },
|
|
165
|
+
relay,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
await relay.emit({ sender: 'Peer', text: 'ping', messageId: 'msg-2' });
|
|
169
|
+
|
|
170
|
+
assert.equal(runner.calls[0].userId, 'custom-user');
|
|
171
|
+
assert.equal(runner.calls[0].sessionId, 'custom-session');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('Google ADK onRelay unsubscribe stops message routing', async () => {
|
|
175
|
+
const { onRelay } = await loadAdkAdapterModule();
|
|
176
|
+
const relay = new FakeRelay();
|
|
177
|
+
const agent = createMockAgent();
|
|
178
|
+
const runner = createMockRunner();
|
|
179
|
+
|
|
180
|
+
const result = onRelay('AdkTester', { agent, runner }, relay);
|
|
181
|
+
|
|
182
|
+
// Unsubscribe from message routing
|
|
183
|
+
result.unsubscribe();
|
|
184
|
+
|
|
185
|
+
await relay.emit({ sender: 'Lead', text: 'Should not arrive', messageId: 'msg-3' });
|
|
186
|
+
|
|
187
|
+
assert.equal(runner.calls.length, 0);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('Google ADK onRelay without runner does not subscribe to messages', async () => {
|
|
191
|
+
const { onRelay } = await loadAdkAdapterModule();
|
|
192
|
+
const relay = new FakeRelay();
|
|
193
|
+
const agent = createMockAgent();
|
|
194
|
+
|
|
195
|
+
const result = onRelay('AdkTester', { agent }, relay);
|
|
196
|
+
|
|
197
|
+
// unsubscribe should be a no-op
|
|
198
|
+
assert.equal(typeof result.unsubscribe, 'function');
|
|
199
|
+
result.unsubscribe(); // should not throw
|
|
200
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
const langgraphAdapterModulePath = '../../../communicate/adapters/langgraph.js';
|
|
5
|
+
|
|
6
|
+
async function loadLangGraphAdapterModule(): Promise<any> {
|
|
7
|
+
return import(langgraphAdapterModulePath);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class FakeRelay {
|
|
11
|
+
private callbacks: Array<(message: any) => void | Promise<void>> = [];
|
|
12
|
+
|
|
13
|
+
async send(_to: string, _text: string): Promise<void> {}
|
|
14
|
+
|
|
15
|
+
async post(_channel: string, _text: string): Promise<void> {}
|
|
16
|
+
|
|
17
|
+
async inbox(): Promise<any[]> {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async agents(): Promise<string[]> {
|
|
22
|
+
return ['Lead', 'Impl-TS'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onMessage(callback: (message: any) => void | Promise<void>): () => void {
|
|
26
|
+
this.callbacks.push(callback);
|
|
27
|
+
return () => {
|
|
28
|
+
this.callbacks = this.callbacks.filter((entry) => entry !== callback);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async emit(message: any): Promise<void> {
|
|
33
|
+
for (const callback of [...this.callbacks]) {
|
|
34
|
+
await callback(message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createFakeGraph() {
|
|
40
|
+
return {
|
|
41
|
+
nodes: {},
|
|
42
|
+
invokeCalls: [] as Array<{ input: Record<string, unknown>; config?: Record<string, unknown> }>,
|
|
43
|
+
async invoke(input: Record<string, unknown>, config?: Record<string, unknown>) {
|
|
44
|
+
this.invokeCalls.push({ input, config });
|
|
45
|
+
return { messages: [] };
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
test('LangGraph onRelay returns relay tools', async () => {
|
|
51
|
+
const { onRelay } = await loadLangGraphAdapterModule();
|
|
52
|
+
const relay = new FakeRelay();
|
|
53
|
+
const graph = createFakeGraph();
|
|
54
|
+
|
|
55
|
+
const result = onRelay(graph, relay);
|
|
56
|
+
|
|
57
|
+
const toolNames = result.tools.map((tool: any) => tool.name);
|
|
58
|
+
assert.deepEqual(toolNames, ['relay_send', 'relay_inbox', 'relay_post', 'relay_agents']);
|
|
59
|
+
|
|
60
|
+
for (const tool of result.tools) {
|
|
61
|
+
assert.equal(typeof tool.invoke, 'function');
|
|
62
|
+
assert.ok(tool.schema);
|
|
63
|
+
assert.ok(tool.description);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('LangGraph relay_send tool calls relay.send', async () => {
|
|
68
|
+
const { onRelay } = await loadLangGraphAdapterModule();
|
|
69
|
+
const relay = new FakeRelay();
|
|
70
|
+
const sendCalls: Array<{ to: string; text: string }> = [];
|
|
71
|
+
relay.send = async (to: string, text: string) => {
|
|
72
|
+
sendCalls.push({ to, text });
|
|
73
|
+
};
|
|
74
|
+
const graph = createFakeGraph();
|
|
75
|
+
|
|
76
|
+
const result = onRelay(graph, relay);
|
|
77
|
+
const sendTool = result.tools.find((t: any) => t.name === 'relay_send');
|
|
78
|
+
|
|
79
|
+
const output = await sendTool.invoke({ to: 'Lead', text: 'Hello' });
|
|
80
|
+
assert.equal(output, 'Sent relay message to Lead.');
|
|
81
|
+
assert.deepEqual(sendCalls, [{ to: 'Lead', text: 'Hello' }]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('LangGraph relay_inbox tool returns formatted inbox', async () => {
|
|
85
|
+
const { onRelay } = await loadLangGraphAdapterModule();
|
|
86
|
+
const relay = new FakeRelay();
|
|
87
|
+
const graph = createFakeGraph();
|
|
88
|
+
|
|
89
|
+
const result = onRelay(graph, relay);
|
|
90
|
+
const inboxTool = result.tools.find((t: any) => t.name === 'relay_inbox');
|
|
91
|
+
|
|
92
|
+
const output = await inboxTool.invoke({});
|
|
93
|
+
assert.equal(output, 'No new relay messages.');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('LangGraph relay_post tool calls relay.post', async () => {
|
|
97
|
+
const { onRelay } = await loadLangGraphAdapterModule();
|
|
98
|
+
const relay = new FakeRelay();
|
|
99
|
+
const postCalls: Array<{ channel: string; text: string }> = [];
|
|
100
|
+
relay.post = async (channel: string, text: string) => {
|
|
101
|
+
postCalls.push({ channel, text });
|
|
102
|
+
};
|
|
103
|
+
const graph = createFakeGraph();
|
|
104
|
+
|
|
105
|
+
const result = onRelay(graph, relay);
|
|
106
|
+
const postTool = result.tools.find((t: any) => t.name === 'relay_post');
|
|
107
|
+
|
|
108
|
+
const output = await postTool.invoke({ channel: 'general', text: 'Update' });
|
|
109
|
+
assert.equal(output, 'Posted relay message to #general.');
|
|
110
|
+
assert.deepEqual(postCalls, [{ channel: 'general', text: 'Update' }]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('LangGraph relay_agents tool lists agents', async () => {
|
|
114
|
+
const { onRelay } = await loadLangGraphAdapterModule();
|
|
115
|
+
const relay = new FakeRelay();
|
|
116
|
+
const graph = createFakeGraph();
|
|
117
|
+
|
|
118
|
+
const result = onRelay(graph, relay);
|
|
119
|
+
const agentsTool = result.tools.find((t: any) => t.name === 'relay_agents');
|
|
120
|
+
|
|
121
|
+
const output = await agentsTool.invoke({});
|
|
122
|
+
assert.equal(output, 'Lead\nImpl-TS');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('LangGraph onRelay routes incoming messages via graph.invoke', async () => {
|
|
126
|
+
const { onRelay } = await loadLangGraphAdapterModule();
|
|
127
|
+
const relay = new FakeRelay();
|
|
128
|
+
const graph = createFakeGraph();
|
|
129
|
+
|
|
130
|
+
onRelay(graph, relay);
|
|
131
|
+
|
|
132
|
+
await relay.emit({
|
|
133
|
+
sender: 'Lead',
|
|
134
|
+
text: 'Need status update',
|
|
135
|
+
messageId: 'msg-1',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
assert.equal(graph.invokeCalls.length, 1);
|
|
139
|
+
const call = graph.invokeCalls[0];
|
|
140
|
+
assert.ok(Array.isArray(call.input.messages));
|
|
141
|
+
const msg = (call.input.messages as any[])[0];
|
|
142
|
+
assert.equal(msg.role, 'user');
|
|
143
|
+
assert.match(msg.content, /Lead/);
|
|
144
|
+
assert.match(msg.content, /Need status update/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('LangGraph onRelay unsubscribe stops message routing', async () => {
|
|
148
|
+
const { onRelay } = await loadLangGraphAdapterModule();
|
|
149
|
+
const relay = new FakeRelay();
|
|
150
|
+
const graph = createFakeGraph();
|
|
151
|
+
|
|
152
|
+
const result = onRelay(graph, relay);
|
|
153
|
+
result.unsubscribe();
|
|
154
|
+
|
|
155
|
+
await relay.emit({
|
|
156
|
+
sender: 'Lead',
|
|
157
|
+
text: 'Should not arrive',
|
|
158
|
+
messageId: 'msg-2',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
assert.equal(graph.invokeCalls.length, 0);
|
|
162
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
const adapterModulePath = '../../../communicate/adapters/openai-agents.js';
|
|
5
|
+
|
|
6
|
+
async function loadModule(): Promise<any> {
|
|
7
|
+
return import(adapterModulePath);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class FakeRelay {
|
|
11
|
+
private callbacks: Array<(message: any) => void | Promise<void>> = [];
|
|
12
|
+
|
|
13
|
+
async send(_to: string, _text: string): Promise<void> {}
|
|
14
|
+
|
|
15
|
+
async post(_channel: string, _text: string): Promise<void> {}
|
|
16
|
+
|
|
17
|
+
async inbox(): Promise<any[]> {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async agents(): Promise<string[]> {
|
|
22
|
+
return ['Lead', 'Impl-TS'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onMessage(callback: (message: any) => void | Promise<void>): () => void {
|
|
26
|
+
this.callbacks.push(callback);
|
|
27
|
+
return () => {
|
|
28
|
+
this.callbacks = this.callbacks.filter((entry) => entry !== callback);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async emit(message: any): Promise<void> {
|
|
33
|
+
for (const callback of [...this.callbacks]) {
|
|
34
|
+
await callback(message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createAgent(instructions: string | ((...args: unknown[]) => string | Promise<string>) = 'You are a helpful agent.') {
|
|
40
|
+
return {
|
|
41
|
+
name: 'TestAgent',
|
|
42
|
+
instructions,
|
|
43
|
+
tools: [] as any[],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test('onRelay appends relay tools to agent.tools', async () => {
|
|
48
|
+
const { onRelay } = await loadModule();
|
|
49
|
+
const relay = new FakeRelay();
|
|
50
|
+
const agent = createAgent();
|
|
51
|
+
agent.tools.push({ type: 'function', name: 'existing-tool' });
|
|
52
|
+
|
|
53
|
+
const result = onRelay(agent, relay);
|
|
54
|
+
|
|
55
|
+
const toolNames = result.agent.tools.map((t: any) => t.name);
|
|
56
|
+
assert.deepEqual(toolNames, [
|
|
57
|
+
'existing-tool',
|
|
58
|
+
'relay_send',
|
|
59
|
+
'relay_inbox',
|
|
60
|
+
'relay_post',
|
|
61
|
+
'relay_agents',
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
for (const toolName of ['relay_send', 'relay_inbox', 'relay_post', 'relay_agents']) {
|
|
65
|
+
const tool = result.agent.tools.find((t: any) => t.name === toolName);
|
|
66
|
+
assert.ok(tool, `Expected ${toolName} to be registered`);
|
|
67
|
+
assert.equal(tool.type, 'function');
|
|
68
|
+
assert.equal(typeof tool.invoke, 'function');
|
|
69
|
+
assert.ok(tool.parameters);
|
|
70
|
+
assert.equal(tool.parameters.type, 'object');
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('relay tools invoke correctly', async () => {
|
|
75
|
+
const { onRelay } = await loadModule();
|
|
76
|
+
const relay = new FakeRelay();
|
|
77
|
+
const agent = createAgent();
|
|
78
|
+
|
|
79
|
+
const result = onRelay(agent, relay);
|
|
80
|
+
|
|
81
|
+
const agentsTool = result.agent.tools.find((t: any) => t.name === 'relay_agents');
|
|
82
|
+
const output = await agentsTool.invoke(null, '{}');
|
|
83
|
+
assert.equal(output, 'Lead\nImpl-TS');
|
|
84
|
+
|
|
85
|
+
const inboxTool = result.agent.tools.find((t: any) => t.name === 'relay_inbox');
|
|
86
|
+
const inboxOutput = await inboxTool.invoke(null, '{}');
|
|
87
|
+
assert.equal(inboxOutput, 'No new relay messages.');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('incoming relay messages are appended to instructions', async () => {
|
|
91
|
+
const { onRelay } = await loadModule();
|
|
92
|
+
const relay = new FakeRelay();
|
|
93
|
+
const agent = createAgent('Base instructions.');
|
|
94
|
+
|
|
95
|
+
onRelay(agent, relay);
|
|
96
|
+
|
|
97
|
+
await relay.emit({ sender: 'Lead', text: 'Need status', messageId: 'msg-1' });
|
|
98
|
+
|
|
99
|
+
const instructions = await (agent.instructions as (...args: unknown[]) => Promise<string>)();
|
|
100
|
+
assert.match(instructions, /Base instructions\./);
|
|
101
|
+
assert.match(instructions, /Lead/);
|
|
102
|
+
assert.match(instructions, /Need status/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('pending messages are drained after instructions call', async () => {
|
|
106
|
+
const { onRelay } = await loadModule();
|
|
107
|
+
const relay = new FakeRelay();
|
|
108
|
+
const agent = createAgent('Base.');
|
|
109
|
+
|
|
110
|
+
onRelay(agent, relay);
|
|
111
|
+
|
|
112
|
+
await relay.emit({ sender: 'Worker', text: 'Done', messageId: 'msg-2' });
|
|
113
|
+
|
|
114
|
+
const first = await (agent.instructions as (...args: unknown[]) => Promise<string>)();
|
|
115
|
+
assert.match(first, /Worker/);
|
|
116
|
+
|
|
117
|
+
const second = await (agent.instructions as (...args: unknown[]) => Promise<string>)();
|
|
118
|
+
assert.ok(!second.includes('Worker'), 'Messages should be drained after first read');
|
|
119
|
+
assert.equal(second, 'Base.');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('instructions function is preserved when original is a function', async () => {
|
|
123
|
+
const { onRelay } = await loadModule();
|
|
124
|
+
const relay = new FakeRelay();
|
|
125
|
+
const agent = {
|
|
126
|
+
name: 'FnAgent',
|
|
127
|
+
instructions: async () => 'Dynamic instructions',
|
|
128
|
+
tools: [] as any[],
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
onRelay(agent, relay);
|
|
132
|
+
|
|
133
|
+
const output = await agent.instructions();
|
|
134
|
+
assert.equal(output, 'Dynamic instructions');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('cleanup restores original instructions and removes relay tools', async () => {
|
|
138
|
+
const { onRelay } = await loadModule();
|
|
139
|
+
const relay = new FakeRelay();
|
|
140
|
+
const originalInstructions = 'Original.';
|
|
141
|
+
const agent = createAgent(originalInstructions);
|
|
142
|
+
|
|
143
|
+
const result = onRelay(agent, relay);
|
|
144
|
+
|
|
145
|
+
assert.equal(result.agent.tools.length, 4);
|
|
146
|
+
assert.equal(typeof result.agent.instructions, 'function');
|
|
147
|
+
|
|
148
|
+
result.cleanup();
|
|
149
|
+
|
|
150
|
+
assert.equal(result.agent.tools.length, 0);
|
|
151
|
+
assert.equal(result.agent.instructions, originalInstructions);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('cleanup stops message routing', async () => {
|
|
155
|
+
const { onRelay } = await loadModule();
|
|
156
|
+
const relay = new FakeRelay();
|
|
157
|
+
const agent = createAgent('Base.');
|
|
158
|
+
|
|
159
|
+
const result = onRelay(agent, relay);
|
|
160
|
+
result.cleanup();
|
|
161
|
+
|
|
162
|
+
await relay.emit({ sender: 'Late', text: 'Should not appear', messageId: 'msg-3' });
|
|
163
|
+
|
|
164
|
+
// instructions was restored to a string, so no messages appended
|
|
165
|
+
assert.equal(result.agent.instructions, 'Base.');
|
|
166
|
+
});
|