@teneo-protocol/sdk 3.0.0 → 3.1.1
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/CHANGELOG.md +21 -0
- package/README.md +106 -1
- package/dist/handlers/message-handlers/agent-details-response-handler.d.ts +378 -54
- package/dist/handlers/message-handlers/agent-details-response-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/agent-status-update-handler.d.ts +378 -54
- package/dist/handlers/message-handlers/agent-status-update-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/all-agents-response-handler.d.ts +135 -54
- package/dist/handlers/message-handlers/all-agents-response-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/list-available-agents-handler.d.ts +378 -54
- package/dist/handlers/message-handlers/list-available-agents-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/list-room-agents-handler.d.ts +378 -54
- package/dist/handlers/message-handlers/list-room-agents-handler.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/managers/admin-manager.d.ts +1 -1
- package/dist/managers/admin-manager.js +1 -1
- package/dist/managers/message-router.d.ts +35 -0
- package/dist/managers/message-router.d.ts.map +1 -1
- package/dist/managers/message-router.js +143 -2
- package/dist/managers/message-router.js.map +1 -1
- package/dist/payments/payment-client.d.ts.map +1 -1
- package/dist/payments/payment-client.js +5 -3
- package/dist/payments/payment-client.js.map +1 -1
- package/dist/teneo-sdk.d.ts +2 -2
- package/dist/teneo-sdk.d.ts.map +1 -1
- package/dist/teneo-sdk.js +21 -3
- package/dist/teneo-sdk.js.map +1 -1
- package/dist/types/config.d.ts +29 -3
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +21 -2
- package/dist/types/config.js.map +1 -1
- package/dist/types/error-codes.d.ts +3 -0
- package/dist/types/error-codes.d.ts.map +1 -1
- package/dist/types/error-codes.js +4 -0
- package/dist/types/error-codes.js.map +1 -1
- package/dist/types/events.d.ts +3 -0
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/events.js.map +1 -1
- package/dist/types/messages.d.ts +3632 -552
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/messages.js +18 -7
- package/dist/types/messages.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/managers/admin-manager.ts +1 -1
- package/src/managers/message-router.ts +183 -3
- package/src/payments/payment-client.ts +6 -3
- package/src/teneo-sdk.ts +22 -3
- package/src/types/config.ts +23 -2
- package/src/types/error-codes.ts +5 -0
- package/src/types/events.ts +5 -0
- package/src/types/messages.ts +18 -7
- package/tests/unit/managers/admin-manager.test.ts +2 -2
- package/tests/unit/managers/message-router-autosummon.test.ts +338 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for MessageRouter autosummon functionality
|
|
3
|
+
* Tests pre-flight autosummon, fallback autosummon, lifecycle events,
|
|
4
|
+
* cache hit/miss scenarios, and error handling with mocked dependencies.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi } from "vitest";
|
|
8
|
+
import { EventEmitter } from "eventemitter3";
|
|
9
|
+
import { MessageRouter, MessageRouterConfig } from "../../../src/managers/message-router";
|
|
10
|
+
import { AgentRoomManager } from "../../../src/managers/agent-room-manager";
|
|
11
|
+
import { Logger } from "../../../src/types";
|
|
12
|
+
|
|
13
|
+
type EmittedEvent = { name: string; args: any[] };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a mock wsClient backed by a real EventEmitter so that
|
|
17
|
+
* waitForEvent / .on() / .off() work correctly in MessageRouter.
|
|
18
|
+
*/
|
|
19
|
+
function createMockWsClient() {
|
|
20
|
+
const ee = new EventEmitter();
|
|
21
|
+
const emittedEvents: EmittedEvent[] = [];
|
|
22
|
+
const originalEmit = ee.emit.bind(ee);
|
|
23
|
+
|
|
24
|
+
const mock = Object.assign(ee, {
|
|
25
|
+
isConnected: true,
|
|
26
|
+
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
27
|
+
_emittedEvents: emittedEvents
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Intercept emit to record events for assertions
|
|
31
|
+
mock.emit = ((...args: any[]) => {
|
|
32
|
+
const [name, ...rest] = args;
|
|
33
|
+
emittedEvents.push({ name, args: rest });
|
|
34
|
+
return originalEmit(name, ...rest);
|
|
35
|
+
}) as any;
|
|
36
|
+
|
|
37
|
+
return mock;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createMockLogger(): Logger {
|
|
41
|
+
return { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createMockAgentRoomManager(opts: {
|
|
45
|
+
checkAgentInRoom?: boolean | undefined;
|
|
46
|
+
availableAgents?: Array<{ agent_id: string; agent_name: string }>;
|
|
47
|
+
addAgentThrows?: Error;
|
|
48
|
+
} = {}) {
|
|
49
|
+
return {
|
|
50
|
+
checkAgentInRoom: vi.fn().mockReturnValue(opts.checkAgentInRoom),
|
|
51
|
+
listAvailableAgents: vi.fn().mockResolvedValue(opts.availableAgents || []),
|
|
52
|
+
addAgentToRoom: opts.addAgentThrows
|
|
53
|
+
? vi.fn().mockRejectedValue(opts.addAgentThrows)
|
|
54
|
+
: vi.fn().mockResolvedValue(undefined)
|
|
55
|
+
} as any as AgentRoomManager;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const QUOTE_DATA = {
|
|
59
|
+
data: {
|
|
60
|
+
task_id: "t1", agent_id: "news-agent", agent_name: "news-agent",
|
|
61
|
+
agent_wallet: "0x123", command: "latest", pricing: { price: 0 },
|
|
62
|
+
expires_at: new Date(Date.now() + 60000).toISOString(),
|
|
63
|
+
settlement_router: "0x", salt: "0x", facilitator_fee: "0", hook: "0x"
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function createRouter(overrides: Partial<MessageRouterConfig> = {}) {
|
|
68
|
+
const wsClient = createMockWsClient();
|
|
69
|
+
const config: MessageRouterConfig = {
|
|
70
|
+
messageTimeout: 5000,
|
|
71
|
+
autoApproveQuotes: true,
|
|
72
|
+
quoteTimeout: 3000,
|
|
73
|
+
autoSummon: true,
|
|
74
|
+
paymentNetwork: "eip155:8453", // Avoids getDefaultNetwork() call
|
|
75
|
+
...overrides
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const webhookHandler = {
|
|
79
|
+
sendMessageWebhook: vi.fn().mockResolvedValue(undefined),
|
|
80
|
+
sendWebhook: vi.fn().mockResolvedValue(undefined)
|
|
81
|
+
} as any;
|
|
82
|
+
|
|
83
|
+
const responseFormatter = {
|
|
84
|
+
format: vi.fn((data: any) => ({ humanized: data.content || "", raw: data }))
|
|
85
|
+
} as any;
|
|
86
|
+
|
|
87
|
+
const router = new MessageRouter(
|
|
88
|
+
wsClient as any,
|
|
89
|
+
webhookHandler,
|
|
90
|
+
responseFormatter,
|
|
91
|
+
createMockLogger(),
|
|
92
|
+
config
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return { router, wsClient };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Helper: fire quote:received to resolve a pending _requestQuoteInternal */
|
|
99
|
+
function resolveQuote(wsClient: ReturnType<typeof createMockWsClient>) {
|
|
100
|
+
wsClient.emit("quote:received", QUOTE_DATA);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
describe("MessageRouter: Autosummon", () => {
|
|
104
|
+
describe("pre-flight autosummon (cache says agent NOT in room)", () => {
|
|
105
|
+
it("should add agent to room before sending command", async () => {
|
|
106
|
+
const { router, wsClient } = createRouter();
|
|
107
|
+
const arm = createMockAgentRoomManager({
|
|
108
|
+
checkAgentInRoom: false,
|
|
109
|
+
availableAgents: [{ agent_id: "news-agent", agent_name: "news-agent" }]
|
|
110
|
+
});
|
|
111
|
+
router.setAgentRoomManager(arm);
|
|
112
|
+
|
|
113
|
+
const promise = (router as any)._requestQuoteInternal(
|
|
114
|
+
"@news-agent latest", "room-1", undefined, false
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
await vi.waitFor(() => {
|
|
118
|
+
expect(arm.addAgentToRoom).toHaveBeenCalledWith("room-1", "news-agent");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
resolveQuote(wsClient);
|
|
122
|
+
const result = await promise;
|
|
123
|
+
expect(result.agentId).toBe("news-agent");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should emit autosummon:start and autosummon:success events", async () => {
|
|
127
|
+
const { router, wsClient } = createRouter();
|
|
128
|
+
const arm = createMockAgentRoomManager({
|
|
129
|
+
checkAgentInRoom: false,
|
|
130
|
+
availableAgents: [{ agent_id: "news-agent", agent_name: "news-agent" }]
|
|
131
|
+
});
|
|
132
|
+
router.setAgentRoomManager(arm);
|
|
133
|
+
|
|
134
|
+
const promise = (router as any)._requestQuoteInternal(
|
|
135
|
+
"@news-agent latest", "room-1", undefined, false
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
await vi.waitFor(() => {
|
|
139
|
+
expect(arm.addAgentToRoom).toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const events = wsClient._emittedEvents;
|
|
143
|
+
const start = events.find((e) => e.name === "autosummon:start");
|
|
144
|
+
const success = events.find((e) => e.name === "autosummon:success");
|
|
145
|
+
|
|
146
|
+
expect(start).toBeDefined();
|
|
147
|
+
expect(start!.args).toEqual(["news-agent", "room-1"]);
|
|
148
|
+
expect(success).toBeDefined();
|
|
149
|
+
expect(success!.args).toEqual(["news-agent", "news-agent", "room-1"]);
|
|
150
|
+
|
|
151
|
+
resolveQuote(wsClient);
|
|
152
|
+
await promise;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should emit autosummon:failed when agent not found in available list", async () => {
|
|
156
|
+
const { router, wsClient } = createRouter();
|
|
157
|
+
const arm = createMockAgentRoomManager({
|
|
158
|
+
checkAgentInRoom: false,
|
|
159
|
+
availableAgents: [] // not found
|
|
160
|
+
});
|
|
161
|
+
router.setAgentRoomManager(arm);
|
|
162
|
+
|
|
163
|
+
const promise = (router as any)._requestQuoteInternal(
|
|
164
|
+
"@ghost-agent test", "room-1", undefined, false
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
await vi.waitFor(() => {
|
|
168
|
+
const failed = wsClient._emittedEvents.find((e) => e.name === "autosummon:failed");
|
|
169
|
+
expect(failed).toBeDefined();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const failed = wsClient._emittedEvents.find((e) => e.name === "autosummon:failed");
|
|
173
|
+
expect(failed!.args[0]).toBe("ghost-agent");
|
|
174
|
+
expect(failed!.args[1]).toBe("room-1");
|
|
175
|
+
expect(failed!.args[2]).toBe("Agent not found or offline");
|
|
176
|
+
|
|
177
|
+
// Command should still be sent (fallback continues)
|
|
178
|
+
expect(wsClient.sendMessage).toHaveBeenCalled();
|
|
179
|
+
|
|
180
|
+
resolveQuote(wsClient);
|
|
181
|
+
await promise.catch(() => {});
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("skip pre-flight (agent already in room)", () => {
|
|
186
|
+
it("should not trigger autosummon when cache says agent IS in room", async () => {
|
|
187
|
+
const { router, wsClient } = createRouter();
|
|
188
|
+
const arm = createMockAgentRoomManager({ checkAgentInRoom: true });
|
|
189
|
+
router.setAgentRoomManager(arm);
|
|
190
|
+
|
|
191
|
+
const promise = (router as any)._requestQuoteInternal(
|
|
192
|
+
"@news-agent latest", "room-1", undefined, false
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Wait for sendMessage to be called (no pre-flight delay)
|
|
196
|
+
await vi.waitFor(() => {
|
|
197
|
+
expect(wsClient.sendMessage).toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(arm.addAgentToRoom).not.toHaveBeenCalled();
|
|
201
|
+
expect(wsClient._emittedEvents.find((e) => e.name === "autosummon:start")).toBeUndefined();
|
|
202
|
+
|
|
203
|
+
resolveQuote(wsClient);
|
|
204
|
+
await promise;
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("skip pre-flight (cache empty — undefined)", () => {
|
|
209
|
+
it("should skip pre-flight and send directly when cache is empty", async () => {
|
|
210
|
+
const { router, wsClient } = createRouter();
|
|
211
|
+
const arm = createMockAgentRoomManager({ checkAgentInRoom: undefined });
|
|
212
|
+
router.setAgentRoomManager(arm);
|
|
213
|
+
|
|
214
|
+
const promise = (router as any)._requestQuoteInternal(
|
|
215
|
+
"@news-agent latest", "room-1", undefined, false
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
await vi.waitFor(() => {
|
|
219
|
+
expect(wsClient.sendMessage).toHaveBeenCalled();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(arm.addAgentToRoom).not.toHaveBeenCalled();
|
|
223
|
+
expect(arm.listAvailableAgents).not.toHaveBeenCalled();
|
|
224
|
+
expect(wsClient._emittedEvents.find((e) => e.name === "autosummon:start")).toBeUndefined();
|
|
225
|
+
|
|
226
|
+
resolveQuote(wsClient);
|
|
227
|
+
await promise;
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("autoSummon disabled", () => {
|
|
232
|
+
it("should never trigger pre-flight when autoSummon is false", async () => {
|
|
233
|
+
const { router, wsClient } = createRouter({ autoSummon: false });
|
|
234
|
+
const arm = createMockAgentRoomManager({ checkAgentInRoom: false });
|
|
235
|
+
router.setAgentRoomManager(arm);
|
|
236
|
+
|
|
237
|
+
const promise = (router as any)._requestQuoteInternal(
|
|
238
|
+
"@news-agent latest", "room-1", undefined, false
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
await vi.waitFor(() => {
|
|
242
|
+
expect(wsClient.sendMessage).toHaveBeenCalled();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(arm.checkAgentInRoom).not.toHaveBeenCalled();
|
|
246
|
+
expect(arm.addAgentToRoom).not.toHaveBeenCalled();
|
|
247
|
+
|
|
248
|
+
resolveQuote(wsClient);
|
|
249
|
+
await promise;
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("fallback autosummon (coordinator rejects)", () => {
|
|
254
|
+
it("should emit events when fallback path triggers on coordinator reject", async () => {
|
|
255
|
+
const { router, wsClient } = createRouter();
|
|
256
|
+
const arm = createMockAgentRoomManager({
|
|
257
|
+
checkAgentInRoom: undefined, // cache empty → pre-flight skipped
|
|
258
|
+
availableAgents: [{ agent_id: "news-agent", agent_name: "news-agent" }]
|
|
259
|
+
});
|
|
260
|
+
router.setAgentRoomManager(arm);
|
|
261
|
+
|
|
262
|
+
const promise = (router as any)._requestQuoteInternal(
|
|
263
|
+
"@news-agent latest", "room-1", undefined, false
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// Wait for sendMessage, then fire coordinator reject
|
|
267
|
+
await vi.waitFor(() => {
|
|
268
|
+
expect(wsClient.sendMessage).toHaveBeenCalled();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
wsClient.emit("agent:response", {
|
|
272
|
+
content: "agent news-agent does not have access to room room-1",
|
|
273
|
+
agentId: "coordinator"
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// handleAutoSummon fires, adds agent, retries
|
|
277
|
+
await vi.waitFor(() => {
|
|
278
|
+
expect(arm.addAgentToRoom).toHaveBeenCalledWith("room-1", "news-agent");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const events = wsClient._emittedEvents;
|
|
282
|
+
expect(events.find((e) => e.name === "autosummon:start")).toBeDefined();
|
|
283
|
+
expect(events.find((e) => e.name === "autosummon:success")).toBeDefined();
|
|
284
|
+
|
|
285
|
+
// Retry sends another quote request — resolve it
|
|
286
|
+
resolveQuote(wsClient);
|
|
287
|
+
const result = await promise;
|
|
288
|
+
expect(result.agentId).toBe("news-agent");
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("isRetry prevents double-summon", () => {
|
|
293
|
+
it("should skip pre-flight on retry even if cache says agent not in room", async () => {
|
|
294
|
+
const { router, wsClient } = createRouter();
|
|
295
|
+
const arm = createMockAgentRoomManager({ checkAgentInRoom: false });
|
|
296
|
+
router.setAgentRoomManager(arm);
|
|
297
|
+
|
|
298
|
+
const promise = (router as any)._requestQuoteInternal(
|
|
299
|
+
"@news-agent latest", "room-1", undefined, true // isRetry
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
await vi.waitFor(() => {
|
|
303
|
+
expect(wsClient.sendMessage).toHaveBeenCalled();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(arm.checkAgentInRoom).not.toHaveBeenCalled();
|
|
307
|
+
expect(arm.addAgentToRoom).not.toHaveBeenCalled();
|
|
308
|
+
|
|
309
|
+
resolveQuote(wsClient);
|
|
310
|
+
await promise;
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe("pre-flight failure falls through gracefully", () => {
|
|
315
|
+
it("should still send command if addAgentToRoom throws", async () => {
|
|
316
|
+
const { router, wsClient } = createRouter();
|
|
317
|
+
const arm = createMockAgentRoomManager({
|
|
318
|
+
checkAgentInRoom: false,
|
|
319
|
+
availableAgents: [{ agent_id: "news-agent", agent_name: "news-agent" }],
|
|
320
|
+
addAgentThrows: new Error("Network timeout")
|
|
321
|
+
});
|
|
322
|
+
router.setAgentRoomManager(arm);
|
|
323
|
+
|
|
324
|
+
const promise = (router as any)._requestQuoteInternal(
|
|
325
|
+
"@news-agent latest", "room-1", undefined, false
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// Pre-flight tried, failed, but sendMessage should still be called
|
|
329
|
+
await vi.waitFor(() => {
|
|
330
|
+
expect(wsClient.sendMessage).toHaveBeenCalled();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
resolveQuote(wsClient);
|
|
334
|
+
const result = await promise;
|
|
335
|
+
expect(result.agentId).toBe("news-agent");
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
});
|