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