@dainprotocol/service-sdk 2.0.91 → 2.0.93
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/api-sdk.js +20 -2
- package/dist/client/api-sdk.js.map +1 -1
- package/dist/client/client-auth.js +6 -7
- package/dist/client/client-auth.js.map +1 -1
- package/dist/client/client.d.ts +39 -6
- package/dist/client/client.js +256 -17
- package/dist/client/client.js.map +1 -1
- package/dist/client/types.d.ts +273 -6
- package/dist/client/types.js +129 -5
- package/dist/client/types.js.map +1 -1
- package/dist/lib/base58.d.ts +17 -0
- package/dist/lib/base58.js +60 -0
- package/dist/lib/base58.js.map +1 -0
- package/dist/lib/payments/index.js.map +1 -1
- package/dist/lib/schemaConversion.js.map +1 -1
- package/dist/lib/schemaStructure.js +1 -0
- package/dist/lib/schemaStructure.js.map +1 -1
- package/dist/lib/sseSignature.d.ts +30 -0
- package/dist/lib/sseSignature.js +38 -0
- package/dist/lib/sseSignature.js.map +1 -0
- package/dist/lib/toolDiscovery.d.ts +20 -0
- package/dist/lib/toolDiscovery.js +30 -0
- package/dist/lib/toolDiscovery.js.map +1 -0
- package/dist/service/auth.js +22 -13
- package/dist/service/auth.js.map +1 -1
- package/dist/service/core.js +50 -1
- package/dist/service/core.js.map +1 -1
- package/dist/service/processes.js +6 -6
- package/dist/service/processes.js.map +1 -1
- package/dist/service/server.js +573 -79
- package/dist/service/server.js.map +1 -1
- package/dist/service/types.d.ts +133 -4
- package/dist/service/webhooks.d.ts +8 -8
- package/dist/service/webhooks.js +16 -12
- package/dist/service/webhooks.js.map +1 -1
- package/package.json +28 -39
package/dist/service/server.js
CHANGED
|
@@ -9,15 +9,15 @@ 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");
|
|
12
13
|
const oauth2_1 = require("./oauth2");
|
|
13
14
|
const bs58_1 = tslib_1.__importDefault(require("bs58"));
|
|
14
15
|
const processes_1 = require("./processes");
|
|
15
16
|
const streaming_1 = require("hono/streaming");
|
|
16
17
|
const package_json_1 = tslib_1.__importDefault(require("../../package.json"));
|
|
17
18
|
const zod_1 = require("zod");
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const utils_1 = require("@noble/hashes/utils");
|
|
19
|
+
const utils_js_1 = require("@noble/hashes/utils.js");
|
|
20
|
+
const sseSignature_1 = require("../lib/sseSignature");
|
|
21
21
|
const auth_2 = require("./auth");
|
|
22
22
|
const core_1 = require("./core");
|
|
23
23
|
const hitl_1 = require("./hitl");
|
|
@@ -32,6 +32,46 @@ function debugWarn(...args) {
|
|
|
32
32
|
console.warn(...args);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Extract a user-safe error message. If `err` is a ZodError (or `err.message`
|
|
37
|
+
* looks like a raw Zod v4 issue-array JSON `"[{...}]"`), re-summarize as
|
|
38
|
+
* `"<path>: <msg>; <path>: <msg>"` so it never lands verbatim in chat text.
|
|
39
|
+
*
|
|
40
|
+
* Backstop for the tool-output validation leak: the 2026-04-17 whitelist
|
|
41
|
+
* regression surfaced `[{"code":"invalid_type","path":["markets"],...}]` in
|
|
42
|
+
* user-visible chat because the four SSE/non-SSE error exits below all do
|
|
43
|
+
* `text: \`Error: ${error.message}\``. This helper neutralizes that class
|
|
44
|
+
* of bug for ANY ZodError thrown anywhere in the tool-exec pipeline, not
|
|
45
|
+
* just the one we already fixed at the source in core.ts.
|
|
46
|
+
*/
|
|
47
|
+
function sanitizeErrorMessage(err) {
|
|
48
|
+
if (err instanceof zod_1.z.ZodError) {
|
|
49
|
+
const summary = err.issues
|
|
50
|
+
.map((i) => `${i.path.join(".") || "root"}: ${i.message}`)
|
|
51
|
+
.join("; ");
|
|
52
|
+
return summary || "Validation failed";
|
|
53
|
+
}
|
|
54
|
+
const raw = err?.message;
|
|
55
|
+
if (typeof raw !== "string" || raw.length === 0)
|
|
56
|
+
return "Unknown error";
|
|
57
|
+
const trimmed = raw.trimStart();
|
|
58
|
+
if (trimmed.startsWith("[{") || trimmed.startsWith("[ {")) {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(trimmed);
|
|
61
|
+
if (Array.isArray(parsed)) {
|
|
62
|
+
const summary = parsed
|
|
63
|
+
.map((i) => `${Array.isArray(i.path) ? i.path.join(".") || "root" : "root"}: ${i.message ?? "invalid"}`)
|
|
64
|
+
.join("; ");
|
|
65
|
+
if (summary)
|
|
66
|
+
return summary;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* fall through, return raw message */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return raw;
|
|
74
|
+
}
|
|
35
75
|
function getFirstForwardedValue(value) {
|
|
36
76
|
if (!value)
|
|
37
77
|
return undefined;
|
|
@@ -78,23 +118,28 @@ function requireScope(requiredScope) {
|
|
|
78
118
|
// Helper to sign and stream SSE events
|
|
79
119
|
function signedStreamSSE(c, privateKey, config, handler) {
|
|
80
120
|
return (0, streaming_1.streamSSE)(c, async (stream) => {
|
|
121
|
+
const requestSignal = c.req.raw?.signal;
|
|
122
|
+
const isAborted = () => stream.aborted || stream.closed || requestSignal?.aborted === true;
|
|
81
123
|
const signedStream = {
|
|
82
124
|
writeSSE: async (event) => {
|
|
125
|
+
if (isAborted())
|
|
126
|
+
return;
|
|
83
127
|
const timestamp = Date.now().toString();
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const signatureBytes = ed25519_1.ed25519.sign(messageHash, privateKey);
|
|
87
|
-
const signature = (0, utils_1.bytesToHex)(signatureBytes);
|
|
128
|
+
const messageHash = (0, sseSignature_1.hashEventMessage)(event.data, timestamp);
|
|
129
|
+
const signature = (0, utils_js_1.bytesToHex)(sseSignature_1.ed25519.sign(messageHash, privateKey));
|
|
88
130
|
// Fast path for non-critical events (progress/UI updates)
|
|
89
|
-
const isCriticalEvent = event.event === 'result' ||
|
|
131
|
+
const isCriticalEvent = event.event === 'result' ||
|
|
132
|
+
event.event === 'process-created' ||
|
|
133
|
+
event.event === 'datasource-update';
|
|
90
134
|
const dataWithSignature = isCriticalEvent
|
|
91
135
|
? JSON.stringify({
|
|
92
136
|
data: JSON.parse(event.data),
|
|
93
137
|
_signature: { signature, timestamp, agentId: config.identity.agentId, orgId: config.identity.orgId, address: config.identity.publicKey }
|
|
94
138
|
})
|
|
95
139
|
: `{"data":${event.data},"_signature":{"signature":"${signature}","timestamp":"${timestamp}","agentId":"${config.identity.agentId}","orgId":"${config.identity.orgId}","address":"${config.identity.publicKey}"}}`;
|
|
96
|
-
|
|
97
|
-
}
|
|
140
|
+
await stream.writeSSE({ event: event.event, data: dataWithSignature, id: event.id });
|
|
141
|
+
},
|
|
142
|
+
isAborted,
|
|
98
143
|
};
|
|
99
144
|
await handler(signedStream);
|
|
100
145
|
});
|
|
@@ -174,11 +219,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
174
219
|
const isHITLPath = hitlPath && c.req.path === hitlPath;
|
|
175
220
|
if (c.req.path.startsWith("/oauth2/callback/") ||
|
|
176
221
|
c.req.path.startsWith("/addons") ||
|
|
177
|
-
c.req.path.startsWith("/getAllToolsAsJsonSchema") ||
|
|
178
|
-
c.req.path.startsWith("/getSkills") ||
|
|
179
|
-
c.req.path.startsWith("/getWebhookTriggers") ||
|
|
180
222
|
c.req.path.startsWith("/metadata") ||
|
|
181
|
-
c.req.path.startsWith("/recommendations") ||
|
|
182
223
|
c.req.path.startsWith("/ping") ||
|
|
183
224
|
isWebhookPath ||
|
|
184
225
|
isHITLPath) {
|
|
@@ -300,15 +341,73 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
300
341
|
debugLog("[getAgentInfo] API Key - Returning agent info:", agentInfo);
|
|
301
342
|
return agentInfo;
|
|
302
343
|
}
|
|
344
|
+
async function resolveHealthStatus() {
|
|
345
|
+
if (!config.healthCheck)
|
|
346
|
+
return true;
|
|
347
|
+
try {
|
|
348
|
+
return await config.healthCheck();
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
303
354
|
// Setup default ping route
|
|
304
|
-
app.get("/ping", (c) =>
|
|
355
|
+
app.get("/ping", async (c) => {
|
|
356
|
+
const healthy = await resolveHealthStatus();
|
|
357
|
+
return c.json({
|
|
358
|
+
message: healthy ? "pong" : "unhealthy",
|
|
359
|
+
platform: "DAIN",
|
|
360
|
+
service_version: metadata.version,
|
|
361
|
+
dain_sdk_version: package_json_1.default.version,
|
|
362
|
+
}, healthy ? 200 : 503);
|
|
363
|
+
});
|
|
305
364
|
// Metadata endpoint - includes computed supportsUserActions from tools
|
|
306
365
|
app.get("/metadata", (c) => {
|
|
307
366
|
// Compute service-level capability: does ANY tool support user actions (HITL)?
|
|
308
367
|
const supportsUserActions = tools.some((tool) => tool.supportsUserActions === true);
|
|
368
|
+
const requestedContract = c.req.header("x-butterfly-contract") || c.req.header("X-Butterfly-Contract");
|
|
369
|
+
const sdkMajor = Number.parseInt(String(package_json_1.default.version).split(".")[0] || "0", 10);
|
|
370
|
+
const contractVersion = Number.isFinite(sdkMajor) && sdkMajor > 0 ? `${sdkMajor}.0.0` : "0.0.0";
|
|
371
|
+
const capabilities = {
|
|
372
|
+
tools: true,
|
|
373
|
+
contexts: true,
|
|
374
|
+
widgets: true,
|
|
375
|
+
datasources: true,
|
|
376
|
+
streamingTools: true,
|
|
377
|
+
streamingDatasources: true,
|
|
378
|
+
datasourcePolicy: true,
|
|
379
|
+
widgetPolicy: true,
|
|
380
|
+
toolSafety: true,
|
|
381
|
+
};
|
|
382
|
+
const compatibility = (() => {
|
|
383
|
+
if (!requestedContract)
|
|
384
|
+
return undefined;
|
|
385
|
+
const requestedMajor = Number.parseInt(String(requestedContract).split(".")[0] || "0", 10);
|
|
386
|
+
if (!Number.isFinite(requestedMajor) || requestedMajor <= 0) {
|
|
387
|
+
return {
|
|
388
|
+
requested: requestedContract,
|
|
389
|
+
contractVersion,
|
|
390
|
+
ok: false,
|
|
391
|
+
reason: "Invalid requested contract version",
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const ok = requestedMajor === sdkMajor;
|
|
395
|
+
return ok
|
|
396
|
+
? { requested: requestedContract, contractVersion, ok: true }
|
|
397
|
+
: {
|
|
398
|
+
requested: requestedContract,
|
|
399
|
+
contractVersion,
|
|
400
|
+
ok: false,
|
|
401
|
+
reason: `Requested major ${requestedMajor} does not match service major ${sdkMajor}`,
|
|
402
|
+
};
|
|
403
|
+
})();
|
|
309
404
|
return c.json({
|
|
310
405
|
...metadata,
|
|
311
406
|
supportsUserActions,
|
|
407
|
+
dainSdkVersion: package_json_1.default.version,
|
|
408
|
+
contractVersion,
|
|
409
|
+
capabilities,
|
|
410
|
+
...(compatibility ? { compatibility } : {}),
|
|
312
411
|
});
|
|
313
412
|
});
|
|
314
413
|
// Tools list endpoint
|
|
@@ -327,6 +426,10 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
327
426
|
outputSchema,
|
|
328
427
|
interface: tool.interface,
|
|
329
428
|
suggestConfirmation: tool.suggestConfirmation,
|
|
429
|
+
sideEffectClass: tool.sideEffectClass,
|
|
430
|
+
supportsParallel: tool.supportsParallel,
|
|
431
|
+
idempotencyScope: tool.idempotencyScope,
|
|
432
|
+
maxConcurrencyHint: tool.maxConcurrencyHint,
|
|
330
433
|
supportsUserActions: tool.supportsUserActions,
|
|
331
434
|
};
|
|
332
435
|
});
|
|
@@ -427,14 +530,26 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
427
530
|
oauth2Client,
|
|
428
531
|
app
|
|
429
532
|
});
|
|
430
|
-
|
|
533
|
+
const response = {
|
|
431
534
|
id: widget.id,
|
|
432
535
|
name: widget.name,
|
|
433
536
|
description: widget.description,
|
|
434
537
|
icon: widget.icon,
|
|
435
538
|
size: widget.size || "sm",
|
|
539
|
+
refreshIntervalMs: widget.refreshIntervalMs,
|
|
436
540
|
...widgetData
|
|
437
541
|
};
|
|
542
|
+
if (!("freshness" in response)) {
|
|
543
|
+
response.freshness = {
|
|
544
|
+
freshAt: Date.now(),
|
|
545
|
+
transport: "poll",
|
|
546
|
+
requestedPolicy: {
|
|
547
|
+
refreshIntervalMs: widget.refreshIntervalMs,
|
|
548
|
+
},
|
|
549
|
+
scope: "account",
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return response;
|
|
438
553
|
}));
|
|
439
554
|
const validWidgets = widgetsFull.filter(w => w !== null);
|
|
440
555
|
const processedResponse = await processPluginsForResponse(validWidgets, body, { extraData: { plugins: processedPluginData.plugins } });
|
|
@@ -475,8 +590,19 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
475
590
|
description: widget.description,
|
|
476
591
|
icon: widget.icon,
|
|
477
592
|
size: widget.size || "sm",
|
|
593
|
+
refreshIntervalMs: widget.refreshIntervalMs,
|
|
478
594
|
...widgetData
|
|
479
595
|
};
|
|
596
|
+
if (!("freshness" in response)) {
|
|
597
|
+
response.freshness = {
|
|
598
|
+
freshAt: Date.now(),
|
|
599
|
+
transport: "poll",
|
|
600
|
+
requestedPolicy: {
|
|
601
|
+
refreshIntervalMs: widget.refreshIntervalMs,
|
|
602
|
+
},
|
|
603
|
+
scope: "account",
|
|
604
|
+
};
|
|
605
|
+
}
|
|
480
606
|
const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
|
|
481
607
|
return c.json(processedResponse);
|
|
482
608
|
});
|
|
@@ -506,6 +632,12 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
506
632
|
name: datasource.name,
|
|
507
633
|
description: datasource.description,
|
|
508
634
|
type: datasource.type,
|
|
635
|
+
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
636
|
+
maxStalenessMs: datasource.maxStalenessMs,
|
|
637
|
+
transport: datasource.transport,
|
|
638
|
+
priority: datasource.priority,
|
|
639
|
+
scope: datasource.scope,
|
|
640
|
+
dataClass: datasource.dataClass,
|
|
509
641
|
inputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(datasource.input),
|
|
510
642
|
};
|
|
511
643
|
}
|
|
@@ -543,12 +675,32 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
543
675
|
plugins: pluginsData,
|
|
544
676
|
oauth2Client
|
|
545
677
|
});
|
|
678
|
+
const requestedPolicy = {
|
|
679
|
+
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
680
|
+
maxStalenessMs: datasource.maxStalenessMs,
|
|
681
|
+
transport: datasource.transport,
|
|
682
|
+
priority: datasource.priority,
|
|
683
|
+
scope: datasource.scope,
|
|
684
|
+
dataClass: datasource.dataClass,
|
|
685
|
+
};
|
|
546
686
|
const response = {
|
|
547
687
|
id: datasource.id,
|
|
548
688
|
name: datasource.name,
|
|
549
689
|
description: datasource.description,
|
|
550
690
|
type: datasource.type,
|
|
691
|
+
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
692
|
+
maxStalenessMs: datasource.maxStalenessMs,
|
|
693
|
+
transport: datasource.transport,
|
|
694
|
+
priority: datasource.priority,
|
|
695
|
+
scope: datasource.scope,
|
|
696
|
+
dataClass: datasource.dataClass,
|
|
551
697
|
data,
|
|
698
|
+
freshness: {
|
|
699
|
+
freshAt: Date.now(),
|
|
700
|
+
transport: datasource.transport || "poll",
|
|
701
|
+
requestedPolicy,
|
|
702
|
+
scope: datasource.scope,
|
|
703
|
+
},
|
|
552
704
|
};
|
|
553
705
|
const processedResponse = await processPluginsForResponse(response, { plugins: pluginsData }, { extraData: { plugins: pluginsData } });
|
|
554
706
|
return c.json(processedResponse);
|
|
@@ -566,6 +718,149 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
566
718
|
throw error;
|
|
567
719
|
}
|
|
568
720
|
});
|
|
721
|
+
// Stream datasource updates over SSE. This is primarily used for "fresh" UIs
|
|
722
|
+
// (positions/orders) where clients want stream-first updates with a poll fallback.
|
|
723
|
+
//
|
|
724
|
+
// Note: This does not require services to implement a separate streaming backend.
|
|
725
|
+
// The server re-runs the datasource handler on an interval and streams results.
|
|
726
|
+
app.post("/datasources/:datasourceId/stream", async (c) => {
|
|
727
|
+
const datasource = datasources.find((ds) => ds.id === c.req.param("datasourceId"));
|
|
728
|
+
if (!datasource) {
|
|
729
|
+
throw new http_exception_1.HTTPException(404, { message: "Datasource not found" });
|
|
730
|
+
}
|
|
731
|
+
const agentInfo = await getAgentInfo(c);
|
|
732
|
+
const rawBody = await safeParseBody(c);
|
|
733
|
+
const hasWrappedParams = rawBody &&
|
|
734
|
+
typeof rawBody === "object" &&
|
|
735
|
+
!Array.isArray(rawBody) &&
|
|
736
|
+
"params" in rawBody;
|
|
737
|
+
const requestedIntervalMsRaw = hasWrappedParams ? rawBody.intervalMs : undefined;
|
|
738
|
+
const requestParamsRaw = hasWrappedParams ? rawBody.params : rawBody;
|
|
739
|
+
let params = await processPluginsForRequest(requestParamsRaw && typeof requestParamsRaw === "object" ? requestParamsRaw : {}, agentInfo);
|
|
740
|
+
const pluginsData = (params.plugins && typeof params.plugins === "object"
|
|
741
|
+
? params.plugins
|
|
742
|
+
: {});
|
|
743
|
+
delete params.plugins;
|
|
744
|
+
let parsedParams;
|
|
745
|
+
try {
|
|
746
|
+
parsedParams = datasource.input.parse(params);
|
|
747
|
+
}
|
|
748
|
+
catch (error) {
|
|
749
|
+
if (error instanceof zod_1.z.ZodError) {
|
|
750
|
+
const missingParams = error.issues
|
|
751
|
+
.map((issue) => issue.path.join("."))
|
|
752
|
+
.join(", ");
|
|
753
|
+
return c.json({
|
|
754
|
+
error: `Missing or invalid parameters: ${missingParams}`,
|
|
755
|
+
code: "INVALID_PARAMS"
|
|
756
|
+
}, 400);
|
|
757
|
+
}
|
|
758
|
+
throw error;
|
|
759
|
+
}
|
|
760
|
+
const pluginRequestContext = hasWrappedParams
|
|
761
|
+
? {
|
|
762
|
+
params: parsedParams,
|
|
763
|
+
intervalMs: requestedIntervalMsRaw,
|
|
764
|
+
plugins: pluginsData,
|
|
765
|
+
}
|
|
766
|
+
: {
|
|
767
|
+
...(typeof parsedParams === "object" && parsedParams !== null
|
|
768
|
+
? parsedParams
|
|
769
|
+
: {}),
|
|
770
|
+
plugins: pluginsData,
|
|
771
|
+
};
|
|
772
|
+
const oauth2Client = app.oauth2?.getClient();
|
|
773
|
+
const requestedPolicy = {
|
|
774
|
+
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
775
|
+
maxStalenessMs: datasource.maxStalenessMs,
|
|
776
|
+
transport: datasource.transport,
|
|
777
|
+
priority: datasource.priority,
|
|
778
|
+
scope: datasource.scope,
|
|
779
|
+
dataClass: datasource.dataClass,
|
|
780
|
+
};
|
|
781
|
+
const requestedIntervalMs = typeof requestedIntervalMsRaw === "number" && Number.isFinite(requestedIntervalMsRaw) && requestedIntervalMsRaw > 0
|
|
782
|
+
? requestedIntervalMsRaw
|
|
783
|
+
: null;
|
|
784
|
+
const baseIntervalMs = requestedIntervalMs ??
|
|
785
|
+
(typeof datasource.refreshIntervalMs === "number" && datasource.refreshIntervalMs > 0
|
|
786
|
+
? datasource.refreshIntervalMs
|
|
787
|
+
: 15_000);
|
|
788
|
+
const intervalMs = Math.max(1_000, Math.floor(baseIntervalMs));
|
|
789
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
790
|
+
debugLog(`[SSE] Datasource ${datasource.id} stream start (${intervalMs}ms interval)`);
|
|
791
|
+
return signedStreamSSE(c, privateKey, config, async (stream) => {
|
|
792
|
+
let eventId = 0;
|
|
793
|
+
const emitUpdate = async () => {
|
|
794
|
+
const data = await datasource.getDatasource(agentInfo, parsedParams, {
|
|
795
|
+
plugins: pluginsData,
|
|
796
|
+
oauth2Client,
|
|
797
|
+
});
|
|
798
|
+
const response = {
|
|
799
|
+
id: datasource.id,
|
|
800
|
+
name: datasource.name,
|
|
801
|
+
description: datasource.description,
|
|
802
|
+
type: datasource.type,
|
|
803
|
+
refreshIntervalMs: datasource.refreshIntervalMs,
|
|
804
|
+
maxStalenessMs: datasource.maxStalenessMs,
|
|
805
|
+
transport: datasource.transport,
|
|
806
|
+
priority: datasource.priority,
|
|
807
|
+
scope: datasource.scope,
|
|
808
|
+
dataClass: datasource.dataClass,
|
|
809
|
+
data,
|
|
810
|
+
freshness: {
|
|
811
|
+
freshAt: Date.now(),
|
|
812
|
+
transport: "stream",
|
|
813
|
+
requestedPolicy,
|
|
814
|
+
scope: datasource.scope,
|
|
815
|
+
},
|
|
816
|
+
};
|
|
817
|
+
const processedResponse = await processPluginsForResponse(response, pluginRequestContext, { extraData: { plugins: pluginsData } });
|
|
818
|
+
await stream.writeSSE({
|
|
819
|
+
event: "datasource-update",
|
|
820
|
+
data: JSON.stringify(processedResponse),
|
|
821
|
+
id: String(eventId++),
|
|
822
|
+
});
|
|
823
|
+
};
|
|
824
|
+
// Send initial snapshot immediately.
|
|
825
|
+
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;
|
|
845
|
+
try {
|
|
846
|
+
await emitUpdate();
|
|
847
|
+
}
|
|
848
|
+
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()) {
|
|
854
|
+
await stream.writeSSE({
|
|
855
|
+
event: "error",
|
|
856
|
+
data: JSON.stringify({ message: error?.message || "Datasource stream error" }),
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
debugLog(`[SSE] Datasource ${datasource.id} stream end`);
|
|
862
|
+
});
|
|
863
|
+
});
|
|
569
864
|
function mapAgentInfo(agent) {
|
|
570
865
|
return {
|
|
571
866
|
id: agent.id,
|
|
@@ -642,13 +937,23 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
642
937
|
outputSchema,
|
|
643
938
|
interface: tool.interface,
|
|
644
939
|
suggestConfirmation: tool.suggestConfirmation,
|
|
940
|
+
sideEffectClass: tool.sideEffectClass,
|
|
941
|
+
supportsParallel: tool.supportsParallel,
|
|
942
|
+
idempotencyScope: tool.idempotencyScope,
|
|
943
|
+
maxConcurrencyHint: tool.maxConcurrencyHint,
|
|
645
944
|
supportsUserActions: tool.supportsUserActions,
|
|
646
945
|
};
|
|
647
946
|
}
|
|
648
947
|
app.get("/getAllToolsAsJsonSchema", (c) => {
|
|
948
|
+
const toolInfo = tools.map(mapToolToJsonSchema);
|
|
949
|
+
const reccomendedPrompts = toolboxes.map((toolbox) => toolbox.recommendedPrompt);
|
|
649
950
|
return c.json({
|
|
650
|
-
tools:
|
|
651
|
-
reccomendedPrompts
|
|
951
|
+
tools: toolInfo,
|
|
952
|
+
reccomendedPrompts,
|
|
953
|
+
schemaVersion: (0, toolDiscovery_1.computeToolDiscoverySchemaVersion)({
|
|
954
|
+
tools: toolInfo,
|
|
955
|
+
reccomendedPrompts,
|
|
956
|
+
}),
|
|
652
957
|
});
|
|
653
958
|
});
|
|
654
959
|
app.post("/getAllToolsAsJsonSchema", async (c) => {
|
|
@@ -657,9 +962,14 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
657
962
|
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
658
963
|
const toolInfo = tools.map(mapToolToJsonSchema);
|
|
659
964
|
debugLog(`[getAllToolsAsJsonSchema POST] Returning ${toolInfo.length} tools`);
|
|
965
|
+
const reccomendedPrompts = toolboxes.map((toolbox) => toolbox.recommendedPrompt);
|
|
660
966
|
const response = {
|
|
661
967
|
tools: toolInfo,
|
|
662
|
-
reccomendedPrompts
|
|
968
|
+
reccomendedPrompts,
|
|
969
|
+
schemaVersion: (0, toolDiscovery_1.computeToolDiscoverySchemaVersion)({
|
|
970
|
+
tools: toolInfo,
|
|
971
|
+
reccomendedPrompts,
|
|
972
|
+
}),
|
|
663
973
|
};
|
|
664
974
|
const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
|
|
665
975
|
return c.json(processedResponse);
|
|
@@ -846,6 +1156,10 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
846
1156
|
outputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(tool.output),
|
|
847
1157
|
interface: tool.interface,
|
|
848
1158
|
suggestConfirmation: tool.suggestConfirmation,
|
|
1159
|
+
sideEffectClass: tool.sideEffectClass,
|
|
1160
|
+
supportsParallel: tool.supportsParallel,
|
|
1161
|
+
idempotencyScope: tool.idempotencyScope,
|
|
1162
|
+
maxConcurrencyHint: tool.maxConcurrencyHint,
|
|
849
1163
|
};
|
|
850
1164
|
return c.json(toolDetails);
|
|
851
1165
|
}
|
|
@@ -873,6 +1187,11 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
873
1187
|
pricing: tool.pricing,
|
|
874
1188
|
inputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(tool.input),
|
|
875
1189
|
outputSchema: (0, schemaStructure_1.getDetailedSchemaStructure)(tool.output),
|
|
1190
|
+
suggestConfirmation: tool.suggestConfirmation,
|
|
1191
|
+
sideEffectClass: tool.sideEffectClass,
|
|
1192
|
+
supportsParallel: tool.supportsParallel,
|
|
1193
|
+
idempotencyScope: tool.idempotencyScope,
|
|
1194
|
+
maxConcurrencyHint: tool.maxConcurrencyHint,
|
|
876
1195
|
}
|
|
877
1196
|
: {
|
|
878
1197
|
id: toolId,
|
|
@@ -898,7 +1217,13 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
898
1217
|
}
|
|
899
1218
|
});
|
|
900
1219
|
// Health check endpoint
|
|
901
|
-
app.get("/health", (c) =>
|
|
1220
|
+
app.get("/health", async (c) => {
|
|
1221
|
+
const healthy = await resolveHealthStatus();
|
|
1222
|
+
return c.json({
|
|
1223
|
+
status: healthy ? "healthy" : "unhealthy",
|
|
1224
|
+
timestamp: new Date().toISOString(),
|
|
1225
|
+
}, healthy ? 200 : 503);
|
|
1226
|
+
});
|
|
902
1227
|
// Setup custom routes if provided
|
|
903
1228
|
if (config.routes) {
|
|
904
1229
|
config.routes(app);
|
|
@@ -1182,64 +1507,231 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1182
1507
|
});
|
|
1183
1508
|
// Toolboxes list endpoint
|
|
1184
1509
|
app.get("/toolboxes", (c) => c.json(toolboxes));
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1510
|
+
function normalizeRecommendationText(value) {
|
|
1511
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
1512
|
+
}
|
|
1513
|
+
function tokenizeRecommendationQuery(query) {
|
|
1514
|
+
const normalized = normalizeRecommendationText(query);
|
|
1515
|
+
if (!normalized)
|
|
1516
|
+
return [];
|
|
1517
|
+
return Array.from(new Set(normalized.split(/\s+/).filter((token) => token.length >= 2)));
|
|
1518
|
+
}
|
|
1519
|
+
function buildRecommendationIndex() {
|
|
1520
|
+
const index = new Map();
|
|
1188
1521
|
for (const tool of tools) {
|
|
1189
|
-
if (tool.recommendations?.length)
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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
|
-
});
|
|
1522
|
+
if (!tool.recommendations?.length)
|
|
1523
|
+
continue;
|
|
1524
|
+
for (const card of tool.recommendations) {
|
|
1525
|
+
const scopedCardId = `${tool.id}:${card.id}`;
|
|
1526
|
+
if (index.has(scopedCardId)) {
|
|
1527
|
+
throw new Error(`Duplicate recommendation card ID "${scopedCardId}". Card IDs must be unique per tool.`);
|
|
1200
1528
|
}
|
|
1529
|
+
const isDynamic = !!card.inputSchema;
|
|
1530
|
+
const inputSchema = isDynamic ? (0, schemaStructure_1.zodToJsonSchema)(card.inputSchema) : undefined;
|
|
1531
|
+
const searchText = normalizeRecommendationText([tool.id, tool.name, card.id, ...card.tags].join(" "));
|
|
1532
|
+
index.set(scopedCardId, {
|
|
1533
|
+
cardId: scopedCardId,
|
|
1534
|
+
localCardId: card.id,
|
|
1535
|
+
toolId: tool.id,
|
|
1536
|
+
toolName: tool.name,
|
|
1537
|
+
tags: card.tags,
|
|
1538
|
+
inputSchema,
|
|
1539
|
+
ui: isDynamic ? undefined : card.ui,
|
|
1540
|
+
isDynamic,
|
|
1541
|
+
card,
|
|
1542
|
+
searchText,
|
|
1543
|
+
});
|
|
1201
1544
|
}
|
|
1202
1545
|
}
|
|
1203
|
-
return
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1546
|
+
return index;
|
|
1547
|
+
}
|
|
1548
|
+
function scoreRecommendationCard(card, tokens) {
|
|
1549
|
+
if (tokens.length === 0)
|
|
1550
|
+
return 0;
|
|
1551
|
+
let score = 0;
|
|
1552
|
+
const tagSet = new Set(card.tags.map((tag) => normalizeRecommendationText(tag)));
|
|
1553
|
+
const scopedId = normalizeRecommendationText(card.cardId);
|
|
1554
|
+
const toolName = normalizeRecommendationText(card.toolName);
|
|
1555
|
+
for (const token of tokens) {
|
|
1556
|
+
if (scopedId === token || card.localCardId.toLowerCase() === token) {
|
|
1557
|
+
score += 6;
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
if (tagSet.has(token)) {
|
|
1561
|
+
score += 4;
|
|
1562
|
+
}
|
|
1563
|
+
if (toolName.includes(token)) {
|
|
1564
|
+
score += 3;
|
|
1565
|
+
}
|
|
1566
|
+
if (card.searchText.includes(token)) {
|
|
1567
|
+
score += 1;
|
|
1219
1568
|
}
|
|
1220
1569
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1570
|
+
return score;
|
|
1571
|
+
}
|
|
1572
|
+
const recommendationCardsById = buildRecommendationIndex();
|
|
1573
|
+
const recommendationSearchSchema = zod_1.z.object({
|
|
1574
|
+
query: zod_1.z.string().optional(),
|
|
1575
|
+
limit: zod_1.z.number().int().min(1).max(50).optional().default(12),
|
|
1576
|
+
toolIds: zod_1.z.array(zod_1.z.string()).optional(),
|
|
1577
|
+
includeStaticUI: zod_1.z.boolean().optional().default(false),
|
|
1578
|
+
});
|
|
1579
|
+
app.post("/recommendations/search", async (c) => {
|
|
1580
|
+
const agentInfo = await getAgentInfo(c);
|
|
1581
|
+
const body = await safeParseBody(c);
|
|
1582
|
+
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
1583
|
+
const requestData = { ...processedPluginData };
|
|
1584
|
+
delete requestData.plugins;
|
|
1585
|
+
const parsed = recommendationSearchSchema.safeParse(requestData);
|
|
1586
|
+
if (!parsed.success) {
|
|
1587
|
+
return c.json({
|
|
1588
|
+
error: "Invalid recommendation search request",
|
|
1589
|
+
details: parsed.error.format(),
|
|
1590
|
+
}, 400);
|
|
1591
|
+
}
|
|
1592
|
+
const { query, limit, toolIds, includeStaticUI, } = parsed.data;
|
|
1593
|
+
const toolIdSet = toolIds?.length ? new Set(toolIds) : null;
|
|
1594
|
+
const filteredCards = Array.from(recommendationCardsById.values()).filter((card) => !toolIdSet || toolIdSet.has(card.toolId));
|
|
1595
|
+
const tokens = tokenizeRecommendationQuery(query ?? "");
|
|
1596
|
+
const ranked = filteredCards
|
|
1597
|
+
.map((card, index) => ({
|
|
1598
|
+
...card,
|
|
1599
|
+
score: scoreRecommendationCard(card, tokens),
|
|
1600
|
+
index,
|
|
1601
|
+
}))
|
|
1602
|
+
.sort((a, b) => {
|
|
1603
|
+
if (tokens.length === 0)
|
|
1604
|
+
return a.index - b.index;
|
|
1605
|
+
if (b.score !== a.score)
|
|
1606
|
+
return b.score - a.score;
|
|
1607
|
+
if (a.toolName !== b.toolName)
|
|
1608
|
+
return a.toolName.localeCompare(b.toolName);
|
|
1609
|
+
return a.localCardId.localeCompare(b.localCardId);
|
|
1610
|
+
})
|
|
1611
|
+
.slice(0, limit);
|
|
1612
|
+
const cards = ranked.map((card) => ({
|
|
1613
|
+
cardId: card.cardId,
|
|
1614
|
+
localCardId: card.localCardId,
|
|
1615
|
+
toolId: card.toolId,
|
|
1616
|
+
toolName: card.toolName,
|
|
1617
|
+
tags: card.tags,
|
|
1618
|
+
score: card.score,
|
|
1619
|
+
inputSchema: card.inputSchema,
|
|
1620
|
+
ui: includeStaticUI && !card.isDynamic ? card.ui : undefined,
|
|
1621
|
+
isDynamic: card.isDynamic,
|
|
1622
|
+
}));
|
|
1623
|
+
const response = {
|
|
1624
|
+
cards,
|
|
1625
|
+
count: cards.length,
|
|
1626
|
+
query,
|
|
1627
|
+
};
|
|
1628
|
+
const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
|
|
1629
|
+
return c.json(processedResponse);
|
|
1630
|
+
});
|
|
1631
|
+
const recommendationRenderSchema = zod_1.z.object({
|
|
1632
|
+
cards: zod_1.z
|
|
1633
|
+
.array(zod_1.z.object({
|
|
1634
|
+
cardId: zod_1.z.string().min(1),
|
|
1635
|
+
params: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
1636
|
+
context: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
1637
|
+
}))
|
|
1638
|
+
.min(1)
|
|
1639
|
+
.max(8),
|
|
1640
|
+
continueOnError: zod_1.z.boolean().optional().default(true),
|
|
1641
|
+
});
|
|
1642
|
+
app.post("/recommendations/render", async (c) => {
|
|
1643
|
+
const agentInfo = await getAgentInfo(c);
|
|
1644
|
+
const body = await safeParseBody(c);
|
|
1645
|
+
const processedPluginData = await processPluginsForRequest(body, agentInfo);
|
|
1646
|
+
const requestData = { ...processedPluginData };
|
|
1647
|
+
delete requestData.plugins;
|
|
1648
|
+
const parsed = recommendationRenderSchema.safeParse(requestData);
|
|
1649
|
+
if (!parsed.success) {
|
|
1650
|
+
return c.json({
|
|
1651
|
+
error: "Invalid recommendation render request",
|
|
1652
|
+
details: parsed.error.format(),
|
|
1653
|
+
}, 400);
|
|
1654
|
+
}
|
|
1655
|
+
const { cards: requestedCards, continueOnError } = parsed.data;
|
|
1656
|
+
const renderedCards = [];
|
|
1657
|
+
const renderErrors = [];
|
|
1658
|
+
for (const requested of requestedCards) {
|
|
1659
|
+
const card = recommendationCardsById.get(requested.cardId);
|
|
1660
|
+
if (!card) {
|
|
1661
|
+
renderErrors.push({
|
|
1662
|
+
cardId: requested.cardId,
|
|
1663
|
+
code: "not_found",
|
|
1664
|
+
message: `Card not found: ${requested.cardId}`,
|
|
1665
|
+
});
|
|
1666
|
+
if (!continueOnError)
|
|
1667
|
+
break;
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
if (!card.isDynamic) {
|
|
1671
|
+
renderedCards.push({
|
|
1672
|
+
cardId: card.cardId,
|
|
1673
|
+
localCardId: card.localCardId,
|
|
1674
|
+
toolId: card.toolId,
|
|
1675
|
+
toolName: card.toolName,
|
|
1676
|
+
tags: card.tags,
|
|
1677
|
+
ui: card.card.ui,
|
|
1678
|
+
});
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
const paramsInput = requested.params ?? {};
|
|
1682
|
+
const parseResult = card.card.inputSchema.safeParse(paramsInput);
|
|
1683
|
+
if (!parseResult.success) {
|
|
1684
|
+
renderErrors.push({
|
|
1685
|
+
cardId: card.cardId,
|
|
1686
|
+
code: "invalid_params",
|
|
1687
|
+
message: "Invalid parameters",
|
|
1688
|
+
details: parseResult.error.format(),
|
|
1689
|
+
});
|
|
1690
|
+
if (!continueOnError)
|
|
1691
|
+
break;
|
|
1692
|
+
continue;
|
|
1693
|
+
}
|
|
1694
|
+
if (typeof card.card.ui !== "function") {
|
|
1695
|
+
renderErrors.push({
|
|
1696
|
+
cardId: card.cardId,
|
|
1697
|
+
code: "render_failed",
|
|
1698
|
+
message: `Card "${card.cardId}" is dynamic but has no UI generator function`,
|
|
1699
|
+
});
|
|
1700
|
+
if (!continueOnError)
|
|
1701
|
+
break;
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
try {
|
|
1705
|
+
const ui = await card.card.ui(parseResult.data, requested.context ?? {});
|
|
1706
|
+
renderedCards.push({
|
|
1707
|
+
cardId: card.cardId,
|
|
1708
|
+
localCardId: card.localCardId,
|
|
1709
|
+
toolId: card.toolId,
|
|
1710
|
+
toolName: card.toolName,
|
|
1711
|
+
tags: card.tags,
|
|
1712
|
+
ui,
|
|
1713
|
+
params: parseResult.data,
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
catch (error) {
|
|
1717
|
+
renderErrors.push({
|
|
1718
|
+
cardId: card.cardId,
|
|
1719
|
+
code: "render_failed",
|
|
1720
|
+
message: error instanceof Error ? error.message : "Failed to render recommendation card",
|
|
1721
|
+
});
|
|
1722
|
+
if (!continueOnError)
|
|
1723
|
+
break;
|
|
1724
|
+
}
|
|
1242
1725
|
}
|
|
1726
|
+
const response = {
|
|
1727
|
+
cards: renderedCards,
|
|
1728
|
+
errors: renderErrors,
|
|
1729
|
+
renderedCount: renderedCards.length,
|
|
1730
|
+
requestedCount: requestedCards.length,
|
|
1731
|
+
};
|
|
1732
|
+
const processedResponse = await processPluginsForResponse(response, body, { extraData: { plugins: processedPluginData.plugins } });
|
|
1733
|
+
const statusCode = !continueOnError && renderErrors.length > 0 ? 400 : 200;
|
|
1734
|
+
return c.json(processedResponse, statusCode);
|
|
1243
1735
|
});
|
|
1244
1736
|
// Process request with plugins
|
|
1245
1737
|
async function processPluginsForRequest(request, agentInfo) {
|
|
@@ -1420,6 +1912,7 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1420
1912
|
}
|
|
1421
1913
|
catch (error) {
|
|
1422
1914
|
console.error(`Error in stream for ${tool.id}:`, error);
|
|
1915
|
+
const safeMsg = sanitizeErrorMessage(error);
|
|
1423
1916
|
// Send an error result rather than letting the stream end without a response
|
|
1424
1917
|
try {
|
|
1425
1918
|
if (withContext) {
|
|
@@ -1427,8 +1920,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1427
1920
|
event: 'result',
|
|
1428
1921
|
data: JSON.stringify({
|
|
1429
1922
|
toolResult: {
|
|
1430
|
-
error:
|
|
1431
|
-
text: `Error: ${
|
|
1923
|
+
error: safeMsg,
|
|
1924
|
+
text: `Error: ${safeMsg}`,
|
|
1432
1925
|
data: null,
|
|
1433
1926
|
ui: null
|
|
1434
1927
|
},
|
|
@@ -1441,8 +1934,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1441
1934
|
await stream.writeSSE({
|
|
1442
1935
|
event: 'result',
|
|
1443
1936
|
data: JSON.stringify({
|
|
1444
|
-
error:
|
|
1445
|
-
text: `Error: ${
|
|
1937
|
+
error: safeMsg,
|
|
1938
|
+
text: `Error: ${safeMsg}`,
|
|
1446
1939
|
data: null,
|
|
1447
1940
|
ui: null
|
|
1448
1941
|
}),
|
|
@@ -1534,13 +2027,14 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1534
2027
|
}
|
|
1535
2028
|
catch (error) {
|
|
1536
2029
|
console.error(`Error executing tool ${tool.id} (non-streaming):`, error);
|
|
2030
|
+
const safeMsg = sanitizeErrorMessage(error);
|
|
1537
2031
|
// Return formatted error response to match streaming behavior
|
|
1538
2032
|
let errorResponse;
|
|
1539
2033
|
if (withContext) {
|
|
1540
2034
|
errorResponse = {
|
|
1541
2035
|
toolResult: {
|
|
1542
|
-
error:
|
|
1543
|
-
text: `Error: ${
|
|
2036
|
+
error: safeMsg,
|
|
2037
|
+
text: `Error: ${safeMsg}`,
|
|
1544
2038
|
data: null,
|
|
1545
2039
|
ui: null
|
|
1546
2040
|
},
|
|
@@ -1549,8 +2043,8 @@ function setupHttpServer(config, tools, services, toolboxes, metadata, privateKe
|
|
|
1549
2043
|
}
|
|
1550
2044
|
else {
|
|
1551
2045
|
errorResponse = {
|
|
1552
|
-
error:
|
|
1553
|
-
text: `Error: ${
|
|
2046
|
+
error: safeMsg,
|
|
2047
|
+
text: `Error: ${safeMsg}`,
|
|
1554
2048
|
data: null,
|
|
1555
2049
|
ui: null
|
|
1556
2050
|
};
|