@dainprotocol/service-sdk 2.0.51 → 2.0.53

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 (81) hide show
  1. package/dist/client/client.d.ts +4 -4
  2. package/dist/client/client.js +4 -6
  3. package/dist/client/client.js.map +1 -1
  4. package/dist/lib/schemaStructure.js +65 -2
  5. package/dist/lib/schemaStructure.js.map +1 -1
  6. package/package.json +10 -9
  7. package/dist/__tests__/api-sdk.test.d.ts +0 -1
  8. package/dist/__tests__/api-sdk.test.js +0 -102
  9. package/dist/__tests__/api-sdk.test.js.map +0 -1
  10. package/dist/__tests__/auth.test.d.ts +0 -1
  11. package/dist/__tests__/auth.test.js +0 -110
  12. package/dist/__tests__/auth.test.js.map +0 -1
  13. package/dist/__tests__/citations-plugin.test.d.ts +0 -1
  14. package/dist/__tests__/citations-plugin.test.js +0 -491
  15. package/dist/__tests__/citations-plugin.test.js.map +0 -1
  16. package/dist/__tests__/context-behavior.test.d.ts +0 -1
  17. package/dist/__tests__/context-behavior.test.js +0 -290
  18. package/dist/__tests__/context-behavior.test.js.map +0 -1
  19. package/dist/__tests__/convertToVercelTool.test.d.ts +0 -1
  20. package/dist/__tests__/convertToVercelTool.test.js +0 -527
  21. package/dist/__tests__/convertToVercelTool.test.js.map +0 -1
  22. package/dist/__tests__/core.test.d.ts +0 -1
  23. package/dist/__tests__/core.test.js +0 -154
  24. package/dist/__tests__/core.test.js.map +0 -1
  25. package/dist/__tests__/crypto-plugin.test.d.ts +0 -1
  26. package/dist/__tests__/crypto-plugin.test.js +0 -694
  27. package/dist/__tests__/crypto-plugin.test.js.map +0 -1
  28. package/dist/__tests__/humanActions.test.d.ts +0 -1
  29. package/dist/__tests__/humanActions.test.js +0 -221
  30. package/dist/__tests__/humanActions.test.js.map +0 -1
  31. package/dist/__tests__/integration.test.d.ts +0 -1
  32. package/dist/__tests__/integration.test.js +0 -1573
  33. package/dist/__tests__/integration.test.js.map +0 -1
  34. package/dist/__tests__/mealMeSchemas.test.d.ts +0 -576
  35. package/dist/__tests__/mealMeSchemas.test.js +0 -627
  36. package/dist/__tests__/mealMeSchemas.test.js.map +0 -1
  37. package/dist/__tests__/oauth-context-simple.test.d.ts +0 -1
  38. package/dist/__tests__/oauth-context-simple.test.js +0 -90
  39. package/dist/__tests__/oauth-context-simple.test.js.map +0 -1
  40. package/dist/__tests__/oauth-context.test.d.ts +0 -1
  41. package/dist/__tests__/oauth-context.test.js +0 -282
  42. package/dist/__tests__/oauth-context.test.js.map +0 -1
  43. package/dist/__tests__/oauth.test.d.ts +0 -1
  44. package/dist/__tests__/oauth.test.js +0 -378
  45. package/dist/__tests__/oauth.test.js.map +0 -1
  46. package/dist/__tests__/oauth2-client-context.test.d.ts +0 -1
  47. package/dist/__tests__/oauth2-client-context.test.js +0 -165
  48. package/dist/__tests__/oauth2-client-context.test.js.map +0 -1
  49. package/dist/__tests__/oauth2-client-integration.test.d.ts +0 -1
  50. package/dist/__tests__/oauth2-client-integration.test.js +0 -182
  51. package/dist/__tests__/oauth2-client-integration.test.js.map +0 -1
  52. package/dist/__tests__/oauth2-client-simple.test.d.ts +0 -1
  53. package/dist/__tests__/oauth2-client-simple.test.js +0 -144
  54. package/dist/__tests__/oauth2-client-simple.test.js.map +0 -1
  55. package/dist/__tests__/oauth2-context.test.d.ts +0 -1
  56. package/dist/__tests__/oauth2-context.test.js +0 -201
  57. package/dist/__tests__/oauth2-context.test.js.map +0 -1
  58. package/dist/__tests__/oauth2-datasource.test.d.ts +0 -1
  59. package/dist/__tests__/oauth2-datasource.test.js +0 -251
  60. package/dist/__tests__/oauth2-datasource.test.js.map +0 -1
  61. package/dist/__tests__/plugin.test.d.ts +0 -1
  62. package/dist/__tests__/plugin.test.js +0 -900
  63. package/dist/__tests__/plugin.test.js.map +0 -1
  64. package/dist/__tests__/processes.test.d.ts +0 -1
  65. package/dist/__tests__/processes.test.js +0 -239
  66. package/dist/__tests__/processes.test.js.map +0 -1
  67. package/dist/__tests__/streaming.test.d.ts +0 -1
  68. package/dist/__tests__/streaming.test.js +0 -592
  69. package/dist/__tests__/streaming.test.js.map +0 -1
  70. package/dist/__tests__/testEnums.d.ts +0 -1
  71. package/dist/__tests__/testEnums.js +0 -73
  72. package/dist/__tests__/testEnums.js.map +0 -1
  73. package/dist/__tests__/testOptionals.test.d.ts +0 -1
  74. package/dist/__tests__/testOptionals.test.js +0 -83
  75. package/dist/__tests__/testOptionals.test.js.map +0 -1
  76. package/dist/__tests__/types.test.d.ts +0 -1
  77. package/dist/__tests__/types.test.js +0 -98
  78. package/dist/__tests__/types.test.js.map +0 -1
  79. package/dist/service/zod-json-converter.d.ts +0 -14
  80. package/dist/service/zod-json-converter.js +0 -203
  81. package/dist/service/zod-json-converter.js.map +0 -1
@@ -1,1573 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- //File: src/___tests___/integration.test.ts
5
- const nodeService_1 = require("../service/nodeService");
6
- const core_1 = require("@/service/core");
7
- const interfaces_1 = require("@/interfaces");
8
- const zod_1 = require("zod");
9
- const supertest_1 = tslib_1.__importDefault(require("supertest"));
10
- const ed25519_1 = require("@noble/curves/ed25519");
11
- const bs58_1 = tslib_1.__importDefault(require("bs58"));
12
- const convertToVercelTool_1 = require("@/lib/convertToVercelTool");
13
- const schemaStructure_1 = require("@/lib/schemaStructure");
14
- const client_auth_1 = require("@/client/client-auth");
15
- const client_1 = require("@/client/client");
16
- describe("DAIN Framework Integration", () => {
17
- const privateKey = ed25519_1.ed25519.utils.randomPrivateKey();
18
- const publicKey = ed25519_1.ed25519.getPublicKey(privateKey);
19
- const address = bs58_1.default.encode(publicKey);
20
- const clientPrivateKey = ed25519_1.ed25519.utils.randomPrivateKey();
21
- const agentAuth = new client_auth_1.DainClientAuth({
22
- privateKeyBase58: bs58_1.default.encode(clientPrivateKey),
23
- agentId: "agent-12",
24
- orgId: "org-12",
25
- });
26
- // Initialize the connection to the DAIN service
27
- const dainConnection = new client_1.DainServiceConnection(
28
- //agent URI
29
- "http://localhost:3003", agentAuth);
30
- const weatherTool = (0, nodeService_1.createTool)({
31
- id: "get-weather",
32
- name: "Get Weather",
33
- description: "Fetches weather for a city",
34
- input: zod_1.z.object({ city: zod_1.z.string() }),
35
- output: zod_1.z.object({ temperature: zod_1.z.number(), condition: zod_1.z.string() }),
36
- pricing: { pricePerUse: 0.01, currency: "USD" },
37
- handler: async ({ city }, agentInfo, context) => {
38
- // Send loading state
39
- if (context.updateUI) {
40
- await context.updateUI({ ui: { type: "loading", message: "Fetching weather..." } });
41
- }
42
- // Add a delay to ensure events are spaced out
43
- await new Promise(resolve => setTimeout(resolve, 1000));
44
- // Create a process
45
- const processId = await context.app.processes.createProcess(agentInfo, "one-time", "Weather Update", `Getting weather for ${city}`);
46
- // Notify about process creation
47
- if (context.addProcess) {
48
- await context.addProcess(processId);
49
- }
50
- // Add another delay
51
- await new Promise(resolve => setTimeout(resolve, 1000));
52
- if (context.updateUI) {
53
- await context.updateUI({ ui: { type: "progress", message: "Processing data..." } });
54
- }
55
- // Final delay before result
56
- await new Promise(resolve => setTimeout(resolve, 1000));
57
- return {
58
- text: `The weather in ${city} is Sunny with a temperature of 22°C.`,
59
- data: { temperature: 22, condition: "Sunny" },
60
- ui: null,
61
- };
62
- },
63
- });
64
- const extraDataTool = (0, nodeService_1.createTool)({
65
- id: "extra-data-tool",
66
- name: "Extra Data Tool",
67
- description: "Tool to test extra data",
68
- input: zod_1.z.object({ cool: zod_1.z.string() }),
69
- output: zod_1.z.any(),
70
- pricing: { pricePerUse: 0.01, currency: "USD" },
71
- handler: async (cool, agentInfo, { extraData }) => {
72
- console.log("cool", cool);
73
- console.log("agentInfo", agentInfo);
74
- console.log("extraData", extraData);
75
- return {
76
- text: `Extra data: ${JSON.stringify(extraData)}`,
77
- data: { extraData },
78
- ui: null,
79
- };
80
- },
81
- });
82
- // Add a tool with all optional parameters for testing empty param calls
83
- const optionalParamsTool = (0, nodeService_1.createTool)({
84
- id: "optional-params-tool",
85
- name: "Optional Parameters Tool",
86
- description: "Tool that can be called with empty parameters",
87
- input: zod_1.z.object({
88
- param1: zod_1.z.string().optional(),
89
- param2: zod_1.z.number().optional(),
90
- param3: zod_1.z.boolean().optional()
91
- }),
92
- output: zod_1.z.object({
93
- providedParams: zod_1.z.array(zod_1.z.string()),
94
- message: zod_1.z.string()
95
- }),
96
- handler: async (params, agentInfo, context) => {
97
- // Track which parameters were provided
98
- const providedParams = Object.keys(params || {});
99
- console.log("Received parameters:", providedParams.length ? providedParams : "none");
100
- return {
101
- text: `Called with ${providedParams.length} parameter(s): ${providedParams.join(', ')}`,
102
- data: {
103
- providedParams,
104
- message: providedParams.length > 0 ? "Parameters received" : "No parameters provided"
105
- },
106
- ui: null,
107
- };
108
- },
109
- });
110
- // Add a tool with no parameters for testing empty param calls
111
- const noParamsTool = (0, nodeService_1.createTool)({
112
- id: "no-params-tool",
113
- name: "No Parameters Tool",
114
- description: "Tool that requires no parameters",
115
- input: zod_1.z.object({}), // Empty schema - no parameters required
116
- output: zod_1.z.object({ message: zod_1.z.string() }),
117
- handler: async (_params, _agentInfo, _context) => {
118
- console.log("No-params tool called successfully");
119
- return {
120
- text: "No parameters tool executed successfully",
121
- data: { message: "Success - no parameters needed" },
122
- ui: null,
123
- };
124
- },
125
- });
126
- // Add a new tool with confirmation for testing
127
- const confirmationTool = (0, nodeService_1.createTool)({
128
- id: "confirmation-test-tool",
129
- name: "Confirmation Test Tool",
130
- description: "Tool that requires confirmation",
131
- input: zod_1.z.object({ action: zod_1.z.string() }),
132
- output: zod_1.z.object({ result: zod_1.z.string() }),
133
- suggestConfirmation: true,
134
- suggestConfirmationUI: async (input) => ({
135
- success: input.action !== "dangerous",
136
- ui: {
137
- type: "confirmation",
138
- title: "Confirm Action",
139
- message: `Are you sure you want to perform: ${input.action}?`,
140
- fields: [
141
- {
142
- type: "text",
143
- label: "Additional Notes",
144
- placeholder: "Add any notes here..."
145
- }
146
- ]
147
- }
148
- }),
149
- handler: async ({ action }) => ({
150
- text: `Performed action: ${action}`,
151
- data: { result: `completed-${action}` },
152
- ui: null,
153
- }),
154
- });
155
- const weatherService = (0, nodeService_1.createService)({
156
- id: "weather-service",
157
- name: "Weather Service",
158
- description: "Provides weather information",
159
- metadata: {
160
- capabilities: ["current-weather"],
161
- languages: ["en"],
162
- },
163
- recommendedPrompt: "Ask about the weather",
164
- recommendedTools: ["get-weather", "extra-data-tool"],
165
- });
166
- const weatherToolbox = (0, nodeService_1.createToolbox)({
167
- id: "weather-toolbox",
168
- name: "Weather Toolbox",
169
- description: "Collection of weather tools",
170
- tools: ["get-weather", "extra-data-tool"],
171
- metadata: {
172
- complexity: "Low",
173
- applicableFields: ["Meteorology", "Travel"],
174
- },
175
- recommendedPrompt: "Use these tools for weather-related tasks",
176
- });
177
- const weatherContext = (0, core_1.createContext)({
178
- id: "weather-context",
179
- name: "Weather Context",
180
- description: "Context for weather data",
181
- getContextData: async () => {
182
- return "Weather data";
183
- },
184
- });
185
- // Create a datasource for testing
186
- const weatherDatasource = (0, core_1.createDatasource)({
187
- id: "weather-datasource",
188
- name: "Weather Datasource",
189
- description: "Datasource for weather data",
190
- type: "json",
191
- input: zod_1.z.object({
192
- city: zod_1.z.string().describe("The city to get weather data for"),
193
- days: zod_1.z.number().optional().describe("Number of days for forecast")
194
- }),
195
- getDatasource: async (agentInfo, params) => {
196
- return {
197
- city: params.city,
198
- forecast: [
199
- { day: 1, temperature: 22, condition: "Sunny" },
200
- { day: 2, temperature: 24, condition: "Partly Cloudy" },
201
- { day: 3, temperature: 20, condition: "Rainy" }
202
- ].slice(0, params.days || 3)
203
- };
204
- },
205
- });
206
- const weatherWidget = (0, core_1.createWidget)({
207
- id: "weather-widget",
208
- name: "Weather Info",
209
- description: "Displays current weather information",
210
- icon: "☀️",
211
- size: "lg",
212
- getWidget: async () => ({
213
- text: "Current weather: Sunny, 22°C",
214
- data: { temperature: 22, condition: "Sunny" },
215
- ui: { type: "text" },
216
- }),
217
- });
218
- // Create a dedicated error tool for testing
219
- const errorTool = (0, nodeService_1.createTool)({
220
- id: "integration-error-tool",
221
- name: "Integration Error Tool",
222
- description: "Tool that deliberately throws errors for testing",
223
- input: zod_1.z.object({
224
- errorType: zod_1.z.enum(["none", "synchronous", "async", "input"]),
225
- errorMessage: zod_1.z.string().optional()
226
- }),
227
- output: zod_1.z.object({ result: zod_1.z.string() }),
228
- handler: async ({ errorType, errorMessage = "Deliberate test error" }, agentInfo, context) => {
229
- // Send UI update first to test partial success
230
- if (context.updateUI) {
231
- await context.updateUI({ ui: { type: "loading", message: "Starting error test..." } });
232
- // Small delay to ensure UI update goes through
233
- await new Promise(resolve => setTimeout(resolve, 200));
234
- }
235
- // Handle different error types
236
- switch (errorType) {
237
- case "none":
238
- return {
239
- text: "No error occurred",
240
- data: { result: "success" },
241
- ui: null
242
- };
243
- case "synchronous":
244
- throw new Error(errorMessage);
245
- case "async":
246
- await new Promise(resolve => setTimeout(resolve, 300));
247
- throw new Error(errorMessage);
248
- case "input":
249
- // This simulates validation errors
250
- throw new Error(`Invalid input: ${errorMessage}`);
251
- }
252
- },
253
- });
254
- // Create a tool that implements the KnowledgeSearch interface
255
- const knowledgeSearchTool = (0, core_1.createToolWithInterface)({
256
- id: "test-knowledge-search",
257
- name: "Test Knowledge Search",
258
- description: "A test implementation of knowledge search",
259
- interface: interfaces_1.ToolInterfaceType.KNOWLEDGE_SEARCH,
260
- input: interfaces_1.KnowledgeSearchInterface.input,
261
- output: interfaces_1.KnowledgeSearchInterface.output,
262
- handler: async ({ query, limit = 5 }) => {
263
- // Simulate search results
264
- const queries = Array.isArray(query) ? query : [query];
265
- const results = queries.flatMap((q, queryIndex) => Array.from({ length: Math.min(limit, 3) }, (_, i) => ({
266
- content: `This is search result ${i + 1} for query "${q}". It contains relevant information about the topic.`,
267
- id: `result-${queryIndex}-${i + 1}`,
268
- citation: {
269
- id: `citation-${queryIndex}-${i + 1}`,
270
- ui: {
271
- type: "citation",
272
- title: `Source ${i + 1} for ${q}`,
273
- preview: `Preview of source ${i + 1}`
274
- },
275
- url: `https://example.com/source-${queryIndex}-${i + 1}`,
276
- title: `Source ${i + 1} for ${q}`,
277
- author: `Author ${i + 1}`,
278
- date: "2024-01-15",
279
- type: "article",
280
- source: "Test Knowledge Base",
281
- }
282
- })));
283
- return {
284
- text: `Found ${results.length} results for your search`,
285
- data: {
286
- results,
287
- totalCount: results.length,
288
- searchId: `search-${Date.now()}`,
289
- },
290
- ui: {
291
- type: "search-results",
292
- results: results.map(r => ({
293
- title: r.citation.title,
294
- preview: r.content.substring(0, 100) + "...",
295
- url: r.citation.url,
296
- }))
297
- },
298
- };
299
- },
300
- });
301
- // Create a test agent
302
- const helpAgent = (0, core_1.createAgent)({
303
- id: "help-assistant",
304
- name: "Help Assistant Agent",
305
- context: ["You are a helpful AI assistant", "Always provide accurate and concise answers"],
306
- prompt: "Answer user questions helpfully and accurately. Provide context when necessary.",
307
- resolveCondition: "User question has been answered completely and accurately",
308
- input: zod_1.z.object({
309
- question: zod_1.z.string().describe("The user's question"),
310
- context: zod_1.z.string().optional().describe("Additional context for the question"),
311
- urgency: zod_1.z.enum(["low", "medium", "high"]).optional().describe("Question urgency level")
312
- }),
313
- output: zod_1.z.object({
314
- answer: zod_1.z.string().describe("The detailed answer to the user's question"),
315
- confidence: zod_1.z.number().min(0).max(1).describe("Confidence score for the answer"),
316
- sources: zod_1.z.array(zod_1.z.string()).optional().describe("Source references used"),
317
- followUpQuestions: zod_1.z.array(zod_1.z.string()).optional().describe("Suggested follow-up questions")
318
- }),
319
- serviceConnections: ["http://localhost:3003", "https://api.example.com"]
320
- });
321
- const dainService = (0, nodeService_1.defineDAINService)({
322
- metadata: {
323
- title: "Weather DAIN Service",
324
- description: "A DAIN service for weather information",
325
- version: "1.0.0",
326
- author: "Test Author",
327
- tags: ["weather", "dain"],
328
- },
329
- identity: {
330
- publicKey: bs58_1.default.encode(publicKey),
331
- agentId: "weather-agent",
332
- orgId: "weather-org",
333
- privateKey: bs58_1.default.encode(privateKey),
334
- },
335
- services: [weatherService],
336
- tools: [weatherTool, extraDataTool, confirmationTool, optionalParamsTool, noParamsTool, errorTool, knowledgeSearchTool],
337
- toolboxes: [weatherToolbox],
338
- contexts: [weatherContext],
339
- widgets: [weatherWidget],
340
- datasources: [weatherDatasource],
341
- agents: [helpAgent],
342
- });
343
- let server;
344
- beforeAll(async () => {
345
- server = await dainService.startNode({ port: 3003 });
346
- });
347
- afterAll(async () => {
348
- // Use a more robust approach to shutting down with proper cleanup
349
- if (server) {
350
- // Set a timeout for shutdown to complete
351
- const timeout = setTimeout(() => {
352
- console.warn('Server shutdown timed out, process may still have open handles');
353
- }, 5000);
354
- try {
355
- await server.shutdown();
356
- }
357
- catch (err) {
358
- console.error('Error shutting down server:', err);
359
- }
360
- finally {
361
- clearTimeout(timeout);
362
- server = null;
363
- // Wait a moment to allow any lingering connections to close
364
- await new Promise(resolve => setTimeout(resolve, 500));
365
- }
366
- }
367
- });
368
- test("Service metadata is correct", () => {
369
- const metadata = dainService.getMetadata();
370
- expect(metadata.title).toBe("Weather DAIN Service");
371
- expect(metadata.tags).toContain("weather");
372
- });
373
- test("Service has correct tools and services", () => {
374
- expect(dainService.getTools()).toHaveLength(7);
375
- expect(dainService.getServices()).toHaveLength(1);
376
- expect(dainService.getToolboxes()).toHaveLength(1);
377
- });
378
- test("Can call weather tool with valid signature", async () => {
379
- const app = server.address();
380
- const port = app.port;
381
- const response = await dainConnection.makeRequest("POST", "/tools/get-weather", { city: "New York" });
382
- expect(response).toEqual({
383
- data: { temperature: 22, condition: "Sunny" },
384
- text: "The weather in New York is Sunny with a temperature of 22°C.",
385
- ui: null,
386
- });
387
- });
388
- test("Rejects call with invalid signature", async () => {
389
- const app = server.address();
390
- const port = app.port;
391
- const method = "POST";
392
- const path = "/tools/get-weather";
393
- const body = JSON.stringify({ city: "New York" });
394
- const headers = {
395
- "Content-Type": "application/json",
396
- "X-DAIN-Address": address,
397
- };
398
- const timestamp = new Date().toISOString();
399
- headers["X-DAIN-Timestamp"] = timestamp;
400
- const response = await (0, supertest_1.default)(`http://localhost:${port}`)
401
- .post("/tools/get-weather")
402
- .set("X-DAIN-Signature", "1231232113121")
403
- .set("X-DAIN-Address", address)
404
- .set("X-DAIN-Timestamp", timestamp)
405
- .send({ city: "New York" });
406
- expect(response.status).toBe(401);
407
- });
408
- describe("HTTP Output to Vercel AI Tool", () => {
409
- it("should convert a DAIN tool to HTTP output and then to Vercel AI tool format", () => {
410
- // Simulate a DAIN tool
411
- const dainTool = {
412
- id: "get-weather",
413
- name: "Get Weather",
414
- description: "Fetches weather for a city",
415
- pricing: { pricePerUse: 0.01, currency: "USD" },
416
- input: zod_1.z.object({
417
- city: zod_1.z.string().describe("The name of the city"),
418
- country: zod_1.z.string().optional().describe("The country of the city"),
419
- }),
420
- output: zod_1.z.object({
421
- temperature: zod_1.z.number(),
422
- condition: zod_1.z.string(),
423
- }),
424
- handler: async () => ({ temperature: 22, condition: "Sunny" }),
425
- };
426
- // Simulate the HTTP output
427
- const httpOutput = {
428
- id: dainTool.id,
429
- name: dainTool.name,
430
- description: dainTool.description,
431
- pricing: dainTool.pricing,
432
- inputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(dainTool.input),
433
- outputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(dainTool.output),
434
- };
435
- // Convert HTTP output to Vercel AI tool
436
- const vercelAITool = (0, convertToVercelTool_1.convertHttpToolToVercelAITool)(httpOutput);
437
- // Assertions
438
- expect(vercelAITool.name).toBe("get-weather");
439
- expect(vercelAITool.description).toBe("Fetches weather for a city");
440
- expect(vercelAITool.parameters).toBeInstanceOf(zod_1.z.ZodObject);
441
- const params = vercelAITool.parameters.safeParse({
442
- city: "New York",
443
- country: "USA",
444
- });
445
- expect(params.success).toBe(true);
446
- // Use Zod's built-in methods to inspect the schema
447
- const schemaShape = vercelAITool.parameters._def.shape();
448
- expect(schemaShape.city.description).toBe("The name of the city");
449
- expect(schemaShape.country.description).toBe("The country of the city");
450
- // Test that the optional field is correctly preserved
451
- const paramsWithoutCountry = vercelAITool.parameters.safeParse({
452
- city: "Tokyo",
453
- });
454
- expect(paramsWithoutCountry.success).toBe(true);
455
- // Additional check for the optional nature of the country field
456
- expect(schemaShape.country instanceof zod_1.z.ZodOptional).toBe(true);
457
- });
458
- });
459
- it("Can List Contexts", async () => {
460
- const app = server.address();
461
- const port = app.port;
462
- const response = await dainConnection.listContexts();
463
- expect(response).toEqual([
464
- {
465
- id: "weather-context",
466
- name: "Weather Context",
467
- description: "Context for weather data",
468
- },
469
- ]);
470
- });
471
- it("Can call weather context with valid signature", async () => {
472
- const app = server.address();
473
- const port = app.port;
474
- const response = await dainConnection.getContext("weather-context");
475
- expect(response).toEqual({
476
- id: "weather-context",
477
- name: "Weather Context",
478
- description: "Context for weather data",
479
- data: "Weather data",
480
- });
481
- });
482
- it("Can get all tools as json schema", async () => {
483
- const app = server.address();
484
- const port = app.port;
485
- const response = await dainConnection.makeRequest("GET", "/getAllToolsAsJsonSchema", {});
486
- console.log("JSON SCHEMA", JSON.stringify(response, null, 2));
487
- // fs.writeFileSync("jsonSchema.json", JSON.stringify(response, null, 2));
488
- // Verify the response structure
489
- expect(response).toHaveProperty('tools');
490
- expect(response).toHaveProperty('reccomendedPrompts');
491
- expect(Array.isArray(response.tools)).toBe(true);
492
- expect(Array.isArray(response.reccomendedPrompts)).toBe(true);
493
- // Verify we have the expected number of tools (7 total)
494
- expect(response.tools).toHaveLength(7);
495
- // Verify each tool has the required properties
496
- response.tools.forEach((tool) => {
497
- expect(tool).toHaveProperty('id');
498
- expect(tool).toHaveProperty('name');
499
- expect(tool).toHaveProperty('description');
500
- expect(tool).toHaveProperty('inputSchema');
501
- expect(tool).toHaveProperty('outputSchema');
502
- // interface property is optional
503
- });
504
- // Verify specific tools exist
505
- const toolIds = response.tools.map((tool) => tool.id);
506
- expect(toolIds).toContain('get-weather');
507
- expect(toolIds).toContain('extra-data-tool');
508
- expect(toolIds).toContain('confirmation-test-tool');
509
- expect(toolIds).toContain('optional-params-tool');
510
- expect(toolIds).toContain('no-params-tool');
511
- expect(toolIds).toContain('integration-error-tool');
512
- expect(toolIds).toContain('test-knowledge-search');
513
- // Verify the knowledge search tool has interface information
514
- const knowledgeTool = response.tools.find((tool) => tool.id === 'test-knowledge-search');
515
- expect(knowledgeTool).toBeDefined();
516
- expect(knowledgeTool.interface).toBe(interfaces_1.ToolInterfaceType.KNOWLEDGE_SEARCH);
517
- // Verify confirmation tool has suggestConfirmation
518
- const confirmationTool = response.tools.find((tool) => tool.id === 'confirmation-test-tool');
519
- expect(confirmationTool).toBeDefined();
520
- expect(confirmationTool.suggestConfirmation).toBe(true);
521
- // Verify recommended prompts
522
- expect(response.reccomendedPrompts).toContain("Use these tools for weather-related tasks");
523
- });
524
- describe("Widgets", () => {
525
- it("Can get widget IDs", async () => {
526
- const response = await dainConnection.getWidgets();
527
- expect(response).toHaveLength(1);
528
- expect(response[0]).toBe("weather-widget");
529
- });
530
- it("Can get a specific widget", async () => {
531
- const response = await dainConnection.getWidget("weather-widget");
532
- expect(response).toEqual({
533
- id: "weather-widget",
534
- name: "Weather Info",
535
- description: "Displays current weather information",
536
- icon: "☀️",
537
- size: "lg",
538
- text: "Current weather: Sunny, 22°C",
539
- data: { temperature: 22, condition: "Sunny" },
540
- ui: { type: "text" },
541
- });
542
- });
543
- it("Can call extra data tool", async () => {
544
- const response = await dainConnection.callTool("extra-data-tool", {
545
- cool: "cool",
546
- DAIN_EXTRA_DATA: {
547
- testExtraData: "testExtraData",
548
- },
549
- });
550
- expect(response).toEqual({
551
- text: `Extra data: ${JSON.stringify({ testExtraData: "testExtraData", plugins: {} })}`,
552
- data: { extraData: { testExtraData: "testExtraData", plugins: {} } },
553
- ui: null,
554
- });
555
- });
556
- it("Can get all widgets with their data", async () => {
557
- const response = await dainConnection.getAllWidgets();
558
- expect(response).toHaveLength(1);
559
- expect(response[0]).toEqual({
560
- id: "weather-widget",
561
- name: "Weather Info",
562
- description: "Displays current weather information",
563
- icon: "☀️",
564
- size: "lg",
565
- text: "Current weather: Sunny, 22°C",
566
- data: { temperature: 22, condition: "Sunny" },
567
- ui: { type: "text" },
568
- });
569
- });
570
- it("Defaults to 'sm' size when not specified", async () => {
571
- // Create a widget without specifying size
572
- const smallWidget = (0, core_1.createWidget)({
573
- id: "small-widget",
574
- name: "Small Widget",
575
- description: "A widget with default size",
576
- icon: "📊",
577
- // No size specified - should default to "sm"
578
- getWidget: async () => ({
579
- text: "Small widget content",
580
- data: { type: "default" },
581
- ui: { type: "text" },
582
- }),
583
- });
584
- // Test that it defaults to "sm"
585
- expect(smallWidget.size).toBe("sm");
586
- });
587
- it("Supports medium 'md' size", async () => {
588
- // Create a widget with medium size
589
- const mediumWidget = (0, core_1.createWidget)({
590
- id: "medium-widget",
591
- name: "Medium Widget",
592
- description: "A widget with medium size",
593
- icon: "📈",
594
- size: "md", // Medium size
595
- getWidget: async () => ({
596
- text: "Medium widget content",
597
- data: { type: "medium" },
598
- ui: { type: "chart" },
599
- }),
600
- });
601
- // Test that medium size is preserved
602
- expect(mediumWidget.size).toBe("md");
603
- });
604
- it("Rejects request for non-existent widget", async () => {
605
- await expect(dainConnection.getWidget("non-existent-widget")).rejects.toThrow();
606
- });
607
- });
608
- // Add new test suite for confirmation functionality
609
- describe("Tool Confirmation", () => {
610
- it("Tool metadata includes confirmation settings", async () => {
611
- const tool = await dainConnection.getTool("confirmation-test-tool");
612
- expect(tool.suggestConfirmation).toBe(true);
613
- });
614
- it("Can get confirmation UI for a tool", async () => {
615
- const confirmation = await dainConnection.getToolConfirmation("confirmation-test-tool", {
616
- action: "test-action"
617
- });
618
- expect(confirmation).toEqual({
619
- success: true,
620
- ui: {
621
- type: "confirmation",
622
- title: "Confirm Action",
623
- message: "Are you sure you want to perform: test-action?",
624
- fields: [
625
- {
626
- type: "text",
627
- label: "Additional Notes",
628
- placeholder: "Add any notes here..."
629
- }
630
- ]
631
- }
632
- });
633
- });
634
- it("Returns success: false for dangerous actions", async () => {
635
- const confirmation = await dainConnection.getToolConfirmation("confirmation-test-tool", {
636
- action: "dangerous"
637
- });
638
- expect(confirmation.success).toBe(false);
639
- });
640
- it("Returns default success response for tools without confirmation UI", async () => {
641
- const confirmation = await dainConnection.getToolConfirmation("get-weather", {
642
- city: "London"
643
- });
644
- expect(confirmation).toEqual({
645
- success: true
646
- });
647
- });
648
- it("Rejects confirmation request for non-existent tool", async () => {
649
- await expect(dainConnection.getToolConfirmation("non-existent-tool", {})).rejects.toThrow();
650
- });
651
- });
652
- it("Can stream UI updates from weather tool", async () => {
653
- const uiUpdates = [];
654
- const result = await dainConnection.callTool("get-weather", { city: "London" }, {
655
- onUIUpdate: (update) => {
656
- uiUpdates.push(update.ui);
657
- }
658
- });
659
- // Verify UI updates were received
660
- expect(uiUpdates).toHaveLength(2);
661
- expect(uiUpdates[0]).toEqual({ type: "loading", message: "Fetching weather..." });
662
- expect(uiUpdates[1]).toEqual({ type: "progress", message: "Processing data..." });
663
- // Verify final result
664
- expect(result).toEqual({
665
- text: "The weather in London is Sunny with a temperature of 22°C.",
666
- data: { temperature: 22, condition: "Sunny" },
667
- ui: null,
668
- });
669
- });
670
- it("Maintains backwards compatibility with non-streaming clients", async () => {
671
- // Create a legacy client by removing the onUIUpdate option
672
- const result = await dainConnection.callTool("get-weather", {
673
- city: "London"
674
- });
675
- // Verify we get the final result without any streaming
676
- expect(result).toEqual({
677
- text: "The weather in London is Sunny with a temperature of 22°C.",
678
- data: { temperature: 22, condition: "Sunny" },
679
- ui: null,
680
- });
681
- });
682
- it("Maintains backwards compatibility for callToolAndGetNewContext", async () => {
683
- const result = await dainConnection.callToolAndGetNewContext("get-weather", {
684
- city: "London"
685
- });
686
- expect(result.toolResult).toEqual({
687
- text: "The weather in London is Sunny with a temperature of 22°C.",
688
- data: { temperature: 22, condition: "Sunny" },
689
- ui: null,
690
- });
691
- // Verify we still get context data
692
- expect(result.context).toBeDefined();
693
- expect(Array.isArray(result.context)).toBe(true);
694
- expect(result.context[0]).toHaveProperty('id', 'weather-context');
695
- });
696
- it("Can stream UI updates from weather tool with callToolAndGetNewContext", async () => {
697
- const uiUpdates = [];
698
- const result = await dainConnection.callToolAndGetNewContext("get-weather", { city: "Paris" }, {
699
- onUIUpdate: (update) => {
700
- uiUpdates.push(update.ui);
701
- }
702
- });
703
- // Verify UI updates were received
704
- expect(uiUpdates).toHaveLength(2);
705
- expect(uiUpdates[0]).toEqual({ type: "loading", message: "Fetching weather..." });
706
- expect(uiUpdates[1]).toEqual({ type: "progress", message: "Processing data..." });
707
- // Verify final result includes both tool result and context
708
- expect(result.toolResult).toEqual({
709
- text: "The weather in Paris is Sunny with a temperature of 22°C.",
710
- data: { temperature: 22, condition: "Sunny" },
711
- ui: null,
712
- });
713
- // Verify context data
714
- expect(result.context).toBeDefined();
715
- expect(Array.isArray(result.context)).toBe(true);
716
- expect(result.context[0]).toHaveProperty('id', 'weather-context');
717
- });
718
- it("Can stream process updates from weather tool", async () => {
719
- const uiUpdates = [];
720
- const processes = [];
721
- const result = await dainConnection.callTool("get-weather", { city: "London" }, {
722
- onUIUpdate: (update) => {
723
- uiUpdates.push(update.ui);
724
- },
725
- onProcess: (processId) => {
726
- processes.push(processId);
727
- }
728
- });
729
- // Verify process updates were received
730
- expect(processes).toHaveLength(1);
731
- expect(processes[0]).toMatch(/^service_.*_.*$/); // Check process ID format
732
- // Verify UI updates still work
733
- expect(uiUpdates).toHaveLength(2);
734
- // ... rest of the assertions ...
735
- });
736
- // Keep this test but modify it to be more explicit about timing
737
- it("Sends SSE events immediately rather than buffering", async () => {
738
- const events = [];
739
- let lastTimestamp = Date.now();
740
- const result = await dainConnection.callTool("get-weather", { city: "London" }, {
741
- onUIUpdate: () => {
742
- const now = Date.now();
743
- const delay = now - lastTimestamp;
744
- events.push({ type: 'ui', timestamp: now, delay });
745
- lastTimestamp = now;
746
- console.log('UI Update received at:', now, 'delay:', delay); // Add logging
747
- },
748
- onProcess: () => {
749
- const now = Date.now();
750
- const delay = now - lastTimestamp;
751
- events.push({ type: 'process', timestamp: now, delay });
752
- lastTimestamp = now;
753
- console.log('Process Update received at:', now, 'delay:', delay); // Add logging
754
- }
755
- });
756
- const now = Date.now();
757
- events.push({ type: 'result', timestamp: now, delay: now - lastTimestamp });
758
- console.log('Final result received at:', now); // Add logging
759
- // Verify we got all expected events
760
- expect(events).toHaveLength(4);
761
- // Log the events to see what's happening
762
- console.log('Events with delays:', events);
763
- // Check each event came after the previous one with reasonable delay
764
- events.slice(1).forEach(event => {
765
- expect(event.delay).toBeGreaterThan(30); // Each event should be at least 30ms after the previous
766
- });
767
- });
768
- // Add tests for datasources
769
- describe("Datasources", () => {
770
- it("Can list datasources", async () => {
771
- const response = await dainConnection.listDatasources();
772
- expect(response).toHaveLength(1);
773
- expect(response[0]).toEqual({
774
- id: "weather-datasource",
775
- name: "Weather Datasource",
776
- description: "Datasource for weather data",
777
- type: "json",
778
- inputSchema: expect.any(Object),
779
- });
780
- });
781
- it("Can get data from a datasource", async () => {
782
- const response = await dainConnection.getDatasource("weather-datasource", {
783
- city: "London",
784
- days: 2
785
- });
786
- expect(response).toEqual({
787
- id: "weather-datasource",
788
- name: "Weather Datasource",
789
- description: "Datasource for weather data",
790
- type: "json",
791
- data: {
792
- city: "London",
793
- forecast: [
794
- { day: 1, temperature: 22, condition: "Sunny" },
795
- { day: 2, temperature: 24, condition: "Partly Cloudy" }
796
- ]
797
- }
798
- });
799
- });
800
- it("Handles missing parameters correctly", async () => {
801
- await expect(dainConnection.getDatasource("weather-datasource", {})).rejects.toThrow();
802
- });
803
- it("Rejects request for non-existent datasource", async () => {
804
- await expect(dainConnection.getDatasource("non-existent-datasource", { city: "London" })).rejects.toThrow();
805
- });
806
- });
807
- // Add tests for empty parameter tool calls
808
- describe("Empty Parameter Tool Calls", () => {
809
- it("Can call a tool with empty parameters object", async () => {
810
- const response = await dainConnection.callTool("optional-params-tool", {});
811
- expect(response.data.providedParams).toEqual([]);
812
- expect(response.data.message).toBe("No parameters provided");
813
- expect(response.text).toBe("Called with 0 parameter(s): ");
814
- });
815
- it("Can call a tool with some optional parameters", async () => {
816
- const response = await dainConnection.callTool("optional-params-tool", {
817
- param1: "test",
818
- param3: true
819
- });
820
- expect(response.data.providedParams).toContain("param1");
821
- expect(response.data.providedParams).toContain("param3");
822
- expect(response.data.providedParams).not.toContain("param2");
823
- expect(response.data.providedParams.length).toBe(2);
824
- });
825
- it("Can call a tool that requires no parameters", async () => {
826
- const response = await dainConnection.callTool("no-params-tool", {});
827
- expect(response.data.message).toBe("Success - no parameters needed");
828
- expect(response.text).toBe("No parameters tool executed successfully");
829
- });
830
- it("Handles undefined parameters gracefully", async () => {
831
- // Call with undefined instead of an empty object
832
- const response = await dainConnection.callTool("optional-params-tool", undefined);
833
- expect(response.data.providedParams).toEqual([]);
834
- expect(response.data.message).toBe("No parameters provided");
835
- });
836
- it("Works with streaming for empty parameter calls", async () => {
837
- const events = [];
838
- const result = await dainConnection.callTool("no-params-tool", {}, {
839
- onUIUpdate: (update) => {
840
- events.push({ type: 'ui', data: update });
841
- }
842
- });
843
- expect(result.data.message).toBe("Success - no parameters needed");
844
- });
845
- });
846
- // Add a new test suite for error handling
847
- describe("Error Handling", () => {
848
- it("Handles synchronous errors in non-streaming mode", async () => {
849
- const errorResult = await dainConnection.callTool("integration-error-tool", {
850
- errorType: "synchronous",
851
- errorMessage: "Test sync error"
852
- });
853
- // Verify the error format matches our expected structure
854
- expect(errorResult).toHaveProperty('error');
855
- expect(errorResult).toHaveProperty('text');
856
- expect(errorResult.error).toBe("Test sync error");
857
- expect(errorResult.text).toBe("Error: Test sync error");
858
- expect(errorResult.data).toBeNull();
859
- expect(errorResult.ui).toBeNull();
860
- });
861
- it("Handles asynchronous errors in non-streaming mode", async () => {
862
- const errorResult = await dainConnection.callTool("integration-error-tool", {
863
- errorType: "async",
864
- errorMessage: "Test async error"
865
- });
866
- expect(errorResult).toHaveProperty('error');
867
- expect(errorResult.error).toBe("Test async error");
868
- expect(errorResult.data).toBeNull();
869
- });
870
- it("Formats errors correctly in context endpoint", async () => {
871
- const errorResult = await dainConnection.callToolAndGetNewContext("integration-error-tool", {
872
- errorType: "synchronous",
873
- errorMessage: "Test context error"
874
- });
875
- // Verify the context endpoint error format
876
- expect(errorResult).toHaveProperty('toolResult');
877
- expect(errorResult).toHaveProperty('context');
878
- expect(errorResult.toolResult).toHaveProperty('error');
879
- expect(errorResult.toolResult.error).toBe("Test context error");
880
- expect(errorResult.toolResult.text).toBe("Error: Test context error");
881
- expect(errorResult.toolResult.data).toBeNull();
882
- expect(errorResult.toolResult.ui).toBeNull();
883
- expect(Array.isArray(errorResult.context)).toBe(true);
884
- expect(errorResult.context.length).toBe(0);
885
- });
886
- it("Handles errors in streaming mode and sends error events", async () => {
887
- const events = [];
888
- const errorResult = await dainConnection.callTool("integration-error-tool", {
889
- errorType: "async",
890
- errorMessage: "Test streaming error"
891
- }, {
892
- onUIUpdate: (update) => {
893
- events.push({ type: 'ui', data: update.ui });
894
- }
895
- });
896
- // Verify we received UI updates before the error
897
- expect(events.length).toBeGreaterThan(0);
898
- expect(events[0].type).toBe('ui');
899
- // Verify the error format in streaming mode
900
- expect(errorResult).toHaveProperty('error');
901
- expect(errorResult.error).toBe("Test streaming error");
902
- expect(errorResult.text).toBe("Error: Test streaming error");
903
- expect(errorResult.data).toBeNull();
904
- expect(errorResult.ui).toBeNull();
905
- });
906
- it("Handles streaming errors in context endpoint", async () => {
907
- const events = [];
908
- const errorResult = await dainConnection.callToolAndGetNewContext("integration-error-tool", {
909
- errorType: "async",
910
- errorMessage: "Test streaming context error"
911
- }, {
912
- onUIUpdate: (update) => {
913
- events.push({ type: 'ui', data: update.ui });
914
- }
915
- });
916
- // Verify we received UI updates before the error
917
- expect(events.length).toBeGreaterThan(0);
918
- // Verify the context endpoint error format in streaming mode
919
- expect(errorResult).toHaveProperty('toolResult');
920
- expect(errorResult).toHaveProperty('context');
921
- expect(errorResult.toolResult).toHaveProperty('error');
922
- expect(errorResult.toolResult.error).toBe("Test streaming context error");
923
- expect(errorResult.toolResult.data).toBeNull();
924
- expect(Array.isArray(errorResult.context)).toBe(true);
925
- });
926
- it("Verifies successful execution still works after error handling improvements", async () => {
927
- const result = await dainConnection.callTool("integration-error-tool", {
928
- errorType: "none"
929
- });
930
- expect(result).toEqual({
931
- text: "No error occurred",
932
- data: { result: "success" },
933
- ui: null
934
- });
935
- });
936
- });
937
- // Add tests for empty collections
938
- describe("Empty Collections", () => {
939
- // Create an empty DAIN service with no contexts or pinnables
940
- let emptyServer;
941
- let emptyConnection;
942
- beforeAll(async () => {
943
- const emptyPrivateKey = ed25519_1.ed25519.utils.randomPrivateKey();
944
- const emptyPublicKey = ed25519_1.ed25519.getPublicKey(emptyPrivateKey);
945
- const emptyAddress = bs58_1.default.encode(emptyPublicKey);
946
- const emptyClientPrivateKey = ed25519_1.ed25519.utils.randomPrivateKey();
947
- const emptyClientAuth = new client_auth_1.DainClientAuth({
948
- privateKeyBase58: bs58_1.default.encode(emptyClientPrivateKey),
949
- agentId: "empty-agent",
950
- orgId: "empty-org",
951
- });
952
- // Create a minimal dummy tool to satisfy validation
953
- const dummyTool = (0, nodeService_1.createTool)({
954
- id: "dummy-tool",
955
- name: "Dummy Tool",
956
- description: "Required dummy tool for validation",
957
- input: zod_1.z.object({}),
958
- output: zod_1.z.object({}),
959
- handler: async () => {
960
- return {
961
- text: "Dummy response",
962
- data: {},
963
- ui: null
964
- };
965
- }
966
- });
967
- // Create a minimal dummy toolbox with the dummy tool
968
- const dummyToolbox = (0, nodeService_1.createToolbox)({
969
- id: "dummy-toolbox",
970
- name: "Dummy Toolbox",
971
- description: "Required dummy toolbox for validation",
972
- tools: ["dummy-tool"],
973
- metadata: {
974
- complexity: "Low",
975
- applicableFields: ["Testing"]
976
- },
977
- recommendedPrompt: "Dummy prompt"
978
- });
979
- // Define an empty service with no contexts or pinnables but with required tool/toolbox
980
- const emptyDainService = (0, nodeService_1.defineDAINService)({
981
- metadata: {
982
- title: "Empty DAIN Service",
983
- description: "A DAIN service with no contexts or pinnables",
984
- version: "1.0.0",
985
- author: "Test Author",
986
- tags: ["empty", "test"],
987
- },
988
- identity: {
989
- publicKey: bs58_1.default.encode(emptyPublicKey),
990
- agentId: "empty-agent",
991
- orgId: "empty-org",
992
- privateKey: bs58_1.default.encode(emptyPrivateKey),
993
- },
994
- services: [],
995
- tools: [dummyTool], // Include the dummy tool
996
- toolboxes: [dummyToolbox], // Include the dummy toolbox
997
- contexts: [], // No contexts - this is what we're testing
998
- widgets: [], // No widgets - this is what we're testing
999
- datasources: [], // No datasources - this is what we're testing
1000
- agents: [], // No agents - this is what we're testing
1001
- });
1002
- emptyServer = await emptyDainService.startNode({ port: 3004 });
1003
- // Connect to the empty service
1004
- emptyConnection = new client_1.DainServiceConnection("http://localhost:3004", emptyClientAuth);
1005
- });
1006
- afterAll(async () => {
1007
- if (emptyServer) {
1008
- await emptyServer.shutdown();
1009
- }
1010
- });
1011
- it("getAllContexts() returns an empty array when no contexts exist", async () => {
1012
- const contexts = await emptyConnection.getAllContexts();
1013
- console.log("getAllContexts", contexts);
1014
- expect(contexts).toEqual([]);
1015
- expect(Array.isArray(contexts)).toBe(true);
1016
- expect(contexts.length).toBe(0);
1017
- });
1018
- it("getWidgets() returns an empty array when no widgets exist", async () => {
1019
- const widgets = await emptyConnection.getWidgets();
1020
- expect(widgets).toEqual([]);
1021
- expect(Array.isArray(widgets)).toBe(true);
1022
- expect(widgets.length).toBe(0);
1023
- });
1024
- it("getAllDatasources() returns an empty array when no datasources exist", async () => {
1025
- const datasources = await emptyConnection.listDatasources();
1026
- expect(datasources).toEqual([]);
1027
- expect(Array.isArray(datasources)).toBe(true);
1028
- expect(datasources.length).toBe(0);
1029
- });
1030
- it("getAgents() returns an empty array when no agents exist", async () => {
1031
- const agents = await emptyConnection.getAgents();
1032
- expect(agents).toEqual([]);
1033
- expect(Array.isArray(agents)).toBe(true);
1034
- expect(agents.length).toBe(0);
1035
- });
1036
- it("listAgents() returns an empty array when no agents exist", async () => {
1037
- const agents = await emptyConnection.listAgents();
1038
- expect(agents).toEqual([]);
1039
- expect(Array.isArray(agents)).toBe(true);
1040
- expect(agents.length).toBe(0);
1041
- });
1042
- it("getAllToolsAsJsonSchema() returns array with only the dummy tool", async () => {
1043
- const result = await emptyConnection.getAllToolsAsJsonSchema();
1044
- expect(Array.isArray(result.tools)).toBe(true);
1045
- expect(result.tools.length).toBe(1);
1046
- expect(result.tools[0].id).toBe("dummy-tool");
1047
- expect(result.reccomendedPrompts).toEqual(["Dummy prompt"]);
1048
- });
1049
- it("should get tools as JSON schema using the POST method", async () => {
1050
- // We have one dummy tool defined in the test setup
1051
- const result = await dainConnection.getAllToolsAsJsonSchema();
1052
- // Verify we get the tools in the result
1053
- expect(result.tools.length).toBeGreaterThan(0);
1054
- expect(result.tools[0]).toHaveProperty('id');
1055
- expect(result.tools[0]).toHaveProperty('inputSchema');
1056
- expect(result.tools[0]).toHaveProperty('outputSchema');
1057
- expect(result.reccomendedPrompts).toBeDefined();
1058
- });
1059
- });
1060
- // Add comprehensive tests for Tool Interfaces
1061
- describe("Tool Interfaces", () => {
1062
- it("should include interface information in tool metadata", async () => {
1063
- const tools = await dainConnection.getTools();
1064
- // Find the knowledge search tool
1065
- const knowledgeTool = tools.find(t => t.id === "test-knowledge-search");
1066
- expect(knowledgeTool).toBeDefined();
1067
- expect(knowledgeTool?.interface).toBe(interfaces_1.ToolInterfaceType.KNOWLEDGE_SEARCH);
1068
- // Verify other tools don't have interfaces (unless they should)
1069
- const weatherTool = tools.find(t => t.id === "get-weather");
1070
- expect(weatherTool?.interface).toBeUndefined();
1071
- });
1072
- it("should include interface information in JSON schema response", async () => {
1073
- const result = await dainConnection.getAllToolsAsJsonSchema();
1074
- // Find the knowledge search tool in the JSON schema response
1075
- const knowledgeTool = result.tools.find(t => t.id === "test-knowledge-search");
1076
- expect(knowledgeTool).toBeDefined();
1077
- expect(knowledgeTool?.interface).toBe(interfaces_1.ToolInterfaceType.KNOWLEDGE_SEARCH);
1078
- });
1079
- it("should filter tools by interface type", async () => {
1080
- const knowledgeTools = await dainConnection.getToolsByInterface(interfaces_1.ToolInterfaceType.KNOWLEDGE_SEARCH);
1081
- expect(knowledgeTools).toHaveLength(1);
1082
- expect(knowledgeTools[0].id).toBe("test-knowledge-search");
1083
- expect(knowledgeTools[0].interface).toBe(interfaces_1.ToolInterfaceType.KNOWLEDGE_SEARCH);
1084
- });
1085
- it("should return empty array for non-existent interface", async () => {
1086
- // Cast to test with a non-existent interface
1087
- const nonExistentInterface = "non-existent-interface";
1088
- const tools = await dainConnection.getToolsByInterface(nonExistentInterface);
1089
- expect(tools).toHaveLength(0);
1090
- });
1091
- it("should get available interfaces from tools", async () => {
1092
- const interfaces = await dainConnection.getAvailableInterfaces();
1093
- expect(interfaces).toContain(interfaces_1.ToolInterfaceType.KNOWLEDGE_SEARCH);
1094
- expect(interfaces).toHaveLength(1); // Only one interface in our test setup
1095
- });
1096
- it("should call knowledge search tool with interface-compliant input/output", async () => {
1097
- const result = await dainConnection.callTool("test-knowledge-search", {
1098
- query: "What is DAIN?",
1099
- limit: 2
1100
- });
1101
- // Verify the response structure matches the KnowledgeSearch interface
1102
- expect(result.data).toHaveProperty('results');
1103
- expect(result.data).toHaveProperty('totalCount');
1104
- expect(result.data).toHaveProperty('searchId');
1105
- // Verify results structure
1106
- expect(Array.isArray(result.data.results)).toBe(true);
1107
- expect(result.data.results.length).toBeGreaterThan(0);
1108
- // Verify each result has the correct structure
1109
- const firstResult = result.data.results[0];
1110
- expect(firstResult).toHaveProperty('content');
1111
- expect(firstResult).toHaveProperty('id');
1112
- expect(firstResult).toHaveProperty('citation');
1113
- // Verify citation structure (including the new id field)
1114
- expect(firstResult.citation).toHaveProperty('id');
1115
- expect(firstResult.citation).toHaveProperty('ui');
1116
- expect(firstResult.citation).toHaveProperty('url');
1117
- expect(firstResult.citation).toHaveProperty('title');
1118
- expect(firstResult.citation).toHaveProperty('author');
1119
- expect(firstResult.citation).toHaveProperty('date');
1120
- expect(firstResult.citation).toHaveProperty('type');
1121
- expect(firstResult.citation).toHaveProperty('source');
1122
- // Verify citation id is present and is a string
1123
- expect(typeof firstResult.citation.id).toBe('string');
1124
- expect(firstResult.citation.id.length).toBeGreaterThan(0);
1125
- });
1126
- it("should handle array queries in knowledge search", async () => {
1127
- const result = await dainConnection.callTool("test-knowledge-search", {
1128
- query: ["What is DAIN?", "How does it work?"],
1129
- limit: 2
1130
- });
1131
- // Should get results for both queries
1132
- expect(result.data.results.length).toBeGreaterThan(0);
1133
- // Verify that results contain content from both queries
1134
- const resultContents = result.data.results.map(r => r.content);
1135
- const hasFirstQuery = resultContents.some(content => content.includes("What is DAIN?"));
1136
- const hasSecondQuery = resultContents.some(content => content.includes("How does it work?"));
1137
- expect(hasFirstQuery || hasSecondQuery).toBe(true); // At least one query should be represented
1138
- });
1139
- it("should handle optional parameters in knowledge search", async () => {
1140
- // Test with minimal parameters
1141
- const result1 = await dainConnection.callTool("test-knowledge-search", {
1142
- query: "test query"
1143
- });
1144
- expect(result1.data.results).toBeDefined();
1145
- expect(result1.data.totalCount).toBeDefined();
1146
- // Test with all parameters
1147
- const result2 = await dainConnection.callTool("test-knowledge-search", {
1148
- query: "test query",
1149
- limit: 1,
1150
- filters: { category: "test" }
1151
- });
1152
- expect(result2.data.results).toBeDefined();
1153
- expect(result2.data.results.length).toBeLessThanOrEqual(1);
1154
- });
1155
- it("should work with streaming for interface-implementing tools", async () => {
1156
- const uiUpdates = [];
1157
- const result = await dainConnection.callTool("test-knowledge-search", { query: "streaming test", limit: 1 }, {
1158
- onUIUpdate: (update) => {
1159
- uiUpdates.push(update.ui);
1160
- }
1161
- });
1162
- // Verify the interface-compliant response structure is maintained in streaming
1163
- expect(result.data).toHaveProperty('results');
1164
- expect(result.data).toHaveProperty('totalCount');
1165
- expect(result.data).toHaveProperty('searchId');
1166
- // Verify citation structure is maintained
1167
- const firstResult = result.data.results[0];
1168
- expect(firstResult.citation).toHaveProperty('id');
1169
- expect(typeof firstResult.citation.id).toBe('string');
1170
- });
1171
- it("should maintain interface compliance in callToolAndGetNewContext", async () => {
1172
- const result = await dainConnection.callToolAndGetNewContext("test-knowledge-search", {
1173
- query: "context test"
1174
- });
1175
- // Verify tool result maintains interface compliance
1176
- expect(result.toolResult.data).toHaveProperty('results');
1177
- expect(result.toolResult.data).toHaveProperty('totalCount');
1178
- expect(result.toolResult.data).toHaveProperty('searchId');
1179
- // Verify context is also returned
1180
- expect(result.context).toBeDefined();
1181
- expect(Array.isArray(result.context)).toBe(true);
1182
- });
1183
- it("should validate that interface tools have correct input/output schemas", async () => {
1184
- const tool = await dainConnection.getTool("test-knowledge-search");
1185
- // Verify the tool has the interface field
1186
- expect(tool.interface).toBe(interfaces_1.ToolInterfaceType.KNOWLEDGE_SEARCH);
1187
- // Verify input schema structure (should match KnowledgeSearch interface)
1188
- expect(tool.inputSchema).toBeDefined();
1189
- expect(tool.outputSchema).toBeDefined();
1190
- // The schemas should be objects with the expected structure
1191
- expect(typeof tool.inputSchema).toBe('object');
1192
- expect(typeof tool.outputSchema).toBe('object');
1193
- });
1194
- });
1195
- // Add tests for Agents
1196
- describe("Agents", () => {
1197
- it("Can list agents", async () => {
1198
- const response = await dainConnection.getAgents();
1199
- expect(response).toHaveLength(1);
1200
- expect(response[0]).toEqual({
1201
- id: "help-assistant",
1202
- name: "Help Assistant Agent",
1203
- context: ["You are a helpful AI assistant", "Always provide accurate and concise answers"],
1204
- prompt: "Answer user questions helpfully and accurately. Provide context when necessary.",
1205
- resolveCondition: "User question has been answered completely and accurately",
1206
- serviceConnections: ["http://localhost:3003", "https://api.example.com"],
1207
- inputSchema: expect.any(Object),
1208
- outputSchema: expect.any(Object),
1209
- });
1210
- });
1211
- it("Can list agents using POST method with plugin support", async () => {
1212
- const response = await dainConnection.listAgents();
1213
- expect(response).toHaveLength(1);
1214
- expect(response[0]).toEqual({
1215
- id: "help-assistant",
1216
- name: "Help Assistant Agent",
1217
- context: ["You are a helpful AI assistant", "Always provide accurate and concise answers"],
1218
- prompt: "Answer user questions helpfully and accurately. Provide context when necessary.",
1219
- resolveCondition: "User question has been answered completely and accurately",
1220
- serviceConnections: ["http://localhost:3003", "https://api.example.com"],
1221
- inputSchema: expect.any(Object),
1222
- outputSchema: expect.any(Object),
1223
- });
1224
- });
1225
- it("Can get a specific agent", async () => {
1226
- const response = await dainConnection.getAgent("help-assistant");
1227
- expect(response).toEqual({
1228
- id: "help-assistant",
1229
- name: "Help Assistant Agent",
1230
- context: ["You are a helpful AI assistant", "Always provide accurate and concise answers"],
1231
- prompt: "Answer user questions helpfully and accurately. Provide context when necessary.",
1232
- resolveCondition: "User question has been answered completely and accurately",
1233
- serviceConnections: ["http://localhost:3003", "https://api.example.com"],
1234
- inputSchema: expect.any(Object),
1235
- outputSchema: expect.any(Object),
1236
- });
1237
- });
1238
- it("Can get a specific agent using POST method with plugin support", async () => {
1239
- const response = await dainConnection.postAgent("help-assistant");
1240
- expect(response).toEqual({
1241
- id: "help-assistant",
1242
- name: "Help Assistant Agent",
1243
- context: ["You are a helpful AI assistant", "Always provide accurate and concise answers"],
1244
- prompt: "Answer user questions helpfully and accurately. Provide context when necessary.",
1245
- resolveCondition: "User question has been answered completely and accurately",
1246
- serviceConnections: ["http://localhost:3003", "https://api.example.com"],
1247
- inputSchema: expect.any(Object),
1248
- outputSchema: expect.any(Object),
1249
- });
1250
- });
1251
- it("Agent schemas are properly converted to JSON Schema format", async () => {
1252
- const agent = await dainConnection.getAgent("help-assistant");
1253
- // Verify input schema structure
1254
- expect(agent.inputSchema).toHaveProperty('type', 'object');
1255
- expect(agent.inputSchema).toHaveProperty('properties');
1256
- expect(agent.inputSchema.properties).toHaveProperty('question');
1257
- expect(agent.inputSchema.properties).toHaveProperty('context');
1258
- expect(agent.inputSchema.properties).toHaveProperty('urgency');
1259
- // Verify required fields
1260
- expect(agent.inputSchema).toHaveProperty('required');
1261
- expect(agent.inputSchema.required).toContain('question');
1262
- expect(agent.inputSchema.required).not.toContain('context'); // Optional field
1263
- // Verify output schema structure
1264
- expect(agent.outputSchema).toHaveProperty('type', 'object');
1265
- expect(agent.outputSchema).toHaveProperty('properties');
1266
- expect(agent.outputSchema.properties).toHaveProperty('answer');
1267
- expect(agent.outputSchema.properties).toHaveProperty('confidence');
1268
- expect(agent.outputSchema.properties).toHaveProperty('sources');
1269
- expect(agent.outputSchema.properties).toHaveProperty('followUpQuestions');
1270
- // Verify descriptions are included
1271
- expect(agent.inputSchema.properties.question).toHaveProperty('description', "The user's question");
1272
- expect(agent.outputSchema.properties.answer).toHaveProperty('description', "The detailed answer to the user's question");
1273
- // Verify enum handling
1274
- expect(agent.inputSchema.properties.urgency).toHaveProperty('enum', ["low", "medium", "high"]);
1275
- // Verify number constraints
1276
- expect(agent.outputSchema.properties.confidence).toHaveProperty('minimum', 0);
1277
- expect(agent.outputSchema.properties.confidence).toHaveProperty('maximum', 1);
1278
- });
1279
- it("Returns 404 for non-existent agent", async () => {
1280
- await expect(dainConnection.getAgent("non-existent-agent")).rejects.toThrow();
1281
- });
1282
- it("Returns 404 for non-existent agent using POST method", async () => {
1283
- await expect(dainConnection.postAgent("non-existent-agent")).rejects.toThrow();
1284
- });
1285
- it("Agent has default service connection when not specified", async () => {
1286
- // Create an agent without serviceConnections to test default behavior
1287
- const defaultAgent = (0, core_1.createAgent)({
1288
- id: "default-connection-agent",
1289
- name: "Default Connection Agent",
1290
- context: ["Test context"],
1291
- prompt: "Test prompt",
1292
- resolveCondition: "Test condition",
1293
- input: zod_1.z.object({ test: zod_1.z.string() }),
1294
- output: zod_1.z.object({ result: zod_1.z.string() }),
1295
- // No serviceConnections specified - should default
1296
- });
1297
- // Verify default service connection is set
1298
- expect(defaultAgent.serviceConnections).toEqual([process.env.BASE_URL || 'http://localhost:3000']);
1299
- });
1300
- it("Validates required agent fields during creation", () => {
1301
- // Test missing id
1302
- expect(() => {
1303
- (0, core_1.createAgent)({
1304
- name: "Test Agent",
1305
- context: [],
1306
- prompt: "Test prompt",
1307
- resolveCondition: "Test condition",
1308
- input: zod_1.z.object({}),
1309
- output: zod_1.z.object({}),
1310
- });
1311
- }).toThrow("Agent config is missing required fields");
1312
- // Test missing name
1313
- expect(() => {
1314
- (0, core_1.createAgent)({
1315
- id: "test-agent",
1316
- context: [],
1317
- prompt: "Test prompt",
1318
- resolveCondition: "Test condition",
1319
- input: zod_1.z.object({}),
1320
- output: zod_1.z.object({}),
1321
- });
1322
- }).toThrow("Agent config is missing required fields");
1323
- // Test missing prompt
1324
- expect(() => {
1325
- (0, core_1.createAgent)({
1326
- id: "test-agent",
1327
- name: "Test Agent",
1328
- context: [],
1329
- resolveCondition: "Test condition",
1330
- input: zod_1.z.object({}),
1331
- output: zod_1.z.object({}),
1332
- });
1333
- }).toThrow("Agent config is missing required fields");
1334
- });
1335
- it("Agent context data is properly included in detailed response", async () => {
1336
- const agent = await dainConnection.getAgent("help-assistant");
1337
- // Verify context array is included and correct
1338
- expect(Array.isArray(agent.context)).toBe(true);
1339
- expect(agent.context).toHaveLength(2);
1340
- expect(agent.context).toContain("You are a helpful AI assistant");
1341
- expect(agent.context).toContain("Always provide accurate and concise answers");
1342
- });
1343
- it("Service metadata reflects the total count including agents", () => {
1344
- // Verify the service includes the agent
1345
- const agents = dainService.getAgents();
1346
- expect(agents).toHaveLength(1);
1347
- expect(agents[0].id).toBe("help-assistant");
1348
- expect(agents[0].name).toBe("Help Assistant Agent");
1349
- });
1350
- it("Can find agent using service's findAgent method", () => {
1351
- const foundAgent = dainService.findAgent("help-assistant");
1352
- expect(foundAgent).toBeDefined();
1353
- expect(foundAgent?.id).toBe("help-assistant");
1354
- expect(foundAgent?.name).toBe("Help Assistant Agent");
1355
- // Test non-existent agent
1356
- const notFound = dainService.findAgent("non-existent");
1357
- expect(notFound).toBeUndefined();
1358
- });
1359
- });
1360
- describe("Home UI", () => {
1361
- it("should get home UI widget ID when configured as string", async () => {
1362
- const homeService = (0, nodeService_1.defineDAINService)({
1363
- metadata: {
1364
- title: "Home UI Test Service",
1365
- description: "Test service for home UI",
1366
- version: "1.0.0",
1367
- author: "Test Author",
1368
- tags: ["test"],
1369
- },
1370
- identity: {
1371
- publicKey: bs58_1.default.encode(publicKey),
1372
- agentId: "home-ui-test-agent",
1373
- orgId: "home-ui-test-org",
1374
- privateKey: bs58_1.default.encode(privateKey),
1375
- },
1376
- services: [weatherService],
1377
- tools: [weatherTool, extraDataTool, confirmationTool, optionalParamsTool, noParamsTool, errorTool, knowledgeSearchTool],
1378
- toolboxes: [weatherToolbox],
1379
- contexts: [weatherContext],
1380
- widgets: [weatherWidget],
1381
- datasources: [weatherDatasource],
1382
- agents: [helpAgent],
1383
- homeUI: "weather-widget", // Static string configuration
1384
- });
1385
- const testServerHome = await homeService.startNode({ port: 3005 });
1386
- const clientHome = new client_1.DainServiceConnection("http://localhost:3005", new client_auth_1.DainClientAuth({
1387
- privateKeyBase58: bs58_1.default.encode(clientPrivateKey),
1388
- agentId: "home-ui-test-agent",
1389
- orgId: "home-ui-test-org",
1390
- }));
1391
- // Get home UI widget ID
1392
- const homeUIId = await clientHome.getHomeUI();
1393
- expect(homeUIId).toBe("weather-widget");
1394
- // Verify home widget exists and is accessible
1395
- const homeWidget = await clientHome.getWidget("weather-widget");
1396
- expect(homeWidget.name).toBe("Weather Info");
1397
- expect(homeWidget.ui.type).toBe("text");
1398
- await testServerHome.shutdown();
1399
- });
1400
- it("should get home UI widget ID when configured as function", async () => {
1401
- const homeService = (0, nodeService_1.defineDAINService)({
1402
- metadata: {
1403
- title: "Dynamic Home UI Test Service",
1404
- description: "Test service for dynamic home UI",
1405
- version: "1.0.0",
1406
- author: "Test Author",
1407
- tags: ["test"],
1408
- },
1409
- identity: {
1410
- publicKey: bs58_1.default.encode(publicKey),
1411
- agentId: "dynamic-home-ui-test-agent",
1412
- orgId: "dynamic-home-ui-test-org",
1413
- privateKey: bs58_1.default.encode(privateKey),
1414
- },
1415
- services: [weatherService],
1416
- tools: [weatherTool, extraDataTool, confirmationTool, optionalParamsTool, noParamsTool, errorTool, knowledgeSearchTool],
1417
- toolboxes: [weatherToolbox],
1418
- contexts: [weatherContext],
1419
- widgets: [weatherWidget],
1420
- datasources: [weatherDatasource],
1421
- agents: [helpAgent],
1422
- // Dynamic function to determine home UI based on agent
1423
- homeUI: async (agentInfo) => {
1424
- // Use different widgets based on agent ID
1425
- if (agentInfo.agentId === "dynamic-home-ui-test-agent") {
1426
- return "weather-widget";
1427
- }
1428
- return "weather-widget"; // Fallback to a default widget
1429
- },
1430
- });
1431
- const testServerDynamic = await homeService.startNode({ port: 3006 });
1432
- // Test with agent dynamic-home-ui-test-agent - should get weather-widget
1433
- const client1 = new client_1.DainServiceConnection("http://localhost:3006", new client_auth_1.DainClientAuth({
1434
- privateKeyBase58: bs58_1.default.encode(clientPrivateKey),
1435
- agentId: "dynamic-home-ui-test-agent",
1436
- orgId: "dynamic-home-ui-test-org",
1437
- }));
1438
- const homeUIId1 = await client1.getHomeUI();
1439
- expect(homeUIId1).toBe("weather-widget");
1440
- // Test with a different agent - should get weather-widget
1441
- const client2 = new client_1.DainServiceConnection("http://localhost:3006", new client_auth_1.DainClientAuth({
1442
- privateKeyBase58: bs58_1.default.encode(clientPrivateKey),
1443
- agentId: "another-agent",
1444
- orgId: "another-org",
1445
- }));
1446
- const homeUIId2 = await client2.getHomeUI();
1447
- expect(homeUIId2).toBe("weather-widget");
1448
- await testServerDynamic.shutdown();
1449
- });
1450
- it("should throw error when no home UI is configured", async () => {
1451
- // Use the main test service which doesn't have homeUI configured
1452
- await expect(dainConnection.getHomeUI()).rejects.toThrow("No home UI configured");
1453
- });
1454
- it("should throw error when home UI widget doesn't exist", async () => {
1455
- const badService = (0, nodeService_1.defineDAINService)({
1456
- metadata: {
1457
- title: "Bad Home UI Test Service",
1458
- description: "Test service with invalid home UI",
1459
- version: "1.0.0",
1460
- author: "Test Author",
1461
- tags: ["test"],
1462
- },
1463
- identity: {
1464
- publicKey: bs58_1.default.encode(publicKey),
1465
- agentId: "bad-home-ui-test-agent",
1466
- orgId: "bad-home-ui-test-org",
1467
- privateKey: bs58_1.default.encode(privateKey),
1468
- },
1469
- services: [weatherService],
1470
- tools: [weatherTool, extraDataTool, confirmationTool, optionalParamsTool, noParamsTool, errorTool, knowledgeSearchTool],
1471
- toolboxes: [weatherToolbox],
1472
- contexts: [weatherContext],
1473
- widgets: [weatherWidget],
1474
- datasources: [weatherDatasource],
1475
- agents: [helpAgent],
1476
- homeUI: "non-existent-widget", // Invalid widget ID
1477
- });
1478
- const testServerBad = await badService.startNode({ port: 3007 });
1479
- const clientBad = new client_1.DainServiceConnection("http://localhost:3007", new client_auth_1.DainClientAuth({
1480
- privateKeyBase58: bs58_1.default.encode(clientPrivateKey),
1481
- agentId: "bad-home-ui-test-agent",
1482
- orgId: "bad-home-ui-test-org",
1483
- }));
1484
- await expect(clientBad.getHomeUI()).rejects.toThrow("Home UI widget not found");
1485
- await testServerBad.shutdown();
1486
- });
1487
- it("should allow home UI widget to be accessible even with getUserWidgets restriction", async () => {
1488
- // Create additional widgets for testing restriction
1489
- const homeWidget = (0, core_1.createWidget)({
1490
- id: "home-ui-widget",
1491
- name: "Home UI Widget",
1492
- description: "The home UI widget",
1493
- icon: "🏠",
1494
- getWidget: async () => ({
1495
- text: "Welcome to home!",
1496
- data: { message: "This is the home UI widget" },
1497
- ui: { type: "home" },
1498
- }),
1499
- });
1500
- const sidebarWidget1 = (0, core_1.createWidget)({
1501
- id: "sidebar-widget-1",
1502
- name: "Sidebar Widget 1",
1503
- description: "First sidebar widget",
1504
- icon: "📊",
1505
- getWidget: async () => ({
1506
- text: "Sidebar 1",
1507
- data: { message: "This is sidebar widget 1" },
1508
- ui: { type: "sidebar1" },
1509
- }),
1510
- });
1511
- const sidebarWidget2 = (0, core_1.createWidget)({
1512
- id: "sidebar-widget-2",
1513
- name: "Sidebar Widget 2",
1514
- description: "Second sidebar widget",
1515
- icon: "📈",
1516
- getWidget: async () => ({
1517
- text: "Sidebar 2",
1518
- data: { message: "This is sidebar widget 2" },
1519
- ui: { type: "sidebar2" },
1520
- }),
1521
- });
1522
- const restrictedService = (0, nodeService_1.defineDAINService)({
1523
- metadata: {
1524
- title: "Restricted Home UI Test Service",
1525
- description: "Test service with widget restrictions",
1526
- version: "1.0.0",
1527
- author: "Test Author",
1528
- tags: ["test"],
1529
- },
1530
- identity: {
1531
- publicKey: bs58_1.default.encode(publicKey),
1532
- agentId: "restricted-home-ui-test-agent",
1533
- orgId: "restricted-home-ui-test-org",
1534
- privateKey: bs58_1.default.encode(privateKey),
1535
- },
1536
- services: [weatherService],
1537
- tools: [weatherTool],
1538
- toolboxes: [weatherToolbox],
1539
- contexts: [weatherContext],
1540
- widgets: [homeWidget, sidebarWidget1, sidebarWidget2],
1541
- datasources: [weatherDatasource],
1542
- agents: [helpAgent],
1543
- homeUI: "home-ui-widget",
1544
- // This function restricts user to only sidebar widgets, but home UI should still work
1545
- getUserWidgets: async (agentInfo) => {
1546
- return ["sidebar-widget-1", "sidebar-widget-2"]; // Only allow sidebar widgets
1547
- },
1548
- });
1549
- const testServerRestricted = await restrictedService.startNode({ port: 3008 });
1550
- const clientRestricted = new client_1.DainServiceConnection("http://localhost:3008", new client_auth_1.DainClientAuth({
1551
- privateKeyBase58: bs58_1.default.encode(clientPrivateKey),
1552
- agentId: "restricted-home-ui-test-agent",
1553
- orgId: "restricted-home-ui-test-org",
1554
- }));
1555
- // Should be able to get home UI even though getUserWidgets doesn't include it
1556
- const homeUIId = await clientRestricted.getHomeUI();
1557
- expect(homeUIId).toBe("home-ui-widget");
1558
- // Verify regular widget access is restricted to sidebar widgets only
1559
- const userWidgets = await clientRestricted.getWidgets();
1560
- expect(userWidgets).toEqual(["sidebar-widget-1", "sidebar-widget-2"]);
1561
- expect(userWidgets).not.toContain("home-ui-widget");
1562
- // Verify we can still fetch the home UI widget data
1563
- // even though it's not in getUserWidgets
1564
- const homeWidgetData = await clientRestricted.getWidget("home-ui-widget");
1565
- expect(homeWidgetData).toBeDefined();
1566
- expect(homeWidgetData.id).toBe("home-ui-widget");
1567
- expect(homeWidgetData.name).toBe("Home UI Widget");
1568
- expect(homeWidgetData.text).toBe("Welcome to home!");
1569
- await testServerRestricted.shutdown();
1570
- });
1571
- });
1572
- });
1573
- //# sourceMappingURL=integration.test.js.map