@agrentingai/paperclip-adapter 0.2.0

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.
@@ -0,0 +1,497 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import {
3
+ createServerAdapter,
4
+ getConfigSchema,
5
+ testEnvironment,
6
+ execute,
7
+ cancelTask,
8
+ discoverAgents,
9
+ getBalance,
10
+ getTransactions,
11
+ getTaskPayment,
12
+ getTaskProgress,
13
+ } from "./adapter.js";
14
+ import { unregisterTaskMapping, getActiveTaskMappings, stopRegistryCleanup } from "./webhook-handler.js";
15
+ import type { AgrentingAdapterConfig } from "./types.js";
16
+
17
+ // Mock the client so we don't hit the real API
18
+ vi.mock("./client.js", () => {
19
+ return {
20
+ AgrentingClient: vi.fn().mockImplementation(function() {
21
+ return {
22
+ testConnection: vi.fn().mockResolvedValue({ ok: true, message: "Connected" }),
23
+ createTask: vi.fn().mockResolvedValue({
24
+ id: "mock-task-id",
25
+ status: "pending",
26
+ client_agent_id: "c1",
27
+ provider_agent_id: "did:agrenting:test",
28
+ capability: "test",
29
+ input: "hello",
30
+ created_at: new Date().toISOString(),
31
+ updated_at: new Date().toISOString(),
32
+ }),
33
+ getTask: vi.fn().mockResolvedValue({
34
+ id: "mock-task-id",
35
+ status: "completed",
36
+ client_agent_id: "c1",
37
+ provider_agent_id: "did:agrenting:test",
38
+ capability: "test",
39
+ input: "hello",
40
+ output: "task output",
41
+ created_at: new Date().toISOString(),
42
+ updated_at: new Date().toISOString(),
43
+ }),
44
+ getTaskProgress: vi.fn().mockResolvedValue({
45
+ status: "completed",
46
+ progress_percent: 100,
47
+ progress_message: "Done",
48
+ updated_at: new Date().toISOString(),
49
+ }),
50
+ getTaskTimeline: vi.fn().mockResolvedValue({ events: [] }),
51
+ cancelTask: vi.fn().mockResolvedValue({ id: "mock-task-id", status: "cancelled" }),
52
+ discoverAgents: vi.fn().mockResolvedValue([{ id: "agent-1", name: "Agent" }]),
53
+ getBalance: vi.fn().mockResolvedValue({ available: "100", escrow: "0", total: "100" }),
54
+ getTransactions: vi.fn().mockResolvedValue([]),
55
+ getTaskPayment: vi.fn().mockResolvedValue({
56
+ id: "pay-1", task_id: "mock-task-id", amount: "10", currency: "USD",
57
+ status: "escrowed", created_at: new Date().toISOString(),
58
+ }),
59
+ createTaskPayment: vi.fn().mockResolvedValue({
60
+ id: "pay-1", task_id: "mock-task-id", amount: "10", currency: "USD",
61
+ status: "escrowed", created_at: new Date().toISOString(),
62
+ }),
63
+ deleteWebhook: vi.fn().mockResolvedValue(undefined),
64
+ uploadDocument: vi.fn().mockResolvedValue({
65
+ id: "doc-1", name: "instructions", file_url: "https://cdn/doc",
66
+ content_type: "text/plain", file_hash: "hash", document_type: "instructions",
67
+ }),
68
+ };
69
+ }),
70
+ };
71
+ });
72
+
73
+ // Mock balance-monitor to avoid API calls
74
+ vi.mock("./balance-monitor.js", () => ({
75
+ canSubmitTask: vi.fn().mockResolvedValue({ ok: true }),
76
+ checkBalance: vi.fn().mockResolvedValue({
77
+ available: 100, escrow: 0, total: 100,
78
+ currency: "USD", isLow: false, isInsufficient: false,
79
+ }),
80
+ formatLowBalanceComment: vi.fn().mockReturnValue("Low balance"),
81
+ formatInsufficientBalanceComment: vi.fn().mockReturnValue("Insufficient"),
82
+ }));
83
+
84
+ // Mock webhook listener
85
+ vi.mock("./webhook-handler.js", async (importOriginal) => {
86
+ const actual = await importOriginal<typeof import("./webhook-handler.js")>();
87
+ return {
88
+ ...actual,
89
+ };
90
+ });
91
+
92
+ const mockConfig: AgrentingAdapterConfig = {
93
+ agrentingUrl: "https://api.agrenting.com",
94
+ apiKey: "test-key",
95
+ agentDid: "did:agrenting:test",
96
+ };
97
+
98
+ beforeEach(() => {
99
+ vi.clearAllMocks();
100
+ // Clear task registry
101
+ const mappings = getActiveTaskMappings();
102
+ for (const key of mappings.keys()) {
103
+ unregisterTaskMapping(key);
104
+ }
105
+ stopRegistryCleanup();
106
+ });
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // createServerAdapter
110
+ // ---------------------------------------------------------------------------
111
+
112
+ describe("createServerAdapter", () => {
113
+ it("returns adapter with name 'agrenting'", () => {
114
+ const adapter = createServerAdapter();
115
+ expect(adapter.name).toBe("agrenting");
116
+ });
117
+
118
+ it("exposes all required methods", () => {
119
+ const adapter = createServerAdapter();
120
+ const methods = [
121
+ "execute",
122
+ "testEnvironment",
123
+ "getConfigSchema",
124
+ "startWebhookListener",
125
+ "stopWebhookListener",
126
+ "registerWebhook",
127
+ "deregisterWebhook",
128
+ "getTaskProgress",
129
+ "getTaskPayment",
130
+ "cancelTask",
131
+ "discoverAgents",
132
+ "getBalance",
133
+ "getTransactions",
134
+ "deposit",
135
+ "withdraw",
136
+ ];
137
+ for (const method of methods) {
138
+ expect(typeof (adapter as Record<string, unknown>)[method]).toBe("function");
139
+ }
140
+ });
141
+ });
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // getConfigSchema
145
+ // ---------------------------------------------------------------------------
146
+
147
+ describe("getConfigSchema", () => {
148
+ it("returns a valid JSON schema with required fields", () => {
149
+ const schema = getConfigSchema();
150
+ expect(schema.type).toBe("object");
151
+ expect(schema.required).toEqual(["agrentingUrl", "apiKey", "agentDid"]);
152
+ });
153
+
154
+ it("includes all config properties", () => {
155
+ const schema = getConfigSchema();
156
+ const props = schema.properties as Record<string, unknown>;
157
+ expect(props).toHaveProperty("agrentingUrl");
158
+ expect(props).toHaveProperty("apiKey");
159
+ expect(props).toHaveProperty("agentDid");
160
+ expect(props).toHaveProperty("webhookSecret");
161
+ expect(props).toHaveProperty("webhookCallbackUrl");
162
+ expect(props).toHaveProperty("pricingModel");
163
+ expect(props).toHaveProperty("timeoutSec");
164
+ expect(props).toHaveProperty("instructionsBundleMode");
165
+ });
166
+
167
+ it("marks apiKey and webhookSecret as sensitive", () => {
168
+ const schema = getConfigSchema();
169
+ const props = schema.properties as Record<string, Record<string, unknown>>;
170
+ expect(props.apiKey.sensitive).toBe(true);
171
+ expect(props.webhookSecret.sensitive).toBe(true);
172
+ });
173
+ });
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // testEnvironment
177
+ // ---------------------------------------------------------------------------
178
+
179
+ describe("testEnvironment", () => {
180
+ it("returns ok when connection succeeds", async () => {
181
+ const result = await testEnvironment(mockConfig);
182
+ expect(result.ok).toBe(true);
183
+ });
184
+ });
185
+
186
+ // ---------------------------------------------------------------------------
187
+ // execute (polling mode — no webhook config)
188
+ // ---------------------------------------------------------------------------
189
+
190
+ describe("execute", () => {
191
+ it("executes a task in polling mode and returns success", async () => {
192
+ const result = await execute(mockConfig, {
193
+ input: "test input",
194
+ capability: "test-capability",
195
+ });
196
+
197
+ expect(result.success).toBe(true);
198
+ expect(result.taskId).toBe("mock-task-id");
199
+ expect(result.output).toBe("task output");
200
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
201
+ });
202
+
203
+ it("returns error when task fails", async () => {
204
+ const { AgrentingClient } = await import("./client.js");
205
+ const MockClient = vi.mocked(AgrentingClient);
206
+ // Both execute() and pollTaskUntilDone() create an AgrentingClient,
207
+ // so mock twice (once for each construction).
208
+ MockClient.mockImplementationOnce(function() {
209
+ return {
210
+ testConnection: vi.fn(),
211
+ createTask: vi.fn().mockResolvedValue({
212
+ id: "fail-task",
213
+ status: "pending",
214
+ client_agent_id: "c1",
215
+ provider_agent_id: "p1",
216
+ capability: "test",
217
+ input: "hello",
218
+ created_at: new Date().toISOString(),
219
+ updated_at: new Date().toISOString(),
220
+ }),
221
+ getTask: vi.fn(),
222
+ getTaskProgress: vi.fn(),
223
+ getTaskTimeline: vi.fn(),
224
+ cancelTask: vi.fn(),
225
+ discoverAgents: vi.fn(),
226
+ getBalance: vi.fn(),
227
+ getTransactions: vi.fn(),
228
+ getTaskPayment: vi.fn(),
229
+ createTaskPayment: vi.fn(),
230
+ deleteWebhook: vi.fn(),
231
+ uploadDocument: vi.fn(),
232
+ };
233
+ });
234
+ MockClient.mockImplementationOnce(function() {
235
+ return {
236
+ testConnection: vi.fn(),
237
+ createTask: vi.fn(),
238
+ getTask: vi.fn().mockResolvedValue({
239
+ id: "fail-task",
240
+ status: "failed",
241
+ error_reason: "Something went wrong",
242
+ client_agent_id: "c1",
243
+ provider_agent_id: "p1",
244
+ capability: "test",
245
+ input: "hello",
246
+ created_at: new Date().toISOString(),
247
+ updated_at: new Date().toISOString(),
248
+ }),
249
+ getTaskProgress: vi.fn(),
250
+ getTaskTimeline: vi.fn(),
251
+ cancelTask: vi.fn(),
252
+ discoverAgents: vi.fn(),
253
+ getBalance: vi.fn(),
254
+ getTransactions: vi.fn(),
255
+ getTaskPayment: vi.fn(),
256
+ createTaskPayment: vi.fn(),
257
+ deleteWebhook: vi.fn(),
258
+ uploadDocument: vi.fn(),
259
+ };
260
+ });
261
+
262
+ const result = await execute(mockConfig, {
263
+ input: "test",
264
+ capability: "test",
265
+ });
266
+
267
+ expect(result.success).toBe(false);
268
+ expect(result.error).toBe("Something went wrong");
269
+ });
270
+
271
+ it("returns error when task is cancelled", async () => {
272
+ const { AgrentingClient } = await import("./client.js");
273
+ const MockClient = vi.mocked(AgrentingClient);
274
+ MockClient.mockImplementationOnce(function() {
275
+ return {
276
+ testConnection: vi.fn(),
277
+ createTask: vi.fn().mockResolvedValue({
278
+ id: "cancel-task",
279
+ status: "pending",
280
+ client_agent_id: "c1",
281
+ provider_agent_id: "p1",
282
+ capability: "test",
283
+ input: "hello",
284
+ created_at: new Date().toISOString(),
285
+ updated_at: new Date().toISOString(),
286
+ }),
287
+ getTask: vi.fn(),
288
+ getTaskProgress: vi.fn(),
289
+ getTaskTimeline: vi.fn(),
290
+ cancelTask: vi.fn(),
291
+ discoverAgents: vi.fn(),
292
+ getBalance: vi.fn(),
293
+ getTransactions: vi.fn(),
294
+ getTaskPayment: vi.fn(),
295
+ createTaskPayment: vi.fn(),
296
+ deleteWebhook: vi.fn(),
297
+ uploadDocument: vi.fn(),
298
+ };
299
+ });
300
+ MockClient.mockImplementationOnce(function() {
301
+ return {
302
+ testConnection: vi.fn(),
303
+ createTask: vi.fn(),
304
+ getTask: vi.fn().mockResolvedValue({
305
+ id: "cancel-task",
306
+ status: "cancelled",
307
+ client_agent_id: "c1",
308
+ provider_agent_id: "p1",
309
+ capability: "test",
310
+ input: "hello",
311
+ created_at: new Date().toISOString(),
312
+ updated_at: new Date().toISOString(),
313
+ }),
314
+ getTaskProgress: vi.fn(),
315
+ getTaskTimeline: vi.fn(),
316
+ cancelTask: vi.fn(),
317
+ discoverAgents: vi.fn(),
318
+ getBalance: vi.fn(),
319
+ getTransactions: vi.fn(),
320
+ getTaskPayment: vi.fn(),
321
+ createTaskPayment: vi.fn(),
322
+ deleteWebhook: vi.fn(),
323
+ uploadDocument: vi.fn(),
324
+ };
325
+ });
326
+
327
+ const result = await execute(mockConfig, {
328
+ input: "test",
329
+ capability: "test",
330
+ });
331
+
332
+ expect(result.success).toBe(false);
333
+ expect(result.error).toBe("Task was cancelled");
334
+ });
335
+
336
+ it("handles escrow payment failure by cancelling the task", async () => {
337
+ const { AgrentingClient } = await import("./client.js");
338
+ const mockCancel = vi.fn().mockResolvedValue({ id: "pay-fail-task", status: "cancelled" });
339
+ const MockClient = vi.mocked(AgrentingClient);
340
+ MockClient.mockImplementationOnce(function() {
341
+ return {
342
+ testConnection: vi.fn(),
343
+ createTask: vi.fn().mockResolvedValue({
344
+ id: "pay-fail-task",
345
+ status: "pending",
346
+ client_agent_id: "c1",
347
+ provider_agent_id: "p1",
348
+ capability: "test",
349
+ input: "hello",
350
+ created_at: new Date().toISOString(),
351
+ updated_at: new Date().toISOString(),
352
+ }),
353
+ getTask: vi.fn(),
354
+ getTaskProgress: vi.fn(),
355
+ getTaskTimeline: vi.fn(),
356
+ cancelTask: mockCancel,
357
+ discoverAgents: vi.fn(),
358
+ getBalance: vi.fn(),
359
+ getTransactions: vi.fn(),
360
+ getTaskPayment: vi.fn(),
361
+ createTaskPayment: vi.fn().mockRejectedValue(new Error("Insufficient funds")),
362
+ deleteWebhook: vi.fn(),
363
+ uploadDocument: vi.fn(),
364
+ };
365
+ });
366
+
367
+ const result = await execute(mockConfig, {
368
+ input: "test",
369
+ capability: "test",
370
+ maxPrice: "50.00",
371
+ });
372
+
373
+ expect(result.success).toBe(false);
374
+ expect(result.error).toContain("Escrow payment failed");
375
+ expect(mockCancel).toHaveBeenCalledWith("pay-fail-task");
376
+ });
377
+ });
378
+
379
+ // ---------------------------------------------------------------------------
380
+ // cancelTask
381
+ // ---------------------------------------------------------------------------
382
+
383
+ describe("cancelTask", () => {
384
+ it("returns success when cancellation works", async () => {
385
+ const result = await cancelTask(mockConfig, "task-1");
386
+ expect(result.success).toBe(true);
387
+ });
388
+
389
+ it("returns error when cancellation fails", async () => {
390
+ const { AgrentingClient } = await import("./client.js");
391
+ const MockClient = vi.mocked(AgrentingClient);
392
+ MockClient.mockImplementationOnce(function() {
393
+ return {
394
+ testConnection: vi.fn(),
395
+ createTask: vi.fn(),
396
+ getTask: vi.fn(),
397
+ getTaskProgress: vi.fn(),
398
+ getTaskTimeline: vi.fn(),
399
+ cancelTask: vi.fn().mockRejectedValue(new Error("Cannot cancel")),
400
+ discoverAgents: vi.fn(),
401
+ getBalance: vi.fn(),
402
+ getTransactions: vi.fn(),
403
+ getTaskPayment: vi.fn(),
404
+ createTaskPayment: vi.fn(),
405
+ deleteWebhook: vi.fn(),
406
+ uploadDocument: vi.fn(),
407
+ };
408
+ });
409
+
410
+ const result = await cancelTask(mockConfig, "task-1");
411
+ expect(result.success).toBe(false);
412
+ expect(result.error).toBe("Cannot cancel");
413
+ });
414
+ });
415
+
416
+ // ---------------------------------------------------------------------------
417
+ // discoverAgents
418
+ // ---------------------------------------------------------------------------
419
+
420
+ describe("discoverAgents", () => {
421
+ it("returns agents from the API", async () => {
422
+ const result = await discoverAgents(mockConfig, { capability: "code-review" });
423
+ expect(result).toEqual([{ id: "agent-1", name: "Agent" }]);
424
+ });
425
+ });
426
+
427
+ // ---------------------------------------------------------------------------
428
+ // getBalance
429
+ // ---------------------------------------------------------------------------
430
+
431
+ describe("getBalance", () => {
432
+ it("returns balance from the API", async () => {
433
+ const result = await getBalance(mockConfig);
434
+ expect(result.available).toBe("100");
435
+ });
436
+ });
437
+
438
+ // ---------------------------------------------------------------------------
439
+ // getTransactions
440
+ // ---------------------------------------------------------------------------
441
+
442
+ describe("getTransactions", () => {
443
+ it("returns transactions from the API", async () => {
444
+ const result = await getTransactions(mockConfig);
445
+ expect(result).toEqual([]);
446
+ });
447
+ });
448
+
449
+ // ---------------------------------------------------------------------------
450
+ // getTaskPayment
451
+ // ---------------------------------------------------------------------------
452
+
453
+ describe("getTaskPayment", () => {
454
+ it("returns payment info when found", async () => {
455
+ const result = await getTaskPayment(mockConfig, "task-1");
456
+ expect(result).toBeDefined();
457
+ expect(result!.id).toBe("pay-1");
458
+ });
459
+
460
+ it("returns undefined when API throws", async () => {
461
+ const { AgrentingClient } = await import("./client.js");
462
+ const MockClient = vi.mocked(AgrentingClient);
463
+ MockClient.mockImplementationOnce(function() {
464
+ return {
465
+ testConnection: vi.fn(),
466
+ createTask: vi.fn(),
467
+ getTask: vi.fn(),
468
+ getTaskProgress: vi.fn(),
469
+ getTaskTimeline: vi.fn(),
470
+ cancelTask: vi.fn(),
471
+ discoverAgents: vi.fn(),
472
+ getBalance: vi.fn(),
473
+ getTransactions: vi.fn(),
474
+ getTaskPayment: vi.fn().mockRejectedValue(new Error("Not found")),
475
+ createTaskPayment: vi.fn(),
476
+ deleteWebhook: vi.fn(),
477
+ uploadDocument: vi.fn(),
478
+ };
479
+ });
480
+
481
+ const result = await getTaskPayment(mockConfig, "nonexistent");
482
+ expect(result).toBeUndefined();
483
+ });
484
+ });
485
+
486
+ // ---------------------------------------------------------------------------
487
+ // getTaskProgress
488
+ // ---------------------------------------------------------------------------
489
+
490
+ describe("getTaskProgress", () => {
491
+ it("returns progress and timeline", async () => {
492
+ const result = await getTaskProgress(mockConfig, "task-1");
493
+ expect(result.status).toBe("completed");
494
+ expect(result.progressPercent).toBe(100);
495
+ expect(result.timeline).toEqual([]);
496
+ });
497
+ });