@dainprotocol/service-sdk 2.0.65 → 2.0.67
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/dist/index.d.ts +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/service/hitl.d.ts +8 -2
- package/dist/service/hitl.js +34 -9
- package/dist/service/hitl.js.map +1 -1
- package/dist/service/index.d.ts +4 -2
- package/dist/service/index.js +7 -1
- package/dist/service/index.js.map +1 -1
- package/dist/service/nodeService.d.ts +4 -2
- package/dist/service/nodeService.js +7 -1
- package/dist/service/nodeService.js.map +1 -1
- package/dist/service/oauth2Manager.js +3 -2
- package/dist/service/oauth2Manager.js.map +1 -1
- package/dist/service/server.js +81 -9
- package/dist/service/server.js.map +1 -1
- package/dist/service/service.js +3 -2
- package/dist/service/service.js.map +1 -1
- package/dist/service/skills.d.ts +80 -0
- package/dist/service/skills.js +102 -0
- package/dist/service/skills.js.map +1 -0
- package/dist/service/trigger-forwarder.d.ts +47 -0
- package/dist/service/trigger-forwarder.js +91 -0
- package/dist/service/trigger-forwarder.js.map +1 -0
- package/dist/service/types.d.ts +4 -0
- package/dist/service/webhooks.d.ts +109 -10
- package/dist/service/webhooks.js +184 -30
- package/dist/service/webhooks.js.map +1 -1
- package/package.json +1 -1
- package/dist/__tests__/api-sdk.test.d.ts +0 -1
- package/dist/__tests__/api-sdk.test.js +0 -102
- package/dist/__tests__/api-sdk.test.js.map +0 -1
- package/dist/__tests__/auth.test.d.ts +0 -1
- package/dist/__tests__/auth.test.js +0 -110
- package/dist/__tests__/auth.test.js.map +0 -1
- package/dist/__tests__/citations-plugin.test.d.ts +0 -1
- package/dist/__tests__/citations-plugin.test.js +0 -491
- package/dist/__tests__/citations-plugin.test.js.map +0 -1
- package/dist/__tests__/context-behavior.test.d.ts +0 -1
- package/dist/__tests__/context-behavior.test.js +0 -290
- package/dist/__tests__/context-behavior.test.js.map +0 -1
- package/dist/__tests__/convertToVercelTool.test.d.ts +0 -1
- package/dist/__tests__/convertToVercelTool.test.js +0 -527
- package/dist/__tests__/convertToVercelTool.test.js.map +0 -1
- package/dist/__tests__/core.test.d.ts +0 -1
- package/dist/__tests__/core.test.js +0 -154
- package/dist/__tests__/core.test.js.map +0 -1
- package/dist/__tests__/crypto-plugin.test.d.ts +0 -1
- package/dist/__tests__/crypto-plugin.test.js +0 -694
- package/dist/__tests__/crypto-plugin.test.js.map +0 -1
- package/dist/__tests__/humanActions.test.d.ts +0 -1
- package/dist/__tests__/humanActions.test.js +0 -221
- package/dist/__tests__/humanActions.test.js.map +0 -1
- package/dist/__tests__/integration.test.d.ts +0 -1
- package/dist/__tests__/integration.test.js +0 -1573
- package/dist/__tests__/integration.test.js.map +0 -1
- package/dist/__tests__/mealMeSchemas.test.d.ts +0 -576
- package/dist/__tests__/mealMeSchemas.test.js +0 -627
- package/dist/__tests__/mealMeSchemas.test.js.map +0 -1
- package/dist/__tests__/oauth-context-simple.test.d.ts +0 -1
- package/dist/__tests__/oauth-context-simple.test.js +0 -90
- package/dist/__tests__/oauth-context-simple.test.js.map +0 -1
- package/dist/__tests__/oauth-context.test.d.ts +0 -1
- package/dist/__tests__/oauth-context.test.js +0 -282
- package/dist/__tests__/oauth-context.test.js.map +0 -1
- package/dist/__tests__/oauth.test.d.ts +0 -1
- package/dist/__tests__/oauth.test.js +0 -378
- package/dist/__tests__/oauth.test.js.map +0 -1
- package/dist/__tests__/oauth2-client-context.test.d.ts +0 -1
- package/dist/__tests__/oauth2-client-context.test.js +0 -165
- package/dist/__tests__/oauth2-client-context.test.js.map +0 -1
- package/dist/__tests__/oauth2-client-integration.test.d.ts +0 -1
- package/dist/__tests__/oauth2-client-integration.test.js +0 -182
- package/dist/__tests__/oauth2-client-integration.test.js.map +0 -1
- package/dist/__tests__/oauth2-client-simple.test.d.ts +0 -1
- package/dist/__tests__/oauth2-client-simple.test.js +0 -144
- package/dist/__tests__/oauth2-client-simple.test.js.map +0 -1
- package/dist/__tests__/oauth2-context.test.d.ts +0 -1
- package/dist/__tests__/oauth2-context.test.js +0 -201
- package/dist/__tests__/oauth2-context.test.js.map +0 -1
- package/dist/__tests__/oauth2-datasource.test.d.ts +0 -1
- package/dist/__tests__/oauth2-datasource.test.js +0 -251
- package/dist/__tests__/oauth2-datasource.test.js.map +0 -1
- package/dist/__tests__/plugin.test.d.ts +0 -1
- package/dist/__tests__/plugin.test.js +0 -900
- package/dist/__tests__/plugin.test.js.map +0 -1
- package/dist/__tests__/processes.test.d.ts +0 -1
- package/dist/__tests__/processes.test.js +0 -239
- package/dist/__tests__/processes.test.js.map +0 -1
- package/dist/__tests__/streaming.test.d.ts +0 -1
- package/dist/__tests__/streaming.test.js +0 -592
- package/dist/__tests__/streaming.test.js.map +0 -1
- package/dist/__tests__/testEnums.d.ts +0 -1
- package/dist/__tests__/testEnums.js +0 -73
- package/dist/__tests__/testEnums.js.map +0 -1
- package/dist/__tests__/testOptionals.test.d.ts +0 -1
- package/dist/__tests__/testOptionals.test.js +0 -83
- package/dist/__tests__/testOptionals.test.js.map +0 -1
- package/dist/__tests__/types.test.d.ts +0 -1
- package/dist/__tests__/types.test.js +0 -98
- package/dist/__tests__/types.test.js.map +0 -1
- package/dist/service/zod-json-converter.d.ts +0 -14
- package/dist/service/zod-json-converter.js +0 -203
- 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
|