@dainprotocol/service-sdk 2.0.94 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/client/api-sdk.d.ts +7 -1
- package/dist/client/api-sdk.js +2 -2
- package/dist/client/api-sdk.js.map +1 -1
- package/dist/client/client-auth.d.ts +38 -0
- package/dist/client/client-auth.js +131 -2
- package/dist/client/client-auth.js.map +1 -1
- package/dist/client/client.d.ts +1 -0
- package/dist/client/client.js +92 -25
- package/dist/client/client.js.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +252 -0
- package/dist/client/types.js +159 -31
- package/dist/client/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/crypto-plugin.d.ts +26 -0
- package/dist/plugins/crypto-plugin.js +44 -4
- package/dist/plugins/crypto-plugin.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/protocol/account.d.ts +91 -0
- package/dist/protocol/account.js +3 -0
- package/dist/protocol/account.js.map +1 -0
- package/dist/protocol/grants.d.ts +34 -0
- package/dist/protocol/grants.js +3 -0
- package/dist/protocol/grants.js.map +1 -0
- package/dist/protocol/index.d.ts +8 -0
- package/dist/protocol/index.js +12 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/payments.d.ts +53 -0
- package/dist/protocol/payments.js +3 -0
- package/dist/protocol/payments.js.map +1 -0
- package/dist/protocol/permissions.d.ts +21 -0
- package/dist/protocol/permissions.js +3 -0
- package/dist/protocol/permissions.js.map +1 -0
- package/dist/protocol/programs.d.ts +44 -0
- package/dist/protocol/programs.js +46 -0
- package/dist/protocol/programs.js.map +1 -0
- package/dist/protocol/registry.d.ts +43 -0
- package/dist/protocol/registry.js +3 -0
- package/dist/protocol/registry.js.map +1 -0
- package/dist/protocol/runtime.d.ts +15 -0
- package/dist/protocol/runtime.js +3 -0
- package/dist/protocol/runtime.js.map +1 -0
- package/dist/protocol/transactions.d.ts +63 -0
- package/dist/protocol/transactions.js +3 -0
- package/dist/protocol/transactions.js.map +1 -0
- package/dist/service/auth.d.ts +15 -1
- package/dist/service/auth.js +156 -35
- package/dist/service/auth.js.map +1 -1
- package/dist/service/core.js +9 -0
- package/dist/service/core.js.map +1 -1
- package/dist/service/index.d.ts +1 -0
- package/dist/service/index.js +1 -0
- package/dist/service/index.js.map +1 -1
- package/dist/service/nodeService.js +30 -2
- package/dist/service/nodeService.js.map +1 -1
- package/dist/service/server.js +733 -242
- package/dist/service/server.js.map +1 -1
- package/dist/service/service.js +6 -8
- package/dist/service/service.js.map +1 -1
- package/dist/service/types.d.ts +48 -34
- package/package.json +20 -7
package/dist/service/server.js
CHANGED
|
@@ -98,18 +98,151 @@ async function safeParseBody(c) {
|
|
|
98
98
|
return {};
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
function isRecord(value) {
|
|
102
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
103
|
+
}
|
|
104
|
+
const DAIN_RUNTIME_CONTEXT_KEYS = new Set([
|
|
105
|
+
"dainAccountId",
|
|
106
|
+
"dainGroupId",
|
|
107
|
+
"smartAccountPDA",
|
|
108
|
+
"account",
|
|
109
|
+
"group",
|
|
110
|
+
"auth",
|
|
111
|
+
"grants",
|
|
112
|
+
"dataPermissions",
|
|
113
|
+
"registryPolicy",
|
|
114
|
+
]);
|
|
115
|
+
function stripRuntimeContextFields(value) {
|
|
116
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => !DAIN_RUNTIME_CONTEXT_KEYS.has(key)));
|
|
117
|
+
}
|
|
118
|
+
function getStringClaim(payload, ...keys) {
|
|
119
|
+
if (!payload)
|
|
120
|
+
return undefined;
|
|
121
|
+
for (const key of keys) {
|
|
122
|
+
const value = payload[key];
|
|
123
|
+
if (typeof value === "string" && value.length > 0) {
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
function getObjectClaim(payload, key) {
|
|
130
|
+
if (!payload)
|
|
131
|
+
return undefined;
|
|
132
|
+
const value = payload[key];
|
|
133
|
+
return isRecord(value) ? value : undefined;
|
|
134
|
+
}
|
|
135
|
+
function getArrayClaim(payload, key) {
|
|
136
|
+
if (!payload)
|
|
137
|
+
return undefined;
|
|
138
|
+
const value = payload[key];
|
|
139
|
+
return Array.isArray(value) ? value : undefined;
|
|
140
|
+
}
|
|
141
|
+
function getRuntimeContextFromJwtPayload(payload, options = {}) {
|
|
142
|
+
const account = getObjectClaim(payload, "account_context");
|
|
143
|
+
const group = getObjectClaim(payload, "group_context");
|
|
144
|
+
const auth = getObjectClaim(payload, "auth_context");
|
|
145
|
+
const dainAccountId = getStringClaim(payload, "dain_account_id", "account_id") ?? account?.id;
|
|
146
|
+
const dainGroupId = getStringClaim(payload, "dain_group_id", "group_id") ?? group?.id;
|
|
147
|
+
const smartAccountPDA = getStringClaim(payload, "smart_account_pda") ??
|
|
148
|
+
options.fallbackSmartAccountPDA;
|
|
149
|
+
if (account?.id && dainAccountId && account.id !== dainAccountId) {
|
|
150
|
+
throw new http_exception_1.HTTPException(401, {
|
|
151
|
+
message: "JWT DAIN context mismatch: dain_account_id does not match account_context.id",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (group?.id && dainGroupId && group.id !== dainGroupId) {
|
|
155
|
+
throw new http_exception_1.HTTPException(401, {
|
|
156
|
+
message: "JWT DAIN context mismatch: dain_group_id does not match group_context.id",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (account?.smartAccount?.pda &&
|
|
160
|
+
smartAccountPDA &&
|
|
161
|
+
account.smartAccount.pda !== smartAccountPDA) {
|
|
162
|
+
throw new http_exception_1.HTTPException(401, {
|
|
163
|
+
message: "JWT DAIN context mismatch: smart_account_pda does not match account_context.smartAccount.pda",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const grants = getArrayClaim(payload, "action_grants");
|
|
167
|
+
const dataPermissions = getArrayClaim(payload, "data_permissions");
|
|
168
|
+
const registryPolicy = getObjectClaim(payload, "registry_policy");
|
|
169
|
+
const subject = getStringClaim(payload, "sub") ??
|
|
170
|
+
options.smartAccountId ??
|
|
171
|
+
dainAccountId ??
|
|
172
|
+
dainGroupId ??
|
|
173
|
+
smartAccountPDA;
|
|
174
|
+
return {
|
|
175
|
+
...(dainAccountId ? { dainAccountId } : {}),
|
|
176
|
+
...(dainGroupId ? { dainGroupId } : {}),
|
|
177
|
+
...(smartAccountPDA ? { smartAccountPDA } : {}),
|
|
178
|
+
...(account ? { account } : {}),
|
|
179
|
+
...(group ? { group } : {}),
|
|
180
|
+
auth: auth ?? {
|
|
181
|
+
subject: subject ?? "unknown",
|
|
182
|
+
issuer: getStringClaim(payload, "iss"),
|
|
183
|
+
scopes: options.scopes,
|
|
184
|
+
source: "dain_id",
|
|
185
|
+
},
|
|
186
|
+
...(grants ? { grants } : {}),
|
|
187
|
+
...(dataPermissions ? { dataPermissions } : {}),
|
|
188
|
+
...(registryPolicy ? { registryPolicy } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function getRuntimeContextFromAgentInfo(agentInfo) {
|
|
192
|
+
return {
|
|
193
|
+
...(agentInfo.dainAccountId
|
|
194
|
+
? { dainAccountId: agentInfo.dainAccountId }
|
|
195
|
+
: {}),
|
|
196
|
+
...(agentInfo.dainGroupId ? { dainGroupId: agentInfo.dainGroupId } : {}),
|
|
197
|
+
...(agentInfo.smartAccountPDA
|
|
198
|
+
? { smartAccountPDA: agentInfo.smartAccountPDA }
|
|
199
|
+
: {}),
|
|
200
|
+
...(agentInfo.account ? { account: agentInfo.account } : {}),
|
|
201
|
+
...(agentInfo.group ? { group: agentInfo.group } : {}),
|
|
202
|
+
...(agentInfo.auth ? { auth: agentInfo.auth } : {}),
|
|
203
|
+
...(agentInfo.grants ? { grants: agentInfo.grants } : {}),
|
|
204
|
+
...(agentInfo.dataPermissions
|
|
205
|
+
? { dataPermissions: agentInfo.dataPermissions }
|
|
206
|
+
: {}),
|
|
207
|
+
...(agentInfo.registryPolicy
|
|
208
|
+
? { registryPolicy: agentInfo.registryPolicy }
|
|
209
|
+
: {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function getRequestExtraData(request) {
|
|
213
|
+
if (!isRecord(request))
|
|
214
|
+
return {};
|
|
215
|
+
return isRecord(request.DAIN_EXTRA_DATA)
|
|
216
|
+
? stripRuntimeContextFields(request.DAIN_EXTRA_DATA)
|
|
217
|
+
: {};
|
|
218
|
+
}
|
|
219
|
+
function buildServiceExtraData(agentInfo, options = {}) {
|
|
220
|
+
const requestExtra = getRequestExtraData(options.request);
|
|
221
|
+
const runtimeContext = getRuntimeContextFromAgentInfo(agentInfo);
|
|
222
|
+
return {
|
|
223
|
+
...requestExtra,
|
|
224
|
+
...(options.extra ?? {}),
|
|
225
|
+
...runtimeContext,
|
|
226
|
+
...(options.plugins ? { plugins: options.plugins } : {}),
|
|
227
|
+
...(Object.prototype.hasOwnProperty.call(options, "oauth2Client")
|
|
228
|
+
? { oauth2Client: options.oauth2Client }
|
|
229
|
+
: {}),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
101
232
|
/**
|
|
102
233
|
* Middleware factory to require specific OAuth scopes.
|
|
103
234
|
* Defense-in-depth: Validates scopes even though JWT middleware already checked them.
|
|
104
235
|
*/
|
|
105
236
|
function requireScope(requiredScope) {
|
|
106
237
|
return async (c, next) => {
|
|
107
|
-
const scopes = c.get(
|
|
108
|
-
const requiredScopes = Array.isArray(requiredScope)
|
|
109
|
-
|
|
238
|
+
const scopes = c.get("scope") || [];
|
|
239
|
+
const requiredScopes = Array.isArray(requiredScope)
|
|
240
|
+
? requiredScope
|
|
241
|
+
: [requiredScope];
|
|
242
|
+
const hasRequiredScope = requiredScopes.some((scope) => (0, auth_2.hasScope)(scopes, scope));
|
|
110
243
|
if (!hasRequiredScope) {
|
|
111
244
|
throw new http_exception_1.HTTPException(403, {
|
|
112
|
-
message: `Insufficient scope. Required: ${requiredScopes.join(
|
|
245
|
+
message: `Insufficient scope. Required: ${requiredScopes.join(" OR ")}, Have: ${scopes.join(", ")}`,
|
|
113
246
|
});
|
|
114
247
|
}
|
|
115
248
|
await next();
|
|
@@ -120,6 +253,17 @@ function signedStreamSSE(c, privateKey, config, handler) {
|
|
|
120
253
|
return (0, streaming_1.streamSSE)(c, async (stream) => {
|
|
121
254
|
const requestSignal = c.req.raw?.signal;
|
|
122
255
|
const isAborted = () => stream.aborted || stream.closed || requestSignal?.aborted === true;
|
|
256
|
+
const abortStream = () => {
|
|
257
|
+
if (!stream.closed) {
|
|
258
|
+
stream.abort();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
if (requestSignal?.aborted) {
|
|
262
|
+
abortStream();
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
requestSignal?.addEventListener("abort", abortStream, { once: true });
|
|
266
|
+
}
|
|
123
267
|
const signedStream = {
|
|
124
268
|
writeSSE: async (event) => {
|
|
125
269
|
if (isAborted())
|
|
@@ -128,29 +272,46 @@ function signedStreamSSE(c, privateKey, config, handler) {
|
|
|
128
272
|
const messageHash = (0, sseSignature_1.hashEventMessage)(event.data, timestamp);
|
|
129
273
|
const signature = (0, utils_js_1.bytesToHex)(sseSignature_1.ed25519.sign(messageHash, privateKey));
|
|
130
274
|
// Fast path for non-critical events (progress/UI updates)
|
|
131
|
-
const isCriticalEvent = event.event ===
|
|
132
|
-
event.event ===
|
|
133
|
-
event.event ===
|
|
275
|
+
const isCriticalEvent = event.event === "result" ||
|
|
276
|
+
event.event === "process-created" ||
|
|
277
|
+
event.event === "datasource-update";
|
|
134
278
|
const dataWithSignature = isCriticalEvent
|
|
135
279
|
? JSON.stringify({
|
|
136
280
|
data: JSON.parse(event.data),
|
|
137
|
-
_signature: {
|
|
281
|
+
_signature: {
|
|
282
|
+
signature,
|
|
283
|
+
timestamp,
|
|
284
|
+
agentId: config.identity.agentId,
|
|
285
|
+
orgId: config.identity.orgId,
|
|
286
|
+
address: config.identity.publicKey,
|
|
287
|
+
},
|
|
138
288
|
})
|
|
139
289
|
: `{"data":${event.data},"_signature":{"signature":"${signature}","timestamp":"${timestamp}","agentId":"${config.identity.agentId}","orgId":"${config.identity.orgId}","address":"${config.identity.publicKey}"}}`;
|
|
140
|
-
await stream.writeSSE({
|
|
290
|
+
await stream.writeSSE({
|
|
291
|
+
event: event.event,
|
|
292
|
+
data: dataWithSignature,
|
|
293
|
+
id: event.id,
|
|
294
|
+
});
|
|
141
295
|
},
|
|
142
296
|
isAborted,
|
|
297
|
+
onAbort: (listener) => stream.onAbort(listener),
|
|
143
298
|
};
|
|
144
|
-
|
|
299
|
+
try {
|
|
300
|
+
await handler(signedStream);
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
requestSignal?.removeEventListener("abort", abortStream);
|
|
304
|
+
}
|
|
145
305
|
});
|
|
146
306
|
}
|
|
147
307
|
function setupHttpServer(config, tools, services, toolboxes, metadata, privateKey, contexts, widgets, datasources = [], agents = []) {
|
|
148
308
|
const app = new hono_1.Hono();
|
|
309
|
+
const datasourceStreamAbortControllers = new Map();
|
|
149
310
|
const processHandler = new processes_1.ProcessHandler({
|
|
150
311
|
serviceId: "service_" + config.identity.orgId + "_" + config.identity.agentId,
|
|
151
312
|
privateKey,
|
|
152
313
|
store: config.processStore,
|
|
153
|
-
onHumanActionResponse: config.onHumanActionResponse
|
|
314
|
+
onHumanActionResponse: config.onHumanActionResponse,
|
|
154
315
|
});
|
|
155
316
|
app.processes = processHandler;
|
|
156
317
|
// CORS middleware - apply to all routes
|
|
@@ -160,6 +321,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
160
321
|
allowHeaders: [
|
|
161
322
|
"X-DAIN-SIGNATURE",
|
|
162
323
|
"X-DAIN-SMART-ACCOUNT-PDA",
|
|
324
|
+
"X-DAIN-ACCOUNT-ID",
|
|
325
|
+
"X-DAIN-GROUP-ID",
|
|
163
326
|
"X-DAIN-AGENT-ID",
|
|
164
327
|
"X-DAIN-ORG-ID",
|
|
165
328
|
"X-DAIN-ADDRESS",
|
|
@@ -177,7 +340,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
177
340
|
return c.text("Enabling CORS Pre-Flight", 200, {
|
|
178
341
|
"Access-Control-Allow-Origin": "*",
|
|
179
342
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
180
|
-
"Access-Control-Allow-Headers": "X-DAIN-SIGNATURE, X-DAIN-SMART-ACCOUNT-PDA, X-DAIN-AGENT-ID, X-DAIN-ORG-ID, X-DAIN-ADDRESS, X-DAIN-TIMESTAMP, Content-Type, Authorization, Accept, Origin, X-Requested-With",
|
|
343
|
+
"Access-Control-Allow-Headers": "X-DAIN-SIGNATURE, X-DAIN-SMART-ACCOUNT-PDA, X-DAIN-ACCOUNT-ID, X-DAIN-GROUP-ID, X-DAIN-AGENT-ID, X-DAIN-ORG-ID, X-DAIN-ADDRESS, X-DAIN-TIMESTAMP, Content-Type, Authorization, Accept, Origin, X-Requested-With",
|
|
181
344
|
"Access-Control-Max-Age": "86400",
|
|
182
345
|
});
|
|
183
346
|
});
|
|
@@ -197,7 +360,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
197
360
|
const isStreaming = acceptHeader.includes("text/event-stream");
|
|
198
361
|
await next();
|
|
199
362
|
// Skip signature for streaming responses
|
|
200
|
-
if (isStreaming ||
|
|
363
|
+
if (isStreaming ||
|
|
364
|
+
c.res.headers.get("content-type")?.includes("text/event-stream")) {
|
|
201
365
|
return;
|
|
202
366
|
}
|
|
203
367
|
const body = await c.res.clone().text();
|
|
@@ -210,12 +374,14 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
210
374
|
});
|
|
211
375
|
// Get all webhook paths for auth bypass
|
|
212
376
|
const webhookPaths = config.webhookTriggers?.getWebhookPaths() || [];
|
|
213
|
-
const hitlPath = config.hitl?.enabled
|
|
377
|
+
const hitlPath = config.hitl?.enabled
|
|
378
|
+
? config.hitl.webhookPath || "/actions"
|
|
379
|
+
: null;
|
|
214
380
|
// Dual Authentication: JWT (users) or API Key (services)
|
|
215
381
|
app.use("*", async (c, next) => {
|
|
216
382
|
debugLog(`[Auth Middleware] Path: ${c.req.path}, Method: ${c.req.method}`);
|
|
217
383
|
// Check if path is a webhook path (auth bypass)
|
|
218
|
-
const isWebhookPath = webhookPaths.some(path => c.req.path === path);
|
|
384
|
+
const isWebhookPath = webhookPaths.some((path) => c.req.path === path);
|
|
219
385
|
const isHITLPath = hitlPath && c.req.path === hitlPath;
|
|
220
386
|
if (c.req.path.startsWith("/oauth2/callback/") ||
|
|
221
387
|
c.req.path.startsWith("/addons") ||
|
|
@@ -237,16 +403,20 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
237
403
|
const host = getFirstForwardedValue(c.req.header("x-forwarded-host")) ||
|
|
238
404
|
getFirstForwardedValue(c.req.header("host"));
|
|
239
405
|
if (!host) {
|
|
240
|
-
throw new http_exception_1.HTTPException(400, {
|
|
406
|
+
throw new http_exception_1.HTTPException(400, {
|
|
407
|
+
message: "Unable to determine service URL. Host header is required.",
|
|
408
|
+
});
|
|
241
409
|
}
|
|
242
410
|
// Validate host format to prevent obvious attacks
|
|
243
|
-
if (host.includes(
|
|
411
|
+
if (host.includes(" ") || host.includes("\n") || host.includes("\r")) {
|
|
244
412
|
throw new http_exception_1.HTTPException(400, { message: "Invalid Host header format" });
|
|
245
413
|
}
|
|
246
414
|
const xForwardedProto = getFirstForwardedValue(c.req.header("x-forwarded-proto"));
|
|
247
415
|
const protocol = xForwardedProto === "http" || xForwardedProto === "https"
|
|
248
416
|
? xForwardedProto
|
|
249
|
-
:
|
|
417
|
+
: host.includes("localhost") || host.startsWith("127.")
|
|
418
|
+
? "http"
|
|
419
|
+
: "https";
|
|
250
420
|
const forwardedPrefix = normalizeForwardedPrefix(getFirstForwardedValue(c.req.header("x-forwarded-prefix")));
|
|
251
421
|
const jwtAudiences = [`${protocol}://${host}`];
|
|
252
422
|
if (forwardedPrefix) {
|
|
@@ -259,7 +429,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
259
429
|
audience: jwtAudiences,
|
|
260
430
|
});
|
|
261
431
|
if (!result.valid) {
|
|
262
|
-
throw new http_exception_1.HTTPException(401, {
|
|
432
|
+
throw new http_exception_1.HTTPException(401, {
|
|
433
|
+
message: `JWT verification failed: ${result.error}`,
|
|
434
|
+
});
|
|
263
435
|
}
|
|
264
436
|
// Defense in depth: double-check audience claim
|
|
265
437
|
const tokenAudiences = Array.isArray(result.payload?.aud)
|
|
@@ -269,15 +441,21 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
269
441
|
const audienceMatched = tokenAudiences.some((aud) => aud && expectedAudienceSet.has(aud));
|
|
270
442
|
if (!audienceMatched) {
|
|
271
443
|
throw new http_exception_1.HTTPException(403, {
|
|
272
|
-
message: `JWT audience mismatch. Expected one of: ${jwtAudiences.join(", ")}, Got: ${tokenAudiences.filter(Boolean).join(", ")}
|
|
444
|
+
message: `JWT audience mismatch. Expected one of: ${jwtAudiences.join(", ")}, Got: ${tokenAudiences.filter(Boolean).join(", ")}`,
|
|
273
445
|
});
|
|
274
446
|
}
|
|
275
|
-
c.set(
|
|
276
|
-
c.set(
|
|
277
|
-
c.set(
|
|
278
|
-
c.set(
|
|
279
|
-
c.set(
|
|
280
|
-
|
|
447
|
+
c.set("authMethod", "jwt");
|
|
448
|
+
c.set("smartAccountId", result.smartAccountId);
|
|
449
|
+
c.set("address", result.smartAccountId); // Use smartAccountId as address for JWT auth
|
|
450
|
+
c.set("scope", result.scope);
|
|
451
|
+
c.set("jwtPayload", result.payload);
|
|
452
|
+
const jwtRuntimeContext = getRuntimeContextFromJwtPayload(result.payload, {
|
|
453
|
+
scopes: result.scope,
|
|
454
|
+
smartAccountId: result.smartAccountId,
|
|
455
|
+
fallbackSmartAccountPDA: c.req.header("X-DAIN-SMART-ACCOUNT-PDA"),
|
|
456
|
+
});
|
|
457
|
+
c.set("dainRuntimeContext", jwtRuntimeContext);
|
|
458
|
+
c.set("smartAccountPDA", jwtRuntimeContext.smartAccountPDA);
|
|
281
459
|
debugLog(`[Auth Middleware] JWT auth SUCCESS - smartAccountId: ${result.smartAccountId}`);
|
|
282
460
|
}
|
|
283
461
|
else if (apiKey) {
|
|
@@ -295,11 +473,22 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
295
473
|
throw new http_exception_1.HTTPException(401, { message: "Invalid API key" });
|
|
296
474
|
}
|
|
297
475
|
}
|
|
298
|
-
c.set(
|
|
299
|
-
c.set(
|
|
300
|
-
c.set(
|
|
301
|
-
c.set(
|
|
302
|
-
c.set(
|
|
476
|
+
c.set("authMethod", "apiKey");
|
|
477
|
+
c.set("agentId", parsed.agentId);
|
|
478
|
+
c.set("orgId", parsed.orgId);
|
|
479
|
+
c.set("address", parsed.agentId); // Use agentId as address for API key auth
|
|
480
|
+
c.set("smartAccountPDA", c.req.header("X-DAIN-SMART-ACCOUNT-PDA"));
|
|
481
|
+
c.set("dainRuntimeContext", {
|
|
482
|
+
...(c.req.header("X-DAIN-ACCOUNT-ID")
|
|
483
|
+
? { dainAccountId: c.req.header("X-DAIN-ACCOUNT-ID") }
|
|
484
|
+
: {}),
|
|
485
|
+
...(c.req.header("X-DAIN-GROUP-ID")
|
|
486
|
+
? { dainGroupId: c.req.header("X-DAIN-GROUP-ID") }
|
|
487
|
+
: {}),
|
|
488
|
+
...(c.req.header("X-DAIN-SMART-ACCOUNT-PDA")
|
|
489
|
+
? { smartAccountPDA: c.req.header("X-DAIN-SMART-ACCOUNT-PDA") }
|
|
490
|
+
: {}),
|
|
491
|
+
});
|
|
303
492
|
debugLog(`[Auth Middleware] API Key auth SUCCESS - agentId: ${parsed.agentId}, orgId: ${parsed.orgId}`);
|
|
304
493
|
}
|
|
305
494
|
else {
|
|
@@ -310,32 +499,58 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
310
499
|
});
|
|
311
500
|
async function getAgentInfo(c) {
|
|
312
501
|
debugLog("[getAgentInfo] START");
|
|
313
|
-
debugLog("[getAgentInfo] Auth method:", c.get(
|
|
314
|
-
const
|
|
502
|
+
debugLog("[getAgentInfo] Auth method:", c.get("authMethod"));
|
|
503
|
+
const runtimeContext = (c.get("dainRuntimeContext") ||
|
|
504
|
+
{});
|
|
505
|
+
const smartAccountPDA = runtimeContext.smartAccountPDA ||
|
|
506
|
+
c.get("smartAccountPDA") ||
|
|
507
|
+
c.req.header("X-DAIN-SMART-ACCOUNT-PDA");
|
|
315
508
|
const webhookUrl = c.req.header("X-DAIN-WEBHOOK-URL");
|
|
316
509
|
debugLog("[getAgentInfo] smartAccountPDA:", smartAccountPDA);
|
|
317
510
|
debugLog("[getAgentInfo] webhookUrl:", webhookUrl);
|
|
318
|
-
if (c.get(
|
|
319
|
-
const smartAccountId = c.get(
|
|
511
|
+
if (c.get("authMethod") === "jwt") {
|
|
512
|
+
const smartAccountId = c.get("smartAccountId");
|
|
320
513
|
debugLog("[getAgentInfo] JWT auth - smartAccountId:", smartAccountId);
|
|
321
514
|
const agentInfo = {
|
|
322
515
|
agentId: smartAccountId,
|
|
323
516
|
address: smartAccountId,
|
|
324
517
|
smartAccountPDA,
|
|
325
|
-
|
|
518
|
+
dainAccountId: runtimeContext.dainAccountId,
|
|
519
|
+
dainGroupId: runtimeContext.dainGroupId,
|
|
520
|
+
account: runtimeContext.account,
|
|
521
|
+
group: runtimeContext.group,
|
|
522
|
+
auth: runtimeContext.auth,
|
|
523
|
+
grants: runtimeContext.grants,
|
|
524
|
+
dataPermissions: runtimeContext.dataPermissions,
|
|
525
|
+
registryPolicy: runtimeContext.registryPolicy,
|
|
526
|
+
id: runtimeContext.dainAccountId ??
|
|
527
|
+
runtimeContext.dainGroupId ??
|
|
528
|
+
(smartAccountPDA
|
|
529
|
+
? `dain_id_${smartAccountPDA}`
|
|
530
|
+
: `smart_account_${smartAccountId}`),
|
|
326
531
|
webhookUrl,
|
|
327
532
|
};
|
|
328
533
|
debugLog("[getAgentInfo] JWT - Returning agent info:", agentInfo);
|
|
329
534
|
return agentInfo;
|
|
330
535
|
}
|
|
331
|
-
const agentId = c.get(
|
|
332
|
-
const address = c.get(
|
|
536
|
+
const agentId = c.get("agentId");
|
|
537
|
+
const address = c.get("address");
|
|
333
538
|
debugLog("[getAgentInfo] API Key auth - agentId:", agentId, "address:", address);
|
|
334
539
|
const agentInfo = {
|
|
335
540
|
agentId,
|
|
336
541
|
address,
|
|
337
542
|
smartAccountPDA,
|
|
338
|
-
|
|
543
|
+
dainAccountId: runtimeContext.dainAccountId,
|
|
544
|
+
dainGroupId: runtimeContext.dainGroupId,
|
|
545
|
+
account: runtimeContext.account,
|
|
546
|
+
group: runtimeContext.group,
|
|
547
|
+
auth: runtimeContext.auth,
|
|
548
|
+
grants: runtimeContext.grants,
|
|
549
|
+
dataPermissions: runtimeContext.dataPermissions,
|
|
550
|
+
registryPolicy: runtimeContext.registryPolicy,
|
|
551
|
+
id: runtimeContext.dainAccountId ??
|
|
552
|
+
runtimeContext.dainGroupId ??
|
|
553
|
+
(smartAccountPDA ? `dain_id_${smartAccountPDA}` : `address_${address}`),
|
|
339
554
|
webhookUrl,
|
|
340
555
|
};
|
|
341
556
|
debugLog("[getAgentInfo] API Key - Returning agent info:", agentInfo);
|
|
@@ -365,7 +580,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
365
580
|
app.get("/metadata", (c) => {
|
|
366
581
|
// Compute service-level capability: does ANY tool support user actions (HITL)?
|
|
367
582
|
const supportsUserActions = tools.some((tool) => tool.supportsUserActions === true);
|
|
368
|
-
const requestedContract = c.req.header("x-butterfly-contract") ||
|
|
583
|
+
const requestedContract = c.req.header("x-butterfly-contract") ||
|
|
584
|
+
c.req.header("X-Butterfly-Contract");
|
|
369
585
|
const sdkMajor = Number.parseInt(String(package_json_1.default.version).split(".")[0] || "0", 10);
|
|
370
586
|
const contractVersion = Number.isFinite(sdkMajor) && sdkMajor > 0 ? `${sdkMajor}.0.0` : "0.0.0";
|
|
371
587
|
const capabilities = {
|
|
@@ -378,6 +594,14 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
378
594
|
datasourcePolicy: true,
|
|
379
595
|
widgetPolicy: true,
|
|
380
596
|
toolSafety: true,
|
|
597
|
+
dainAccount: true,
|
|
598
|
+
registryDiscovery: true,
|
|
599
|
+
dataPermissions: true,
|
|
600
|
+
actionGrants: true,
|
|
601
|
+
smartAccountTransactions: true,
|
|
602
|
+
payments: true,
|
|
603
|
+
registryPolicy: true,
|
|
604
|
+
grantPolicies: true,
|
|
381
605
|
};
|
|
382
606
|
const compatibility = (() => {
|
|
383
607
|
if (!requestedContract)
|
|
@@ -444,38 +668,53 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
444
668
|
const autoContexts = app.oauth2?.getDirectProviderContexts?.() || [];
|
|
445
669
|
const serviceSlug = (0, core_1.toSlug)(metadata.title);
|
|
446
670
|
const skillsContext = config.skills?.getSkillsContext?.(serviceSlug);
|
|
447
|
-
const allContexts = [
|
|
671
|
+
const allContexts = [
|
|
672
|
+
...contexts,
|
|
673
|
+
...autoContexts,
|
|
674
|
+
...(skillsContext ? [skillsContext] : []),
|
|
675
|
+
];
|
|
448
676
|
const contextInfo = allContexts.map((context) => ({
|
|
449
677
|
id: context.id,
|
|
450
678
|
name: context.name,
|
|
451
679
|
description: context.description,
|
|
452
680
|
}));
|
|
453
681
|
// Process plugins for the response
|
|
454
|
-
const processedResponse = await processPluginsForResponse(contextInfo, body, {
|
|
682
|
+
const processedResponse = await processPluginsForResponse(contextInfo, body, {
|
|
683
|
+
extraData: buildServiceExtraData(agentInfo, {
|
|
684
|
+
request: processedPluginData,
|
|
685
|
+
plugins: processedPluginData.plugins,
|
|
686
|
+
}),
|
|
687
|
+
});
|
|
455
688
|
return c.json(processedResponse);
|
|
456
689
|
});
|
|
457
690
|
app.post("/contexts/:contextId", async (c) => {
|
|
458
691
|
const autoContexts = app.oauth2?.getDirectProviderContexts?.() || [];
|
|
459
692
|
const serviceSlug = (0, core_1.toSlug)(metadata.title);
|
|
460
693
|
const skillsContext = config.skills?.getSkillsContext?.(serviceSlug);
|
|
461
|
-
const allContexts = [
|
|
694
|
+
const allContexts = [
|
|
695
|
+
...contexts,
|
|
696
|
+
...autoContexts,
|
|
697
|
+
...(skillsContext ? [skillsContext] : []),
|
|
698
|
+
];
|
|
462
699
|
const context = allContexts.find((ctx) => ctx.id === c.req.param("contextId"));
|
|
463
700
|
if (context) {
|
|
464
701
|
const agentInfo = await getAgentInfo(c);
|
|
465
702
|
const body = await safeParseBody(c);
|
|
466
703
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
467
704
|
const oauth2Client = app.oauth2?.getClient();
|
|
468
|
-
const
|
|
705
|
+
const extraData = buildServiceExtraData(agentInfo, {
|
|
706
|
+
request: processedPluginData,
|
|
469
707
|
plugins: processedPluginData.plugins,
|
|
470
|
-
oauth2Client
|
|
708
|
+
oauth2Client,
|
|
471
709
|
});
|
|
710
|
+
const contextData = await context.getContextData(agentInfo, extraData);
|
|
472
711
|
const response = {
|
|
473
712
|
id: context.id,
|
|
474
713
|
name: context.name,
|
|
475
714
|
description: context.description,
|
|
476
|
-
data: contextData
|
|
715
|
+
data: contextData,
|
|
477
716
|
};
|
|
478
|
-
const processedResponse = await processPluginsForResponse(response, body, { extraData
|
|
717
|
+
const processedResponse = await processPluginsForResponse(response, body, { extraData });
|
|
479
718
|
return c.json(processedResponse);
|
|
480
719
|
}
|
|
481
720
|
else {
|
|
@@ -487,30 +726,42 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
487
726
|
const body = await safeParseBody(c);
|
|
488
727
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
489
728
|
const oauth2Client = app.oauth2?.getClient();
|
|
729
|
+
const extraData = buildServiceExtraData(agentInfo, {
|
|
730
|
+
request: processedPluginData,
|
|
731
|
+
plugins: processedPluginData.plugins,
|
|
732
|
+
oauth2Client,
|
|
733
|
+
});
|
|
490
734
|
const autoContexts = app.oauth2?.getDirectProviderContexts?.() || [];
|
|
491
735
|
const serviceSlug = (0, core_1.toSlug)(metadata.title);
|
|
492
736
|
const skillsContext = config.skills?.getSkillsContext?.(serviceSlug);
|
|
493
|
-
const allContexts = [
|
|
737
|
+
const allContexts = [
|
|
738
|
+
...contexts,
|
|
739
|
+
...autoContexts,
|
|
740
|
+
...(skillsContext ? [skillsContext] : []),
|
|
741
|
+
];
|
|
494
742
|
const contextsFull = await Promise.all(allContexts.map(async (ctx) => ({
|
|
495
743
|
id: ctx.id,
|
|
496
744
|
name: ctx.name,
|
|
497
745
|
description: ctx.description,
|
|
498
|
-
data: await ctx.getContextData(agentInfo,
|
|
499
|
-
plugins: processedPluginData.plugins,
|
|
500
|
-
oauth2Client
|
|
501
|
-
}),
|
|
746
|
+
data: await ctx.getContextData(agentInfo, extraData),
|
|
502
747
|
})));
|
|
503
|
-
const processedResponse = await processPluginsForResponse(contextsFull, body, { extraData
|
|
748
|
+
const processedResponse = await processPluginsForResponse(contextsFull, body, { extraData });
|
|
504
749
|
return c.json(processedResponse);
|
|
505
750
|
});
|
|
506
751
|
app.post("/widgets", async (c) => {
|
|
507
752
|
const agentInfo = await getAgentInfo(c);
|
|
508
753
|
const body = await safeParseBody(c);
|
|
509
754
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
755
|
+
const extraData = buildServiceExtraData(agentInfo, {
|
|
756
|
+
request: processedPluginData,
|
|
757
|
+
plugins: processedPluginData.plugins,
|
|
758
|
+
});
|
|
510
759
|
const widgetIds = config.getUserWidgets
|
|
511
|
-
? await config.getUserWidgets(agentInfo,
|
|
512
|
-
: widgets.map(widget => widget.id);
|
|
513
|
-
const processedResponse = await processPluginsForResponse(widgetIds, body, {
|
|
760
|
+
? await config.getUserWidgets(agentInfo, extraData)
|
|
761
|
+
: widgets.map((widget) => widget.id);
|
|
762
|
+
const processedResponse = await processPluginsForResponse(widgetIds, body, {
|
|
763
|
+
extraData,
|
|
764
|
+
});
|
|
514
765
|
return c.json(processedResponse);
|
|
515
766
|
});
|
|
516
767
|
app.post("/widgets/all", async (c) => {
|
|
@@ -518,18 +769,23 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
518
769
|
const body = await safeParseBody(c);
|
|
519
770
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
520
771
|
const widgetIds = config.getUserWidgets
|
|
521
|
-
? await config.getUserWidgets(agentInfo, {
|
|
522
|
-
|
|
772
|
+
? await config.getUserWidgets(agentInfo, buildServiceExtraData(agentInfo, {
|
|
773
|
+
request: processedPluginData,
|
|
774
|
+
plugins: processedPluginData.plugins,
|
|
775
|
+
}))
|
|
776
|
+
: widgets.map((widget) => widget.id);
|
|
523
777
|
const oauth2Client = app.oauth2?.getClient();
|
|
778
|
+
const extraData = buildServiceExtraData(agentInfo, {
|
|
779
|
+
request: processedPluginData,
|
|
780
|
+
plugins: processedPluginData.plugins,
|
|
781
|
+
oauth2Client,
|
|
782
|
+
app,
|
|
783
|
+
});
|
|
524
784
|
const widgetsFull = await Promise.all(widgetIds.map(async (widgetId) => {
|
|
525
|
-
const widget = widgets.find(w => w.id === widgetId);
|
|
785
|
+
const widget = widgets.find((w) => w.id === widgetId);
|
|
526
786
|
if (!widget)
|
|
527
787
|
return null;
|
|
528
|
-
const widgetData = await widget.getWidget(agentInfo,
|
|
529
|
-
plugins: processedPluginData.plugins,
|
|
530
|
-
oauth2Client,
|
|
531
|
-
app
|
|
532
|
-
});
|
|
788
|
+
const widgetData = await widget.getWidget(agentInfo, extraData);
|
|
533
789
|
const response = {
|
|
534
790
|
id: widget.id,
|
|
535
791
|
name: widget.name,
|
|
@@ -537,7 +793,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
537
793
|
icon: widget.icon,
|
|
538
794
|
size: widget.size || "sm",
|
|
539
795
|
refreshIntervalMs: widget.refreshIntervalMs,
|
|
540
|
-
...widgetData
|
|
796
|
+
...widgetData,
|
|
541
797
|
};
|
|
542
798
|
if (!("freshness" in response)) {
|
|
543
799
|
response.freshness = {
|
|
@@ -551,13 +807,13 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
551
807
|
}
|
|
552
808
|
return response;
|
|
553
809
|
}));
|
|
554
|
-
const validWidgets = widgetsFull.filter(w => w !== null);
|
|
555
|
-
const processedResponse = await processPluginsForResponse(validWidgets, body, { extraData
|
|
810
|
+
const validWidgets = widgetsFull.filter((w) => w !== null);
|
|
811
|
+
const processedResponse = await processPluginsForResponse(validWidgets, body, { extraData });
|
|
556
812
|
return c.json(processedResponse);
|
|
557
813
|
});
|
|
558
814
|
app.post("/widgets/:widgetId", async (c) => {
|
|
559
815
|
const widgetId = c.req.param("widgetId");
|
|
560
|
-
const widget = widgets.find(w => w.id === widgetId);
|
|
816
|
+
const widget = widgets.find((w) => w.id === widgetId);
|
|
561
817
|
if (!widget) {
|
|
562
818
|
throw new http_exception_1.HTTPException(404, { message: "Widget not found" });
|
|
563
819
|
}
|
|
@@ -565,25 +821,34 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
565
821
|
const body = await safeParseBody(c);
|
|
566
822
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
567
823
|
if (config.getUserWidgets) {
|
|
824
|
+
const accessExtraData = buildServiceExtraData(agentInfo, {
|
|
825
|
+
request: processedPluginData,
|
|
826
|
+
plugins: processedPluginData.plugins,
|
|
827
|
+
});
|
|
568
828
|
const userWidgetIds = await config.getUserWidgets(agentInfo, {
|
|
569
|
-
|
|
829
|
+
...accessExtraData,
|
|
570
830
|
});
|
|
571
831
|
let homeUIWidgetId = null;
|
|
572
832
|
if (config.homeUI) {
|
|
573
|
-
homeUIWidgetId =
|
|
574
|
-
|
|
575
|
-
|
|
833
|
+
homeUIWidgetId =
|
|
834
|
+
typeof config.homeUI === "string"
|
|
835
|
+
? config.homeUI
|
|
836
|
+
: await config.homeUI(agentInfo, accessExtraData);
|
|
576
837
|
}
|
|
577
838
|
if (!userWidgetIds.includes(widgetId) && widgetId !== homeUIWidgetId) {
|
|
578
|
-
throw new http_exception_1.HTTPException(403, {
|
|
839
|
+
throw new http_exception_1.HTTPException(403, {
|
|
840
|
+
message: "Access denied to this widget",
|
|
841
|
+
});
|
|
579
842
|
}
|
|
580
843
|
}
|
|
581
844
|
const oauth2Client = app.oauth2?.getClient();
|
|
582
|
-
const
|
|
845
|
+
const extraData = buildServiceExtraData(agentInfo, {
|
|
846
|
+
request: processedPluginData,
|
|
583
847
|
plugins: processedPluginData.plugins,
|
|
584
848
|
oauth2Client,
|
|
585
|
-
app
|
|
849
|
+
app,
|
|
586
850
|
});
|
|
851
|
+
const widgetData = await widget.getWidget(agentInfo, extraData);
|
|
587
852
|
const response = {
|
|
588
853
|
id: widget.id,
|
|
589
854
|
name: widget.name,
|
|
@@ -591,7 +856,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
591
856
|
icon: widget.icon,
|
|
592
857
|
size: widget.size || "sm",
|
|
593
858
|
refreshIntervalMs: widget.refreshIntervalMs,
|
|
594
|
-
...widgetData
|
|
859
|
+
...widgetData,
|
|
595
860
|
};
|
|
596
861
|
if (!("freshness" in response)) {
|
|
597
862
|
response.freshness = {
|
|
@@ -603,7 +868,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
603
868
|
scope: "account",
|
|
604
869
|
};
|
|
605
870
|
}
|
|
606
|
-
const processedResponse = await processPluginsForResponse(response, body, {
|
|
871
|
+
const processedResponse = await processPluginsForResponse(response, body, {
|
|
872
|
+
extraData,
|
|
873
|
+
});
|
|
607
874
|
return c.json(processedResponse);
|
|
608
875
|
});
|
|
609
876
|
app.post("/homeUI", async (c) => {
|
|
@@ -613,17 +880,21 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
613
880
|
const agentInfo = await getAgentInfo(c);
|
|
614
881
|
const body = await safeParseBody(c);
|
|
615
882
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
616
|
-
const
|
|
883
|
+
const extraData = buildServiceExtraData(agentInfo, {
|
|
884
|
+
request: processedPluginData,
|
|
885
|
+
plugins: processedPluginData.plugins,
|
|
886
|
+
});
|
|
887
|
+
const homeUIWidgetId = typeof config.homeUI === "string"
|
|
617
888
|
? config.homeUI
|
|
618
|
-
: await config.homeUI(agentInfo,
|
|
889
|
+
: await config.homeUI(agentInfo, extraData);
|
|
619
890
|
if (!homeUIWidgetId) {
|
|
620
891
|
return c.json({ widgetId: null });
|
|
621
892
|
}
|
|
622
|
-
const widget = widgets.find(w => w.id === homeUIWidgetId);
|
|
893
|
+
const widget = widgets.find((w) => w.id === homeUIWidgetId);
|
|
623
894
|
if (!widget) {
|
|
624
895
|
return c.json({ widgetId: null });
|
|
625
896
|
}
|
|
626
|
-
const processedResponse = await processPluginsForResponse({ widgetId: homeUIWidgetId }, body, { extraData
|
|
897
|
+
const processedResponse = await processPluginsForResponse({ widgetId: homeUIWidgetId }, body, { extraData });
|
|
627
898
|
return c.json(processedResponse);
|
|
628
899
|
});
|
|
629
900
|
function mapDatasourceInfo(datasource) {
|
|
@@ -648,14 +919,24 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
648
919
|
const agentInfo = await getAgentInfo(c);
|
|
649
920
|
const body = await safeParseBody(c);
|
|
650
921
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
651
|
-
const processedResponse = await processPluginsForResponse(datasources.map(mapDatasourceInfo), body, {
|
|
922
|
+
const processedResponse = await processPluginsForResponse(datasources.map(mapDatasourceInfo), body, {
|
|
923
|
+
extraData: buildServiceExtraData(agentInfo, {
|
|
924
|
+
request: processedPluginData,
|
|
925
|
+
plugins: processedPluginData.plugins,
|
|
926
|
+
}),
|
|
927
|
+
});
|
|
652
928
|
return c.json(processedResponse);
|
|
653
929
|
});
|
|
654
930
|
app.post("/datasources/all", async (c) => {
|
|
655
931
|
const agentInfo = await getAgentInfo(c);
|
|
656
932
|
const body = await safeParseBody(c);
|
|
657
933
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
658
|
-
const processedResponse = await processPluginsForResponse(datasources.map(mapDatasourceInfo), body, {
|
|
934
|
+
const processedResponse = await processPluginsForResponse(datasources.map(mapDatasourceInfo), body, {
|
|
935
|
+
extraData: buildServiceExtraData(agentInfo, {
|
|
936
|
+
request: processedPluginData,
|
|
937
|
+
plugins: processedPluginData.plugins,
|
|
938
|
+
}),
|
|
939
|
+
});
|
|
659
940
|
return c.json(processedResponse);
|
|
660
941
|
});
|
|
661
942
|
app.post("/datasources/:datasourceId", async (c) => {
|
|
@@ -671,10 +952,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
671
952
|
try {
|
|
672
953
|
const parsedParams = datasource.input.parse(params);
|
|
673
954
|
const oauth2Client = app.oauth2?.getClient();
|
|
674
|
-
const
|
|
955
|
+
const extraData = buildServiceExtraData(agentInfo, {
|
|
956
|
+
request: params,
|
|
675
957
|
plugins: pluginsData,
|
|
676
|
-
oauth2Client
|
|
958
|
+
oauth2Client,
|
|
677
959
|
});
|
|
960
|
+
const data = await datasource.getDatasource(agentInfo, parsedParams, extraData);
|
|
678
961
|
const requestedPolicy = {
|
|
679
962
|
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
680
963
|
maxStalenessMs: datasource.maxStalenessMs,
|
|
@@ -702,7 +985,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
702
985
|
scope: datasource.scope,
|
|
703
986
|
},
|
|
704
987
|
};
|
|
705
|
-
const processedResponse = await processPluginsForResponse(response, { plugins: pluginsData }, { extraData
|
|
988
|
+
const processedResponse = await processPluginsForResponse(response, { plugins: pluginsData }, { extraData });
|
|
706
989
|
return c.json(processedResponse);
|
|
707
990
|
}
|
|
708
991
|
catch (error) {
|
|
@@ -712,12 +995,23 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
712
995
|
.join(", ");
|
|
713
996
|
return c.json({
|
|
714
997
|
error: `Missing or invalid parameters: ${missingParams}`,
|
|
715
|
-
code: "INVALID_PARAMS"
|
|
998
|
+
code: "INVALID_PARAMS",
|
|
716
999
|
}, 400);
|
|
717
1000
|
}
|
|
718
1001
|
throw error;
|
|
719
1002
|
}
|
|
720
1003
|
});
|
|
1004
|
+
app.delete("/datasources/:datasourceId/stream/:streamId", async (c) => {
|
|
1005
|
+
const datasourceId = c.req.param("datasourceId");
|
|
1006
|
+
const streamId = c.req.param("streamId");
|
|
1007
|
+
const streamKey = `${datasourceId}:${streamId}`;
|
|
1008
|
+
const abortController = datasourceStreamAbortControllers.get(streamKey);
|
|
1009
|
+
if (abortController) {
|
|
1010
|
+
abortController.abort();
|
|
1011
|
+
datasourceStreamAbortControllers.delete(streamKey);
|
|
1012
|
+
}
|
|
1013
|
+
return c.json({ success: true });
|
|
1014
|
+
});
|
|
721
1015
|
// Stream datasource updates over SSE. This is primarily used for "fresh" UIs
|
|
722
1016
|
// (positions/orders) where clients want stream-first updates with a poll fallback.
|
|
723
1017
|
//
|
|
@@ -734,12 +1028,23 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
734
1028
|
typeof rawBody === "object" &&
|
|
735
1029
|
!Array.isArray(rawBody) &&
|
|
736
1030
|
"params" in rawBody;
|
|
737
|
-
const requestedIntervalMsRaw = hasWrappedParams
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
?
|
|
742
|
-
:
|
|
1031
|
+
const requestedIntervalMsRaw = hasWrappedParams
|
|
1032
|
+
? rawBody.intervalMs
|
|
1033
|
+
: undefined;
|
|
1034
|
+
const requestedStreamIdRaw = hasWrappedParams
|
|
1035
|
+
? rawBody.streamId
|
|
1036
|
+
: undefined;
|
|
1037
|
+
const streamId = typeof requestedStreamIdRaw === "string" &&
|
|
1038
|
+
/^[A-Za-z0-9._:-]{1,128}$/.test(requestedStreamIdRaw)
|
|
1039
|
+
? requestedStreamIdRaw
|
|
1040
|
+
: null;
|
|
1041
|
+
const requestParamsRaw = hasWrappedParams
|
|
1042
|
+
? rawBody.params
|
|
1043
|
+
: rawBody;
|
|
1044
|
+
let params = await processPluginsForRequest(requestParamsRaw && typeof requestParamsRaw === "object"
|
|
1045
|
+
? requestParamsRaw
|
|
1046
|
+
: {}, agentInfo);
|
|
1047
|
+
const pluginsData = (params.plugins && typeof params.plugins === "object" ? params.plugins : {});
|
|
743
1048
|
delete params.plugins;
|
|
744
1049
|
let parsedParams;
|
|
745
1050
|
try {
|
|
@@ -752,24 +1057,31 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
752
1057
|
.join(", ");
|
|
753
1058
|
return c.json({
|
|
754
1059
|
error: `Missing or invalid parameters: ${missingParams}`,
|
|
755
|
-
code: "INVALID_PARAMS"
|
|
1060
|
+
code: "INVALID_PARAMS",
|
|
756
1061
|
}, 400);
|
|
757
1062
|
}
|
|
758
1063
|
throw error;
|
|
759
1064
|
}
|
|
1065
|
+
const oauth2Client = app.oauth2?.getClient();
|
|
1066
|
+
const extraData = buildServiceExtraData(agentInfo, {
|
|
1067
|
+
request: params,
|
|
1068
|
+
plugins: pluginsData,
|
|
1069
|
+
oauth2Client,
|
|
1070
|
+
});
|
|
760
1071
|
const pluginRequestContext = hasWrappedParams
|
|
761
1072
|
? {
|
|
762
1073
|
params: parsedParams,
|
|
763
1074
|
intervalMs: requestedIntervalMsRaw,
|
|
764
1075
|
plugins: pluginsData,
|
|
1076
|
+
DAIN_EXTRA_DATA: extraData,
|
|
765
1077
|
}
|
|
766
1078
|
: {
|
|
767
1079
|
...(typeof parsedParams === "object" && parsedParams !== null
|
|
768
1080
|
? parsedParams
|
|
769
1081
|
: {}),
|
|
770
1082
|
plugins: pluginsData,
|
|
1083
|
+
DAIN_EXTRA_DATA: extraData,
|
|
771
1084
|
};
|
|
772
|
-
const oauth2Client = app.oauth2?.getClient();
|
|
773
1085
|
const requestedPolicy = {
|
|
774
1086
|
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
775
1087
|
maxStalenessMs: datasource.maxStalenessMs,
|
|
@@ -778,23 +1090,35 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
778
1090
|
scope: datasource.scope,
|
|
779
1091
|
dataClass: datasource.dataClass,
|
|
780
1092
|
};
|
|
781
|
-
const requestedIntervalMs = typeof requestedIntervalMsRaw === "number" &&
|
|
1093
|
+
const requestedIntervalMs = typeof requestedIntervalMsRaw === "number" &&
|
|
1094
|
+
Number.isFinite(requestedIntervalMsRaw) &&
|
|
1095
|
+
requestedIntervalMsRaw > 0
|
|
782
1096
|
? requestedIntervalMsRaw
|
|
783
1097
|
: null;
|
|
784
1098
|
const baseIntervalMs = requestedIntervalMs ??
|
|
785
|
-
(typeof datasource.refreshIntervalMs === "number" &&
|
|
1099
|
+
(typeof datasource.refreshIntervalMs === "number" &&
|
|
1100
|
+
datasource.refreshIntervalMs > 0
|
|
786
1101
|
? datasource.refreshIntervalMs
|
|
787
1102
|
: 15_000);
|
|
788
1103
|
const intervalMs = Math.max(1_000, Math.floor(baseIntervalMs));
|
|
789
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
790
1104
|
debugLog(`[SSE] Datasource ${datasource.id} stream start (${intervalMs}ms interval)`);
|
|
791
1105
|
return signedStreamSSE(c, privateKey, config, async (stream) => {
|
|
792
1106
|
let eventId = 0;
|
|
1107
|
+
const signal = c.req.raw?.signal;
|
|
1108
|
+
const streamAbortController = new AbortController();
|
|
1109
|
+
const streamKey = streamId ? `${datasource.id}:${streamId}` : null;
|
|
1110
|
+
if (streamKey) {
|
|
1111
|
+
datasourceStreamAbortControllers.set(streamKey, streamAbortController);
|
|
1112
|
+
}
|
|
1113
|
+
const isCancelled = () => stream.isAborted() ||
|
|
1114
|
+
signal?.aborted === true ||
|
|
1115
|
+
streamAbortController.signal.aborted;
|
|
793
1116
|
const emitUpdate = async () => {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1117
|
+
if (isCancelled())
|
|
1118
|
+
return false;
|
|
1119
|
+
const data = await datasource.getDatasource(agentInfo, parsedParams, extraData);
|
|
1120
|
+
if (isCancelled())
|
|
1121
|
+
return false;
|
|
798
1122
|
const response = {
|
|
799
1123
|
id: datasource.id,
|
|
800
1124
|
name: datasource.name,
|
|
@@ -814,51 +1138,81 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
814
1138
|
scope: datasource.scope,
|
|
815
1139
|
},
|
|
816
1140
|
};
|
|
817
|
-
const processedResponse = await processPluginsForResponse(response, pluginRequestContext, { extraData
|
|
1141
|
+
const processedResponse = await processPluginsForResponse(response, pluginRequestContext, { extraData });
|
|
1142
|
+
if (isCancelled())
|
|
1143
|
+
return false;
|
|
818
1144
|
await stream.writeSSE({
|
|
819
1145
|
event: "datasource-update",
|
|
820
1146
|
data: JSON.stringify(processedResponse),
|
|
821
1147
|
id: String(eventId++),
|
|
822
1148
|
});
|
|
1149
|
+
return true;
|
|
823
1150
|
};
|
|
824
|
-
// Send initial snapshot immediately.
|
|
825
1151
|
try {
|
|
826
|
-
|
|
827
|
-
}
|
|
828
|
-
catch (error) {
|
|
829
|
-
console.error(`[SSE] Datasource ${datasource.id} initial update failed:`, error);
|
|
830
|
-
if (!stream.isAborted()) {
|
|
831
|
-
await stream.writeSSE({
|
|
832
|
-
event: "error",
|
|
833
|
-
data: JSON.stringify({ message: error?.message || "Datasource stream error" }),
|
|
834
|
-
});
|
|
835
|
-
}
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
// Continue sending updates until client disconnects.
|
|
839
|
-
// Hono exposes the underlying Request via c.req.raw, which includes an AbortSignal.
|
|
840
|
-
const signal = c.req.raw?.signal;
|
|
841
|
-
while (!stream.isAborted() && !signal?.aborted) {
|
|
842
|
-
await sleep(intervalMs);
|
|
843
|
-
if (stream.isAborted() || signal?.aborted)
|
|
844
|
-
break;
|
|
1152
|
+
// Send initial snapshot immediately.
|
|
845
1153
|
try {
|
|
846
|
-
await emitUpdate();
|
|
1154
|
+
const didEmitInitialUpdate = await emitUpdate();
|
|
1155
|
+
if (!didEmitInitialUpdate)
|
|
1156
|
+
return;
|
|
847
1157
|
}
|
|
848
1158
|
catch (error) {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
console.error(`[SSE] Datasource ${datasource.id} update failed:`, error);
|
|
852
|
-
// Keep the stream alive; clients should rely on poll fallback if needed.
|
|
853
|
-
if (!stream.isAborted()) {
|
|
1159
|
+
console.error(`[SSE] Datasource ${datasource.id} initial update failed:`, error);
|
|
1160
|
+
if (!isCancelled()) {
|
|
854
1161
|
await stream.writeSSE({
|
|
855
1162
|
event: "error",
|
|
856
|
-
data: JSON.stringify({
|
|
1163
|
+
data: JSON.stringify({
|
|
1164
|
+
message: error?.message || "Datasource stream error",
|
|
1165
|
+
}),
|
|
857
1166
|
});
|
|
858
1167
|
}
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
// Continue sending updates until client disconnects.
|
|
1171
|
+
// Hono exposes the underlying Request via c.req.raw, which includes an AbortSignal.
|
|
1172
|
+
const abortPromise = new Promise((resolve) => {
|
|
1173
|
+
if (isCancelled()) {
|
|
1174
|
+
resolve();
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
stream.onAbort(() => resolve());
|
|
1178
|
+
signal?.addEventListener("abort", () => resolve(), { once: true });
|
|
1179
|
+
streamAbortController.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
1180
|
+
});
|
|
1181
|
+
const sleepUntilNextUpdate = (ms) => Promise.race([
|
|
1182
|
+
new Promise((resolve) => setTimeout(resolve, ms)),
|
|
1183
|
+
abortPromise,
|
|
1184
|
+
]);
|
|
1185
|
+
while (!isCancelled()) {
|
|
1186
|
+
await sleepUntilNextUpdate(intervalMs);
|
|
1187
|
+
if (isCancelled())
|
|
1188
|
+
break;
|
|
1189
|
+
try {
|
|
1190
|
+
const didEmitUpdate = await emitUpdate();
|
|
1191
|
+
if (!didEmitUpdate)
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1194
|
+
catch (error) {
|
|
1195
|
+
if (isCancelled())
|
|
1196
|
+
break;
|
|
1197
|
+
console.error(`[SSE] Datasource ${datasource.id} update failed:`, error);
|
|
1198
|
+
// Keep the stream alive; clients should rely on poll fallback if needed.
|
|
1199
|
+
if (!isCancelled()) {
|
|
1200
|
+
await stream.writeSSE({
|
|
1201
|
+
event: "error",
|
|
1202
|
+
data: JSON.stringify({
|
|
1203
|
+
message: error?.message || "Datasource stream error",
|
|
1204
|
+
}),
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
859
1208
|
}
|
|
860
1209
|
}
|
|
861
|
-
|
|
1210
|
+
finally {
|
|
1211
|
+
if (streamKey) {
|
|
1212
|
+
datasourceStreamAbortControllers.delete(streamKey);
|
|
1213
|
+
}
|
|
1214
|
+
debugLog(`[SSE] Datasource ${datasource.id} stream end`);
|
|
1215
|
+
}
|
|
862
1216
|
});
|
|
863
1217
|
});
|
|
864
1218
|
function mapAgentInfo(agent) {
|
|
@@ -868,7 +1222,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
868
1222
|
context: agent.context,
|
|
869
1223
|
prompt: agent.prompt,
|
|
870
1224
|
resolveCondition: agent.resolveCondition,
|
|
871
|
-
serviceConnections: agent.serviceConnections || [
|
|
1225
|
+
serviceConnections: agent.serviceConnections || [
|
|
1226
|
+
process.env.BASE_URL || "http://localhost:3000",
|
|
1227
|
+
],
|
|
872
1228
|
inputSchema: (0, schemaStructure_1.zodToJsonSchema)(agent.input),
|
|
873
1229
|
outputSchema: (0, schemaStructure_1.zodToJsonSchema)(agent.output),
|
|
874
1230
|
};
|
|
@@ -880,7 +1236,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
880
1236
|
const agentInfo = await getAgentInfo(c);
|
|
881
1237
|
const body = await safeParseBody(c);
|
|
882
1238
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
883
|
-
const processedResponse = await processPluginsForResponse(agents.map(mapAgentInfo), body, {
|
|
1239
|
+
const processedResponse = await processPluginsForResponse(agents.map(mapAgentInfo), body, {
|
|
1240
|
+
extraData: buildServiceExtraData(agentInfo, {
|
|
1241
|
+
request: processedPluginData,
|
|
1242
|
+
plugins: processedPluginData.plugins,
|
|
1243
|
+
}),
|
|
1244
|
+
});
|
|
884
1245
|
return c.json(processedResponse);
|
|
885
1246
|
});
|
|
886
1247
|
app.get("/agents/:agentId", (c) => {
|
|
@@ -898,18 +1259,25 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
898
1259
|
const agentInfo = await getAgentInfo(c);
|
|
899
1260
|
const body = await safeParseBody(c);
|
|
900
1261
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
901
|
-
const processedResponse = await processPluginsForResponse(mapAgentInfo(agent), body, {
|
|
1262
|
+
const processedResponse = await processPluginsForResponse(mapAgentInfo(agent), body, {
|
|
1263
|
+
extraData: buildServiceExtraData(agentInfo, {
|
|
1264
|
+
request: processedPluginData,
|
|
1265
|
+
plugins: processedPluginData.plugins,
|
|
1266
|
+
}),
|
|
1267
|
+
});
|
|
902
1268
|
return c.json(processedResponse);
|
|
903
1269
|
});
|
|
904
1270
|
function fixEmptySchemas(schema) {
|
|
905
|
-
if (!schema || typeof schema !==
|
|
1271
|
+
if (!schema || typeof schema !== "object")
|
|
906
1272
|
return;
|
|
907
|
-
if (schema.properties && typeof schema.properties ===
|
|
1273
|
+
if (schema.properties && typeof schema.properties === "object") {
|
|
908
1274
|
const props = schema.properties;
|
|
909
1275
|
for (const key of Object.keys(props)) {
|
|
910
1276
|
const prop = props[key];
|
|
911
|
-
if (prop &&
|
|
912
|
-
|
|
1277
|
+
if (prop &&
|
|
1278
|
+
typeof prop === "object" &&
|
|
1279
|
+
Object.keys(prop).length === 0) {
|
|
1280
|
+
props[key] = { type: "object" };
|
|
913
1281
|
}
|
|
914
1282
|
fixEmptySchemas(prop);
|
|
915
1283
|
}
|
|
@@ -922,10 +1290,10 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
922
1290
|
let inputSchema = (0, schemaStructure_1.zodToJsonSchema)(tool.input);
|
|
923
1291
|
let outputSchema = (0, schemaStructure_1.zodToJsonSchema)(tool.output);
|
|
924
1292
|
if (!inputSchema.type) {
|
|
925
|
-
inputSchema = { ...inputSchema, type:
|
|
1293
|
+
inputSchema = { ...inputSchema, type: "object" };
|
|
926
1294
|
}
|
|
927
1295
|
if (!outputSchema.type) {
|
|
928
|
-
outputSchema = { ...outputSchema, type:
|
|
1296
|
+
outputSchema = { ...outputSchema, type: "object" };
|
|
929
1297
|
}
|
|
930
1298
|
fixEmptySchemas(inputSchema);
|
|
931
1299
|
fixEmptySchemas(outputSchema);
|
|
@@ -971,7 +1339,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
971
1339
|
reccomendedPrompts,
|
|
972
1340
|
}),
|
|
973
1341
|
};
|
|
974
|
-
const processedResponse = await processPluginsForResponse(response, body, {
|
|
1342
|
+
const processedResponse = await processPluginsForResponse(response, body, {
|
|
1343
|
+
extraData: buildServiceExtraData(agentInfo, {
|
|
1344
|
+
request: processedPluginData,
|
|
1345
|
+
plugins: processedPluginData.plugins,
|
|
1346
|
+
}),
|
|
1347
|
+
});
|
|
975
1348
|
return c.json(processedResponse);
|
|
976
1349
|
});
|
|
977
1350
|
// Webhook triggers endpoint
|
|
@@ -987,11 +1360,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
987
1360
|
// Configure trigger forwarding (uses HITL config for automation API)
|
|
988
1361
|
if (config.webhookTriggers) {
|
|
989
1362
|
// Resolve automation API URL (priority: hitl config > env)
|
|
990
|
-
const automationApiUrl = config.hitl?.automationApiUrl ||
|
|
991
|
-
process.env.AUTOMATION_API_URL;
|
|
1363
|
+
const automationApiUrl = config.hitl?.automationApiUrl || process.env.AUTOMATION_API_URL;
|
|
992
1364
|
// Resolve API key
|
|
993
|
-
const automationApiKey = config.hitl?.automationApiKey ||
|
|
994
|
-
process.env.AUTOMATION_API_KEY;
|
|
1365
|
+
const automationApiKey = config.hitl?.automationApiKey || process.env.AUTOMATION_API_KEY;
|
|
995
1366
|
// Resolve service URL (priority: oauth2.baseUrl > env)
|
|
996
1367
|
const serviceUrl = config.oauth2?.baseUrl ||
|
|
997
1368
|
process.env.SERVICE_BASE_URL ||
|
|
@@ -1014,10 +1385,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1014
1385
|
try {
|
|
1015
1386
|
const registerUrl = `${automationApiUrl}/webhooks/register-triggers`;
|
|
1016
1387
|
const response = await fetch(registerUrl, {
|
|
1017
|
-
method:
|
|
1388
|
+
method: "POST",
|
|
1018
1389
|
headers: {
|
|
1019
|
-
|
|
1020
|
-
...(automationApiKey
|
|
1390
|
+
"Content-Type": "application/json",
|
|
1391
|
+
...(automationApiKey
|
|
1392
|
+
? { Authorization: `Bearer ${automationApiKey}` }
|
|
1393
|
+
: {}),
|
|
1021
1394
|
},
|
|
1022
1395
|
body: JSON.stringify({
|
|
1023
1396
|
serviceUrl,
|
|
@@ -1042,10 +1415,10 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1042
1415
|
}
|
|
1043
1416
|
else {
|
|
1044
1417
|
if (!automationApiUrl) {
|
|
1045
|
-
console.warn(
|
|
1418
|
+
console.warn("[Webhook] Trigger forwarding disabled: missing automationApiUrl (set hitl.automationApiUrl or AUTOMATION_API_URL)");
|
|
1046
1419
|
}
|
|
1047
1420
|
if (!serviceUrl) {
|
|
1048
|
-
console.warn(
|
|
1421
|
+
console.warn("[Webhook] Trigger forwarding disabled: missing serviceUrl (set oauth2.baseUrl or SERVICE_BASE_URL)");
|
|
1049
1422
|
}
|
|
1050
1423
|
}
|
|
1051
1424
|
}
|
|
@@ -1070,7 +1443,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1070
1443
|
success: true,
|
|
1071
1444
|
triggerId: result.triggerId,
|
|
1072
1445
|
triggerName: result.triggerName,
|
|
1073
|
-
data: result.data
|
|
1446
|
+
data: result.data,
|
|
1074
1447
|
});
|
|
1075
1448
|
}
|
|
1076
1449
|
else {
|
|
@@ -1078,7 +1451,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1078
1451
|
// No trigger matched - return 200 OK but with success: false
|
|
1079
1452
|
return c.json({
|
|
1080
1453
|
success: false,
|
|
1081
|
-
message:
|
|
1454
|
+
message: "No trigger matched",
|
|
1082
1455
|
}, 200);
|
|
1083
1456
|
}
|
|
1084
1457
|
}
|
|
@@ -1086,22 +1459,22 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1086
1459
|
console.error(`[Webhook] Error processing webhook at ${path}:`, error);
|
|
1087
1460
|
// Return error response
|
|
1088
1461
|
throw new http_exception_1.HTTPException(500, {
|
|
1089
|
-
message: `Webhook processing failed: ${error.message}
|
|
1462
|
+
message: `Webhook processing failed: ${error.message}`,
|
|
1090
1463
|
});
|
|
1091
1464
|
}
|
|
1092
1465
|
});
|
|
1093
1466
|
}
|
|
1094
|
-
console.log(`[Webhook] Registered ${webhookPaths.length} webhook endpoint(s): ${webhookPaths.join(
|
|
1467
|
+
console.log(`[Webhook] Registered ${webhookPaths.length} webhook endpoint(s): ${webhookPaths.join(", ")}`);
|
|
1095
1468
|
}
|
|
1096
1469
|
// Auto-register HITL action webhook
|
|
1097
1470
|
if (config.hitl?.enabled) {
|
|
1098
|
-
const hitlPath = config.hitl.webhookPath ||
|
|
1471
|
+
const hitlPath = config.hitl.webhookPath || "/actions";
|
|
1099
1472
|
const automationApiUrl = config.hitl.automationApiUrl ||
|
|
1100
1473
|
process.env.AUTOMATION_API_URL ||
|
|
1101
|
-
|
|
1474
|
+
"http://localhost:3000";
|
|
1102
1475
|
const automationApiKey = config.hitl.automationApiKey || process.env.AUTOMATION_API_KEY;
|
|
1103
1476
|
if (!automationApiUrl) {
|
|
1104
|
-
console.warn(
|
|
1477
|
+
console.warn("[HITL] Enabled but missing automationApiUrl - HITL webhook will not work correctly");
|
|
1105
1478
|
}
|
|
1106
1479
|
const hitlHandler = new hitl_1.HITLHandler({
|
|
1107
1480
|
automationApiUrl,
|
|
@@ -1112,19 +1485,19 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1112
1485
|
try {
|
|
1113
1486
|
const body = await c.req.json();
|
|
1114
1487
|
const { action, context } = body;
|
|
1115
|
-
if (!action || typeof action !==
|
|
1488
|
+
if (!action || typeof action !== "string") {
|
|
1116
1489
|
throw new http_exception_1.HTTPException(400, {
|
|
1117
1490
|
message: 'Missing or invalid "action" field in request body',
|
|
1118
1491
|
});
|
|
1119
1492
|
}
|
|
1120
|
-
if (!context || typeof context !==
|
|
1493
|
+
if (!context || typeof context !== "object") {
|
|
1121
1494
|
throw new http_exception_1.HTTPException(400, {
|
|
1122
1495
|
message: 'Missing or invalid "context" field in request body',
|
|
1123
1496
|
});
|
|
1124
1497
|
}
|
|
1125
1498
|
if (!hitlHandler.isValidAction(action)) {
|
|
1126
1499
|
throw new http_exception_1.HTTPException(400, {
|
|
1127
|
-
message:
|
|
1500
|
+
message: "Invalid action format. Expected: scope:id:action[:payload]",
|
|
1128
1501
|
});
|
|
1129
1502
|
}
|
|
1130
1503
|
const result = await hitlHandler.handleAction(action, context);
|
|
@@ -1134,7 +1507,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1134
1507
|
if (error instanceof http_exception_1.HTTPException) {
|
|
1135
1508
|
throw error;
|
|
1136
1509
|
}
|
|
1137
|
-
console.error(
|
|
1510
|
+
console.error("[HITL] Error processing action:", error);
|
|
1138
1511
|
throw new http_exception_1.HTTPException(500, {
|
|
1139
1512
|
message: `Action processing failed: ${error.message}`,
|
|
1140
1513
|
});
|
|
@@ -1246,7 +1619,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1246
1619
|
const oauthTool = (0, core_1.createOAuth2Tool)(providerName, serviceSlug, {
|
|
1247
1620
|
logoUrl: providerConfig.logoUrl,
|
|
1248
1621
|
reason: providerConfig.reason,
|
|
1249
|
-
requiredTools: providerConfig.requiredTools
|
|
1622
|
+
requiredTools: providerConfig.requiredTools,
|
|
1250
1623
|
});
|
|
1251
1624
|
autoOAuth2Tools.push(oauthTool);
|
|
1252
1625
|
});
|
|
@@ -1276,11 +1649,17 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1276
1649
|
}
|
|
1277
1650
|
try {
|
|
1278
1651
|
await app.oauth2.handleCallback(code, state);
|
|
1279
|
-
return c.html("<html><body><script>window.opener.postMessage({ type: 'oauth2-success', provider: '" +
|
|
1652
|
+
return c.html("<html><body><script>window.opener.postMessage({ type: 'oauth2-success', provider: '" +
|
|
1653
|
+
provider +
|
|
1654
|
+
"' }, '*');window.close();</script><h1>Authentication successful! You can close this window.</h1></body></html>");
|
|
1280
1655
|
}
|
|
1281
1656
|
catch (error) {
|
|
1282
1657
|
console.error("OAuth callback error:", error);
|
|
1283
|
-
return c.html("<html><body><script>window.opener.postMessage({ type: 'oauth2-error', provider: '" +
|
|
1658
|
+
return c.html("<html><body><script>window.opener.postMessage({ type: 'oauth2-error', provider: '" +
|
|
1659
|
+
provider +
|
|
1660
|
+
"', error: '" +
|
|
1661
|
+
error.message +
|
|
1662
|
+
"' }, '*');window.close();</script><h1>Authentication failed! You can close this window.</h1></body></html>");
|
|
1284
1663
|
}
|
|
1285
1664
|
});
|
|
1286
1665
|
// Make oauth2Handler available to tools
|
|
@@ -1299,7 +1678,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1299
1678
|
}
|
|
1300
1679
|
catch (error) {
|
|
1301
1680
|
if (error.message === "Unauthorized access to process") {
|
|
1302
|
-
throw new http_exception_1.HTTPException(403, {
|
|
1681
|
+
throw new http_exception_1.HTTPException(403, {
|
|
1682
|
+
message: "Unauthorized access to process",
|
|
1683
|
+
});
|
|
1303
1684
|
}
|
|
1304
1685
|
throw error;
|
|
1305
1686
|
}
|
|
@@ -1315,7 +1696,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1315
1696
|
const providersWithStatus = await Promise.all(providers.map(async ({ name, config }) => ({
|
|
1316
1697
|
name,
|
|
1317
1698
|
config,
|
|
1318
|
-
connected: await app.oauth2.hasValidTokens(name, agentInfo.id)
|
|
1699
|
+
connected: await app.oauth2.hasValidTokens(name, agentInfo.id),
|
|
1319
1700
|
})));
|
|
1320
1701
|
return c.json(providersWithStatus);
|
|
1321
1702
|
});
|
|
@@ -1332,7 +1713,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1332
1713
|
app.get("/oauth2/connect/:provider", async (c) => {
|
|
1333
1714
|
debugLog("[OAuth2 Connect] Route hit - START");
|
|
1334
1715
|
debugLog("[OAuth2 Connect] Provider param:", c.req.param("provider"));
|
|
1335
|
-
debugLog("[OAuth2 Connect] Auth method:", c.get(
|
|
1716
|
+
debugLog("[OAuth2 Connect] Auth method:", c.get("authMethod"));
|
|
1336
1717
|
debugLog("[OAuth2 Connect] Headers:", {
|
|
1337
1718
|
authorization: c.req.header("Authorization"),
|
|
1338
1719
|
apiKey: c.req.header("X-DAIN-API-KEY"),
|
|
@@ -1363,7 +1744,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1363
1744
|
catch (error) {
|
|
1364
1745
|
debugLog("[OAuth2 Connect] ERROR generating auth URL:", error);
|
|
1365
1746
|
throw new http_exception_1.HTTPException(400, {
|
|
1366
|
-
message: `Invalid provider: ${provider}
|
|
1747
|
+
message: `Invalid provider: ${provider}`,
|
|
1367
1748
|
});
|
|
1368
1749
|
}
|
|
1369
1750
|
});
|
|
@@ -1381,8 +1762,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1381
1762
|
if (isComplete) {
|
|
1382
1763
|
const config = await setupManager.getProviderConfig(agentInfo.id, provider);
|
|
1383
1764
|
return c.json({
|
|
1384
|
-
status:
|
|
1385
|
-
config: config ? { configuredAt: config.configuredAt } : undefined
|
|
1765
|
+
status: "complete",
|
|
1766
|
+
config: config ? { configuredAt: config.configuredAt } : undefined,
|
|
1386
1767
|
});
|
|
1387
1768
|
}
|
|
1388
1769
|
// Check if there's a pending setup
|
|
@@ -1390,15 +1771,15 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1390
1771
|
if (pendingSetup) {
|
|
1391
1772
|
const expiresIn = Math.max(0, Math.floor((pendingSetup.expiresAt - Date.now()) / 1000));
|
|
1392
1773
|
return c.json({
|
|
1393
|
-
status:
|
|
1774
|
+
status: "authorization_pending",
|
|
1394
1775
|
expires_in: expiresIn,
|
|
1395
|
-
interval: 5 // Poll every 5 seconds
|
|
1776
|
+
interval: 5, // Poll every 5 seconds
|
|
1396
1777
|
});
|
|
1397
1778
|
}
|
|
1398
1779
|
// No setup in progress
|
|
1399
1780
|
return c.json({
|
|
1400
|
-
status:
|
|
1401
|
-
error:
|
|
1781
|
+
status: "not_found",
|
|
1782
|
+
error: "No setup in progress for this provider",
|
|
1402
1783
|
});
|
|
1403
1784
|
});
|
|
1404
1785
|
// Get all human actions for a process
|
|
@@ -1411,13 +1792,15 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1411
1792
|
}
|
|
1412
1793
|
if (process.agentId !== agentInfo.id) {
|
|
1413
1794
|
debugLog("Unauthorized access to process", process.agentId, agentInfo.id);
|
|
1414
|
-
throw new http_exception_1.HTTPException(403, {
|
|
1795
|
+
throw new http_exception_1.HTTPException(403, {
|
|
1796
|
+
message: "Unauthorized access to process",
|
|
1797
|
+
});
|
|
1415
1798
|
}
|
|
1416
1799
|
return c.json(process.humanActions);
|
|
1417
1800
|
});
|
|
1418
1801
|
// Get specific human action
|
|
1419
1802
|
app.get("/processes/:processId/human-actions/:stepId", async (c) => {
|
|
1420
|
-
debugLog(
|
|
1803
|
+
debugLog("GET human action request:", c.req.path);
|
|
1421
1804
|
const processId = c.req.param("processId");
|
|
1422
1805
|
const stepId = c.req.param("stepId");
|
|
1423
1806
|
const agentInfo = await getAgentInfo(c);
|
|
@@ -1427,7 +1810,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1427
1810
|
}
|
|
1428
1811
|
if (process.agentId !== agentInfo.id) {
|
|
1429
1812
|
debugLog("Unauthorized access to process", process.agentId, agentInfo.id);
|
|
1430
|
-
throw new http_exception_1.HTTPException(403, {
|
|
1813
|
+
throw new http_exception_1.HTTPException(403, {
|
|
1814
|
+
message: "Unauthorized access to process",
|
|
1815
|
+
});
|
|
1431
1816
|
}
|
|
1432
1817
|
const step = await app.processes.checkHumanAction(processId, stepId);
|
|
1433
1818
|
if (!step) {
|
|
@@ -1446,7 +1831,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1446
1831
|
}
|
|
1447
1832
|
if (process.agentId !== agentInfo.id) {
|
|
1448
1833
|
debugLog("Unauthorized access to process", process.agentId, agentInfo.id);
|
|
1449
|
-
throw new http_exception_1.HTTPException(403, {
|
|
1834
|
+
throw new http_exception_1.HTTPException(403, {
|
|
1835
|
+
message: "Unauthorized access to process",
|
|
1836
|
+
});
|
|
1450
1837
|
}
|
|
1451
1838
|
const body = await c.req.json();
|
|
1452
1839
|
const { actionId, responseText, data } = body;
|
|
@@ -1477,7 +1864,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1477
1864
|
}
|
|
1478
1865
|
catch (error) {
|
|
1479
1866
|
console.error(`Error generating confirmation UI for tool ${tool.id}:`, error);
|
|
1480
|
-
throw new http_exception_1.HTTPException(500, {
|
|
1867
|
+
throw new http_exception_1.HTTPException(500, {
|
|
1868
|
+
message: "Error generating confirmation UI",
|
|
1869
|
+
});
|
|
1481
1870
|
}
|
|
1482
1871
|
});
|
|
1483
1872
|
app.get("/exampleQueries", (c) => {
|
|
@@ -1487,7 +1876,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1487
1876
|
const agentInfo = await getAgentInfo(c);
|
|
1488
1877
|
const body = await safeParseBody(c);
|
|
1489
1878
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
1490
|
-
const processedResponse = await processPluginsForResponse(config.exampleQueries || [], body, {
|
|
1879
|
+
const processedResponse = await processPluginsForResponse(config.exampleQueries || [], body, {
|
|
1880
|
+
extraData: buildServiceExtraData(agentInfo, {
|
|
1881
|
+
request: processedPluginData,
|
|
1882
|
+
plugins: processedPluginData.plugins,
|
|
1883
|
+
}),
|
|
1884
|
+
});
|
|
1491
1885
|
return c.json(processedResponse);
|
|
1492
1886
|
});
|
|
1493
1887
|
// Services list endpoint
|
|
@@ -1508,7 +1902,10 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1508
1902
|
// Toolboxes list endpoint
|
|
1509
1903
|
app.get("/toolboxes", (c) => c.json(toolboxes));
|
|
1510
1904
|
function normalizeRecommendationText(value) {
|
|
1511
|
-
return value
|
|
1905
|
+
return value
|
|
1906
|
+
.toLowerCase()
|
|
1907
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
1908
|
+
.trim();
|
|
1512
1909
|
}
|
|
1513
1910
|
function tokenizeRecommendationQuery(query) {
|
|
1514
1911
|
const normalized = normalizeRecommendationText(query);
|
|
@@ -1527,7 +1924,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1527
1924
|
throw new Error(`Duplicate recommendation card ID "${scopedCardId}". Card IDs must be unique per tool.`);
|
|
1528
1925
|
}
|
|
1529
1926
|
const isDynamic = !!card.inputSchema;
|
|
1530
|
-
const inputSchema = isDynamic
|
|
1927
|
+
const inputSchema = isDynamic
|
|
1928
|
+
? (0, schemaStructure_1.zodToJsonSchema)(card.inputSchema)
|
|
1929
|
+
: undefined;
|
|
1531
1930
|
const searchText = normalizeRecommendationText([tool.id, tool.name, card.id, ...card.tags].join(" "));
|
|
1532
1931
|
index.set(scopedCardId, {
|
|
1533
1932
|
cardId: scopedCardId,
|
|
@@ -1589,7 +1988,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1589
1988
|
details: parsed.error.format(),
|
|
1590
1989
|
}, 400);
|
|
1591
1990
|
}
|
|
1592
|
-
const { query, limit, toolIds, includeStaticUI
|
|
1991
|
+
const { query, limit, toolIds, includeStaticUI } = parsed.data;
|
|
1593
1992
|
const toolIdSet = toolIds?.length ? new Set(toolIds) : null;
|
|
1594
1993
|
const filteredCards = Array.from(recommendationCardsById.values()).filter((card) => !toolIdSet || toolIdSet.has(card.toolId));
|
|
1595
1994
|
const tokens = tokenizeRecommendationQuery(query ?? "");
|
|
@@ -1625,7 +2024,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1625
2024
|
count: cards.length,
|
|
1626
2025
|
query,
|
|
1627
2026
|
};
|
|
1628
|
-
const processedResponse = await processPluginsForResponse(response, body, {
|
|
2027
|
+
const processedResponse = await processPluginsForResponse(response, body, {
|
|
2028
|
+
extraData: buildServiceExtraData(agentInfo, {
|
|
2029
|
+
request: processedPluginData,
|
|
2030
|
+
plugins: processedPluginData.plugins,
|
|
2031
|
+
}),
|
|
2032
|
+
});
|
|
1629
2033
|
return c.json(processedResponse);
|
|
1630
2034
|
});
|
|
1631
2035
|
const recommendationRenderSchema = zod_1.z.object({
|
|
@@ -1717,7 +2121,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1717
2121
|
renderErrors.push({
|
|
1718
2122
|
cardId: card.cardId,
|
|
1719
2123
|
code: "render_failed",
|
|
1720
|
-
message: error instanceof Error
|
|
2124
|
+
message: error instanceof Error
|
|
2125
|
+
? error.message
|
|
2126
|
+
: "Failed to render recommendation card",
|
|
1721
2127
|
});
|
|
1722
2128
|
if (!continueOnError)
|
|
1723
2129
|
break;
|
|
@@ -1729,7 +2135,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1729
2135
|
renderedCount: renderedCards.length,
|
|
1730
2136
|
requestedCount: requestedCards.length,
|
|
1731
2137
|
};
|
|
1732
|
-
const processedResponse = await processPluginsForResponse(response, body, {
|
|
2138
|
+
const processedResponse = await processPluginsForResponse(response, body, {
|
|
2139
|
+
extraData: buildServiceExtraData(agentInfo, {
|
|
2140
|
+
request: processedPluginData,
|
|
2141
|
+
plugins: processedPluginData.plugins,
|
|
2142
|
+
}),
|
|
2143
|
+
});
|
|
1733
2144
|
const statusCode = !continueOnError && renderErrors.length > 0 ? 400 : 200;
|
|
1734
2145
|
return c.json(processedResponse, statusCode);
|
|
1735
2146
|
});
|
|
@@ -1740,17 +2151,57 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1740
2151
|
if (!processedRequest.plugins) {
|
|
1741
2152
|
processedRequest.plugins = {};
|
|
1742
2153
|
}
|
|
2154
|
+
processedRequest.DAIN_EXTRA_DATA = buildServiceExtraData(agentInfo, {
|
|
2155
|
+
request: processedRequest,
|
|
2156
|
+
plugins: processedRequest.plugins,
|
|
2157
|
+
});
|
|
1743
2158
|
// Auto-inject smartAccountPDA into CryptoPlugin wallet context for automations
|
|
1744
2159
|
// This allows automation wallets to work seamlessly without manual configuration
|
|
1745
2160
|
if (agentInfo.smartAccountPDA) {
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
processedRequest.plugins[
|
|
1750
|
-
|
|
2161
|
+
processedRequest.plugins["crypto-plugin"] =
|
|
2162
|
+
processedRequest.plugins["crypto-plugin"] || {};
|
|
2163
|
+
const cryptoData = isRecord(processedRequest.plugins["crypto-plugin"])
|
|
2164
|
+
? stripRuntimeContextFields(processedRequest.plugins["crypto-plugin"])
|
|
2165
|
+
: {};
|
|
2166
|
+
processedRequest.plugins["crypto-plugin"] = cryptoData;
|
|
2167
|
+
const smartAccountWallet = {
|
|
2168
|
+
chain: "sol",
|
|
2169
|
+
address: agentInfo.smartAccountPDA,
|
|
2170
|
+
accountId: agentInfo.dainAccountId ?? agentInfo.dainGroupId,
|
|
2171
|
+
kind: "smart_account_vault",
|
|
2172
|
+
capabilities: ["solana-smart-account"],
|
|
2173
|
+
};
|
|
2174
|
+
if (Array.isArray(cryptoData.wallets)) {
|
|
2175
|
+
const hasSmartAccountWallet = cryptoData.wallets.some((wallet) => isRecord(wallet) &&
|
|
2176
|
+
wallet.chain === "sol" &&
|
|
2177
|
+
wallet.address === agentInfo.smartAccountPDA);
|
|
2178
|
+
if (!hasSmartAccountWallet) {
|
|
2179
|
+
cryptoData.wallets = [smartAccountWallet, ...cryptoData.wallets];
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
else if (isRecord(cryptoData.wallets)) {
|
|
2183
|
+
cryptoData.wallets = [
|
|
2184
|
+
smartAccountWallet,
|
|
2185
|
+
...Object.entries(cryptoData.wallets).map(([chain, address]) => ({
|
|
2186
|
+
chain,
|
|
2187
|
+
address: String(address),
|
|
2188
|
+
})),
|
|
1751
2189
|
];
|
|
1752
2190
|
}
|
|
2191
|
+
else {
|
|
2192
|
+
cryptoData.wallets = [smartAccountWallet];
|
|
2193
|
+
}
|
|
1753
2194
|
}
|
|
2195
|
+
if (processedRequest.plugins["crypto-plugin"]) {
|
|
2196
|
+
processedRequest.plugins["crypto-plugin"] = {
|
|
2197
|
+
...stripRuntimeContextFields(processedRequest.plugins["crypto-plugin"]),
|
|
2198
|
+
...getRuntimeContextFromAgentInfo(agentInfo),
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
processedRequest.DAIN_EXTRA_DATA = buildServiceExtraData(agentInfo, {
|
|
2202
|
+
request: processedRequest,
|
|
2203
|
+
plugins: processedRequest.plugins,
|
|
2204
|
+
});
|
|
1754
2205
|
// Process plugins if configured
|
|
1755
2206
|
if (!config.plugins || config.plugins.length === 0) {
|
|
1756
2207
|
return processedRequest;
|
|
@@ -1760,6 +2211,10 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1760
2211
|
processedRequest = await plugin.processInputService(processedRequest);
|
|
1761
2212
|
}
|
|
1762
2213
|
}
|
|
2214
|
+
processedRequest.DAIN_EXTRA_DATA = buildServiceExtraData(agentInfo, {
|
|
2215
|
+
request: processedRequest,
|
|
2216
|
+
plugins: processedRequest.plugins,
|
|
2217
|
+
});
|
|
1763
2218
|
return processedRequest;
|
|
1764
2219
|
}
|
|
1765
2220
|
// Process response with plugins
|
|
@@ -1769,6 +2224,11 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1769
2224
|
}
|
|
1770
2225
|
let processedResponse = { ...response };
|
|
1771
2226
|
processedResponse.plugins = processedResponse.plugins || {};
|
|
2227
|
+
const extraData = isRecord(context?.extraData)
|
|
2228
|
+
? context.extraData
|
|
2229
|
+
: isRecord(context)
|
|
2230
|
+
? context
|
|
2231
|
+
: {};
|
|
1772
2232
|
for (const plugin of config.plugins) {
|
|
1773
2233
|
if (plugin.processOutputService) {
|
|
1774
2234
|
// Provide context to the plugin
|
|
@@ -1776,8 +2236,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1776
2236
|
...processedResponse,
|
|
1777
2237
|
context: {
|
|
1778
2238
|
request,
|
|
1779
|
-
extraData
|
|
1780
|
-
}
|
|
2239
|
+
extraData,
|
|
2240
|
+
},
|
|
1781
2241
|
});
|
|
1782
2242
|
if (pluginOutput) {
|
|
1783
2243
|
processedResponse.plugins[plugin.id] = pluginOutput;
|
|
@@ -1787,6 +2247,31 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1787
2247
|
return processedResponse;
|
|
1788
2248
|
}
|
|
1789
2249
|
// Automatically create routes for each tool
|
|
2250
|
+
function buildToolContext(agentInfo, request, pluginsData, hooks = {}) {
|
|
2251
|
+
const oauth2Client = app.oauth2 ? app.oauth2.getClient() : undefined;
|
|
2252
|
+
const extraData = buildServiceExtraData(agentInfo, {
|
|
2253
|
+
request,
|
|
2254
|
+
plugins: pluginsData,
|
|
2255
|
+
oauth2Client,
|
|
2256
|
+
app,
|
|
2257
|
+
});
|
|
2258
|
+
const runtimeContext = getRuntimeContextFromAgentInfo(agentInfo);
|
|
2259
|
+
return {
|
|
2260
|
+
app,
|
|
2261
|
+
oauth2Client,
|
|
2262
|
+
extraData,
|
|
2263
|
+
dainAccountId: runtimeContext.dainAccountId ?? extraData.dainAccountId,
|
|
2264
|
+
dainGroupId: runtimeContext.dainGroupId ?? extraData.dainGroupId,
|
|
2265
|
+
smartAccountPDA: runtimeContext.smartAccountPDA ?? extraData.smartAccountPDA,
|
|
2266
|
+
account: runtimeContext.account ?? extraData.account,
|
|
2267
|
+
group: runtimeContext.group ?? extraData.group,
|
|
2268
|
+
auth: runtimeContext.auth ?? extraData.auth,
|
|
2269
|
+
grants: runtimeContext.grants ?? extraData.grants,
|
|
2270
|
+
dataPermissions: runtimeContext.dataPermissions ?? extraData.dataPermissions,
|
|
2271
|
+
registryPolicy: runtimeContext.registryPolicy ?? extraData.registryPolicy,
|
|
2272
|
+
...hooks,
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
1790
2275
|
tools.forEach((tool) => {
|
|
1791
2276
|
/**
|
|
1792
2277
|
* Shared handler for tool execution with streaming support
|
|
@@ -1800,27 +2285,25 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1800
2285
|
return signedStreamSSE(c, privateKey, config, async (stream) => {
|
|
1801
2286
|
try {
|
|
1802
2287
|
// Execute the tool first
|
|
1803
|
-
debugLog(`[SSE] Executing tool ${tool.id} in streaming mode${withContext ?
|
|
1804
|
-
const
|
|
1805
|
-
|
|
1806
|
-
oauth2Client: app.oauth2 ? app.oauth2.getClient() : undefined,
|
|
1807
|
-
extraData: {
|
|
1808
|
-
...body.DAIN_EXTRA_DATA,
|
|
1809
|
-
plugins: pluginsData
|
|
1810
|
-
},
|
|
2288
|
+
debugLog(`[SSE] Executing tool ${tool.id} in streaming mode${withContext ? " with context" : ""}`);
|
|
2289
|
+
const { DAIN_EXTRA_DATA: _dainExtraData, ...toolInput } = body;
|
|
2290
|
+
const result = await tool.handler(toolInput, agentInfo, buildToolContext(agentInfo, body, pluginsData, {
|
|
1811
2291
|
updateUI: async (update) => {
|
|
1812
2292
|
try {
|
|
1813
2293
|
// Check if this is a progress update
|
|
1814
|
-
if (update.type ===
|
|
2294
|
+
if (update.type === "progress") {
|
|
1815
2295
|
await stream.writeSSE({
|
|
1816
|
-
event:
|
|
1817
|
-
data: JSON.stringify({
|
|
2296
|
+
event: "progress",
|
|
2297
|
+
data: JSON.stringify({
|
|
2298
|
+
text: update.text,
|
|
2299
|
+
data: update.data,
|
|
2300
|
+
}),
|
|
1818
2301
|
});
|
|
1819
2302
|
}
|
|
1820
2303
|
else {
|
|
1821
2304
|
// Legacy UI page updates for Canvas rendering
|
|
1822
2305
|
await stream.writeSSE({
|
|
1823
|
-
event:
|
|
2306
|
+
event: "uipage-update",
|
|
1824
2307
|
data: JSON.stringify(update),
|
|
1825
2308
|
});
|
|
1826
2309
|
}
|
|
@@ -1832,7 +2315,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1832
2315
|
addProcess: async (processId) => {
|
|
1833
2316
|
try {
|
|
1834
2317
|
await stream.writeSSE({
|
|
1835
|
-
event:
|
|
2318
|
+
event: "process-created",
|
|
1836
2319
|
data: JSON.stringify({ processId }),
|
|
1837
2320
|
id: Date.now().toString(),
|
|
1838
2321
|
});
|
|
@@ -1840,8 +2323,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1840
2323
|
catch (error) {
|
|
1841
2324
|
console.error(`Error sending process update in ${tool.id}:`, error);
|
|
1842
2325
|
}
|
|
1843
|
-
}
|
|
1844
|
-
});
|
|
2326
|
+
},
|
|
2327
|
+
}));
|
|
1845
2328
|
// If we need to include context data
|
|
1846
2329
|
let response = result;
|
|
1847
2330
|
if (withContext) {
|
|
@@ -1856,8 +2339,13 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1856
2339
|
name: context.name,
|
|
1857
2340
|
description: context.description,
|
|
1858
2341
|
data: await context.getContextData(agentInfo, {
|
|
1859
|
-
|
|
1860
|
-
|
|
2342
|
+
...buildServiceExtraData(agentInfo, {
|
|
2343
|
+
request: body,
|
|
2344
|
+
plugins: pluginsData,
|
|
2345
|
+
oauth2Client: app.oauth2
|
|
2346
|
+
? app.oauth2.getClient()
|
|
2347
|
+
: undefined,
|
|
2348
|
+
}),
|
|
1861
2349
|
}),
|
|
1862
2350
|
};
|
|
1863
2351
|
}
|
|
@@ -1875,11 +2363,11 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1875
2363
|
// Use Promise.allSettled to continue even if some contexts fail
|
|
1876
2364
|
const settledContexts = await Promise.allSettled(contextPromises);
|
|
1877
2365
|
contextsNow = settledContexts
|
|
1878
|
-
.filter(result => result.status ===
|
|
1879
|
-
.map(result => result.value);
|
|
1880
|
-
// Log any rejected promises
|
|
2366
|
+
.filter((result) => result.status === "fulfilled")
|
|
2367
|
+
.map((result) => result.value);
|
|
2368
|
+
// Log any rejected promises
|
|
1881
2369
|
settledContexts
|
|
1882
|
-
.filter(result => result.status ===
|
|
2370
|
+
.filter((result) => result.status === "rejected")
|
|
1883
2371
|
.forEach((result, index) => {
|
|
1884
2372
|
console.error(`Context at index ${index} failed:`, result.reason);
|
|
1885
2373
|
});
|
|
@@ -1900,12 +2388,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1900
2388
|
const processedResult = await processPluginsForResponse(response, body, {
|
|
1901
2389
|
extraData: {
|
|
1902
2390
|
...body.DAIN_EXTRA_DATA,
|
|
1903
|
-
plugins: pluginsData
|
|
1904
|
-
}
|
|
2391
|
+
plugins: pluginsData,
|
|
2392
|
+
},
|
|
1905
2393
|
});
|
|
1906
2394
|
// Send the final result
|
|
1907
2395
|
await stream.writeSSE({
|
|
1908
|
-
event:
|
|
2396
|
+
event: "result",
|
|
1909
2397
|
data: JSON.stringify(processedResult),
|
|
1910
2398
|
id: Date.now().toString(),
|
|
1911
2399
|
});
|
|
@@ -1917,13 +2405,13 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1917
2405
|
try {
|
|
1918
2406
|
if (withContext) {
|
|
1919
2407
|
await stream.writeSSE({
|
|
1920
|
-
event:
|
|
2408
|
+
event: "result",
|
|
1921
2409
|
data: JSON.stringify({
|
|
1922
2410
|
toolResult: {
|
|
1923
2411
|
error: safeMsg,
|
|
1924
2412
|
text: `Error: ${safeMsg}`,
|
|
1925
2413
|
data: null,
|
|
1926
|
-
ui: null
|
|
2414
|
+
ui: null,
|
|
1927
2415
|
},
|
|
1928
2416
|
context: [],
|
|
1929
2417
|
}),
|
|
@@ -1932,12 +2420,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1932
2420
|
}
|
|
1933
2421
|
else {
|
|
1934
2422
|
await stream.writeSSE({
|
|
1935
|
-
event:
|
|
2423
|
+
event: "result",
|
|
1936
2424
|
data: JSON.stringify({
|
|
1937
2425
|
error: safeMsg,
|
|
1938
2426
|
text: `Error: ${safeMsg}`,
|
|
1939
2427
|
data: null,
|
|
1940
|
-
ui: null
|
|
2428
|
+
ui: null,
|
|
1941
2429
|
}),
|
|
1942
2430
|
id: Date.now().toString(),
|
|
1943
2431
|
});
|
|
@@ -1957,17 +2445,15 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1957
2445
|
const uiUpdates = [];
|
|
1958
2446
|
const progressUpdates = [];
|
|
1959
2447
|
const processes = [];
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
oauth2Client: app.oauth2 ? app.oauth2.getClient() : undefined,
|
|
1963
|
-
extraData: {
|
|
1964
|
-
...body.DAIN_EXTRA_DATA,
|
|
1965
|
-
plugins: pluginsData
|
|
1966
|
-
},
|
|
2448
|
+
const { DAIN_EXTRA_DATA: _dainExtraData, ...toolInput } = body;
|
|
2449
|
+
const result = await tool.handler(toolInput, agentInfo, buildToolContext(agentInfo, body, pluginsData, {
|
|
1967
2450
|
updateUI: (update) => {
|
|
1968
2451
|
// Collect UI updates instead of streaming them (synchronous for performance)
|
|
1969
|
-
if (update.type ===
|
|
1970
|
-
progressUpdates.push({
|
|
2452
|
+
if (update.type === "progress") {
|
|
2453
|
+
progressUpdates.push({
|
|
2454
|
+
text: update.text,
|
|
2455
|
+
data: update.data,
|
|
2456
|
+
});
|
|
1971
2457
|
}
|
|
1972
2458
|
else {
|
|
1973
2459
|
uiUpdates.push(update);
|
|
@@ -1978,8 +2464,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1978
2464
|
// Collect process IDs (synchronous for performance)
|
|
1979
2465
|
processes.push(processId);
|
|
1980
2466
|
return Promise.resolve();
|
|
1981
|
-
}
|
|
1982
|
-
});
|
|
2467
|
+
},
|
|
2468
|
+
}));
|
|
1983
2469
|
// If we need to include context data
|
|
1984
2470
|
let response = result;
|
|
1985
2471
|
if (withContext) {
|
|
@@ -1990,8 +2476,13 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1990
2476
|
name: context.name,
|
|
1991
2477
|
description: context.description,
|
|
1992
2478
|
data: await context.getContextData(agentInfo, {
|
|
1993
|
-
|
|
1994
|
-
|
|
2479
|
+
...buildServiceExtraData(agentInfo, {
|
|
2480
|
+
request: body,
|
|
2481
|
+
plugins: pluginsData,
|
|
2482
|
+
oauth2Client: app.oauth2
|
|
2483
|
+
? app.oauth2.getClient()
|
|
2484
|
+
: undefined,
|
|
2485
|
+
}),
|
|
1995
2486
|
}),
|
|
1996
2487
|
})));
|
|
1997
2488
|
// Create the complete response with context
|
|
@@ -2013,15 +2504,15 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
2013
2504
|
const processedResult = await processPluginsForResponse(response, body, {
|
|
2014
2505
|
extraData: {
|
|
2015
2506
|
...body.DAIN_EXTRA_DATA,
|
|
2016
|
-
plugins: pluginsData
|
|
2017
|
-
}
|
|
2507
|
+
plugins: pluginsData,
|
|
2508
|
+
},
|
|
2018
2509
|
});
|
|
2019
2510
|
// Add collected updates to response for non-streaming clients
|
|
2020
2511
|
const finalResponse = {
|
|
2021
2512
|
...processedResult,
|
|
2022
2513
|
_updates: uiUpdates.length > 0 ? uiUpdates : undefined,
|
|
2023
2514
|
_progress: progressUpdates.length > 0 ? progressUpdates : undefined,
|
|
2024
|
-
_processes: processes.length > 0 ? processes : undefined
|
|
2515
|
+
_processes: processes.length > 0 ? processes : undefined,
|
|
2025
2516
|
};
|
|
2026
2517
|
return c.json(finalResponse);
|
|
2027
2518
|
}
|
|
@@ -2036,7 +2527,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
2036
2527
|
error: safeMsg,
|
|
2037
2528
|
text: `Error: ${safeMsg}`,
|
|
2038
2529
|
data: null,
|
|
2039
|
-
ui: null
|
|
2530
|
+
ui: null,
|
|
2040
2531
|
},
|
|
2041
2532
|
context: [],
|
|
2042
2533
|
};
|
|
@@ -2046,7 +2537,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
2046
2537
|
error: safeMsg,
|
|
2047
2538
|
text: `Error: ${safeMsg}`,
|
|
2048
2539
|
data: null,
|
|
2049
|
-
ui: null
|
|
2540
|
+
ui: null,
|
|
2050
2541
|
};
|
|
2051
2542
|
}
|
|
2052
2543
|
// We return status 200 with error details in the body
|
|
@@ -2060,7 +2551,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
2060
2551
|
debugLog(`[Tool ${tool.id}] ========== TOOL EXECUTION START ==========`);
|
|
2061
2552
|
debugLog(`[Tool ${tool.id}] Path: ${c.req.path}`);
|
|
2062
2553
|
debugLog(`[Tool ${tool.id}] Method: ${c.req.method}`);
|
|
2063
|
-
debugLog(`[Tool ${tool.id}] Auth Method: ${c.get(
|
|
2554
|
+
debugLog(`[Tool ${tool.id}] Auth Method: ${c.get("authMethod")}`);
|
|
2064
2555
|
debugLog(`[Tool ${tool.id}] Headers:`, {
|
|
2065
2556
|
authorization: c.req.header("Authorization") ? "Bearer ***" : undefined,
|
|
2066
2557
|
apiKey: c.req.header("X-DAIN-API-KEY") ? "***" : undefined,
|
|
@@ -2097,7 +2588,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
2097
2588
|
debugLog(`[Tool ${tool.id} +Context] ========== TOOL EXECUTION START (WITH CONTEXT) ==========`);
|
|
2098
2589
|
debugLog(`[Tool ${tool.id} +Context] Path: ${c.req.path}`);
|
|
2099
2590
|
debugLog(`[Tool ${tool.id} +Context] Method: ${c.req.method}`);
|
|
2100
|
-
debugLog(`[Tool ${tool.id} +Context] Auth Method: ${c.get(
|
|
2591
|
+
debugLog(`[Tool ${tool.id} +Context] Auth Method: ${c.get("authMethod")}`);
|
|
2101
2592
|
debugLog(`[Tool ${tool.id} +Context] Headers:`, {
|
|
2102
2593
|
authorization: c.req.header("Authorization") ? "Bearer ***" : undefined,
|
|
2103
2594
|
apiKey: c.req.header("X-DAIN-API-KEY") ? "***" : undefined,
|
|
@@ -2130,7 +2621,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
2130
2621
|
console.error(`[Tool ${tool.id} +Context] Stack:`, error?.stack);
|
|
2131
2622
|
throw new http_exception_1.HTTPException(500, {
|
|
2132
2623
|
message: error?.message || "Internal server error",
|
|
2133
|
-
cause: error
|
|
2624
|
+
cause: error,
|
|
2134
2625
|
});
|
|
2135
2626
|
}
|
|
2136
2627
|
});
|