@dainprotocol/service-sdk 2.0.93 → 2.0.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/client/api-sdk.d.ts +7 -1
  2. package/dist/client/api-sdk.js +2 -2
  3. package/dist/client/api-sdk.js.map +1 -1
  4. package/dist/client/client-auth.d.ts +38 -0
  5. package/dist/client/client-auth.js +131 -2
  6. package/dist/client/client-auth.js.map +1 -1
  7. package/dist/client/client.d.ts +1 -0
  8. package/dist/client/client.js +92 -25
  9. package/dist/client/client.js.map +1 -1
  10. package/dist/client/index.d.ts +1 -0
  11. package/dist/client/index.js +1 -0
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +3 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/lib/schemaConversion.js +67 -1
  17. package/dist/lib/schemaConversion.js.map +1 -1
  18. package/dist/lib/schemaStructure.js +134 -17
  19. package/dist/lib/schemaStructure.js.map +1 -1
  20. package/dist/plugins/crypto-plugin.d.ts +26 -0
  21. package/dist/plugins/crypto-plugin.js +44 -4
  22. package/dist/plugins/crypto-plugin.js.map +1 -1
  23. package/dist/plugins/index.d.ts +1 -0
  24. package/dist/plugins/index.js +1 -0
  25. package/dist/plugins/index.js.map +1 -1
  26. package/dist/protocol/account.d.ts +91 -0
  27. package/dist/protocol/account.js +3 -0
  28. package/dist/protocol/account.js.map +1 -0
  29. package/dist/protocol/grants.d.ts +22 -0
  30. package/dist/protocol/grants.js +3 -0
  31. package/dist/protocol/grants.js.map +1 -0
  32. package/dist/protocol/index.d.ts +8 -0
  33. package/dist/protocol/index.js +12 -0
  34. package/dist/protocol/index.js.map +1 -0
  35. package/dist/protocol/payments.d.ts +53 -0
  36. package/dist/protocol/payments.js +3 -0
  37. package/dist/protocol/payments.js.map +1 -0
  38. package/dist/protocol/permissions.d.ts +21 -0
  39. package/dist/protocol/permissions.js +3 -0
  40. package/dist/protocol/permissions.js.map +1 -0
  41. package/dist/protocol/programs.d.ts +44 -0
  42. package/dist/protocol/programs.js +46 -0
  43. package/dist/protocol/programs.js.map +1 -0
  44. package/dist/protocol/registry.d.ts +41 -0
  45. package/dist/protocol/registry.js +3 -0
  46. package/dist/protocol/registry.js.map +1 -0
  47. package/dist/protocol/runtime.d.ts +15 -0
  48. package/dist/protocol/runtime.js +3 -0
  49. package/dist/protocol/runtime.js.map +1 -0
  50. package/dist/protocol/transactions.d.ts +63 -0
  51. package/dist/protocol/transactions.js +3 -0
  52. package/dist/protocol/transactions.js.map +1 -0
  53. package/dist/service/core.js +9 -0
  54. package/dist/service/core.js.map +1 -1
  55. package/dist/service/index.d.ts +1 -0
  56. package/dist/service/index.js +1 -0
  57. package/dist/service/index.js.map +1 -1
  58. package/dist/service/nodeService.js +30 -2
  59. package/dist/service/nodeService.js.map +1 -1
  60. package/dist/service/server.js +470 -102
  61. package/dist/service/server.js.map +1 -1
  62. package/dist/service/types.d.ts +38 -25
  63. package/package.json +20 -7
@@ -98,6 +98,123 @@ 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") ??
146
+ account?.id;
147
+ const dainGroupId = getStringClaim(payload, "dain_group_id", "group_id") ??
148
+ group?.id;
149
+ const smartAccountPDA = getStringClaim(payload, "smart_account_pda") ??
150
+ options.fallbackSmartAccountPDA;
151
+ if (account?.id && dainAccountId && account.id !== dainAccountId) {
152
+ throw new http_exception_1.HTTPException(401, { message: "JWT DAIN context mismatch: dain_account_id does not match account_context.id" });
153
+ }
154
+ if (group?.id && dainGroupId && group.id !== dainGroupId) {
155
+ throw new http_exception_1.HTTPException(401, { message: "JWT DAIN context mismatch: dain_group_id does not match group_context.id" });
156
+ }
157
+ if (account?.smartAccount?.pda && smartAccountPDA && account.smartAccount.pda !== smartAccountPDA) {
158
+ throw new http_exception_1.HTTPException(401, { message: "JWT DAIN context mismatch: smart_account_pda does not match account_context.smartAccount.pda" });
159
+ }
160
+ const grants = getArrayClaim(payload, "action_grants");
161
+ const dataPermissions = getArrayClaim(payload, "data_permissions");
162
+ const registryPolicy = getObjectClaim(payload, "registry_policy");
163
+ const subject = getStringClaim(payload, "sub") ??
164
+ options.smartAccountId ??
165
+ dainAccountId ??
166
+ dainGroupId ??
167
+ smartAccountPDA;
168
+ return {
169
+ ...(dainAccountId ? { dainAccountId } : {}),
170
+ ...(dainGroupId ? { dainGroupId } : {}),
171
+ ...(smartAccountPDA ? { smartAccountPDA } : {}),
172
+ ...(account ? { account } : {}),
173
+ ...(group ? { group } : {}),
174
+ auth: auth ?? {
175
+ subject: subject ?? "unknown",
176
+ issuer: getStringClaim(payload, "iss"),
177
+ scopes: options.scopes,
178
+ source: "dain_id",
179
+ },
180
+ ...(grants ? { grants } : {}),
181
+ ...(dataPermissions ? { dataPermissions } : {}),
182
+ ...(registryPolicy ? { registryPolicy } : {}),
183
+ };
184
+ }
185
+ function getRuntimeContextFromAgentInfo(agentInfo) {
186
+ return {
187
+ ...(agentInfo.dainAccountId ? { dainAccountId: agentInfo.dainAccountId } : {}),
188
+ ...(agentInfo.dainGroupId ? { dainGroupId: agentInfo.dainGroupId } : {}),
189
+ ...(agentInfo.smartAccountPDA ? { smartAccountPDA: agentInfo.smartAccountPDA } : {}),
190
+ ...(agentInfo.account ? { account: agentInfo.account } : {}),
191
+ ...(agentInfo.group ? { group: agentInfo.group } : {}),
192
+ ...(agentInfo.auth ? { auth: agentInfo.auth } : {}),
193
+ ...(agentInfo.grants ? { grants: agentInfo.grants } : {}),
194
+ ...(agentInfo.dataPermissions ? { dataPermissions: agentInfo.dataPermissions } : {}),
195
+ ...(agentInfo.registryPolicy ? { registryPolicy: agentInfo.registryPolicy } : {}),
196
+ };
197
+ }
198
+ function getRequestExtraData(request) {
199
+ if (!isRecord(request))
200
+ return {};
201
+ return isRecord(request.DAIN_EXTRA_DATA)
202
+ ? stripRuntimeContextFields(request.DAIN_EXTRA_DATA)
203
+ : {};
204
+ }
205
+ function buildServiceExtraData(agentInfo, options = {}) {
206
+ const requestExtra = getRequestExtraData(options.request);
207
+ const runtimeContext = getRuntimeContextFromAgentInfo(agentInfo);
208
+ return {
209
+ ...requestExtra,
210
+ ...(options.extra ?? {}),
211
+ ...runtimeContext,
212
+ ...(options.plugins ? { plugins: options.plugins } : {}),
213
+ ...(Object.prototype.hasOwnProperty.call(options, "oauth2Client")
214
+ ? { oauth2Client: options.oauth2Client }
215
+ : {}),
216
+ };
217
+ }
101
218
  /**
102
219
  * Middleware factory to require specific OAuth scopes.
103
220
  * Defense-in-depth: Validates scopes even though JWT middleware already checked them.
@@ -120,6 +237,17 @@ function signedStreamSSE(c, privateKey, config, handler) {
120
237
  return (0, streaming_1.streamSSE)(c, async (stream) => {
121
238
  const requestSignal = c.req.raw?.signal;
122
239
  const isAborted = () => stream.aborted || stream.closed || requestSignal?.aborted === true;
240
+ const abortStream = () => {
241
+ if (!stream.closed) {
242
+ stream.abort();
243
+ }
244
+ };
245
+ if (requestSignal?.aborted) {
246
+ abortStream();
247
+ }
248
+ else {
249
+ requestSignal?.addEventListener("abort", abortStream, { once: true });
250
+ }
123
251
  const signedStream = {
124
252
  writeSSE: async (event) => {
125
253
  if (isAborted())
@@ -140,12 +268,19 @@ function signedStreamSSE(c, privateKey, config, handler) {
140
268
  await stream.writeSSE({ event: event.event, data: dataWithSignature, id: event.id });
141
269
  },
142
270
  isAborted,
271
+ onAbort: (listener) => stream.onAbort(listener),
143
272
  };
144
- await handler(signedStream);
273
+ try {
274
+ await handler(signedStream);
275
+ }
276
+ finally {
277
+ requestSignal?.removeEventListener("abort", abortStream);
278
+ }
145
279
  });
146
280
  }
147
281
  function setupHttpServer(config, tools, services, toolboxes, metadata, privateKey, contexts, widgets, datasources = [], agents = []) {
148
282
  const app = new hono_1.Hono();
283
+ const datasourceStreamAbortControllers = new Map();
149
284
  const processHandler = new processes_1.ProcessHandler({
150
285
  serviceId: "service_" + config.identity.orgId + "_" + config.identity.agentId,
151
286
  privateKey,
@@ -160,6 +295,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
160
295
  allowHeaders: [
161
296
  "X-DAIN-SIGNATURE",
162
297
  "X-DAIN-SMART-ACCOUNT-PDA",
298
+ "X-DAIN-ACCOUNT-ID",
299
+ "X-DAIN-GROUP-ID",
163
300
  "X-DAIN-AGENT-ID",
164
301
  "X-DAIN-ORG-ID",
165
302
  "X-DAIN-ADDRESS",
@@ -177,7 +314,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
177
314
  return c.text("Enabling CORS Pre-Flight", 200, {
178
315
  "Access-Control-Allow-Origin": "*",
179
316
  "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",
317
+ "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
318
  "Access-Control-Max-Age": "86400",
182
319
  });
183
320
  });
@@ -277,7 +414,13 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
277
414
  c.set('address', result.smartAccountId); // Use smartAccountId as address for JWT auth
278
415
  c.set('scope', result.scope);
279
416
  c.set('jwtPayload', result.payload);
280
- c.set('smartAccountPDA', c.req.header("X-DAIN-SMART-ACCOUNT-PDA"));
417
+ const jwtRuntimeContext = getRuntimeContextFromJwtPayload(result.payload, {
418
+ scopes: result.scope,
419
+ smartAccountId: result.smartAccountId,
420
+ fallbackSmartAccountPDA: c.req.header("X-DAIN-SMART-ACCOUNT-PDA"),
421
+ });
422
+ c.set('dainRuntimeContext', jwtRuntimeContext);
423
+ c.set('smartAccountPDA', jwtRuntimeContext.smartAccountPDA);
281
424
  debugLog(`[Auth Middleware] JWT auth SUCCESS - smartAccountId: ${result.smartAccountId}`);
282
425
  }
283
426
  else if (apiKey) {
@@ -300,6 +443,11 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
300
443
  c.set('orgId', parsed.orgId);
301
444
  c.set('address', parsed.agentId); // Use agentId as address for API key auth
302
445
  c.set('smartAccountPDA', c.req.header("X-DAIN-SMART-ACCOUNT-PDA"));
446
+ c.set('dainRuntimeContext', {
447
+ ...(c.req.header("X-DAIN-ACCOUNT-ID") ? { dainAccountId: c.req.header("X-DAIN-ACCOUNT-ID") } : {}),
448
+ ...(c.req.header("X-DAIN-GROUP-ID") ? { dainGroupId: c.req.header("X-DAIN-GROUP-ID") } : {}),
449
+ ...(c.req.header("X-DAIN-SMART-ACCOUNT-PDA") ? { smartAccountPDA: c.req.header("X-DAIN-SMART-ACCOUNT-PDA") } : {}),
450
+ });
303
451
  debugLog(`[Auth Middleware] API Key auth SUCCESS - agentId: ${parsed.agentId}, orgId: ${parsed.orgId}`);
304
452
  }
305
453
  else {
@@ -311,7 +459,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
311
459
  async function getAgentInfo(c) {
312
460
  debugLog("[getAgentInfo] START");
313
461
  debugLog("[getAgentInfo] Auth method:", c.get('authMethod'));
314
- const smartAccountPDA = c.get('smartAccountPDA') || c.req.header("X-DAIN-SMART-ACCOUNT-PDA");
462
+ const runtimeContext = (c.get('dainRuntimeContext') || {});
463
+ const smartAccountPDA = runtimeContext.smartAccountPDA || c.get('smartAccountPDA') || c.req.header("X-DAIN-SMART-ACCOUNT-PDA");
315
464
  const webhookUrl = c.req.header("X-DAIN-WEBHOOK-URL");
316
465
  debugLog("[getAgentInfo] smartAccountPDA:", smartAccountPDA);
317
466
  debugLog("[getAgentInfo] webhookUrl:", webhookUrl);
@@ -322,7 +471,17 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
322
471
  agentId: smartAccountId,
323
472
  address: smartAccountId,
324
473
  smartAccountPDA,
325
- id: smartAccountPDA ? `dain_id_${smartAccountPDA}` : `smart_account_${smartAccountId}`,
474
+ dainAccountId: runtimeContext.dainAccountId,
475
+ dainGroupId: runtimeContext.dainGroupId,
476
+ account: runtimeContext.account,
477
+ group: runtimeContext.group,
478
+ auth: runtimeContext.auth,
479
+ grants: runtimeContext.grants,
480
+ dataPermissions: runtimeContext.dataPermissions,
481
+ registryPolicy: runtimeContext.registryPolicy,
482
+ id: runtimeContext.dainAccountId ??
483
+ runtimeContext.dainGroupId ??
484
+ (smartAccountPDA ? `dain_id_${smartAccountPDA}` : `smart_account_${smartAccountId}`),
326
485
  webhookUrl,
327
486
  };
328
487
  debugLog("[getAgentInfo] JWT - Returning agent info:", agentInfo);
@@ -335,7 +494,17 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
335
494
  agentId,
336
495
  address,
337
496
  smartAccountPDA,
338
- id: smartAccountPDA ? `dain_id_${smartAccountPDA}` : `address_${address}`,
497
+ dainAccountId: runtimeContext.dainAccountId,
498
+ dainGroupId: runtimeContext.dainGroupId,
499
+ account: runtimeContext.account,
500
+ group: runtimeContext.group,
501
+ auth: runtimeContext.auth,
502
+ grants: runtimeContext.grants,
503
+ dataPermissions: runtimeContext.dataPermissions,
504
+ registryPolicy: runtimeContext.registryPolicy,
505
+ id: runtimeContext.dainAccountId ??
506
+ runtimeContext.dainGroupId ??
507
+ (smartAccountPDA ? `dain_id_${smartAccountPDA}` : `address_${address}`),
339
508
  webhookUrl,
340
509
  };
341
510
  debugLog("[getAgentInfo] API Key - Returning agent info:", agentInfo);
@@ -378,6 +547,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
378
547
  datasourcePolicy: true,
379
548
  widgetPolicy: true,
380
549
  toolSafety: true,
550
+ dainAccount: true,
551
+ registryDiscovery: true,
552
+ dataPermissions: true,
553
+ actionGrants: true,
554
+ smartAccountTransactions: true,
555
+ payments: true,
381
556
  };
382
557
  const compatibility = (() => {
383
558
  if (!requestedContract)
@@ -451,7 +626,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
451
626
  description: context.description,
452
627
  }));
453
628
  // Process plugins for the response
454
- const processedResponse = await processPluginsForResponse(contextInfo, body, { extraData: { plugins: processedPluginData.plugins } });
629
+ const processedResponse = await processPluginsForResponse(contextInfo, body, {
630
+ extraData: buildServiceExtraData(agentInfo, {
631
+ request: processedPluginData,
632
+ plugins: processedPluginData.plugins,
633
+ }),
634
+ });
455
635
  return c.json(processedResponse);
456
636
  });
457
637
  app.post("/contexts/:contextId", async (c) => {
@@ -465,17 +645,19 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
465
645
  const body = await safeParseBody(c);
466
646
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
467
647
  const oauth2Client = app.oauth2?.getClient();
468
- const contextData = await context.getContextData(agentInfo, {
648
+ const extraData = buildServiceExtraData(agentInfo, {
649
+ request: processedPluginData,
469
650
  plugins: processedPluginData.plugins,
470
- oauth2Client
651
+ oauth2Client,
471
652
  });
653
+ const contextData = await context.getContextData(agentInfo, extraData);
472
654
  const response = {
473
655
  id: context.id,
474
656
  name: context.name,
475
657
  description: context.description,
476
658
  data: contextData
477
659
  };
478
- const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
660
+ const processedResponse = await processPluginsForResponse(response, body, { extraData });
479
661
  return c.json(processedResponse);
480
662
  }
481
663
  else {
@@ -487,6 +669,11 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
487
669
  const body = await safeParseBody(c);
488
670
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
489
671
  const oauth2Client = app.oauth2?.getClient();
672
+ const extraData = buildServiceExtraData(agentInfo, {
673
+ request: processedPluginData,
674
+ plugins: processedPluginData.plugins,
675
+ oauth2Client,
676
+ });
490
677
  const autoContexts = app.oauth2?.getDirectProviderContexts?.() || [];
491
678
  const serviceSlug = (0, core_1.toSlug)(metadata.title);
492
679
  const skillsContext = config.skills?.getSkillsContext?.(serviceSlug);
@@ -495,22 +682,23 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
495
682
  id: ctx.id,
496
683
  name: ctx.name,
497
684
  description: ctx.description,
498
- data: await ctx.getContextData(agentInfo, {
499
- plugins: processedPluginData.plugins,
500
- oauth2Client
501
- }),
685
+ data: await ctx.getContextData(agentInfo, extraData),
502
686
  })));
503
- const processedResponse = await processPluginsForResponse(contextsFull, body, { extraData: { plugins: processedPluginData.plugins } });
687
+ const processedResponse = await processPluginsForResponse(contextsFull, body, { extraData });
504
688
  return c.json(processedResponse);
505
689
  });
506
690
  app.post("/widgets", async (c) => {
507
691
  const agentInfo = await getAgentInfo(c);
508
692
  const body = await safeParseBody(c);
509
693
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
694
+ const extraData = buildServiceExtraData(agentInfo, {
695
+ request: processedPluginData,
696
+ plugins: processedPluginData.plugins,
697
+ });
510
698
  const widgetIds = config.getUserWidgets
511
- ? await config.getUserWidgets(agentInfo, { plugins: processedPluginData.plugins })
699
+ ? await config.getUserWidgets(agentInfo, extraData)
512
700
  : widgets.map(widget => widget.id);
513
- const processedResponse = await processPluginsForResponse(widgetIds, body, { extraData: { plugins: processedPluginData.plugins } });
701
+ const processedResponse = await processPluginsForResponse(widgetIds, body, { extraData });
514
702
  return c.json(processedResponse);
515
703
  });
516
704
  app.post("/widgets/all", async (c) => {
@@ -518,18 +706,23 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
518
706
  const body = await safeParseBody(c);
519
707
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
520
708
  const widgetIds = config.getUserWidgets
521
- ? await config.getUserWidgets(agentInfo, { plugins: processedPluginData.plugins })
709
+ ? await config.getUserWidgets(agentInfo, buildServiceExtraData(agentInfo, {
710
+ request: processedPluginData,
711
+ plugins: processedPluginData.plugins,
712
+ }))
522
713
  : widgets.map(widget => widget.id);
523
714
  const oauth2Client = app.oauth2?.getClient();
715
+ const extraData = buildServiceExtraData(agentInfo, {
716
+ request: processedPluginData,
717
+ plugins: processedPluginData.plugins,
718
+ oauth2Client,
719
+ app,
720
+ });
524
721
  const widgetsFull = await Promise.all(widgetIds.map(async (widgetId) => {
525
722
  const widget = widgets.find(w => w.id === widgetId);
526
723
  if (!widget)
527
724
  return null;
528
- const widgetData = await widget.getWidget(agentInfo, {
529
- plugins: processedPluginData.plugins,
530
- oauth2Client,
531
- app
532
- });
725
+ const widgetData = await widget.getWidget(agentInfo, extraData);
533
726
  const response = {
534
727
  id: widget.id,
535
728
  name: widget.name,
@@ -552,7 +745,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
552
745
  return response;
553
746
  }));
554
747
  const validWidgets = widgetsFull.filter(w => w !== null);
555
- const processedResponse = await processPluginsForResponse(validWidgets, body, { extraData: { plugins: processedPluginData.plugins } });
748
+ const processedResponse = await processPluginsForResponse(validWidgets, body, { extraData });
556
749
  return c.json(processedResponse);
557
750
  });
558
751
  app.post("/widgets/:widgetId", async (c) => {
@@ -565,25 +758,31 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
565
758
  const body = await safeParseBody(c);
566
759
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
567
760
  if (config.getUserWidgets) {
761
+ const accessExtraData = buildServiceExtraData(agentInfo, {
762
+ request: processedPluginData,
763
+ plugins: processedPluginData.plugins,
764
+ });
568
765
  const userWidgetIds = await config.getUserWidgets(agentInfo, {
569
- plugins: processedPluginData.plugins
766
+ ...accessExtraData
570
767
  });
571
768
  let homeUIWidgetId = null;
572
769
  if (config.homeUI) {
573
770
  homeUIWidgetId = typeof config.homeUI === 'string'
574
771
  ? config.homeUI
575
- : await config.homeUI(agentInfo, { plugins: processedPluginData.plugins });
772
+ : await config.homeUI(agentInfo, accessExtraData);
576
773
  }
577
774
  if (!userWidgetIds.includes(widgetId) && widgetId !== homeUIWidgetId) {
578
775
  throw new http_exception_1.HTTPException(403, { message: "Access denied to this widget" });
579
776
  }
580
777
  }
581
778
  const oauth2Client = app.oauth2?.getClient();
582
- const widgetData = await widget.getWidget(agentInfo, {
779
+ const extraData = buildServiceExtraData(agentInfo, {
780
+ request: processedPluginData,
583
781
  plugins: processedPluginData.plugins,
584
782
  oauth2Client,
585
- app
783
+ app,
586
784
  });
785
+ const widgetData = await widget.getWidget(agentInfo, extraData);
587
786
  const response = {
588
787
  id: widget.id,
589
788
  name: widget.name,
@@ -603,7 +802,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
603
802
  scope: "account",
604
803
  };
605
804
  }
606
- const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
805
+ const processedResponse = await processPluginsForResponse(response, body, { extraData });
607
806
  return c.json(processedResponse);
608
807
  });
609
808
  app.post("/homeUI", async (c) => {
@@ -613,9 +812,13 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
613
812
  const agentInfo = await getAgentInfo(c);
614
813
  const body = await safeParseBody(c);
615
814
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
815
+ const extraData = buildServiceExtraData(agentInfo, {
816
+ request: processedPluginData,
817
+ plugins: processedPluginData.plugins,
818
+ });
616
819
  const homeUIWidgetId = typeof config.homeUI === 'string'
617
820
  ? config.homeUI
618
- : await config.homeUI(agentInfo, { plugins: processedPluginData.plugins });
821
+ : await config.homeUI(agentInfo, extraData);
619
822
  if (!homeUIWidgetId) {
620
823
  return c.json({ widgetId: null });
621
824
  }
@@ -623,7 +826,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
623
826
  if (!widget) {
624
827
  return c.json({ widgetId: null });
625
828
  }
626
- const processedResponse = await processPluginsForResponse({ widgetId: homeUIWidgetId }, body, { extraData: { plugins: processedPluginData.plugins } });
829
+ const processedResponse = await processPluginsForResponse({ widgetId: homeUIWidgetId }, body, { extraData });
627
830
  return c.json(processedResponse);
628
831
  });
629
832
  function mapDatasourceInfo(datasource) {
@@ -648,14 +851,24 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
648
851
  const agentInfo = await getAgentInfo(c);
649
852
  const body = await safeParseBody(c);
650
853
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
651
- const processedResponse = await processPluginsForResponse(datasources.map(mapDatasourceInfo), body, { extraData: { plugins: processedPluginData.plugins } });
854
+ const processedResponse = await processPluginsForResponse(datasources.map(mapDatasourceInfo), body, {
855
+ extraData: buildServiceExtraData(agentInfo, {
856
+ request: processedPluginData,
857
+ plugins: processedPluginData.plugins,
858
+ }),
859
+ });
652
860
  return c.json(processedResponse);
653
861
  });
654
862
  app.post("/datasources/all", async (c) => {
655
863
  const agentInfo = await getAgentInfo(c);
656
864
  const body = await safeParseBody(c);
657
865
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
658
- const processedResponse = await processPluginsForResponse(datasources.map(mapDatasourceInfo), body, { extraData: { plugins: processedPluginData.plugins } });
866
+ const processedResponse = await processPluginsForResponse(datasources.map(mapDatasourceInfo), body, {
867
+ extraData: buildServiceExtraData(agentInfo, {
868
+ request: processedPluginData,
869
+ plugins: processedPluginData.plugins,
870
+ }),
871
+ });
659
872
  return c.json(processedResponse);
660
873
  });
661
874
  app.post("/datasources/:datasourceId", async (c) => {
@@ -671,10 +884,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
671
884
  try {
672
885
  const parsedParams = datasource.input.parse(params);
673
886
  const oauth2Client = app.oauth2?.getClient();
674
- const data = await datasource.getDatasource(agentInfo, parsedParams, {
887
+ const extraData = buildServiceExtraData(agentInfo, {
888
+ request: params,
675
889
  plugins: pluginsData,
676
- oauth2Client
890
+ oauth2Client,
677
891
  });
892
+ const data = await datasource.getDatasource(agentInfo, parsedParams, extraData);
678
893
  const requestedPolicy = {
679
894
  refreshIntervalMs: datasource.refreshIntervalMs,
680
895
  maxStalenessMs: datasource.maxStalenessMs,
@@ -702,7 +917,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
702
917
  scope: datasource.scope,
703
918
  },
704
919
  };
705
- const processedResponse = await processPluginsForResponse(response, { plugins: pluginsData }, { extraData: { plugins: pluginsData } });
920
+ const processedResponse = await processPluginsForResponse(response, { plugins: pluginsData }, { extraData });
706
921
  return c.json(processedResponse);
707
922
  }
708
923
  catch (error) {
@@ -718,6 +933,17 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
718
933
  throw error;
719
934
  }
720
935
  });
936
+ app.delete("/datasources/:datasourceId/stream/:streamId", async (c) => {
937
+ const datasourceId = c.req.param("datasourceId");
938
+ const streamId = c.req.param("streamId");
939
+ const streamKey = `${datasourceId}:${streamId}`;
940
+ const abortController = datasourceStreamAbortControllers.get(streamKey);
941
+ if (abortController) {
942
+ abortController.abort();
943
+ datasourceStreamAbortControllers.delete(streamKey);
944
+ }
945
+ return c.json({ success: true });
946
+ });
721
947
  // Stream datasource updates over SSE. This is primarily used for "fresh" UIs
722
948
  // (positions/orders) where clients want stream-first updates with a poll fallback.
723
949
  //
@@ -735,6 +961,11 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
735
961
  !Array.isArray(rawBody) &&
736
962
  "params" in rawBody;
737
963
  const requestedIntervalMsRaw = hasWrappedParams ? rawBody.intervalMs : undefined;
964
+ const requestedStreamIdRaw = hasWrappedParams ? rawBody.streamId : undefined;
965
+ const streamId = typeof requestedStreamIdRaw === "string" &&
966
+ /^[A-Za-z0-9._:-]{1,128}$/.test(requestedStreamIdRaw)
967
+ ? requestedStreamIdRaw
968
+ : null;
738
969
  const requestParamsRaw = hasWrappedParams ? rawBody.params : rawBody;
739
970
  let params = await processPluginsForRequest(requestParamsRaw && typeof requestParamsRaw === "object" ? requestParamsRaw : {}, agentInfo);
740
971
  const pluginsData = (params.plugins && typeof params.plugins === "object"
@@ -757,19 +988,26 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
757
988
  }
758
989
  throw error;
759
990
  }
991
+ const oauth2Client = app.oauth2?.getClient();
992
+ const extraData = buildServiceExtraData(agentInfo, {
993
+ request: params,
994
+ plugins: pluginsData,
995
+ oauth2Client,
996
+ });
760
997
  const pluginRequestContext = hasWrappedParams
761
998
  ? {
762
999
  params: parsedParams,
763
1000
  intervalMs: requestedIntervalMsRaw,
764
1001
  plugins: pluginsData,
1002
+ DAIN_EXTRA_DATA: extraData,
765
1003
  }
766
1004
  : {
767
1005
  ...(typeof parsedParams === "object" && parsedParams !== null
768
1006
  ? parsedParams
769
1007
  : {}),
770
1008
  plugins: pluginsData,
1009
+ DAIN_EXTRA_DATA: extraData,
771
1010
  };
772
- const oauth2Client = app.oauth2?.getClient();
773
1011
  const requestedPolicy = {
774
1012
  refreshIntervalMs: datasource.refreshIntervalMs,
775
1013
  maxStalenessMs: datasource.maxStalenessMs,
@@ -786,15 +1024,22 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
786
1024
  ? datasource.refreshIntervalMs
787
1025
  : 15_000);
788
1026
  const intervalMs = Math.max(1_000, Math.floor(baseIntervalMs));
789
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
790
1027
  debugLog(`[SSE] Datasource ${datasource.id} stream start (${intervalMs}ms interval)`);
791
1028
  return signedStreamSSE(c, privateKey, config, async (stream) => {
792
1029
  let eventId = 0;
1030
+ const signal = c.req.raw?.signal;
1031
+ const streamAbortController = new AbortController();
1032
+ const streamKey = streamId ? `${datasource.id}:${streamId}` : null;
1033
+ if (streamKey) {
1034
+ datasourceStreamAbortControllers.set(streamKey, streamAbortController);
1035
+ }
1036
+ const isCancelled = () => stream.isAborted() || signal?.aborted === true || streamAbortController.signal.aborted;
793
1037
  const emitUpdate = async () => {
794
- const data = await datasource.getDatasource(agentInfo, parsedParams, {
795
- plugins: pluginsData,
796
- oauth2Client,
797
- });
1038
+ if (isCancelled())
1039
+ return false;
1040
+ const data = await datasource.getDatasource(agentInfo, parsedParams, extraData);
1041
+ if (isCancelled())
1042
+ return false;
798
1043
  const response = {
799
1044
  id: datasource.id,
800
1045
  name: datasource.name,
@@ -814,51 +1059,77 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
814
1059
  scope: datasource.scope,
815
1060
  },
816
1061
  };
817
- const processedResponse = await processPluginsForResponse(response, pluginRequestContext, { extraData: { plugins: pluginsData } });
1062
+ const processedResponse = await processPluginsForResponse(response, pluginRequestContext, { extraData });
1063
+ if (isCancelled())
1064
+ return false;
818
1065
  await stream.writeSSE({
819
1066
  event: "datasource-update",
820
1067
  data: JSON.stringify(processedResponse),
821
1068
  id: String(eventId++),
822
1069
  });
1070
+ return true;
823
1071
  };
824
- // Send initial snapshot immediately.
825
1072
  try {
826
- await emitUpdate();
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;
1073
+ // Send initial snapshot immediately.
845
1074
  try {
846
- await emitUpdate();
1075
+ const didEmitInitialUpdate = await emitUpdate();
1076
+ if (!didEmitInitialUpdate)
1077
+ return;
847
1078
  }
848
1079
  catch (error) {
849
- if (stream.isAborted() || signal?.aborted)
850
- break;
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()) {
1080
+ console.error(`[SSE] Datasource ${datasource.id} initial update failed:`, error);
1081
+ if (!isCancelled()) {
854
1082
  await stream.writeSSE({
855
1083
  event: "error",
856
1084
  data: JSON.stringify({ message: error?.message || "Datasource stream error" }),
857
1085
  });
858
1086
  }
1087
+ return;
1088
+ }
1089
+ // Continue sending updates until client disconnects.
1090
+ // Hono exposes the underlying Request via c.req.raw, which includes an AbortSignal.
1091
+ const abortPromise = new Promise((resolve) => {
1092
+ if (isCancelled()) {
1093
+ resolve();
1094
+ return;
1095
+ }
1096
+ stream.onAbort(() => resolve());
1097
+ signal?.addEventListener("abort", () => resolve(), { once: true });
1098
+ streamAbortController.signal.addEventListener("abort", () => resolve(), { once: true });
1099
+ });
1100
+ const sleepUntilNextUpdate = (ms) => Promise.race([
1101
+ new Promise((resolve) => setTimeout(resolve, ms)),
1102
+ abortPromise,
1103
+ ]);
1104
+ while (!isCancelled()) {
1105
+ await sleepUntilNextUpdate(intervalMs);
1106
+ if (isCancelled())
1107
+ break;
1108
+ try {
1109
+ const didEmitUpdate = await emitUpdate();
1110
+ if (!didEmitUpdate)
1111
+ break;
1112
+ }
1113
+ catch (error) {
1114
+ if (isCancelled())
1115
+ break;
1116
+ console.error(`[SSE] Datasource ${datasource.id} update failed:`, error);
1117
+ // Keep the stream alive; clients should rely on poll fallback if needed.
1118
+ if (!isCancelled()) {
1119
+ await stream.writeSSE({
1120
+ event: "error",
1121
+ data: JSON.stringify({ message: error?.message || "Datasource stream error" }),
1122
+ });
1123
+ }
1124
+ }
859
1125
  }
860
1126
  }
861
- debugLog(`[SSE] Datasource ${datasource.id} stream end`);
1127
+ finally {
1128
+ if (streamKey) {
1129
+ datasourceStreamAbortControllers.delete(streamKey);
1130
+ }
1131
+ debugLog(`[SSE] Datasource ${datasource.id} stream end`);
1132
+ }
862
1133
  });
863
1134
  });
864
1135
  function mapAgentInfo(agent) {
@@ -880,7 +1151,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
880
1151
  const agentInfo = await getAgentInfo(c);
881
1152
  const body = await safeParseBody(c);
882
1153
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
883
- const processedResponse = await processPluginsForResponse(agents.map(mapAgentInfo), body, { extraData: { plugins: processedPluginData.plugins } });
1154
+ const processedResponse = await processPluginsForResponse(agents.map(mapAgentInfo), body, {
1155
+ extraData: buildServiceExtraData(agentInfo, {
1156
+ request: processedPluginData,
1157
+ plugins: processedPluginData.plugins,
1158
+ }),
1159
+ });
884
1160
  return c.json(processedResponse);
885
1161
  });
886
1162
  app.get("/agents/:agentId", (c) => {
@@ -898,7 +1174,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
898
1174
  const agentInfo = await getAgentInfo(c);
899
1175
  const body = await safeParseBody(c);
900
1176
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
901
- const processedResponse = await processPluginsForResponse(mapAgentInfo(agent), body, { extraData: { plugins: processedPluginData.plugins } });
1177
+ const processedResponse = await processPluginsForResponse(mapAgentInfo(agent), body, {
1178
+ extraData: buildServiceExtraData(agentInfo, {
1179
+ request: processedPluginData,
1180
+ plugins: processedPluginData.plugins,
1181
+ }),
1182
+ });
902
1183
  return c.json(processedResponse);
903
1184
  });
904
1185
  function fixEmptySchemas(schema) {
@@ -971,7 +1252,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
971
1252
  reccomendedPrompts,
972
1253
  }),
973
1254
  };
974
- const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
1255
+ const processedResponse = await processPluginsForResponse(response, body, {
1256
+ extraData: buildServiceExtraData(agentInfo, {
1257
+ request: processedPluginData,
1258
+ plugins: processedPluginData.plugins,
1259
+ }),
1260
+ });
975
1261
  return c.json(processedResponse);
976
1262
  });
977
1263
  // Webhook triggers endpoint
@@ -1487,7 +1773,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1487
1773
  const agentInfo = await getAgentInfo(c);
1488
1774
  const body = await safeParseBody(c);
1489
1775
  const processedPluginData = await processPluginsForRequest(body, agentInfo);
1490
- const processedResponse = await processPluginsForResponse(config.exampleQueries || [], body, { extraData: { plugins: processedPluginData.plugins } });
1776
+ const processedResponse = await processPluginsForResponse(config.exampleQueries || [], body, {
1777
+ extraData: buildServiceExtraData(agentInfo, {
1778
+ request: processedPluginData,
1779
+ plugins: processedPluginData.plugins,
1780
+ }),
1781
+ });
1491
1782
  return c.json(processedResponse);
1492
1783
  });
1493
1784
  // Services list endpoint
@@ -1625,7 +1916,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1625
1916
  count: cards.length,
1626
1917
  query,
1627
1918
  };
1628
- const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
1919
+ const processedResponse = await processPluginsForResponse(response, body, {
1920
+ extraData: buildServiceExtraData(agentInfo, {
1921
+ request: processedPluginData,
1922
+ plugins: processedPluginData.plugins,
1923
+ }),
1924
+ });
1629
1925
  return c.json(processedResponse);
1630
1926
  });
1631
1927
  const recommendationRenderSchema = zod_1.z.object({
@@ -1729,7 +2025,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1729
2025
  renderedCount: renderedCards.length,
1730
2026
  requestedCount: requestedCards.length,
1731
2027
  };
1732
- const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
2028
+ const processedResponse = await processPluginsForResponse(response, body, {
2029
+ extraData: buildServiceExtraData(agentInfo, {
2030
+ request: processedPluginData,
2031
+ plugins: processedPluginData.plugins,
2032
+ }),
2033
+ });
1733
2034
  const statusCode = !continueOnError && renderErrors.length > 0 ? 400 : 200;
1734
2035
  return c.json(processedResponse, statusCode);
1735
2036
  });
@@ -1740,17 +2041,56 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1740
2041
  if (!processedRequest.plugins) {
1741
2042
  processedRequest.plugins = {};
1742
2043
  }
2044
+ processedRequest.DAIN_EXTRA_DATA = buildServiceExtraData(agentInfo, {
2045
+ request: processedRequest,
2046
+ plugins: processedRequest.plugins,
2047
+ });
1743
2048
  // Auto-inject smartAccountPDA into CryptoPlugin wallet context for automations
1744
2049
  // This allows automation wallets to work seamlessly without manual configuration
1745
2050
  if (agentInfo.smartAccountPDA) {
1746
- // Only inject if wallets aren't already provided (client can override)
1747
- if (!processedRequest.plugins['crypto-plugin']?.wallets) {
1748
- processedRequest.plugins['crypto-plugin'] = processedRequest.plugins['crypto-plugin'] || {};
1749
- processedRequest.plugins['crypto-plugin'].wallets = [
1750
- { chain: 'sol', address: agentInfo.smartAccountPDA }
2051
+ processedRequest.plugins['crypto-plugin'] = processedRequest.plugins['crypto-plugin'] || {};
2052
+ const cryptoData = isRecord(processedRequest.plugins['crypto-plugin'])
2053
+ ? stripRuntimeContextFields(processedRequest.plugins['crypto-plugin'])
2054
+ : {};
2055
+ processedRequest.plugins['crypto-plugin'] = cryptoData;
2056
+ const smartAccountWallet = {
2057
+ chain: 'sol',
2058
+ address: agentInfo.smartAccountPDA,
2059
+ accountId: agentInfo.dainAccountId ?? agentInfo.dainGroupId,
2060
+ kind: 'smart_account_vault',
2061
+ capabilities: ['solana-smart-account'],
2062
+ };
2063
+ if (Array.isArray(cryptoData.wallets)) {
2064
+ const hasSmartAccountWallet = cryptoData.wallets.some((wallet) => isRecord(wallet) &&
2065
+ wallet.chain === 'sol' &&
2066
+ wallet.address === agentInfo.smartAccountPDA);
2067
+ if (!hasSmartAccountWallet) {
2068
+ cryptoData.wallets = [smartAccountWallet, ...cryptoData.wallets];
2069
+ }
2070
+ }
2071
+ else if (isRecord(cryptoData.wallets)) {
2072
+ cryptoData.wallets = [
2073
+ smartAccountWallet,
2074
+ ...Object.entries(cryptoData.wallets).map(([chain, address]) => ({
2075
+ chain,
2076
+ address: String(address),
2077
+ })),
1751
2078
  ];
1752
2079
  }
2080
+ else {
2081
+ cryptoData.wallets = [smartAccountWallet];
2082
+ }
1753
2083
  }
2084
+ if (processedRequest.plugins['crypto-plugin']) {
2085
+ processedRequest.plugins['crypto-plugin'] = {
2086
+ ...stripRuntimeContextFields(processedRequest.plugins['crypto-plugin']),
2087
+ ...getRuntimeContextFromAgentInfo(agentInfo),
2088
+ };
2089
+ }
2090
+ processedRequest.DAIN_EXTRA_DATA = buildServiceExtraData(agentInfo, {
2091
+ request: processedRequest,
2092
+ plugins: processedRequest.plugins,
2093
+ });
1754
2094
  // Process plugins if configured
1755
2095
  if (!config.plugins || config.plugins.length === 0) {
1756
2096
  return processedRequest;
@@ -1760,6 +2100,10 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1760
2100
  processedRequest = await plugin.processInputService(processedRequest);
1761
2101
  }
1762
2102
  }
2103
+ processedRequest.DAIN_EXTRA_DATA = buildServiceExtraData(agentInfo, {
2104
+ request: processedRequest,
2105
+ plugins: processedRequest.plugins,
2106
+ });
1763
2107
  return processedRequest;
1764
2108
  }
1765
2109
  // Process response with plugins
@@ -1769,6 +2113,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1769
2113
  }
1770
2114
  let processedResponse = { ...response };
1771
2115
  processedResponse.plugins = processedResponse.plugins || {};
2116
+ const extraData = isRecord(context?.extraData)
2117
+ ? context.extraData
2118
+ : (isRecord(context) ? context : {});
1772
2119
  for (const plugin of config.plugins) {
1773
2120
  if (plugin.processOutputService) {
1774
2121
  // Provide context to the plugin
@@ -1776,7 +2123,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1776
2123
  ...processedResponse,
1777
2124
  context: {
1778
2125
  request,
1779
- extraData: context
2126
+ extraData
1780
2127
  }
1781
2128
  });
1782
2129
  if (pluginOutput) {
@@ -1787,6 +2134,31 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1787
2134
  return processedResponse;
1788
2135
  }
1789
2136
  // Automatically create routes for each tool
2137
+ function buildToolContext(agentInfo, request, pluginsData, hooks = {}) {
2138
+ const oauth2Client = app.oauth2 ? app.oauth2.getClient() : undefined;
2139
+ const extraData = buildServiceExtraData(agentInfo, {
2140
+ request,
2141
+ plugins: pluginsData,
2142
+ oauth2Client,
2143
+ app,
2144
+ });
2145
+ const runtimeContext = getRuntimeContextFromAgentInfo(agentInfo);
2146
+ return {
2147
+ app,
2148
+ oauth2Client,
2149
+ extraData,
2150
+ dainAccountId: runtimeContext.dainAccountId ?? extraData.dainAccountId,
2151
+ dainGroupId: runtimeContext.dainGroupId ?? extraData.dainGroupId,
2152
+ smartAccountPDA: runtimeContext.smartAccountPDA ?? extraData.smartAccountPDA,
2153
+ account: runtimeContext.account ?? extraData.account,
2154
+ group: runtimeContext.group ?? extraData.group,
2155
+ auth: runtimeContext.auth ?? extraData.auth,
2156
+ grants: runtimeContext.grants ?? extraData.grants,
2157
+ dataPermissions: runtimeContext.dataPermissions ?? extraData.dataPermissions,
2158
+ registryPolicy: runtimeContext.registryPolicy ?? extraData.registryPolicy,
2159
+ ...hooks,
2160
+ };
2161
+ }
1790
2162
  tools.forEach((tool) => {
1791
2163
  /**
1792
2164
  * Shared handler for tool execution with streaming support
@@ -1801,13 +2173,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1801
2173
  try {
1802
2174
  // Execute the tool first
1803
2175
  debugLog(`[SSE] Executing tool ${tool.id} in streaming mode${withContext ? ' with context' : ''}`);
1804
- const result = await tool.handler({ ...body, DAIN_EXTRA_DATA: undefined }, agentInfo, {
1805
- app,
1806
- oauth2Client: app.oauth2 ? app.oauth2.getClient() : undefined,
1807
- extraData: {
1808
- ...body.DAIN_EXTRA_DATA,
1809
- plugins: pluginsData
1810
- },
2176
+ const { DAIN_EXTRA_DATA: _dainExtraData, ...toolInput } = body;
2177
+ const result = await tool.handler(toolInput, agentInfo, buildToolContext(agentInfo, body, pluginsData, {
1811
2178
  updateUI: async (update) => {
1812
2179
  try {
1813
2180
  // Check if this is a progress update
@@ -1841,7 +2208,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1841
2208
  console.error(`Error sending process update in ${tool.id}:`, error);
1842
2209
  }
1843
2210
  }
1844
- });
2211
+ }));
1845
2212
  // If we need to include context data
1846
2213
  let response = result;
1847
2214
  if (withContext) {
@@ -1856,8 +2223,11 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1856
2223
  name: context.name,
1857
2224
  description: context.description,
1858
2225
  data: await context.getContextData(agentInfo, {
1859
- plugins: pluginsData,
1860
- oauth2Client: app.oauth2 ? app.oauth2.getClient() : undefined
2226
+ ...buildServiceExtraData(agentInfo, {
2227
+ request: body,
2228
+ plugins: pluginsData,
2229
+ oauth2Client: app.oauth2 ? app.oauth2.getClient() : undefined,
2230
+ }),
1861
2231
  }),
1862
2232
  };
1863
2233
  }
@@ -1957,13 +2327,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1957
2327
  const uiUpdates = [];
1958
2328
  const progressUpdates = [];
1959
2329
  const processes = [];
1960
- const result = await tool.handler({ ...body, DAIN_EXTRA_DATA: undefined }, agentInfo, {
1961
- app,
1962
- oauth2Client: app.oauth2 ? app.oauth2.getClient() : undefined,
1963
- extraData: {
1964
- ...body.DAIN_EXTRA_DATA,
1965
- plugins: pluginsData
1966
- },
2330
+ const { DAIN_EXTRA_DATA: _dainExtraData, ...toolInput } = body;
2331
+ const result = await tool.handler(toolInput, agentInfo, buildToolContext(agentInfo, body, pluginsData, {
1967
2332
  updateUI: (update) => {
1968
2333
  // Collect UI updates instead of streaming them (synchronous for performance)
1969
2334
  if (update.type === 'progress') {
@@ -1979,7 +2344,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1979
2344
  processes.push(processId);
1980
2345
  return Promise.resolve();
1981
2346
  }
1982
- });
2347
+ }));
1983
2348
  // If we need to include context data
1984
2349
  let response = result;
1985
2350
  if (withContext) {
@@ -1990,8 +2355,11 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
1990
2355
  name: context.name,
1991
2356
  description: context.description,
1992
2357
  data: await context.getContextData(agentInfo, {
1993
- plugins: pluginsData,
1994
- oauth2Client: app.oauth2 ? app.oauth2.getClient() : undefined
2358
+ ...buildServiceExtraData(agentInfo, {
2359
+ request: body,
2360
+ plugins: pluginsData,
2361
+ oauth2Client: app.oauth2 ? app.oauth2.getClient() : undefined,
2362
+ }),
1995
2363
  }),
1996
2364
  })));
1997
2365
  // Create the complete response with context