@dainprotocol/service-sdk 2.0.89 → 2.0.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/client.d.ts +7 -39
- package/dist/client/client.js +30 -257
- package/dist/client/client.js.map +1 -1
- package/dist/client/types.d.ts +6 -273
- package/dist/client/types.js +5 -129
- package/dist/client/types.js.map +1 -1
- package/dist/lib/schemaStructure.js +0 -1
- package/dist/lib/schemaStructure.js.map +1 -1
- package/dist/service/auth.js +1 -10
- package/dist/service/auth.js.map +1 -1
- package/dist/service/core.js +1 -3
- package/dist/service/core.js.map +1 -1
- package/dist/service/server.js +64 -519
- package/dist/service/server.js.map +1 -1
- package/dist/service/types.d.ts +4 -133
- package/dist/service/webhooks.d.ts +8 -8
- package/dist/service/webhooks.js +1 -2
- package/dist/service/webhooks.js.map +1 -1
- package/package.json +4 -4
- package/dist/lib/toolDiscovery.d.ts +0 -20
- package/dist/lib/toolDiscovery.js +0 -30
- package/dist/lib/toolDiscovery.js.map +0 -1
package/dist/service/server.js
CHANGED
|
@@ -9,7 +9,6 @@ const cors_1 = require("hono/cors");
|
|
|
9
9
|
const http_exception_1 = require("hono/http-exception");
|
|
10
10
|
const auth_1 = require("./auth");
|
|
11
11
|
const schemaStructure_1 = require("../lib/schemaStructure");
|
|
12
|
-
const toolDiscovery_1 = require("../lib/toolDiscovery");
|
|
13
12
|
const oauth2_1 = require("./oauth2");
|
|
14
13
|
const bs58_1 = tslib_1.__importDefault(require("bs58"));
|
|
15
14
|
const processes_1 = require("./processes");
|
|
@@ -79,30 +78,23 @@ function requireScope(requiredScope) {
|
|
|
79
78
|
// Helper to sign and stream SSE events
|
|
80
79
|
function signedStreamSSE(c, privateKey, config, handler) {
|
|
81
80
|
return (0, streaming_1.streamSSE)(c, async (stream) => {
|
|
82
|
-
const requestSignal = c.req.raw?.signal;
|
|
83
|
-
const isAborted = () => stream.aborted || stream.closed || requestSignal?.aborted === true;
|
|
84
81
|
const signedStream = {
|
|
85
82
|
writeSSE: async (event) => {
|
|
86
|
-
if (isAborted())
|
|
87
|
-
return;
|
|
88
83
|
const timestamp = Date.now().toString();
|
|
89
84
|
const message = `${event.data}:${timestamp}`;
|
|
90
85
|
const messageHash = (0, sha256_1.sha256)(message);
|
|
91
86
|
const signatureBytes = ed25519_1.ed25519.sign(messageHash, privateKey);
|
|
92
87
|
const signature = (0, utils_1.bytesToHex)(signatureBytes);
|
|
93
88
|
// Fast path for non-critical events (progress/UI updates)
|
|
94
|
-
const isCriticalEvent = event.event === 'result' ||
|
|
95
|
-
event.event === 'process-created' ||
|
|
96
|
-
event.event === 'datasource-update';
|
|
89
|
+
const isCriticalEvent = event.event === 'result' || event.event === 'process-created';
|
|
97
90
|
const dataWithSignature = isCriticalEvent
|
|
98
91
|
? JSON.stringify({
|
|
99
92
|
data: JSON.parse(event.data),
|
|
100
93
|
_signature: { signature, timestamp, agentId: config.identity.agentId, orgId: config.identity.orgId, address: config.identity.publicKey }
|
|
101
94
|
})
|
|
102
95
|
: `{"data":${event.data},"_signature":{"signature":"${signature}","timestamp":"${timestamp}","agentId":"${config.identity.agentId}","orgId":"${config.identity.orgId}","address":"${config.identity.publicKey}"}}`;
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
isAborted,
|
|
96
|
+
return stream.writeSSE({ event: event.event, data: dataWithSignature, id: event.id });
|
|
97
|
+
}
|
|
106
98
|
};
|
|
107
99
|
await handler(signedStream);
|
|
108
100
|
});
|
|
@@ -182,7 +174,11 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
182
174
|
const isHITLPath = hitlPath && c.req.path === hitlPath;
|
|
183
175
|
if (c.req.path.startsWith("/oauth2/callback/") ||
|
|
184
176
|
c.req.path.startsWith("/addons") ||
|
|
177
|
+
c.req.path.startsWith("/getAllToolsAsJsonSchema") ||
|
|
178
|
+
c.req.path.startsWith("/getSkills") ||
|
|
179
|
+
c.req.path.startsWith("/getWebhookTriggers") ||
|
|
185
180
|
c.req.path.startsWith("/metadata") ||
|
|
181
|
+
c.req.path.startsWith("/recommendations") ||
|
|
186
182
|
c.req.path.startsWith("/ping") ||
|
|
187
183
|
isWebhookPath ||
|
|
188
184
|
isHITLPath) {
|
|
@@ -304,73 +300,15 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
304
300
|
debugLog("[getAgentInfo] API Key - Returning agent info:", agentInfo);
|
|
305
301
|
return agentInfo;
|
|
306
302
|
}
|
|
307
|
-
async function resolveHealthStatus() {
|
|
308
|
-
if (!config.healthCheck)
|
|
309
|
-
return true;
|
|
310
|
-
try {
|
|
311
|
-
return await config.healthCheck();
|
|
312
|
-
}
|
|
313
|
-
catch {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
303
|
// Setup default ping route
|
|
318
|
-
app.get("/ping",
|
|
319
|
-
const healthy = await resolveHealthStatus();
|
|
320
|
-
return c.json({
|
|
321
|
-
message: healthy ? "pong" : "unhealthy",
|
|
322
|
-
platform: "DAIN",
|
|
323
|
-
service_version: metadata.version,
|
|
324
|
-
dain_sdk_version: package_json_1.default.version,
|
|
325
|
-
}, healthy ? 200 : 503);
|
|
326
|
-
});
|
|
304
|
+
app.get("/ping", (c) => c.json({ message: "pong", platform: "DAIN", service_version: metadata.version, dain_sdk_version: package_json_1.default.version }));
|
|
327
305
|
// Metadata endpoint - includes computed supportsUserActions from tools
|
|
328
306
|
app.get("/metadata", (c) => {
|
|
329
307
|
// Compute service-level capability: does ANY tool support user actions (HITL)?
|
|
330
308
|
const supportsUserActions = tools.some((tool) => tool.supportsUserActions === true);
|
|
331
|
-
const requestedContract = c.req.header("x-butterfly-contract") || c.req.header("X-Butterfly-Contract");
|
|
332
|
-
const sdkMajor = Number.parseInt(String(package_json_1.default.version).split(".")[0] || "0", 10);
|
|
333
|
-
const contractVersion = Number.isFinite(sdkMajor) && sdkMajor > 0 ? `${sdkMajor}.0.0` : "0.0.0";
|
|
334
|
-
const capabilities = {
|
|
335
|
-
tools: true,
|
|
336
|
-
contexts: true,
|
|
337
|
-
widgets: true,
|
|
338
|
-
datasources: true,
|
|
339
|
-
streamingTools: true,
|
|
340
|
-
streamingDatasources: true,
|
|
341
|
-
datasourcePolicy: true,
|
|
342
|
-
widgetPolicy: true,
|
|
343
|
-
toolSafety: true,
|
|
344
|
-
};
|
|
345
|
-
const compatibility = (() => {
|
|
346
|
-
if (!requestedContract)
|
|
347
|
-
return undefined;
|
|
348
|
-
const requestedMajor = Number.parseInt(String(requestedContract).split(".")[0] || "0", 10);
|
|
349
|
-
if (!Number.isFinite(requestedMajor) || requestedMajor <= 0) {
|
|
350
|
-
return {
|
|
351
|
-
requested: requestedContract,
|
|
352
|
-
contractVersion,
|
|
353
|
-
ok: false,
|
|
354
|
-
reason: "Invalid requested contract version",
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
const ok = requestedMajor === sdkMajor;
|
|
358
|
-
return ok
|
|
359
|
-
? { requested: requestedContract, contractVersion, ok: true }
|
|
360
|
-
: {
|
|
361
|
-
requested: requestedContract,
|
|
362
|
-
contractVersion,
|
|
363
|
-
ok: false,
|
|
364
|
-
reason: `Requested major ${requestedMajor} does not match service major ${sdkMajor}`,
|
|
365
|
-
};
|
|
366
|
-
})();
|
|
367
309
|
return c.json({
|
|
368
310
|
...metadata,
|
|
369
311
|
supportsUserActions,
|
|
370
|
-
dainSdkVersion: package_json_1.default.version,
|
|
371
|
-
contractVersion,
|
|
372
|
-
capabilities,
|
|
373
|
-
...(compatibility ? { compatibility } : {}),
|
|
374
312
|
});
|
|
375
313
|
});
|
|
376
314
|
// Tools list endpoint
|
|
@@ -389,10 +327,6 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
389
327
|
outputSchema,
|
|
390
328
|
interface: tool.interface,
|
|
391
329
|
suggestConfirmation: tool.suggestConfirmation,
|
|
392
|
-
sideEffectClass: tool.sideEffectClass,
|
|
393
|
-
supportsParallel: tool.supportsParallel,
|
|
394
|
-
idempotencyScope: tool.idempotencyScope,
|
|
395
|
-
maxConcurrencyHint: tool.maxConcurrencyHint,
|
|
396
330
|
supportsUserActions: tool.supportsUserActions,
|
|
397
331
|
};
|
|
398
332
|
});
|
|
@@ -493,26 +427,14 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
493
427
|
oauth2Client,
|
|
494
428
|
app
|
|
495
429
|
});
|
|
496
|
-
|
|
430
|
+
return {
|
|
497
431
|
id: widget.id,
|
|
498
432
|
name: widget.name,
|
|
499
433
|
description: widget.description,
|
|
500
434
|
icon: widget.icon,
|
|
501
435
|
size: widget.size || "sm",
|
|
502
|
-
refreshIntervalMs: widget.refreshIntervalMs,
|
|
503
436
|
...widgetData
|
|
504
437
|
};
|
|
505
|
-
if (!("freshness" in response)) {
|
|
506
|
-
response.freshness = {
|
|
507
|
-
freshAt: Date.now(),
|
|
508
|
-
transport: "poll",
|
|
509
|
-
requestedPolicy: {
|
|
510
|
-
refreshIntervalMs: widget.refreshIntervalMs,
|
|
511
|
-
},
|
|
512
|
-
scope: "account",
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
return response;
|
|
516
438
|
}));
|
|
517
439
|
const validWidgets = widgetsFull.filter(w => w !== null);
|
|
518
440
|
const processedResponse = await processPluginsForResponse(validWidgets, body, { extraData: { plugins: processedPluginData.plugins } });
|
|
@@ -553,19 +475,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
553
475
|
description: widget.description,
|
|
554
476
|
icon: widget.icon,
|
|
555
477
|
size: widget.size || "sm",
|
|
556
|
-
refreshIntervalMs: widget.refreshIntervalMs,
|
|
557
478
|
...widgetData
|
|
558
479
|
};
|
|
559
|
-
if (!("freshness" in response)) {
|
|
560
|
-
response.freshness = {
|
|
561
|
-
freshAt: Date.now(),
|
|
562
|
-
transport: "poll",
|
|
563
|
-
requestedPolicy: {
|
|
564
|
-
refreshIntervalMs: widget.refreshIntervalMs,
|
|
565
|
-
},
|
|
566
|
-
scope: "account",
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
480
|
const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
|
|
570
481
|
return c.json(processedResponse);
|
|
571
482
|
});
|
|
@@ -595,12 +506,6 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
595
506
|
name: datasource.name,
|
|
596
507
|
description: datasource.description,
|
|
597
508
|
type: datasource.type,
|
|
598
|
-
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
599
|
-
maxStalenessMs: datasource.maxStalenessMs,
|
|
600
|
-
transport: datasource.transport,
|
|
601
|
-
priority: datasource.priority,
|
|
602
|
-
scope: datasource.scope,
|
|
603
|
-
dataClass: datasource.dataClass,
|
|
604
509
|
inputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(datasource.input),
|
|
605
510
|
};
|
|
606
511
|
}
|
|
@@ -638,32 +543,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
638
543
|
plugins: pluginsData,
|
|
639
544
|
oauth2Client
|
|
640
545
|
});
|
|
641
|
-
const requestedPolicy = {
|
|
642
|
-
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
643
|
-
maxStalenessMs: datasource.maxStalenessMs,
|
|
644
|
-
transport: datasource.transport,
|
|
645
|
-
priority: datasource.priority,
|
|
646
|
-
scope: datasource.scope,
|
|
647
|
-
dataClass: datasource.dataClass,
|
|
648
|
-
};
|
|
649
546
|
const response = {
|
|
650
547
|
id: datasource.id,
|
|
651
548
|
name: datasource.name,
|
|
652
549
|
description: datasource.description,
|
|
653
550
|
type: datasource.type,
|
|
654
|
-
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
655
|
-
maxStalenessMs: datasource.maxStalenessMs,
|
|
656
|
-
transport: datasource.transport,
|
|
657
|
-
priority: datasource.priority,
|
|
658
|
-
scope: datasource.scope,
|
|
659
|
-
dataClass: datasource.dataClass,
|
|
660
551
|
data,
|
|
661
|
-
freshness: {
|
|
662
|
-
freshAt: Date.now(),
|
|
663
|
-
transport: datasource.transport || "poll",
|
|
664
|
-
requestedPolicy,
|
|
665
|
-
scope: datasource.scope,
|
|
666
|
-
},
|
|
667
552
|
};
|
|
668
553
|
const processedResponse = await processPluginsForResponse(response, { plugins: pluginsData }, { extraData: { plugins: pluginsData } });
|
|
669
554
|
return c.json(processedResponse);
|
|
@@ -681,149 +566,6 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
681
566
|
throw error;
|
|
682
567
|
}
|
|
683
568
|
});
|
|
684
|
-
// Stream datasource updates over SSE. This is primarily used for "fresh" UIs
|
|
685
|
-
// (positions/orders) where clients want stream-first updates with a poll fallback.
|
|
686
|
-
//
|
|
687
|
-
// Note: This does not require services to implement a separate streaming backend.
|
|
688
|
-
// The server re-runs the datasource handler on an interval and streams results.
|
|
689
|
-
app.post("/datasources/:datasourceId/stream", async (c) => {
|
|
690
|
-
const datasource = datasources.find((ds) => ds.id === c.req.param("datasourceId"));
|
|
691
|
-
if (!datasource) {
|
|
692
|
-
throw new http_exception_1.HTTPException(404, { message: "Datasource not found" });
|
|
693
|
-
}
|
|
694
|
-
const agentInfo = await getAgentInfo(c);
|
|
695
|
-
const rawBody = await safeParseBody(c);
|
|
696
|
-
const hasWrappedParams = rawBody &&
|
|
697
|
-
typeof rawBody === "object" &&
|
|
698
|
-
!Array.isArray(rawBody) &&
|
|
699
|
-
"params" in rawBody;
|
|
700
|
-
const requestedIntervalMsRaw = hasWrappedParams ? rawBody.intervalMs : undefined;
|
|
701
|
-
const requestParamsRaw = hasWrappedParams ? rawBody.params : rawBody;
|
|
702
|
-
let params = await processPluginsForRequest(requestParamsRaw && typeof requestParamsRaw === "object" ? requestParamsRaw : {}, agentInfo);
|
|
703
|
-
const pluginsData = (params.plugins && typeof params.plugins === "object"
|
|
704
|
-
? params.plugins
|
|
705
|
-
: {});
|
|
706
|
-
delete params.plugins;
|
|
707
|
-
let parsedParams;
|
|
708
|
-
try {
|
|
709
|
-
parsedParams = datasource.input.parse(params);
|
|
710
|
-
}
|
|
711
|
-
catch (error) {
|
|
712
|
-
if (error instanceof zod_1.z.ZodError) {
|
|
713
|
-
const missingParams = error.issues
|
|
714
|
-
.map((issue) => issue.path.join("."))
|
|
715
|
-
.join(", ");
|
|
716
|
-
return c.json({
|
|
717
|
-
error: `Missing or invalid parameters: ${missingParams}`,
|
|
718
|
-
code: "INVALID_PARAMS"
|
|
719
|
-
}, 400);
|
|
720
|
-
}
|
|
721
|
-
throw error;
|
|
722
|
-
}
|
|
723
|
-
const pluginRequestContext = hasWrappedParams
|
|
724
|
-
? {
|
|
725
|
-
params: parsedParams,
|
|
726
|
-
intervalMs: requestedIntervalMsRaw,
|
|
727
|
-
plugins: pluginsData,
|
|
728
|
-
}
|
|
729
|
-
: {
|
|
730
|
-
...(typeof parsedParams === "object" && parsedParams !== null
|
|
731
|
-
? parsedParams
|
|
732
|
-
: {}),
|
|
733
|
-
plugins: pluginsData,
|
|
734
|
-
};
|
|
735
|
-
const oauth2Client = app.oauth2?.getClient();
|
|
736
|
-
const requestedPolicy = {
|
|
737
|
-
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
738
|
-
maxStalenessMs: datasource.maxStalenessMs,
|
|
739
|
-
transport: datasource.transport,
|
|
740
|
-
priority: datasource.priority,
|
|
741
|
-
scope: datasource.scope,
|
|
742
|
-
dataClass: datasource.dataClass,
|
|
743
|
-
};
|
|
744
|
-
const requestedIntervalMs = typeof requestedIntervalMsRaw === "number" && Number.isFinite(requestedIntervalMsRaw) && requestedIntervalMsRaw > 0
|
|
745
|
-
? requestedIntervalMsRaw
|
|
746
|
-
: null;
|
|
747
|
-
const baseIntervalMs = requestedIntervalMs ??
|
|
748
|
-
(typeof datasource.refreshIntervalMs === "number" && datasource.refreshIntervalMs > 0
|
|
749
|
-
? datasource.refreshIntervalMs
|
|
750
|
-
: 15_000);
|
|
751
|
-
const intervalMs = Math.max(1_000, Math.floor(baseIntervalMs));
|
|
752
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
753
|
-
debugLog(`[SSE] Datasource ${datasource.id} stream start (${intervalMs}ms interval)`);
|
|
754
|
-
return signedStreamSSE(c, privateKey, config, async (stream) => {
|
|
755
|
-
let eventId = 0;
|
|
756
|
-
const emitUpdate = async () => {
|
|
757
|
-
const data = await datasource.getDatasource(agentInfo, parsedParams, {
|
|
758
|
-
plugins: pluginsData,
|
|
759
|
-
oauth2Client,
|
|
760
|
-
});
|
|
761
|
-
const response = {
|
|
762
|
-
id: datasource.id,
|
|
763
|
-
name: datasource.name,
|
|
764
|
-
description: datasource.description,
|
|
765
|
-
type: datasource.type,
|
|
766
|
-
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
767
|
-
maxStalenessMs: datasource.maxStalenessMs,
|
|
768
|
-
transport: datasource.transport,
|
|
769
|
-
priority: datasource.priority,
|
|
770
|
-
scope: datasource.scope,
|
|
771
|
-
dataClass: datasource.dataClass,
|
|
772
|
-
data,
|
|
773
|
-
freshness: {
|
|
774
|
-
freshAt: Date.now(),
|
|
775
|
-
transport: "stream",
|
|
776
|
-
requestedPolicy,
|
|
777
|
-
scope: datasource.scope,
|
|
778
|
-
},
|
|
779
|
-
};
|
|
780
|
-
const processedResponse = await processPluginsForResponse(response, pluginRequestContext, { extraData: { plugins: pluginsData } });
|
|
781
|
-
await stream.writeSSE({
|
|
782
|
-
event: "datasource-update",
|
|
783
|
-
data: JSON.stringify(processedResponse),
|
|
784
|
-
id: String(eventId++),
|
|
785
|
-
});
|
|
786
|
-
};
|
|
787
|
-
// Send initial snapshot immediately.
|
|
788
|
-
try {
|
|
789
|
-
await emitUpdate();
|
|
790
|
-
}
|
|
791
|
-
catch (error) {
|
|
792
|
-
console.error(`[SSE] Datasource ${datasource.id} initial update failed:`, error);
|
|
793
|
-
if (!stream.isAborted()) {
|
|
794
|
-
await stream.writeSSE({
|
|
795
|
-
event: "error",
|
|
796
|
-
data: JSON.stringify({ message: error?.message || "Datasource stream error" }),
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
// Continue sending updates until client disconnects.
|
|
802
|
-
// Hono exposes the underlying Request via c.req.raw, which includes an AbortSignal.
|
|
803
|
-
const signal = c.req.raw?.signal;
|
|
804
|
-
while (!stream.isAborted() && !signal?.aborted) {
|
|
805
|
-
await sleep(intervalMs);
|
|
806
|
-
if (stream.isAborted() || signal?.aborted)
|
|
807
|
-
break;
|
|
808
|
-
try {
|
|
809
|
-
await emitUpdate();
|
|
810
|
-
}
|
|
811
|
-
catch (error) {
|
|
812
|
-
if (stream.isAborted() || signal?.aborted)
|
|
813
|
-
break;
|
|
814
|
-
console.error(`[SSE] Datasource ${datasource.id} update failed:`, error);
|
|
815
|
-
// Keep the stream alive; clients should rely on poll fallback if needed.
|
|
816
|
-
if (!stream.isAborted()) {
|
|
817
|
-
await stream.writeSSE({
|
|
818
|
-
event: "error",
|
|
819
|
-
data: JSON.stringify({ message: error?.message || "Datasource stream error" }),
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
debugLog(`[SSE] Datasource ${datasource.id} stream end`);
|
|
825
|
-
});
|
|
826
|
-
});
|
|
827
569
|
function mapAgentInfo(agent) {
|
|
828
570
|
return {
|
|
829
571
|
id: agent.id,
|
|
@@ -900,23 +642,13 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
900
642
|
outputSchema,
|
|
901
643
|
interface: tool.interface,
|
|
902
644
|
suggestConfirmation: tool.suggestConfirmation,
|
|
903
|
-
sideEffectClass: tool.sideEffectClass,
|
|
904
|
-
supportsParallel: tool.supportsParallel,
|
|
905
|
-
idempotencyScope: tool.idempotencyScope,
|
|
906
|
-
maxConcurrencyHint: tool.maxConcurrencyHint,
|
|
907
645
|
supportsUserActions: tool.supportsUserActions,
|
|
908
646
|
};
|
|
909
647
|
}
|
|
910
648
|
app.get("/getAllToolsAsJsonSchema", (c) => {
|
|
911
|
-
const toolInfo = tools.map(mapToolToJsonSchema);
|
|
912
|
-
const reccomendedPrompts = toolboxes.map((toolbox) => toolbox.recommendedPrompt);
|
|
913
649
|
return c.json({
|
|
914
|
-
tools:
|
|
915
|
-
reccomendedPrompts,
|
|
916
|
-
schemaVersion: (0, toolDiscovery_1.computeToolDiscoverySchemaVersion)({
|
|
917
|
-
tools: toolInfo,
|
|
918
|
-
reccomendedPrompts,
|
|
919
|
-
}),
|
|
650
|
+
tools: tools.map(mapToolToJsonSchema),
|
|
651
|
+
reccomendedPrompts: toolboxes.map((toolbox) => toolbox.recommendedPrompt),
|
|
920
652
|
});
|
|
921
653
|
});
|
|
922
654
|
app.post("/getAllToolsAsJsonSchema", async (c) => {
|
|
@@ -925,14 +657,9 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
925
657
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
926
658
|
const toolInfo = tools.map(mapToolToJsonSchema);
|
|
927
659
|
debugLog(`[getAllToolsAsJsonSchema POST] Returning ${toolInfo.length} tools`);
|
|
928
|
-
const reccomendedPrompts = toolboxes.map((toolbox) => toolbox.recommendedPrompt);
|
|
929
660
|
const response = {
|
|
930
661
|
tools: toolInfo,
|
|
931
|
-
reccomendedPrompts,
|
|
932
|
-
schemaVersion: (0, toolDiscovery_1.computeToolDiscoverySchemaVersion)({
|
|
933
|
-
tools: toolInfo,
|
|
934
|
-
reccomendedPrompts,
|
|
935
|
-
}),
|
|
662
|
+
reccomendedPrompts: toolboxes.map((toolbox) => toolbox.recommendedPrompt),
|
|
936
663
|
};
|
|
937
664
|
const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
|
|
938
665
|
return c.json(processedResponse);
|
|
@@ -1119,10 +846,6 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1119
846
|
outputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(tool.output),
|
|
1120
847
|
interface: tool.interface,
|
|
1121
848
|
suggestConfirmation: tool.suggestConfirmation,
|
|
1122
|
-
sideEffectClass: tool.sideEffectClass,
|
|
1123
|
-
supportsParallel: tool.supportsParallel,
|
|
1124
|
-
idempotencyScope: tool.idempotencyScope,
|
|
1125
|
-
maxConcurrencyHint: tool.maxConcurrencyHint,
|
|
1126
849
|
};
|
|
1127
850
|
return c.json(toolDetails);
|
|
1128
851
|
}
|
|
@@ -1150,11 +873,6 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1150
873
|
pricing: tool.pricing,
|
|
1151
874
|
inputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(tool.input),
|
|
1152
875
|
outputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(tool.output),
|
|
1153
|
-
suggestConfirmation: tool.suggestConfirmation,
|
|
1154
|
-
sideEffectClass: tool.sideEffectClass,
|
|
1155
|
-
supportsParallel: tool.supportsParallel,
|
|
1156
|
-
idempotencyScope: tool.idempotencyScope,
|
|
1157
|
-
maxConcurrencyHint: tool.maxConcurrencyHint,
|
|
1158
876
|
}
|
|
1159
877
|
: {
|
|
1160
878
|
id: toolId,
|
|
@@ -1180,13 +898,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1180
898
|
}
|
|
1181
899
|
});
|
|
1182
900
|
// Health check endpoint
|
|
1183
|
-
app.get("/health",
|
|
1184
|
-
const healthy = await resolveHealthStatus();
|
|
1185
|
-
return c.json({
|
|
1186
|
-
status: healthy ? "healthy" : "unhealthy",
|
|
1187
|
-
timestamp: new Date().toISOString(),
|
|
1188
|
-
}, healthy ? 200 : 503);
|
|
1189
|
-
});
|
|
901
|
+
app.get("/health", (c) => c.json({ status: "healthy", timestamp: new Date().toISOString() }));
|
|
1190
902
|
// Setup custom routes if provided
|
|
1191
903
|
if (config.routes) {
|
|
1192
904
|
config.routes(app);
|
|
@@ -1470,231 +1182,64 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1470
1182
|
});
|
|
1471
1183
|
// Toolboxes list endpoint
|
|
1472
1184
|
app.get("/toolboxes", (c) => c.json(toolboxes));
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
function tokenizeRecommendationQuery(query) {
|
|
1477
|
-
const normalized = normalizeRecommendationText(query);
|
|
1478
|
-
if (!normalized)
|
|
1479
|
-
return [];
|
|
1480
|
-
return Array.from(new Set(normalized.split(/\s+/).filter((token) => token.length >= 2)));
|
|
1481
|
-
}
|
|
1482
|
-
function buildRecommendationIndex() {
|
|
1483
|
-
const index = new Map();
|
|
1185
|
+
// Recommendations endpoint - returns cards for AI selection
|
|
1186
|
+
app.get("/recommendations", (c) => {
|
|
1187
|
+
const cards = [];
|
|
1484
1188
|
for (const tool of tools) {
|
|
1485
|
-
if (
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1189
|
+
if (tool.recommendations?.length) {
|
|
1190
|
+
for (const card of tool.recommendations) {
|
|
1191
|
+
const hasDynamicSchema = !!card.inputSchema;
|
|
1192
|
+
cards.push({
|
|
1193
|
+
id: card.id,
|
|
1194
|
+
tags: card.tags,
|
|
1195
|
+
ui: hasDynamicSchema ? undefined : card.ui,
|
|
1196
|
+
inputSchema: hasDynamicSchema ? (0, schemaStructure_1.zodToJsonSchema)(card.inputSchema) : undefined,
|
|
1197
|
+
toolId: tool.id,
|
|
1198
|
+
toolName: tool.name,
|
|
1199
|
+
});
|
|
1491
1200
|
}
|
|
1492
|
-
const isDynamic = !!card.inputSchema;
|
|
1493
|
-
const inputSchema = isDynamic ? (0, schemaStructure_1.zodToJsonSchema)(card.inputSchema) : undefined;
|
|
1494
|
-
const searchText = normalizeRecommendationText([tool.id, tool.name, card.id, ...card.tags].join(" "));
|
|
1495
|
-
index.set(scopedCardId, {
|
|
1496
|
-
cardId: scopedCardId,
|
|
1497
|
-
localCardId: card.id,
|
|
1498
|
-
toolId: tool.id,
|
|
1499
|
-
toolName: tool.name,
|
|
1500
|
-
tags: card.tags,
|
|
1501
|
-
inputSchema,
|
|
1502
|
-
ui: isDynamic ? undefined : card.ui,
|
|
1503
|
-
isDynamic,
|
|
1504
|
-
card,
|
|
1505
|
-
searchText,
|
|
1506
|
-
});
|
|
1507
1201
|
}
|
|
1508
1202
|
}
|
|
1509
|
-
return
|
|
1510
|
-
}
|
|
1511
|
-
function scoreRecommendationCard(card, tokens) {
|
|
1512
|
-
if (tokens.length === 0)
|
|
1513
|
-
return 0;
|
|
1514
|
-
let score = 0;
|
|
1515
|
-
const tagSet = new Set(card.tags.map((tag) => normalizeRecommendationText(tag)));
|
|
1516
|
-
const scopedId = normalizeRecommendationText(card.cardId);
|
|
1517
|
-
const toolName = normalizeRecommendationText(card.toolName);
|
|
1518
|
-
for (const token of tokens) {
|
|
1519
|
-
if (scopedId === token || card.localCardId.toLowerCase() === token) {
|
|
1520
|
-
score += 6;
|
|
1521
|
-
continue;
|
|
1522
|
-
}
|
|
1523
|
-
if (tagSet.has(token)) {
|
|
1524
|
-
score += 4;
|
|
1525
|
-
}
|
|
1526
|
-
if (toolName.includes(token)) {
|
|
1527
|
-
score += 3;
|
|
1528
|
-
}
|
|
1529
|
-
if (card.searchText.includes(token)) {
|
|
1530
|
-
score += 1;
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
return score;
|
|
1534
|
-
}
|
|
1535
|
-
const recommendationCardsById = buildRecommendationIndex();
|
|
1536
|
-
const recommendationSearchSchema = zod_1.z.object({
|
|
1537
|
-
query: zod_1.z.string().optional(),
|
|
1538
|
-
limit: zod_1.z.number().int().min(1).max(50).optional().default(12),
|
|
1539
|
-
toolIds: zod_1.z.array(zod_1.z.string()).optional(),
|
|
1540
|
-
includeStaticUI: zod_1.z.boolean().optional().default(false),
|
|
1541
|
-
});
|
|
1542
|
-
app.post("/recommendations/search", async (c) => {
|
|
1543
|
-
const agentInfo = await getAgentInfo(c);
|
|
1544
|
-
const body = await safeParseBody(c);
|
|
1545
|
-
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
1546
|
-
const requestData = { ...processedPluginData };
|
|
1547
|
-
delete requestData.plugins;
|
|
1548
|
-
const parsed = recommendationSearchSchema.safeParse(requestData);
|
|
1549
|
-
if (!parsed.success) {
|
|
1550
|
-
return c.json({
|
|
1551
|
-
error: "Invalid recommendation search request",
|
|
1552
|
-
details: parsed.error.format(),
|
|
1553
|
-
}, 400);
|
|
1554
|
-
}
|
|
1555
|
-
const { query, limit, toolIds, includeStaticUI, } = parsed.data;
|
|
1556
|
-
const toolIdSet = toolIds?.length ? new Set(toolIds) : null;
|
|
1557
|
-
const filteredCards = Array.from(recommendationCardsById.values()).filter((card) => !toolIdSet || toolIdSet.has(card.toolId));
|
|
1558
|
-
const tokens = tokenizeRecommendationQuery(query ?? "");
|
|
1559
|
-
const ranked = filteredCards
|
|
1560
|
-
.map((card, index) => ({
|
|
1561
|
-
...card,
|
|
1562
|
-
score: scoreRecommendationCard(card, tokens),
|
|
1563
|
-
index,
|
|
1564
|
-
}))
|
|
1565
|
-
.sort((a, b) => {
|
|
1566
|
-
if (tokens.length === 0)
|
|
1567
|
-
return a.index - b.index;
|
|
1568
|
-
if (b.score !== a.score)
|
|
1569
|
-
return b.score - a.score;
|
|
1570
|
-
if (a.toolName !== b.toolName)
|
|
1571
|
-
return a.toolName.localeCompare(b.toolName);
|
|
1572
|
-
return a.localCardId.localeCompare(b.localCardId);
|
|
1573
|
-
})
|
|
1574
|
-
.slice(0, limit);
|
|
1575
|
-
const cards = ranked.map((card) => ({
|
|
1576
|
-
cardId: card.cardId,
|
|
1577
|
-
localCardId: card.localCardId,
|
|
1578
|
-
toolId: card.toolId,
|
|
1579
|
-
toolName: card.toolName,
|
|
1580
|
-
tags: card.tags,
|
|
1581
|
-
score: card.score,
|
|
1582
|
-
inputSchema: card.inputSchema,
|
|
1583
|
-
ui: includeStaticUI && !card.isDynamic ? card.ui : undefined,
|
|
1584
|
-
isDynamic: card.isDynamic,
|
|
1585
|
-
}));
|
|
1586
|
-
const response = {
|
|
1587
|
-
cards,
|
|
1588
|
-
count: cards.length,
|
|
1589
|
-
query,
|
|
1590
|
-
};
|
|
1591
|
-
const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
|
|
1592
|
-
return c.json(processedResponse);
|
|
1203
|
+
return c.json({ cards, count: cards.length });
|
|
1593
1204
|
});
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
1609
|
-
const requestData = { ...processedPluginData };
|
|
1610
|
-
delete requestData.plugins;
|
|
1611
|
-
const parsed = recommendationRenderSchema.safeParse(requestData);
|
|
1612
|
-
if (!parsed.success) {
|
|
1613
|
-
return c.json({
|
|
1614
|
-
error: "Invalid recommendation render request",
|
|
1615
|
-
details: parsed.error.format(),
|
|
1616
|
-
}, 400);
|
|
1617
|
-
}
|
|
1618
|
-
const { cards: requestedCards, continueOnError } = parsed.data;
|
|
1619
|
-
const renderedCards = [];
|
|
1620
|
-
const renderErrors = [];
|
|
1621
|
-
for (const requested of requestedCards) {
|
|
1622
|
-
const card = recommendationCardsById.get(requested.cardId);
|
|
1623
|
-
if (!card) {
|
|
1624
|
-
renderErrors.push({
|
|
1625
|
-
cardId: requested.cardId,
|
|
1626
|
-
code: "not_found",
|
|
1627
|
-
message: `Card not found: ${requested.cardId}`,
|
|
1628
|
-
});
|
|
1629
|
-
if (!continueOnError)
|
|
1630
|
-
break;
|
|
1631
|
-
continue;
|
|
1632
|
-
}
|
|
1633
|
-
if (!card.isDynamic) {
|
|
1634
|
-
renderedCards.push({
|
|
1635
|
-
cardId: card.cardId,
|
|
1636
|
-
localCardId: card.localCardId,
|
|
1637
|
-
toolId: card.toolId,
|
|
1638
|
-
toolName: card.toolName,
|
|
1639
|
-
tags: card.tags,
|
|
1640
|
-
ui: card.card.ui,
|
|
1641
|
-
});
|
|
1642
|
-
continue;
|
|
1643
|
-
}
|
|
1644
|
-
const paramsInput = requested.params ?? {};
|
|
1645
|
-
const parseResult = card.card.inputSchema.safeParse(paramsInput);
|
|
1646
|
-
if (!parseResult.success) {
|
|
1647
|
-
renderErrors.push({
|
|
1648
|
-
cardId: card.cardId,
|
|
1649
|
-
code: "invalid_params",
|
|
1650
|
-
message: "Invalid parameters",
|
|
1651
|
-
details: parseResult.error.format(),
|
|
1652
|
-
});
|
|
1653
|
-
if (!continueOnError)
|
|
1654
|
-
break;
|
|
1655
|
-
continue;
|
|
1656
|
-
}
|
|
1657
|
-
if (typeof card.card.ui !== "function") {
|
|
1658
|
-
renderErrors.push({
|
|
1659
|
-
cardId: card.cardId,
|
|
1660
|
-
code: "render_failed",
|
|
1661
|
-
message: `Card "${card.cardId}" is dynamic but has no UI generator function`,
|
|
1662
|
-
});
|
|
1663
|
-
if (!continueOnError)
|
|
1664
|
-
break;
|
|
1665
|
-
continue;
|
|
1666
|
-
}
|
|
1667
|
-
try {
|
|
1668
|
-
const ui = await card.card.ui(parseResult.data, requested.context ?? {});
|
|
1669
|
-
renderedCards.push({
|
|
1670
|
-
cardId: card.cardId,
|
|
1671
|
-
localCardId: card.localCardId,
|
|
1672
|
-
toolId: card.toolId,
|
|
1673
|
-
toolName: card.toolName,
|
|
1674
|
-
tags: card.tags,
|
|
1675
|
-
ui,
|
|
1676
|
-
params: parseResult.data,
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
catch (error) {
|
|
1680
|
-
renderErrors.push({
|
|
1681
|
-
cardId: card.cardId,
|
|
1682
|
-
code: "render_failed",
|
|
1683
|
-
message: error instanceof Error ? error.message : "Failed to render recommendation card",
|
|
1684
|
-
});
|
|
1685
|
-
if (!continueOnError)
|
|
1686
|
-
break;
|
|
1205
|
+
// Generate recommendation card UI
|
|
1206
|
+
app.post("/recommendations/:id/generate", async (c) => {
|
|
1207
|
+
const cardId = c.req.param("id");
|
|
1208
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1209
|
+
const params = body.params ?? {};
|
|
1210
|
+
const context = body.context ?? {};
|
|
1211
|
+
let foundCard;
|
|
1212
|
+
let foundTool;
|
|
1213
|
+
for (const tool of tools) {
|
|
1214
|
+
const card = tool.recommendations?.find((r) => r.id === cardId);
|
|
1215
|
+
if (card) {
|
|
1216
|
+
foundCard = card;
|
|
1217
|
+
foundTool = tool;
|
|
1218
|
+
break;
|
|
1687
1219
|
}
|
|
1688
1220
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
const
|
|
1697
|
-
|
|
1221
|
+
if (!foundCard) {
|
|
1222
|
+
return c.json({ error: `Card not found: ${cardId}` }, 404);
|
|
1223
|
+
}
|
|
1224
|
+
const baseResponse = { id: cardId, toolId: foundTool?.id, toolName: foundTool?.name };
|
|
1225
|
+
if (!foundCard.inputSchema) {
|
|
1226
|
+
return c.json({ ...baseResponse, ui: foundCard.ui });
|
|
1227
|
+
}
|
|
1228
|
+
const parseResult = foundCard.inputSchema.safeParse(params);
|
|
1229
|
+
if (!parseResult.success) {
|
|
1230
|
+
return c.json({ error: "Invalid parameters", details: parseResult.error.format() }, 400);
|
|
1231
|
+
}
|
|
1232
|
+
if (typeof foundCard.ui !== "function") {
|
|
1233
|
+
return c.json({ error: `Card ${cardId} has inputSchema but ui is not a function` }, 500);
|
|
1234
|
+
}
|
|
1235
|
+
try {
|
|
1236
|
+
const ui = await foundCard.ui(parseResult.data, context);
|
|
1237
|
+
return c.json({ ...baseResponse, ui, params: parseResult.data });
|
|
1238
|
+
}
|
|
1239
|
+
catch (e) {
|
|
1240
|
+
const message = e instanceof Error ? e.message : "Unknown error";
|
|
1241
|
+
return c.json({ error: `Failed to generate UI: ${message}` }, 500);
|
|
1242
|
+
}
|
|
1698
1243
|
});
|
|
1699
1244
|
// Process request with plugins
|
|
1700
1245
|
async function processPluginsForRequest(request, agentInfo) {
|