dexto 1.1.10 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -84
- package/dist/agents/agent-registry.json +39 -1
- package/dist/agents/agent-template.yml +1 -1
- package/dist/agents/coding-agent/README.md +188 -0
- package/dist/agents/coding-agent/coding-agent.yml +203 -0
- package/dist/agents/database-agent/database-agent.yml +44 -2
- package/dist/agents/default-agent.yml +137 -13
- package/dist/agents/github-agent/github-agent.yml +42 -0
- package/dist/agents/image-editor-agent/image-editor-agent.yml +9 -2
- package/dist/agents/music-agent/README.md +1 -1
- package/dist/agents/music-agent/music-agent.yml +36 -2
- package/dist/agents/nano-banana-agent/nano-banana-agent.yml +35 -1
- package/dist/agents/podcast-agent/README.md +1 -1
- package/dist/agents/podcast-agent/podcast-agent.yml +37 -3
- package/dist/agents/product-name-researcher/product-name-researcher.yml +37 -1
- package/dist/agents/sora-video-agent/README.md +122 -0
- package/dist/agents/sora-video-agent/sora-video-agent.yml +98 -0
- package/dist/agents/talk2pdf-agent/talk2pdf-agent.yml +17 -2
- package/dist/agents/triage-demo/README.md +6 -6
- package/dist/agents/triage-demo/billing-agent.yml +1 -1
- package/dist/agents/triage-demo/escalation-agent.yml +1 -1
- package/dist/agents/triage-demo/product-info-agent.yml +1 -1
- package/dist/agents/triage-demo/technical-support-agent.yml +1 -1
- package/dist/agents/triage-demo/triage-agent.yml +16 -1
- package/dist/analytics/wrapper.d.ts.map +1 -1
- package/dist/analytics/wrapper.js +5 -3
- package/dist/api/a2a.d.ts +2 -2
- package/dist/api/a2a.d.ts.map +1 -1
- package/dist/api/a2a.js +3 -2
- package/dist/api/mcp/mcp_handler.d.ts +3 -3
- package/dist/api/mcp/mcp_handler.d.ts.map +1 -1
- package/dist/api/mcp/mcp_handler.js +7 -4
- package/dist/api/mcp/tool-aggregation-handler.d.ts.map +1 -1
- package/dist/api/mcp/tool-aggregation-handler.js +34 -42
- package/dist/api/memory/memory-handler.d.ts +18 -0
- package/dist/api/memory/memory-handler.d.ts.map +1 -0
- package/dist/api/memory/memory-handler.js +137 -0
- package/dist/api/middleware/errorHandler.d.ts.map +1 -1
- package/dist/api/middleware/errorHandler.js +2 -0
- package/dist/api/server.d.ts +2 -2
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +1129 -257
- package/dist/api/webhook-subscriber.d.ts.map +1 -1
- package/dist/api/webhook-subscriber.js +2 -1
- package/dist/api/websocket-subscriber.d.ts.map +1 -1
- package/dist/api/websocket-subscriber.js +67 -10
- package/dist/cli/cli-subscriber.d.ts +2 -1
- package/dist/cli/cli-subscriber.d.ts.map +1 -1
- package/dist/cli/cli-subscriber.js +11 -3
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +1 -0
- package/dist/cli/commands/install.d.ts +3 -3
- package/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +223 -41
- package/dist/cli/commands/interactive-commands/model/model-commands.js +2 -2
- package/dist/cli/commands/interactive-commands/prompt-commands.d.ts +8 -1
- package/dist/cli/commands/interactive-commands/prompt-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/prompt-commands.js +252 -4
- package/dist/cli/commands/list-agents.d.ts.map +1 -1
- package/dist/cli/commands/list-agents.js +22 -3
- package/dist/cli/commands/setup.d.ts +4 -4
- package/dist/cli/commands/uninstall.d.ts +1 -1
- package/dist/cli/tool-confirmation/cli-confirmation-handler.d.ts +36 -7
- package/dist/cli/tool-confirmation/cli-confirmation-handler.d.ts.map +1 -1
- package/dist/cli/tool-confirmation/cli-confirmation-handler.js +314 -34
- package/dist/cli/utils/options.js +2 -2
- package/dist/index.js +117 -64
- package/dist/webui/.next/standalone/.next/static/chunks/419-6d449dcb2b056299.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/614-3519f8a6051e0088.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/656-5a9f6405badf66a8.js +25 -0
- package/dist/webui/.next/standalone/.next/static/chunks/765-755286dc586b1a51.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/804-f40df92a3adffcc0.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/854-232126f3c77e6c0b.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/app/chat/[sessionId]/page-a695b09e6bac5274.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/app/layout-f4a6ee5a028899d1.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/app/page-d1f127a0cac96246.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/app/playground/page-6f8d2abe76e51dfc.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/main-7decd42f62688419.js +1 -0
- package/dist/webui/.next/standalone/.next/static/chunks/{webpack-7c234e7e7e272295.js → webpack-7229fd0786f0483c.js} +1 -1
- package/dist/webui/.next/standalone/.next/static/css/c3c26ec984df1deb.css +1 -0
- package/dist/webui/.next/standalone/.next/static/css/de70bee13400563f.css +1 -0
- package/dist/webui/.next/standalone/.next/static/uqfH8SY_uhwdc0ZkpMwCO/_buildManifest.js +1 -0
- package/dist/webui/.next/standalone/package.json +7 -2
- package/dist/webui/.next/standalone/packages/webui/.next/BUILD_ID +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/app-build-manifest.json +30 -15
- package/dist/webui/.next/standalone/packages/webui/.next/app-path-routes-manifest.json +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/build-manifest.json +7 -7
- package/dist/webui/.next/standalone/packages/webui/.next/prerender-manifest.json +3 -3
- package/dist/webui/.next/standalone/packages/webui/.next/required-server-files.json +1 -11
- package/dist/webui/.next/standalone/packages/webui/.next/routes-manifest.json +11 -8
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/_not-found/page.js +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/chat/[sessionId]/page.js +2 -0
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/chat/[sessionId]/page.js.nft.json +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/chat/[sessionId]/page_client-reference-manifest.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/page.js +2 -11
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/page.js.nft.json +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/playground/page.js +4 -8
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/playground/page.js.nft.json +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/playground/page_client-reference-manifest.js +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app-paths-manifest.json +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/server/chunks/1.js +12 -0
- package/dist/webui/.next/standalone/packages/webui/.next/server/chunks/102.js +25 -0
- package/dist/webui/.next/standalone/packages/webui/.next/server/chunks/383.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/server/chunks/{619.js → 426.js} +2 -2
- package/dist/webui/.next/standalone/packages/webui/.next/server/chunks/43.js +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/chunks/985.js +5 -0
- package/dist/webui/.next/standalone/packages/webui/.next/server/middleware-build-manifest.js +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/pages/500.html +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/server-reference-manifest.json +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/419-6d449dcb2b056299.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/614-3519f8a6051e0088.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/656-5a9f6405badf66a8.js +25 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/765-755286dc586b1a51.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/804-f40df92a3adffcc0.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/854-232126f3c77e6c0b.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/app/chat/[sessionId]/page-a695b09e6bac5274.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/app/layout-f4a6ee5a028899d1.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/app/page-d1f127a0cac96246.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/app/playground/page-6f8d2abe76e51dfc.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/main-7decd42f62688419.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/{webpack-7c234e7e7e272295.js → webpack-7229fd0786f0483c.js} +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/css/c3c26ec984df1deb.css +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/css/de70bee13400563f.css +1 -0
- package/dist/webui/.next/standalone/packages/webui/.next/static/uqfH8SY_uhwdc0ZkpMwCO/_buildManifest.js +1 -0
- package/dist/webui/.next/standalone/packages/webui/package.json +11 -4
- package/dist/webui/.next/standalone/packages/webui/server.js +1 -1
- package/dist/webui/.next/static/chunks/419-6d449dcb2b056299.js +1 -0
- package/dist/webui/.next/static/chunks/614-3519f8a6051e0088.js +1 -0
- package/dist/webui/.next/static/chunks/656-5a9f6405badf66a8.js +25 -0
- package/dist/webui/.next/static/chunks/765-755286dc586b1a51.js +1 -0
- package/dist/webui/.next/static/chunks/804-f40df92a3adffcc0.js +1 -0
- package/dist/webui/.next/static/chunks/854-232126f3c77e6c0b.js +1 -0
- package/dist/webui/.next/static/chunks/app/chat/[sessionId]/page-a695b09e6bac5274.js +1 -0
- package/dist/webui/.next/static/chunks/app/layout-f4a6ee5a028899d1.js +1 -0
- package/dist/webui/.next/static/chunks/app/page-d1f127a0cac96246.js +1 -0
- package/dist/webui/.next/static/chunks/app/playground/page-6f8d2abe76e51dfc.js +1 -0
- package/dist/webui/.next/static/chunks/main-7decd42f62688419.js +1 -0
- package/dist/webui/.next/static/chunks/{webpack-7c234e7e7e272295.js → webpack-7229fd0786f0483c.js} +1 -1
- package/dist/webui/.next/static/css/c3c26ec984df1deb.css +1 -0
- package/dist/webui/.next/static/css/de70bee13400563f.css +1 -0
- package/dist/webui/.next/static/uqfH8SY_uhwdc0ZkpMwCO/_buildManifest.js +1 -0
- package/dist/webui/package.json +11 -4
- package/package.json +5 -4
- package/dist/webui/.next/standalone/.next/static/PvkEd_BO6ZXxX99T_gUvs/_buildManifest.js +0 -1
- package/dist/webui/.next/standalone/.next/static/chunks/179-78abc2eacbc41da9.js +0 -1
- package/dist/webui/.next/standalone/.next/static/chunks/442-b1916bec348454b3.js +0 -1
- package/dist/webui/.next/standalone/.next/static/chunks/544-c4a8f278ed1a25d7.js +0 -1
- package/dist/webui/.next/standalone/.next/static/chunks/854-2a6d5a5297a15d52.js +0 -1
- package/dist/webui/.next/standalone/.next/static/chunks/app/layout-dde711766eda096b.js +0 -1
- package/dist/webui/.next/standalone/.next/static/chunks/app/page-5e94d5a49dc718d0.js +0 -1
- package/dist/webui/.next/standalone/.next/static/chunks/app/playground/page-9ae40e0b219583e3.js +0 -1
- package/dist/webui/.next/standalone/.next/static/chunks/main-b65ece3506a2355c.js +0 -1
- package/dist/webui/.next/standalone/.next/static/css/045cc65741e38fbd.css +0 -3
- package/dist/webui/.next/standalone/packages/webui/.next/server/chunks/549.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/chunks/950.js +0 -5
- package/dist/webui/.next/standalone/packages/webui/.next/static/PvkEd_BO6ZXxX99T_gUvs/_buildManifest.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/179-78abc2eacbc41da9.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/442-b1916bec348454b3.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/544-c4a8f278ed1a25d7.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/854-2a6d5a5297a15d52.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/app/layout-dde711766eda096b.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/app/page-5e94d5a49dc718d0.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/app/playground/page-9ae40e0b219583e3.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/main-b65ece3506a2355c.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/css/045cc65741e38fbd.css +0 -3
- package/dist/webui/.next/static/PvkEd_BO6ZXxX99T_gUvs/_buildManifest.js +0 -1
- package/dist/webui/.next/static/chunks/179-78abc2eacbc41da9.js +0 -1
- package/dist/webui/.next/static/chunks/442-b1916bec348454b3.js +0 -1
- package/dist/webui/.next/static/chunks/544-c4a8f278ed1a25d7.js +0 -1
- package/dist/webui/.next/static/chunks/854-2a6d5a5297a15d52.js +0 -1
- package/dist/webui/.next/static/chunks/app/layout-dde711766eda096b.js +0 -1
- package/dist/webui/.next/static/chunks/app/page-5e94d5a49dc718d0.js +0 -1
- package/dist/webui/.next/static/chunks/app/playground/page-9ae40e0b219583e3.js +0 -1
- package/dist/webui/.next/static/chunks/main-b65ece3506a2355c.js +0 -1
- package/dist/webui/.next/static/css/045cc65741e38fbd.css +0 -3
- /package/dist/webui/.next/standalone/.next/static/{PvkEd_BO6ZXxX99T_gUvs → uqfH8SY_uhwdc0ZkpMwCO}/_ssgManifest.js +0 -0
- /package/dist/webui/.next/standalone/packages/webui/.next/static/{PvkEd_BO6ZXxX99T_gUvs → uqfH8SY_uhwdc0ZkpMwCO}/_ssgManifest.js +0 -0
- /package/dist/webui/.next/static/{PvkEd_BO6ZXxX99T_gUvs → uqfH8SY_uhwdc0ZkpMwCO}/_ssgManifest.js +0 -0
package/dist/api/server.js
CHANGED
|
@@ -3,23 +3,28 @@ import http from 'http';
|
|
|
3
3
|
import { WebSocketServer } from 'ws';
|
|
4
4
|
import { WebSocketEventSubscriber } from './websocket-subscriber.js';
|
|
5
5
|
import { WebhookEventSubscriber } from './webhook-subscriber.js';
|
|
6
|
-
import { logger, redactSensitiveData } from '@dexto/core';
|
|
6
|
+
import { logger, redactSensitiveData, deriveDisplayName } from '@dexto/core';
|
|
7
7
|
import { setupA2ARoutes } from './a2a.js';
|
|
8
|
+
import { setupMemoryRoutes } from './memory/memory-handler.js';
|
|
8
9
|
import { createMcpTransport, initializeMcpServer, initializeMcpServerApiEndpoints, } from './mcp/mcp_handler.js';
|
|
9
|
-
import { createAgentCard, DextoAgent } from '@dexto/core';
|
|
10
|
-
import { stringify as yamlStringify } from 'yaml';
|
|
10
|
+
import { createAgentCard, Dexto, DextoAgent, loadAgentConfig } from '@dexto/core';
|
|
11
|
+
import { stringify as yamlStringify, parse as yamlParse } from 'yaml';
|
|
11
12
|
import os from 'os';
|
|
13
|
+
import { promises as fs } from 'fs';
|
|
14
|
+
import path from 'path';
|
|
12
15
|
import { expressRedactionMiddleware } from './middleware/expressRedactionMiddleware.js';
|
|
13
16
|
import { z } from 'zod';
|
|
14
17
|
import { LLMUpdatesSchema } from '@dexto/core';
|
|
15
18
|
import { registerGracefulShutdown } from '../utils/graceful-shutdown.js';
|
|
16
19
|
import { validateInputForLLM } from '@dexto/core';
|
|
17
20
|
import { LLM_REGISTRY, LLM_PROVIDERS, LLM_ROUTERS, SUPPORTED_FILE_TYPES, getSupportedRoutersForProvider, supportsBaseURL, isRouterSupportedForModel, } from '@dexto/core';
|
|
18
|
-
import { getProviderKeyStatus, saveProviderApiKey } from '@dexto/core';
|
|
21
|
+
import { getProviderKeyStatus, saveProviderApiKey, getPrimaryApiKeyEnvVar } from '@dexto/core';
|
|
19
22
|
import { errorHandler } from './middleware/errorHandler.js';
|
|
20
23
|
import { McpServerConfigSchema } from '@dexto/core';
|
|
21
24
|
import { sendWebSocketError, sendWebSocketValidationError } from './websocket-error-handler.js';
|
|
22
|
-
import { DextoValidationError, ErrorScope, ErrorType, AgentErrorCode, AgentError, } from '@dexto/core';
|
|
25
|
+
import { DextoValidationError, ErrorScope, ErrorType, AgentErrorCode, AgentError, AgentConfigSchema, ApprovalResponseSchema, } from '@dexto/core';
|
|
26
|
+
import { ResourceError } from '@dexto/core';
|
|
27
|
+
import { PromptError } from '@dexto/core';
|
|
23
28
|
/**
|
|
24
29
|
* Helper function to send JSON response with optional pretty printing
|
|
25
30
|
*/
|
|
@@ -34,58 +39,6 @@ function sendJsonResponse(res, data, statusCode = 200) {
|
|
|
34
39
|
res.json(data);
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
|
-
// Note: Request body may include a sessionId alongside LLM updates.
|
|
38
|
-
// We parse sessionId separately and validate the rest against LLMUpdatesSchema
|
|
39
|
-
/**
|
|
40
|
-
* API request validation schemas based on actual usage
|
|
41
|
-
*/
|
|
42
|
-
const MessageRequestSchema = z
|
|
43
|
-
.object({
|
|
44
|
-
message: z.string().optional(),
|
|
45
|
-
sessionId: z.string().optional(),
|
|
46
|
-
stream: z.boolean().optional(),
|
|
47
|
-
imageData: z
|
|
48
|
-
.object({
|
|
49
|
-
base64: z.string(),
|
|
50
|
-
mimeType: z.string(),
|
|
51
|
-
})
|
|
52
|
-
.optional(),
|
|
53
|
-
fileData: z
|
|
54
|
-
.object({
|
|
55
|
-
base64: z.string(),
|
|
56
|
-
mimeType: z.string(),
|
|
57
|
-
filename: z.string().optional(),
|
|
58
|
-
})
|
|
59
|
-
.optional(),
|
|
60
|
-
})
|
|
61
|
-
.refine((data) => {
|
|
62
|
-
const msg = (data.message ?? '').trim();
|
|
63
|
-
// Must have either message text, image data, or file data
|
|
64
|
-
return msg.length > 0 || !!data.imageData || !!data.fileData;
|
|
65
|
-
}, { message: 'Must provide either message text, image data, or file data' });
|
|
66
|
-
// Reuse existing MCP server config schema
|
|
67
|
-
const McpServerRequestSchema = z.object({
|
|
68
|
-
name: z.string().min(1, 'Server name is required'),
|
|
69
|
-
config: McpServerConfigSchema,
|
|
70
|
-
});
|
|
71
|
-
// Based on existing WebhookRegistrationRequest interface
|
|
72
|
-
const WebhookRequestSchema = z.object({
|
|
73
|
-
url: z.string().url('Invalid URL format'),
|
|
74
|
-
secret: z.string().optional(),
|
|
75
|
-
description: z.string().optional(),
|
|
76
|
-
});
|
|
77
|
-
// Schema for search query parameters
|
|
78
|
-
const SearchQuerySchema = z.object({
|
|
79
|
-
q: z.string().min(1, 'Search query is required'),
|
|
80
|
-
limit: z.coerce.number().min(1).max(100).optional(),
|
|
81
|
-
offset: z.coerce.number().min(0).optional(),
|
|
82
|
-
sessionId: z.string().optional(),
|
|
83
|
-
role: z.enum(['user', 'assistant', 'system', 'tool']).optional(),
|
|
84
|
-
});
|
|
85
|
-
// Schema for cancel request parameters
|
|
86
|
-
const CancelRequestSchema = z.object({
|
|
87
|
-
sessionId: z.string().min(1, 'Session ID is required'),
|
|
88
|
-
});
|
|
89
42
|
// Helper to parse and validate request body
|
|
90
43
|
function parseBody(schema, body) {
|
|
91
44
|
return schema.parse(body); // ZodError handled by error middleware
|
|
@@ -95,19 +48,62 @@ function parseQuery(schema, query) {
|
|
|
95
48
|
return schema.parse(query); // ZodError handled by error middleware
|
|
96
49
|
}
|
|
97
50
|
// TODO: API endpoint names are work in progress and might be refactored/renamed in future versions
|
|
98
|
-
export async function initializeApi(agent, agentCardOverride, listenPort,
|
|
51
|
+
export async function initializeApi(agent, agentCardOverride, listenPort, agentId) {
|
|
99
52
|
const app = express();
|
|
100
53
|
// Declare before registering shutdown hook to avoid TDZ on signals
|
|
101
54
|
let activeAgent = agent;
|
|
102
|
-
let
|
|
55
|
+
let activeAgentId = agentId || 'default-agent';
|
|
103
56
|
let isSwitchingAgent = false;
|
|
104
57
|
registerGracefulShutdown(() => activeAgent);
|
|
58
|
+
// CORS middleware to allow frontend to connect from different ports
|
|
59
|
+
app.use((req, res, next) => {
|
|
60
|
+
const origin = req.headers.origin;
|
|
61
|
+
// Define allowed origins based on environment
|
|
62
|
+
const allowedOrigins = [];
|
|
63
|
+
// 1. Always allow localhost/127.0.0.1 on any port (for local development)
|
|
64
|
+
if (origin) {
|
|
65
|
+
const originUrl = new URL(origin);
|
|
66
|
+
const hostname = originUrl.hostname;
|
|
67
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
|
|
68
|
+
allowedOrigins.push(origin);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// 2. Allow custom origins from environment variable (for production/network deployments)
|
|
72
|
+
const customOrigins = process.env.DEXTO_ALLOWED_ORIGINS;
|
|
73
|
+
if (customOrigins) {
|
|
74
|
+
allowedOrigins.push(...customOrigins.split(',').map((o) => o.trim()));
|
|
75
|
+
}
|
|
76
|
+
// 3. Set CORS headers
|
|
77
|
+
if (origin && allowedOrigins.includes(origin)) {
|
|
78
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
79
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
80
|
+
}
|
|
81
|
+
else if (allowedOrigins.length === 0 && !origin) {
|
|
82
|
+
// If no origin header (e.g., server-to-server), allow it
|
|
83
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
84
|
+
}
|
|
85
|
+
// If origin is not allowed, don't set CORS headers (browser will block)
|
|
86
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD');
|
|
87
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
88
|
+
// Handle preflight requests
|
|
89
|
+
if (req.method === 'OPTIONS') {
|
|
90
|
+
return res.status(204).end();
|
|
91
|
+
}
|
|
92
|
+
return next();
|
|
93
|
+
});
|
|
105
94
|
// this will apply middleware to all /api/llm/* routes
|
|
106
95
|
app.use('/api/llm', expressRedactionMiddleware);
|
|
107
96
|
app.use('/api/config.yaml', expressRedactionMiddleware);
|
|
108
97
|
const server = http.createServer(app);
|
|
109
98
|
const wss = new WebSocketServer({ server });
|
|
110
|
-
logger.info(`Initializing API server with agent: ${
|
|
99
|
+
logger.info(`Initializing API server with agent: ${activeAgentId}`);
|
|
100
|
+
// Initialize event subscribers
|
|
101
|
+
const webSubscriber = new WebSocketEventSubscriber(wss);
|
|
102
|
+
const webhookSubscriber = new WebhookEventSubscriber();
|
|
103
|
+
// Register subscribers before starting agent
|
|
104
|
+
logger.info('Registering event subscribers with agent...');
|
|
105
|
+
activeAgent.registerSubscriber(webSubscriber);
|
|
106
|
+
activeAgent.registerSubscriber(webhookSubscriber);
|
|
111
107
|
// Ensure the initial agent is started
|
|
112
108
|
if (!activeAgent.isStarted() && !activeAgent.isStopped()) {
|
|
113
109
|
logger.info('Starting initial agent...');
|
|
@@ -116,18 +112,11 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
116
112
|
else if (activeAgent.isStopped()) {
|
|
117
113
|
logger.warn('Initial agent is stopped, this may cause issues');
|
|
118
114
|
}
|
|
119
|
-
const webSubscriber = new WebSocketEventSubscriber(wss);
|
|
120
|
-
logger.info('Setting up API event subscriptions...');
|
|
121
|
-
webSubscriber.subscribe(activeAgent.agentEventBus);
|
|
122
|
-
// Initialize webhook subscriber
|
|
123
|
-
const webhookSubscriber = new WebhookEventSubscriber();
|
|
124
|
-
logger.info('Setting up webhook event subscriptions...');
|
|
125
|
-
webhookSubscriber.subscribe(activeAgent.agentEventBus);
|
|
126
115
|
// Tool confirmation responses are handled by the main WebSocket handler below
|
|
127
116
|
function ensureAgentAvailable() {
|
|
128
117
|
// Gate requests during agent switching
|
|
129
118
|
if (isSwitchingAgent) {
|
|
130
|
-
throw AgentError.
|
|
119
|
+
throw AgentError.switchInProgress();
|
|
131
120
|
}
|
|
132
121
|
// Fast path: most common case is agent is started and running
|
|
133
122
|
if (activeAgent.isStarted() && !activeAgent.isStopped()) {
|
|
@@ -141,53 +130,103 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
141
130
|
throw AgentError.notStarted();
|
|
142
131
|
}
|
|
143
132
|
}
|
|
144
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Common agent switching logic shared by switchAgentById and switchAgentByPath.
|
|
135
|
+
* Handles: registering subscribers, starting agent, stopping previous agent, updating global state.
|
|
136
|
+
*
|
|
137
|
+
* @param newAgent The new DextoAgent instance to switch to
|
|
138
|
+
* @param agentId The identifier for the agent (used for logging and state tracking)
|
|
139
|
+
* @returns Agent info for the newly activated agent
|
|
140
|
+
*/
|
|
141
|
+
async function performAgentSwitch(newAgent, agentId) {
|
|
142
|
+
// Register event subscribers with new agent before starting
|
|
143
|
+
logger.info('Registering event subscribers with new agent...');
|
|
144
|
+
newAgent.registerSubscriber(webSubscriber);
|
|
145
|
+
newAgent.registerSubscriber(webhookSubscriber);
|
|
146
|
+
logger.info(`Starting new agent: ${agentId}`);
|
|
147
|
+
await newAgent.start();
|
|
148
|
+
// Stop previous agent last (only after new one is fully operational)
|
|
149
|
+
const previousAgent = activeAgent;
|
|
150
|
+
activeAgent = newAgent;
|
|
151
|
+
activeAgentId = agentId;
|
|
152
|
+
// Update agent card for A2A and MCP routes
|
|
153
|
+
agentCardData = createAgentCard({
|
|
154
|
+
defaultName: agentId,
|
|
155
|
+
defaultVersion: overrides.version ?? '1.0.0',
|
|
156
|
+
defaultBaseUrl: baseApiUrl,
|
|
157
|
+
webSubscriber,
|
|
158
|
+
}, overrides);
|
|
159
|
+
logger.info(`Successfully switched to agent: ${agentId}`);
|
|
160
|
+
// Now safely stop the previous agent
|
|
161
|
+
try {
|
|
162
|
+
if (previousAgent && previousAgent !== newAgent) {
|
|
163
|
+
logger.info('Stopping previous agent...');
|
|
164
|
+
await previousAgent.stop();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
logger.warn(`Stopping previous agent failed: ${err}`);
|
|
169
|
+
// Don't throw here as the switch was successful
|
|
170
|
+
}
|
|
171
|
+
return await resolveAgentInfo(agentId);
|
|
172
|
+
}
|
|
173
|
+
async function switchAgentById(agentId) {
|
|
145
174
|
if (isSwitchingAgent) {
|
|
146
|
-
throw AgentError.
|
|
175
|
+
throw AgentError.switchInProgress();
|
|
147
176
|
}
|
|
148
177
|
isSwitchingAgent = true;
|
|
149
178
|
let newAgent;
|
|
150
179
|
try {
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
logger.info(
|
|
154
|
-
await
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
webhookSubscriber.subscribe(newAgent.agentEventBus);
|
|
171
|
-
// Stop previous agent last (only after new one is fully operational)
|
|
172
|
-
const previousAgent = activeAgent;
|
|
173
|
-
activeAgent = newAgent;
|
|
174
|
-
activeAgentName = name;
|
|
175
|
-
logger.info(`Successfully switched to agent: ${name}`);
|
|
176
|
-
// Now safely stop the previous agent
|
|
177
|
-
try {
|
|
178
|
-
if (previousAgent && previousAgent !== newAgent) {
|
|
179
|
-
logger.info('Stopping previous agent...');
|
|
180
|
-
await previousAgent.stop();
|
|
180
|
+
// 1. SHUTDOWN OLD TELEMETRY FIRST (before creating new agent)
|
|
181
|
+
// This allows new agent to have different telemetry config (endpoint, protocol, etc.)
|
|
182
|
+
logger.info('Shutting down telemetry for agent switch...');
|
|
183
|
+
const { Telemetry } = await import('@dexto/core');
|
|
184
|
+
await Telemetry.shutdownGlobal();
|
|
185
|
+
// 2. Create new agent from registry (will initialize fresh telemetry in createAgentServices)
|
|
186
|
+
newAgent = await Dexto.createAgent(agentId);
|
|
187
|
+
// 3. Use common switch logic (register subscribers, start agent, stop previous)
|
|
188
|
+
return await performAgentSwitch(newAgent, agentId);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
logger.error(`Failed to switch to agent '${agentId}': ${error instanceof Error ? error.message : String(error)}`, { error });
|
|
192
|
+
// Clean up the failed new agent if it was created
|
|
193
|
+
if (newAgent) {
|
|
194
|
+
try {
|
|
195
|
+
await newAgent.stop();
|
|
196
|
+
}
|
|
197
|
+
catch (cleanupErr) {
|
|
198
|
+
logger.warn(`Failed to cleanup new agent: ${cleanupErr}`);
|
|
181
199
|
}
|
|
182
200
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
isSwitchingAgent = false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function switchAgentByPath(filePath) {
|
|
208
|
+
if (isSwitchingAgent) {
|
|
209
|
+
throw AgentError.switchInProgress();
|
|
210
|
+
}
|
|
211
|
+
isSwitchingAgent = true;
|
|
212
|
+
let newAgent;
|
|
213
|
+
try {
|
|
214
|
+
// 1. SHUTDOWN OLD TELEMETRY FIRST (before creating new agent)
|
|
215
|
+
// This allows new agent to have different telemetry config (endpoint, protocol, etc.)
|
|
216
|
+
logger.info('Shutting down telemetry for agent switch...');
|
|
217
|
+
const { Telemetry } = await import('@dexto/core');
|
|
218
|
+
await Telemetry.shutdownGlobal();
|
|
219
|
+
// 2. Load agent configuration from file path
|
|
220
|
+
const config = await loadAgentConfig(filePath);
|
|
221
|
+
// 3. Create new agent instance directly (will initialize fresh telemetry in createAgentServices)
|
|
222
|
+
newAgent = new DextoAgent(config, filePath);
|
|
223
|
+
// 4. Derive agent ID from config or filename
|
|
224
|
+
const agentId = config.agentCard?.name || path.basename(filePath, path.extname(filePath));
|
|
225
|
+
// 5. Use common switch logic (register subscribers, start agent, stop previous)
|
|
226
|
+
return await performAgentSwitch(newAgent, agentId);
|
|
188
227
|
}
|
|
189
228
|
catch (error) {
|
|
190
|
-
logger.error(`Failed to switch to agent '${
|
|
229
|
+
logger.error(`Failed to switch to agent from path '${filePath}': ${error instanceof Error ? error.message : String(error)}`, { error });
|
|
191
230
|
// Clean up the failed new agent if it was created
|
|
192
231
|
if (newAgent) {
|
|
193
232
|
try {
|
|
@@ -204,10 +243,244 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
204
243
|
}
|
|
205
244
|
}
|
|
206
245
|
// HTTP endpoints
|
|
246
|
+
// ---- Helpers (local) ----
|
|
247
|
+
/**
|
|
248
|
+
* Helper to decode URI components with consistent error handling.
|
|
249
|
+
*
|
|
250
|
+
* Wraps native decodeURIComponent() to provide domain-specific error handling.
|
|
251
|
+
* While normally 1-line wrappers are discouraged, this is justified because:
|
|
252
|
+
* 1. Native TS function with no control over error type
|
|
253
|
+
* 2. Ensures consistent ResourceError across all URI decoding
|
|
254
|
+
* 3. Reused in 5+ Zod transform schemas
|
|
255
|
+
*/
|
|
256
|
+
function decodeUriComponent(encoded) {
|
|
257
|
+
try {
|
|
258
|
+
return decodeURIComponent(encoded);
|
|
259
|
+
}
|
|
260
|
+
catch (_error) {
|
|
261
|
+
throw ResourceError.invalidUriFormat(encoded, 'valid URI-encoded resource identifier');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Helper function to redact sensitive environment variables
|
|
266
|
+
*/
|
|
267
|
+
function redactEnvValue(value) {
|
|
268
|
+
if (value && typeof value === 'string' && value.length > 0) {
|
|
269
|
+
return '[REDACTED]';
|
|
270
|
+
}
|
|
271
|
+
return String(value ?? '');
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Helper function to redact environment variables in a server config
|
|
275
|
+
*/
|
|
276
|
+
function redactServerEnvVars(serverConfig) {
|
|
277
|
+
if (serverConfig.type !== 'stdio' || !serverConfig.env) {
|
|
278
|
+
return serverConfig;
|
|
279
|
+
}
|
|
280
|
+
const redactedEnv = {};
|
|
281
|
+
for (const [key, value] of Object.entries(serverConfig.env)) {
|
|
282
|
+
redactedEnv[key] = redactEnvValue(value);
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
...serverConfig,
|
|
286
|
+
env: redactedEnv,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Helper function to redact all MCP servers configuration
|
|
291
|
+
*/
|
|
292
|
+
function redactMcpServersConfig(mcpServers) {
|
|
293
|
+
if (!mcpServers) {
|
|
294
|
+
return {};
|
|
295
|
+
}
|
|
296
|
+
const redactedServers = {};
|
|
297
|
+
for (const [name, serverConfig] of Object.entries(mcpServers)) {
|
|
298
|
+
redactedServers[name] = redactServerEnvVars(serverConfig);
|
|
299
|
+
}
|
|
300
|
+
return redactedServers;
|
|
301
|
+
}
|
|
207
302
|
// Health check endpoint
|
|
208
|
-
app.get('/health', (
|
|
303
|
+
app.get('/health', (_req, res) => {
|
|
209
304
|
res.status(200).send('OK');
|
|
210
305
|
});
|
|
306
|
+
// Prompts listing endpoint (for WebUI slash command autocomplete)
|
|
307
|
+
app.get('/api/prompts', async (_req, res, next) => {
|
|
308
|
+
try {
|
|
309
|
+
ensureAgentAvailable();
|
|
310
|
+
const prompts = await activeAgent.listPrompts();
|
|
311
|
+
const list = Object.values(prompts);
|
|
312
|
+
return res.status(200).json({ prompts: list });
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
return next(error);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
const CustomPromptRequestSchema = z
|
|
319
|
+
.object({
|
|
320
|
+
name: z.string().min(1, 'Prompt name is required'),
|
|
321
|
+
title: z.string().optional(),
|
|
322
|
+
description: z.string().optional(),
|
|
323
|
+
content: z.string().min(1, 'Prompt content is required'),
|
|
324
|
+
arguments: z
|
|
325
|
+
.array(z
|
|
326
|
+
.object({
|
|
327
|
+
name: z.string().min(1, 'Argument name is required'),
|
|
328
|
+
description: z.string().optional(),
|
|
329
|
+
required: z.boolean().optional(),
|
|
330
|
+
})
|
|
331
|
+
.strict())
|
|
332
|
+
.optional(),
|
|
333
|
+
resource: z
|
|
334
|
+
.object({
|
|
335
|
+
base64: z.string().min(1, 'Resource data is required'),
|
|
336
|
+
mimeType: z.string().min(1, 'Resource MIME type is required'),
|
|
337
|
+
filename: z.string().optional(),
|
|
338
|
+
})
|
|
339
|
+
.strict()
|
|
340
|
+
.optional(),
|
|
341
|
+
})
|
|
342
|
+
.strict();
|
|
343
|
+
app.post('/api/prompts/custom', express.json({ limit: '10mb' }), async (req, res, next) => {
|
|
344
|
+
try {
|
|
345
|
+
ensureAgentAvailable();
|
|
346
|
+
const payload = parseBody(CustomPromptRequestSchema, req.body);
|
|
347
|
+
const promptArguments = payload.arguments
|
|
348
|
+
?.map((arg) => ({
|
|
349
|
+
name: arg.name,
|
|
350
|
+
...(arg.description ? { description: arg.description } : {}),
|
|
351
|
+
...(typeof arg.required === 'boolean' ? { required: arg.required } : {}),
|
|
352
|
+
}))
|
|
353
|
+
.filter(Boolean);
|
|
354
|
+
const createPayload = {
|
|
355
|
+
name: payload.name,
|
|
356
|
+
content: payload.content,
|
|
357
|
+
...(payload.title ? { title: payload.title } : {}),
|
|
358
|
+
...(payload.description ? { description: payload.description } : {}),
|
|
359
|
+
...(promptArguments && promptArguments.length > 0
|
|
360
|
+
? { arguments: promptArguments }
|
|
361
|
+
: {}),
|
|
362
|
+
...(payload.resource
|
|
363
|
+
? {
|
|
364
|
+
resource: {
|
|
365
|
+
base64: payload.resource.base64,
|
|
366
|
+
mimeType: payload.resource.mimeType,
|
|
367
|
+
...(payload.resource.filename
|
|
368
|
+
? { filename: payload.resource.filename }
|
|
369
|
+
: {}),
|
|
370
|
+
},
|
|
371
|
+
}
|
|
372
|
+
: {}),
|
|
373
|
+
};
|
|
374
|
+
const prompt = await activeAgent.createCustomPrompt(createPayload);
|
|
375
|
+
return res.status(201).json({ prompt });
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
return next(error);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
const DeleteCustomPromptParamsSchema = z.object({
|
|
382
|
+
name: z
|
|
383
|
+
.string()
|
|
384
|
+
.min(1, 'Prompt name is required')
|
|
385
|
+
.transform((encoded) => decodeUriComponent(encoded)),
|
|
386
|
+
});
|
|
387
|
+
app.delete('/api/prompts/custom/:name', async (req, res, next) => {
|
|
388
|
+
try {
|
|
389
|
+
ensureAgentAvailable();
|
|
390
|
+
const { name } = parseQuery(DeleteCustomPromptParamsSchema, req.params);
|
|
391
|
+
await activeAgent.deleteCustomPrompt(name);
|
|
392
|
+
return res.status(204).send();
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
return next(error);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
// Get a specific prompt definition
|
|
399
|
+
const GetPromptDefinitionParamsSchema = z.object({
|
|
400
|
+
name: z.string().min(1, 'Prompt name is required'),
|
|
401
|
+
});
|
|
402
|
+
app.get('/api/prompts/:name', async (req, res, next) => {
|
|
403
|
+
try {
|
|
404
|
+
ensureAgentAvailable();
|
|
405
|
+
const { name } = parseQuery(GetPromptDefinitionParamsSchema, req.params);
|
|
406
|
+
const definition = await activeAgent.getPromptDefinition(name);
|
|
407
|
+
if (!definition)
|
|
408
|
+
throw PromptError.notFound(name);
|
|
409
|
+
return sendJsonResponse(res, { definition }, 200);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
return next(error);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
// Resolve a prompt to text content (without sending to the agent)
|
|
416
|
+
// Supports optional args via query string. For natural language after the
|
|
417
|
+
// slash command, pass as `context`.
|
|
418
|
+
const ResolvePromptParamsSchema = z.object({
|
|
419
|
+
name: z.string().min(1, 'Prompt name is required'),
|
|
420
|
+
});
|
|
421
|
+
const ResolvePromptQuerySchema = z.object({
|
|
422
|
+
context: z.string().optional(),
|
|
423
|
+
args: z.string().optional(),
|
|
424
|
+
});
|
|
425
|
+
app.get('/api/prompts/:name/resolve', async (req, res, next) => {
|
|
426
|
+
try {
|
|
427
|
+
ensureAgentAvailable();
|
|
428
|
+
const { name: inputName } = parseQuery(ResolvePromptParamsSchema, req.params);
|
|
429
|
+
const { context, args: argsString } = parseQuery(ResolvePromptQuerySchema, req.query);
|
|
430
|
+
// Optional structured args in `args` query param as JSON
|
|
431
|
+
let parsedArgs;
|
|
432
|
+
if (argsString) {
|
|
433
|
+
try {
|
|
434
|
+
const parsed = JSON.parse(argsString);
|
|
435
|
+
if (parsed && typeof parsed === 'object') {
|
|
436
|
+
parsedArgs = parsed;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
// Ignore malformed args JSON; continue with whatever we have
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
// Build options object with only defined values (exactOptionalPropertyTypes compatibility)
|
|
444
|
+
const options = {};
|
|
445
|
+
if (context !== undefined)
|
|
446
|
+
options.context = context;
|
|
447
|
+
if (parsedArgs !== undefined)
|
|
448
|
+
options.args = parsedArgs;
|
|
449
|
+
// Use DextoAgent's resolvePrompt method
|
|
450
|
+
const result = await activeAgent.resolvePrompt(inputName, options);
|
|
451
|
+
return sendJsonResponse(res, { text: result.text, resources: result.resources }, 200);
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
return next(error);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
// Note: We intentionally omit an "execute" endpoint; clients resolve prompts
|
|
458
|
+
// and then call the regular message endpoint, keeping server surface minimal.
|
|
459
|
+
// Message request schema (shared by /api/message and /api/message-sync)
|
|
460
|
+
const MessageRequestSchema = z
|
|
461
|
+
.object({
|
|
462
|
+
message: z.string().optional(),
|
|
463
|
+
sessionId: z.string().optional(),
|
|
464
|
+
stream: z.boolean().optional(),
|
|
465
|
+
imageData: z
|
|
466
|
+
.object({
|
|
467
|
+
base64: z.string(),
|
|
468
|
+
mimeType: z.string(),
|
|
469
|
+
})
|
|
470
|
+
.optional(),
|
|
471
|
+
fileData: z
|
|
472
|
+
.object({
|
|
473
|
+
base64: z.string(),
|
|
474
|
+
mimeType: z.string(),
|
|
475
|
+
filename: z.string().optional(),
|
|
476
|
+
})
|
|
477
|
+
.optional(),
|
|
478
|
+
})
|
|
479
|
+
.refine((data) => {
|
|
480
|
+
const msg = (data.message ?? '').trim();
|
|
481
|
+
// Must have either message text, image data, or file data
|
|
482
|
+
return msg.length > 0 || !!data.imageData || !!data.fileData;
|
|
483
|
+
}, { message: 'Must provide either message text, image data, or file data' });
|
|
211
484
|
// JSON body size limit for message endpoints supporting base64 image/file payloads
|
|
212
485
|
// Both /api/message and /api/message-sync accept base64 attachments; increased limit to avoid 413s.
|
|
213
486
|
app.post('/api/message', express.json({ limit: process.env.MESSAGE_JSON_LIMIT || '10mb' }), async (req, res, next) => {
|
|
@@ -240,8 +513,12 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
240
513
|
}
|
|
241
514
|
});
|
|
242
515
|
// Cancel an in-flight run for a session
|
|
516
|
+
const CancelRequestSchema = z.object({
|
|
517
|
+
sessionId: z.string().min(1, 'Session ID is required'),
|
|
518
|
+
});
|
|
243
519
|
app.post('/api/sessions/:sessionId/cancel', async (req, res, next) => {
|
|
244
520
|
try {
|
|
521
|
+
ensureAgentAvailable();
|
|
245
522
|
const { sessionId } = parseQuery(CancelRequestSchema, req.params);
|
|
246
523
|
const cancelled = await activeAgent.cancel(sessionId);
|
|
247
524
|
if (!cancelled) {
|
|
@@ -286,11 +563,14 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
286
563
|
return next(error);
|
|
287
564
|
}
|
|
288
565
|
});
|
|
566
|
+
const ResetRequestSchema = z.object({
|
|
567
|
+
sessionId: z.string().optional(),
|
|
568
|
+
});
|
|
289
569
|
app.post('/api/reset', express.json(), async (req, res, next) => {
|
|
290
570
|
logger.info('Received request via POST /api/reset');
|
|
291
571
|
try {
|
|
292
572
|
ensureAgentAvailable();
|
|
293
|
-
const { sessionId } = parseBody(
|
|
573
|
+
const { sessionId } = parseBody(ResetRequestSchema, req.body);
|
|
294
574
|
await activeAgent.resetConversation(sessionId);
|
|
295
575
|
return res.status(200).send({ status: 'reset initiated', sessionId });
|
|
296
576
|
}
|
|
@@ -299,25 +579,40 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
299
579
|
}
|
|
300
580
|
});
|
|
301
581
|
// Dynamic MCP server connection endpoint (legacy)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
await activeAgent.connectMcpServer(name, config);
|
|
307
|
-
logger.info(`Successfully connected to new server '${name}' via API request.`);
|
|
308
|
-
return res.status(200).send({ status: 'connected', name });
|
|
309
|
-
}
|
|
310
|
-
catch (error) {
|
|
311
|
-
return next(error);
|
|
312
|
-
}
|
|
582
|
+
const McpServerRequestSchema = z.object({
|
|
583
|
+
name: z.string().min(1, 'Server name is required'),
|
|
584
|
+
config: McpServerConfigSchema,
|
|
585
|
+
persistToAgent: z.boolean().optional(),
|
|
313
586
|
});
|
|
314
587
|
// Add a new MCP server
|
|
315
588
|
app.post('/api/mcp/servers', express.json(), async (req, res, next) => {
|
|
316
589
|
try {
|
|
317
590
|
ensureAgentAvailable();
|
|
318
|
-
const { name, config } = parseBody(McpServerRequestSchema, req.body);
|
|
591
|
+
const { name, config, persistToAgent } = parseBody(McpServerRequestSchema, req.body);
|
|
592
|
+
// Connect the server
|
|
319
593
|
await activeAgent.connectMcpServer(name, config);
|
|
320
|
-
|
|
594
|
+
logger.info(`Successfully connected to new server '${name}' via API request.`);
|
|
595
|
+
// If persistToAgent is true, save to agent config file
|
|
596
|
+
if (persistToAgent === true) {
|
|
597
|
+
try {
|
|
598
|
+
// Get the current effective config to read existing mcpServers
|
|
599
|
+
const currentConfig = activeAgent.getEffectiveConfig();
|
|
600
|
+
// Create update with new server added to mcpServers
|
|
601
|
+
const updates = {
|
|
602
|
+
mcpServers: {
|
|
603
|
+
...(currentConfig.mcpServers || {}),
|
|
604
|
+
[name]: config,
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
await activeAgent.updateAndSaveConfig(updates);
|
|
608
|
+
logger.info(`Saved server '${name}' to agent configuration file`);
|
|
609
|
+
}
|
|
610
|
+
catch (saveError) {
|
|
611
|
+
logger.warn(`Failed to save server '${name}' to agent config:`, saveError);
|
|
612
|
+
// Don't fail the request if saving fails - server is still connected
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return res.status(200).send({ status: 'connected', name });
|
|
321
616
|
}
|
|
322
617
|
catch (error) {
|
|
323
618
|
return next(error);
|
|
@@ -343,10 +638,13 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
343
638
|
}
|
|
344
639
|
});
|
|
345
640
|
// Add MCP server tools listing endpoint
|
|
641
|
+
const ListServerToolsParamsSchema = z.object({
|
|
642
|
+
serverId: z.string().min(1, 'Server ID is required'),
|
|
643
|
+
});
|
|
346
644
|
app.get('/api/mcp/servers/:serverId/tools', async (req, res, next) => {
|
|
347
645
|
try {
|
|
348
646
|
ensureAgentAvailable();
|
|
349
|
-
const serverId = req.params
|
|
647
|
+
const { serverId } = parseQuery(ListServerToolsParamsSchema, req.params);
|
|
350
648
|
const client = activeAgent.getMcpClients().get(serverId);
|
|
351
649
|
if (!client) {
|
|
352
650
|
return res.status(404).json({ error: `Server '${serverId}' not found` });
|
|
@@ -365,10 +663,14 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
365
663
|
}
|
|
366
664
|
});
|
|
367
665
|
// Endpoint to remove/disconnect an MCP server
|
|
666
|
+
const DeleteMcpServerParamsSchema = z.object({
|
|
667
|
+
serverId: z.string().min(1, 'Server ID is required'),
|
|
668
|
+
});
|
|
368
669
|
app.delete('/api/mcp/servers/:serverId', async (req, res, next) => {
|
|
369
|
-
const { serverId } = req.params;
|
|
370
|
-
logger.info(`Received request to DELETE /api/mcp/servers/${serverId}`);
|
|
371
670
|
try {
|
|
671
|
+
ensureAgentAvailable();
|
|
672
|
+
const { serverId } = parseQuery(DeleteMcpServerParamsSchema, req.params);
|
|
673
|
+
logger.info(`Received request to DELETE /api/mcp/servers/${serverId}`);
|
|
372
674
|
// Check if server exists before attempting to disconnect
|
|
373
675
|
const clientExists = activeAgent.getMcpClients().has(serverId) ||
|
|
374
676
|
activeAgent.getMcpFailedConnections()[serverId];
|
|
@@ -383,19 +685,45 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
383
685
|
return next(error);
|
|
384
686
|
}
|
|
385
687
|
});
|
|
688
|
+
// Endpoint to restart an MCP server
|
|
689
|
+
const RestartMcpServerParamsSchema = z.object({
|
|
690
|
+
serverId: z.string().min(1, 'Server ID is required'),
|
|
691
|
+
});
|
|
692
|
+
app.post('/api/mcp/servers/:serverId/restart', async (req, res, next) => {
|
|
693
|
+
try {
|
|
694
|
+
ensureAgentAvailable();
|
|
695
|
+
const { serverId } = parseQuery(RestartMcpServerParamsSchema, req.params);
|
|
696
|
+
logger.info(`Received request to POST /api/mcp/servers/${serverId}/restart`);
|
|
697
|
+
// Check if server exists before attempting to restart
|
|
698
|
+
const clientExists = activeAgent.getMcpClients().has(serverId);
|
|
699
|
+
if (!clientExists) {
|
|
700
|
+
logger.warn(`Attempted to restart non-existent server: ${serverId}`);
|
|
701
|
+
return res.status(404).json({ error: `Server '${serverId}' not found.` });
|
|
702
|
+
}
|
|
703
|
+
await activeAgent.restartMcpServer(serverId);
|
|
704
|
+
return res.status(200).json({ status: 'restarted', id: serverId });
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
return next(error);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
386
710
|
// Execute an MCP tool via REST wrapper
|
|
711
|
+
const ExecuteMcpToolParamsSchema = z.object({
|
|
712
|
+
serverId: z.string().min(1, 'Server ID is required'),
|
|
713
|
+
toolName: z.string().min(1, 'Tool name is required'),
|
|
714
|
+
});
|
|
387
715
|
app.post('/api/mcp/servers/:serverId/tools/:toolName/execute', express.json(), async (req, res, next) => {
|
|
388
|
-
const { serverId, toolName } = req.params;
|
|
389
|
-
// Verify server exists
|
|
390
|
-
const client = activeAgent.getMcpClients().get(serverId);
|
|
391
|
-
if (!client) {
|
|
392
|
-
return res
|
|
393
|
-
.status(404)
|
|
394
|
-
.json({ success: false, error: `Server '${serverId}' not found` });
|
|
395
|
-
}
|
|
396
716
|
try {
|
|
397
|
-
|
|
398
|
-
|
|
717
|
+
const { serverId, toolName } = parseQuery(ExecuteMcpToolParamsSchema, req.params);
|
|
718
|
+
// Verify server exists
|
|
719
|
+
const client = activeAgent.getMcpClients().get(serverId);
|
|
720
|
+
if (!client) {
|
|
721
|
+
return res
|
|
722
|
+
.status(404)
|
|
723
|
+
.json({ success: false, error: `Server '${serverId}' not found` });
|
|
724
|
+
}
|
|
725
|
+
// Execute tool directly on the specified server
|
|
726
|
+
const rawResult = await client.callTool(toolName, req.body);
|
|
399
727
|
// Return standardized result shape
|
|
400
728
|
return res.json({ success: true, data: rawResult });
|
|
401
729
|
}
|
|
@@ -403,6 +731,87 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
403
731
|
return next(error);
|
|
404
732
|
}
|
|
405
733
|
});
|
|
734
|
+
// ============= RESOURCE MANAGEMENT ENDPOINTS =============
|
|
735
|
+
// Get all available resources
|
|
736
|
+
app.get('/api/resources', async (_req, res, next) => {
|
|
737
|
+
try {
|
|
738
|
+
ensureAgentAvailable();
|
|
739
|
+
const resources = await activeAgent.listResources();
|
|
740
|
+
return res.status(200).json({ ok: true, resources: Object.values(resources) });
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
return next(error);
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
// Read resource content
|
|
747
|
+
const ReadResourceContentParamsSchema = z.object({
|
|
748
|
+
resourceId: z
|
|
749
|
+
.string()
|
|
750
|
+
.min(1, 'Resource ID is required')
|
|
751
|
+
.transform((encoded) => decodeUriComponent(encoded)),
|
|
752
|
+
});
|
|
753
|
+
app.get('/api/resources/:resourceId/content', async (req, res, next) => {
|
|
754
|
+
try {
|
|
755
|
+
ensureAgentAvailable();
|
|
756
|
+
const { resourceId } = parseQuery(ReadResourceContentParamsSchema, req.params);
|
|
757
|
+
const content = await activeAgent.readResource(resourceId);
|
|
758
|
+
return res.status(200).json({ ok: true, content });
|
|
759
|
+
}
|
|
760
|
+
catch (error) {
|
|
761
|
+
return next(error);
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
// Check if resource exists
|
|
765
|
+
const CheckResourceExistsParamsSchema = z.object({
|
|
766
|
+
resourceId: z
|
|
767
|
+
.string()
|
|
768
|
+
.min(1, 'Resource ID is required')
|
|
769
|
+
.transform((encoded) => decodeUriComponent(encoded)),
|
|
770
|
+
});
|
|
771
|
+
app.head('/api/resources/:resourceId', async (req, res, next) => {
|
|
772
|
+
try {
|
|
773
|
+
const { resourceId } = parseQuery(CheckResourceExistsParamsSchema, req.params);
|
|
774
|
+
const exists = await activeAgent.hasResource(resourceId);
|
|
775
|
+
return res.status(exists ? 200 : 404).end();
|
|
776
|
+
}
|
|
777
|
+
catch (error) {
|
|
778
|
+
return next(error);
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
// List resources for a specific MCP server
|
|
782
|
+
const ListServerResourcesParamsSchema = z.object({
|
|
783
|
+
serverId: z.string().min(1, 'Server ID is required'),
|
|
784
|
+
});
|
|
785
|
+
app.get('/api/mcp/servers/:serverId/resources', async (req, res, next) => {
|
|
786
|
+
try {
|
|
787
|
+
ensureAgentAvailable();
|
|
788
|
+
const { serverId } = parseQuery(ListServerResourcesParamsSchema, req.params);
|
|
789
|
+
const resources = await activeAgent.listResourcesForServer(serverId);
|
|
790
|
+
return sendJsonResponse(res, { success: true, resources }, 200);
|
|
791
|
+
}
|
|
792
|
+
catch (error) {
|
|
793
|
+
return next(error);
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
// Read resource content from specific MCP server
|
|
797
|
+
const ReadServerResourceContentParamsSchema = z.object({
|
|
798
|
+
serverId: z.string().min(1, 'Server ID is required'),
|
|
799
|
+
resourceId: z
|
|
800
|
+
.string()
|
|
801
|
+
.min(1, 'Resource ID is required')
|
|
802
|
+
.transform((encoded) => decodeUriComponent(encoded)),
|
|
803
|
+
});
|
|
804
|
+
app.get('/api/mcp/servers/:serverId/resources/:resourceId/content', async (req, res, next) => {
|
|
805
|
+
try {
|
|
806
|
+
const { serverId, resourceId } = parseQuery(ReadServerResourceContentParamsSchema, req.params);
|
|
807
|
+
const qualifiedUri = `mcp:${serverId}:${resourceId}`;
|
|
808
|
+
const content = await activeAgent.readResource(qualifiedUri);
|
|
809
|
+
return sendJsonResponse(res, { success: true, data: { content } }, 200);
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
return next(error);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
406
815
|
// WebSocket handling
|
|
407
816
|
// handle inbound client messages over WebSocket
|
|
408
817
|
wss.on('connection', (ws) => {
|
|
@@ -424,9 +833,16 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
424
833
|
}
|
|
425
834
|
try {
|
|
426
835
|
const data = JSON.parse(messageString);
|
|
427
|
-
if (data.type === '
|
|
428
|
-
//
|
|
429
|
-
|
|
836
|
+
if (data.type === 'approvalResponse' && data.data) {
|
|
837
|
+
// Validate the approval response payload with Zod schema
|
|
838
|
+
const validationResult = ApprovalResponseSchema.safeParse(data.data);
|
|
839
|
+
if (!validationResult.success) {
|
|
840
|
+
logger.warn(`Received invalid approval response payload: ${validationResult.error.message}`);
|
|
841
|
+
// Do not emit invalid payloads
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
// Route validated approval response back via AgentEventBus
|
|
845
|
+
activeAgent.agentEventBus.emit('dexto:approvalResponse', validationResult.data);
|
|
430
846
|
return;
|
|
431
847
|
}
|
|
432
848
|
else if (data.type === 'message' &&
|
|
@@ -458,6 +874,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
458
874
|
// Check if agent is available before processing
|
|
459
875
|
try {
|
|
460
876
|
ensureAgentAvailable();
|
|
877
|
+
logger.debug('Agent availability check passed');
|
|
461
878
|
}
|
|
462
879
|
catch (error) {
|
|
463
880
|
logger.error(`Agent not available for WebSocket message: ${error}`);
|
|
@@ -465,7 +882,9 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
465
882
|
return;
|
|
466
883
|
}
|
|
467
884
|
// Comprehensive input validation
|
|
885
|
+
logger.debug('Getting effective config for validation');
|
|
468
886
|
const currentConfig = activeAgent.getEffectiveConfig(sessionId);
|
|
887
|
+
logger.debug('Validating input for LLM');
|
|
469
888
|
const validation = validateInputForLLM({
|
|
470
889
|
text: data.content,
|
|
471
890
|
...(imageDataInput && { imageData: imageDataInput }),
|
|
@@ -501,7 +920,9 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
501
920
|
sendWebSocketError(ws, hierarchicalError, sessionId);
|
|
502
921
|
return;
|
|
503
922
|
}
|
|
923
|
+
logger.debug('Validation passed, calling activeAgent.run()');
|
|
504
924
|
await activeAgent.run(data.content, imageDataInput, fileDataInput, sessionId, stream);
|
|
925
|
+
logger.debug('activeAgent.run() completed');
|
|
505
926
|
}
|
|
506
927
|
else if (data.type === 'reset') {
|
|
507
928
|
const sessionId = data.sessionId;
|
|
@@ -577,7 +998,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
577
998
|
const overrides = agentCardOverride ?? {};
|
|
578
999
|
const resolvedPort = typeof listenPort === 'number' ? listenPort : Number(process.env.PORT || 3000);
|
|
579
1000
|
const baseApiUrl = process.env.DEXTO_BASE_URL || `http://localhost:${resolvedPort}`;
|
|
580
|
-
|
|
1001
|
+
let agentCardData = createAgentCard({
|
|
581
1002
|
defaultName: overrides.name ?? 'dexto',
|
|
582
1003
|
defaultVersion: overrides.version ?? '1.0.0',
|
|
583
1004
|
defaultBaseUrl: baseApiUrl,
|
|
@@ -586,17 +1007,15 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
586
1007
|
const _agentName = agentCardData.name;
|
|
587
1008
|
const _agentVersion = agentCardData.version;
|
|
588
1009
|
// Setup A2A routes
|
|
589
|
-
setupA2ARoutes(app, agentCardData);
|
|
1010
|
+
setupA2ARoutes(app, () => agentCardData);
|
|
1011
|
+
// Setup Memory routes
|
|
1012
|
+
app.use('/api/memory', setupMemoryRoutes(() => activeAgent));
|
|
590
1013
|
// --- Initialize and Setup MCP Server and Endpoints ---
|
|
591
1014
|
// Get transport type from environment variable or default to http
|
|
592
1015
|
try {
|
|
593
1016
|
const transportType = process.env.DEXTO_MCP_TRANSPORT_TYPE || 'http';
|
|
594
1017
|
const mcpTransport = await createMcpTransport(transportType);
|
|
595
|
-
|
|
596
|
-
// initializeMcpServer receives the original agent, so MCP endpoints keep talking to the stale instance post-switch.
|
|
597
|
-
// Make MCP consume the current agent via a getter to stay in sync.
|
|
598
|
-
await initializeMcpServer(agent, agentCardData, // Pass the agent card data for the MCP resource
|
|
599
|
-
mcpTransport);
|
|
1018
|
+
await initializeMcpServer(() => activeAgent, () => agentCardData, mcpTransport);
|
|
600
1019
|
await initializeMcpServerApiEndpoints(app, mcpTransport);
|
|
601
1020
|
}
|
|
602
1021
|
catch (error) {
|
|
@@ -607,14 +1026,30 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
607
1026
|
});
|
|
608
1027
|
}
|
|
609
1028
|
// ===== Agents API =====
|
|
1029
|
+
// TODO: Consider moving to AgentRegistry.getAgentInfo() if this pattern is needed
|
|
1030
|
+
// outside of API response formatting (e.g., in CLI commands, WebUI hooks, client SDK)
|
|
1031
|
+
/**
|
|
1032
|
+
* Helper to resolve agent ID to { id, name } by looking up in registry
|
|
1033
|
+
* @param agentId - The agent ID to resolve
|
|
1034
|
+
* @returns Object with id and name (uses deriveDisplayName as fallback)
|
|
1035
|
+
*/
|
|
1036
|
+
async function resolveAgentInfo(agentId) {
|
|
1037
|
+
const agents = await Dexto.listAgents();
|
|
1038
|
+
const agent = agents.installed.find((a) => a.id === agentId) ??
|
|
1039
|
+
agents.available.find((a) => a.id === agentId);
|
|
1040
|
+
return {
|
|
1041
|
+
id: agentId,
|
|
1042
|
+
name: agent?.name ?? deriveDisplayName(agentId),
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
610
1045
|
app.get('/api/agents', async (_req, res, next) => {
|
|
611
1046
|
try {
|
|
612
|
-
|
|
613
|
-
const
|
|
1047
|
+
const agents = await Dexto.listAgents();
|
|
1048
|
+
const currentId = activeAgentId ?? null;
|
|
614
1049
|
return sendJsonResponse(res, {
|
|
615
1050
|
installed: agents.installed,
|
|
616
1051
|
available: agents.available,
|
|
617
|
-
current: {
|
|
1052
|
+
current: currentId ? await resolveAgentInfo(currentId) : { id: null, name: null },
|
|
618
1053
|
});
|
|
619
1054
|
}
|
|
620
1055
|
catch (error) {
|
|
@@ -623,20 +1058,102 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
623
1058
|
});
|
|
624
1059
|
app.get('/api/agents/current', async (_req, res, next) => {
|
|
625
1060
|
try {
|
|
626
|
-
|
|
627
|
-
|
|
1061
|
+
const currentId = activeAgentId ?? null;
|
|
1062
|
+
if (!currentId) {
|
|
1063
|
+
return sendJsonResponse(res, { id: null, name: null });
|
|
1064
|
+
}
|
|
1065
|
+
return sendJsonResponse(res, await resolveAgentInfo(currentId));
|
|
628
1066
|
}
|
|
629
1067
|
catch (error) {
|
|
630
1068
|
return next(error);
|
|
631
1069
|
}
|
|
632
1070
|
});
|
|
633
|
-
const
|
|
1071
|
+
const AgentIdentifierSchema = z
|
|
1072
|
+
.object({
|
|
1073
|
+
id: z
|
|
1074
|
+
.string()
|
|
1075
|
+
.min(1, 'Agent id is required')
|
|
1076
|
+
.describe('Unique agent identifier (e.g., "database-agent")'),
|
|
1077
|
+
path: z
|
|
1078
|
+
.string()
|
|
1079
|
+
.optional()
|
|
1080
|
+
.describe('Optional absolute file path for file-based agents (e.g., "/path/to/agent.yml")'),
|
|
1081
|
+
})
|
|
1082
|
+
.strict();
|
|
1083
|
+
const UninstallAgentSchema = z
|
|
1084
|
+
.object({
|
|
1085
|
+
id: z
|
|
1086
|
+
.string()
|
|
1087
|
+
.min(1, 'Agent id is required')
|
|
1088
|
+
.describe('Unique agent identifier to uninstall'),
|
|
1089
|
+
force: z
|
|
1090
|
+
.boolean()
|
|
1091
|
+
.default(false)
|
|
1092
|
+
.describe('Force uninstall even if agent is currently active'),
|
|
1093
|
+
})
|
|
1094
|
+
.strict();
|
|
1095
|
+
// Schema for custom agent installation (CLI/automation entrypoint)
|
|
1096
|
+
const CustomAgentInstallSchema = z
|
|
1097
|
+
.object({
|
|
1098
|
+
id: z.string().min(1, 'Agent id is required').describe('Unique agent identifier'),
|
|
1099
|
+
name: z.string().optional().describe('Display name (defaults to derived from id)'),
|
|
1100
|
+
sourcePath: z.string().min(1).describe('Path to agent configuration file or directory'),
|
|
1101
|
+
metadata: z
|
|
1102
|
+
.object({
|
|
1103
|
+
description: z
|
|
1104
|
+
.string()
|
|
1105
|
+
.min(1)
|
|
1106
|
+
.describe('Human-readable description of the agent'),
|
|
1107
|
+
author: z.string().min(1).describe('Agent author or organization name'),
|
|
1108
|
+
tags: z.array(z.string()).describe('Tags for categorizing the agent'),
|
|
1109
|
+
main: z
|
|
1110
|
+
.string()
|
|
1111
|
+
.optional()
|
|
1112
|
+
.describe('Main configuration file name within source directory'),
|
|
1113
|
+
})
|
|
1114
|
+
.strict(),
|
|
1115
|
+
injectPreferences: z
|
|
1116
|
+
.boolean()
|
|
1117
|
+
.default(true)
|
|
1118
|
+
.describe('Whether to inject user preferences into agent config'),
|
|
1119
|
+
})
|
|
1120
|
+
.strict()
|
|
1121
|
+
.transform((value) => {
|
|
1122
|
+
const displayName = value.name?.trim() || deriveDisplayName(value.id);
|
|
1123
|
+
return {
|
|
1124
|
+
id: value.id,
|
|
1125
|
+
displayName,
|
|
1126
|
+
sourcePath: value.sourcePath,
|
|
1127
|
+
metadata: value.metadata,
|
|
1128
|
+
injectPreferences: value.injectPreferences,
|
|
1129
|
+
};
|
|
1130
|
+
});
|
|
634
1131
|
app.post('/api/agents/install', express.json(), async (req, res, next) => {
|
|
635
1132
|
try {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
1133
|
+
// Check if this is a custom agent installation (has sourcePath and metadata)
|
|
1134
|
+
if (req.body.sourcePath && req.body.metadata) {
|
|
1135
|
+
const { id, displayName, sourcePath, metadata, injectPreferences } = CustomAgentInstallSchema.parse(req.body);
|
|
1136
|
+
// Clean metadata to match exact optional property types
|
|
1137
|
+
await Dexto.installCustomAgent(id, sourcePath, {
|
|
1138
|
+
name: displayName,
|
|
1139
|
+
description: metadata.description,
|
|
1140
|
+
author: metadata.author,
|
|
1141
|
+
tags: metadata.tags,
|
|
1142
|
+
...(metadata.main ? { main: metadata.main } : {}),
|
|
1143
|
+
}, injectPreferences);
|
|
1144
|
+
return sendJsonResponse(res, { installed: true, id, name: displayName, type: 'custom' }, 201);
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
// Registry agent installation
|
|
1148
|
+
const { id } = parseBody(AgentIdentifierSchema, req.body);
|
|
1149
|
+
await Dexto.installAgent(id);
|
|
1150
|
+
const agentInfo = await resolveAgentInfo(id);
|
|
1151
|
+
return sendJsonResponse(res, {
|
|
1152
|
+
installed: true,
|
|
1153
|
+
...agentInfo,
|
|
1154
|
+
type: 'builtin',
|
|
1155
|
+
}, 201);
|
|
1156
|
+
}
|
|
640
1157
|
}
|
|
641
1158
|
catch (error) {
|
|
642
1159
|
return next(error);
|
|
@@ -644,61 +1161,351 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
644
1161
|
});
|
|
645
1162
|
app.post('/api/agents/switch', express.json(), async (req, res, next) => {
|
|
646
1163
|
try {
|
|
647
|
-
const {
|
|
648
|
-
|
|
1164
|
+
const { id, path } = parseBody(AgentIdentifierSchema, req.body);
|
|
1165
|
+
// Route based on presence of path parameter
|
|
1166
|
+
const result = path ? await switchAgentByPath(path) : await switchAgentById(id);
|
|
649
1167
|
return sendJsonResponse(res, { switched: true, ...result });
|
|
650
1168
|
}
|
|
651
1169
|
catch (error) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1170
|
+
return next(error);
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
app.post('/api/agents/validate-name', express.json(), async (req, res, next) => {
|
|
1174
|
+
try {
|
|
1175
|
+
const { id } = parseBody(AgentIdentifierSchema, req.body);
|
|
1176
|
+
const agents = await Dexto.listAgents();
|
|
1177
|
+
// Check if name exists in installed agents
|
|
1178
|
+
const installedAgent = agents.installed.find((a) => a.id === id);
|
|
1179
|
+
if (installedAgent) {
|
|
1180
|
+
return sendJsonResponse(res, {
|
|
1181
|
+
valid: false,
|
|
1182
|
+
conflict: installedAgent.type,
|
|
1183
|
+
message: `Agent id '${id}' already exists (${installedAgent.type})`,
|
|
1184
|
+
});
|
|
656
1185
|
}
|
|
1186
|
+
// Check if name exists in available agents (registry)
|
|
1187
|
+
const availableAgent = agents.available.find((a) => a.id === id);
|
|
1188
|
+
if (availableAgent) {
|
|
1189
|
+
return sendJsonResponse(res, {
|
|
1190
|
+
valid: false,
|
|
1191
|
+
conflict: availableAgent.type,
|
|
1192
|
+
message: `Agent id '${id}' conflicts with ${availableAgent.type} agent`,
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
return sendJsonResponse(res, { valid: true });
|
|
1196
|
+
}
|
|
1197
|
+
catch (error) {
|
|
1198
|
+
return next(error);
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
app.post('/api/agents/uninstall', express.json(), async (req, res, next) => {
|
|
1202
|
+
try {
|
|
1203
|
+
const { id, force } = parseBody(UninstallAgentSchema, req.body);
|
|
1204
|
+
await Dexto.uninstallAgent(id, force);
|
|
1205
|
+
return sendJsonResponse(res, { uninstalled: true, id });
|
|
1206
|
+
}
|
|
1207
|
+
catch (error) {
|
|
1208
|
+
return next(error);
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
// Schema for creating custom agents via UI
|
|
1212
|
+
const CustomAgentCreateSchema = z
|
|
1213
|
+
.object({
|
|
1214
|
+
// Registry metadata
|
|
1215
|
+
id: z
|
|
1216
|
+
.string()
|
|
1217
|
+
.min(1, 'Agent ID is required')
|
|
1218
|
+
.regex(/^[a-z0-9-]+$/, 'Agent ID must contain only lowercase letters, numbers, and hyphens')
|
|
1219
|
+
.describe('Unique agent identifier'),
|
|
1220
|
+
name: z
|
|
1221
|
+
.string()
|
|
1222
|
+
.min(1, 'Agent name is required')
|
|
1223
|
+
.describe('Display name for the agent'),
|
|
1224
|
+
description: z
|
|
1225
|
+
.string()
|
|
1226
|
+
.min(1, 'Description is required')
|
|
1227
|
+
.describe('One-line description of the agent'),
|
|
1228
|
+
author: z.string().optional().describe('Author or organization'),
|
|
1229
|
+
tags: z.array(z.string()).default([]).describe('Tags for discovery'),
|
|
1230
|
+
// Agent configuration
|
|
1231
|
+
llm: z
|
|
1232
|
+
.object({
|
|
1233
|
+
provider: z.enum(LLM_PROVIDERS).describe('LLM provider id'),
|
|
1234
|
+
model: z.string().min(1, 'Model is required').describe('Model name'),
|
|
1235
|
+
apiKey: z
|
|
1236
|
+
.string()
|
|
1237
|
+
.optional()
|
|
1238
|
+
.describe('API key or environment variable reference (e.g., $OPENAI_API_KEY)'),
|
|
1239
|
+
})
|
|
1240
|
+
.strict()
|
|
1241
|
+
.describe('LLM configuration'),
|
|
1242
|
+
systemPrompt: z
|
|
1243
|
+
.string()
|
|
1244
|
+
.min(1, 'System prompt is required')
|
|
1245
|
+
.describe('System prompt for the agent'),
|
|
1246
|
+
})
|
|
1247
|
+
.strict();
|
|
1248
|
+
// Create a new custom agent from UI
|
|
1249
|
+
app.post('/api/agents/custom/create', express.json(), async (req, res, next) => {
|
|
1250
|
+
try {
|
|
1251
|
+
const { id, name, description, author, tags, llm, systemPrompt } = parseBody(CustomAgentCreateSchema, req.body);
|
|
1252
|
+
const provider = llm.provider;
|
|
1253
|
+
// Handle API key: if it's a raw key, store securely and use env var reference
|
|
1254
|
+
let apiKeyRef;
|
|
1255
|
+
if (llm.apiKey && !llm.apiKey.startsWith('$')) {
|
|
1256
|
+
// Raw API key provided - store securely and get env var reference
|
|
1257
|
+
const meta = await saveProviderApiKey(provider, llm.apiKey, process.cwd());
|
|
1258
|
+
apiKeyRef = `$${meta.envVar}`;
|
|
1259
|
+
logger.info(`Stored API key securely for ${provider}, using env var: ${meta.envVar}`);
|
|
1260
|
+
}
|
|
1261
|
+
else if (llm.apiKey) {
|
|
1262
|
+
// Already an env var reference
|
|
1263
|
+
apiKeyRef = llm.apiKey;
|
|
1264
|
+
}
|
|
1265
|
+
// Create agent YAML content (with env var reference instead of raw key)
|
|
1266
|
+
const agentConfig = {
|
|
1267
|
+
llm: {
|
|
1268
|
+
provider,
|
|
1269
|
+
model: llm.model,
|
|
1270
|
+
apiKey: apiKeyRef || `$${getPrimaryApiKeyEnvVar(provider)}`,
|
|
1271
|
+
},
|
|
1272
|
+
systemPrompt,
|
|
1273
|
+
};
|
|
1274
|
+
const yamlContent = yamlStringify(agentConfig);
|
|
1275
|
+
logger.info(`Creating agent config for ${id}:`, { agentConfig, yamlContent });
|
|
1276
|
+
// Create temporary file
|
|
1277
|
+
const tmpDir = os.tmpdir();
|
|
1278
|
+
const tmpFile = path.join(tmpDir, `${id}-${Date.now()}.yml`);
|
|
1279
|
+
await fs.writeFile(tmpFile, yamlContent, 'utf-8');
|
|
1280
|
+
try {
|
|
1281
|
+
// Install the custom agent
|
|
1282
|
+
await Dexto.installCustomAgent(id, tmpFile, {
|
|
1283
|
+
name,
|
|
1284
|
+
description,
|
|
1285
|
+
author: author || 'Custom',
|
|
1286
|
+
tags: tags || [],
|
|
1287
|
+
}, false // Don't inject preferences
|
|
1288
|
+
);
|
|
1289
|
+
// Clean up temp file
|
|
1290
|
+
await fs.unlink(tmpFile).catch(() => { });
|
|
1291
|
+
return sendJsonResponse(res, { created: true, id, name }, 201);
|
|
1292
|
+
}
|
|
1293
|
+
catch (installError) {
|
|
1294
|
+
// Clean up temp file on error
|
|
1295
|
+
await fs.unlink(tmpFile).catch(() => { });
|
|
1296
|
+
throw installError;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
catch (error) {
|
|
657
1300
|
return next(error);
|
|
658
1301
|
}
|
|
659
1302
|
});
|
|
660
1303
|
// Configuration export endpoint
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1304
|
+
// Get default greeting (for UI consumption)
|
|
1305
|
+
const GetGreetingQuerySchema = z.object({
|
|
1306
|
+
sessionId: z.string().optional(),
|
|
1307
|
+
});
|
|
1308
|
+
app.get('/api/greeting', async (req, res, next) => {
|
|
1309
|
+
try {
|
|
1310
|
+
ensureAgentAvailable();
|
|
1311
|
+
const { sessionId } = parseQuery(GetGreetingQuerySchema, req.query);
|
|
1312
|
+
const config = activeAgent.getEffectiveConfig(sessionId);
|
|
1313
|
+
res.json({ greeting: config.greeting });
|
|
667
1314
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* Helper function to redact environment variables in a server config
|
|
672
|
-
*/
|
|
673
|
-
function redactServerEnvVars(serverConfig) {
|
|
674
|
-
if (!serverConfig.env) {
|
|
675
|
-
return serverConfig;
|
|
1315
|
+
catch (error) {
|
|
1316
|
+
return next(error);
|
|
676
1317
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1318
|
+
});
|
|
1319
|
+
// ============= AGENT CONFIGURATION MANAGEMENT =============
|
|
1320
|
+
// Get agent file path
|
|
1321
|
+
app.get('/api/agent/path', async (req, res, next) => {
|
|
1322
|
+
try {
|
|
1323
|
+
ensureAgentAvailable();
|
|
1324
|
+
const agentPath = activeAgent.getAgentFilePath();
|
|
1325
|
+
const relativePath = path.basename(agentPath);
|
|
1326
|
+
const ext = path.extname(agentPath);
|
|
1327
|
+
const name = path.basename(agentPath, ext);
|
|
1328
|
+
res.json({
|
|
1329
|
+
path: agentPath,
|
|
1330
|
+
relativePath,
|
|
1331
|
+
name,
|
|
1332
|
+
isDefault: name === 'default-agent',
|
|
1333
|
+
});
|
|
680
1334
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
env: redactedEnv,
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Helper function to redact all MCP servers configuration
|
|
688
|
-
*/
|
|
689
|
-
function redactMcpServersConfig(mcpServers) {
|
|
690
|
-
if (!mcpServers) {
|
|
691
|
-
return {};
|
|
1335
|
+
catch (error) {
|
|
1336
|
+
return next(error);
|
|
692
1337
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1338
|
+
});
|
|
1339
|
+
// Get editable agent configuration (non-redacted YAML)
|
|
1340
|
+
app.get('/api/agent/config', async (req, res, next) => {
|
|
1341
|
+
try {
|
|
1342
|
+
ensureAgentAvailable();
|
|
1343
|
+
// Get the agent file path being used
|
|
1344
|
+
const agentPath = activeAgent.getAgentFilePath();
|
|
1345
|
+
// Read raw YAML from file (not expanded env vars)
|
|
1346
|
+
const yamlContent = await fs.readFile(agentPath, 'utf-8');
|
|
1347
|
+
// Get metadata
|
|
1348
|
+
const stats = await fs.stat(agentPath);
|
|
1349
|
+
res.json({
|
|
1350
|
+
yaml: yamlContent,
|
|
1351
|
+
path: agentPath,
|
|
1352
|
+
relativePath: path.basename(agentPath),
|
|
1353
|
+
lastModified: stats.mtime,
|
|
1354
|
+
warnings: [
|
|
1355
|
+
'Environment variables ($VAR) will be resolved at runtime',
|
|
1356
|
+
'API keys should use environment variables',
|
|
1357
|
+
],
|
|
1358
|
+
});
|
|
696
1359
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
1360
|
+
catch (error) {
|
|
1361
|
+
return next(error);
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
// Validate agent configuration without saving
|
|
1365
|
+
const AgentConfigValidateSchema = z.object({
|
|
1366
|
+
yaml: z.string().min(1, 'YAML content is required'),
|
|
1367
|
+
});
|
|
1368
|
+
app.post('/api/agent/validate', express.json(), async (req, res, next) => {
|
|
1369
|
+
try {
|
|
1370
|
+
ensureAgentAvailable();
|
|
1371
|
+
const { yaml } = parseBody(AgentConfigValidateSchema, req.body);
|
|
1372
|
+
// Parse YAML
|
|
1373
|
+
let parsed;
|
|
1374
|
+
try {
|
|
1375
|
+
parsed = yamlParse(yaml);
|
|
1376
|
+
}
|
|
1377
|
+
catch (parseError) {
|
|
1378
|
+
return res.json({
|
|
1379
|
+
valid: false,
|
|
1380
|
+
errors: [
|
|
1381
|
+
{
|
|
1382
|
+
line: parseError.linePos?.[0]?.line || 1,
|
|
1383
|
+
column: parseError.linePos?.[0]?.col || 1,
|
|
1384
|
+
message: parseError.message,
|
|
1385
|
+
code: 'YAML_PARSE_ERROR',
|
|
1386
|
+
},
|
|
1387
|
+
],
|
|
1388
|
+
warnings: [],
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
// Validate against schema
|
|
1392
|
+
const result = AgentConfigSchema.safeParse(parsed);
|
|
1393
|
+
if (!result.success) {
|
|
1394
|
+
const errors = result.error.errors.map((err) => ({
|
|
1395
|
+
path: err.path.join('.'),
|
|
1396
|
+
message: err.message,
|
|
1397
|
+
code: 'SCHEMA_VALIDATION_ERROR',
|
|
1398
|
+
}));
|
|
1399
|
+
return res.json({
|
|
1400
|
+
valid: false,
|
|
1401
|
+
errors,
|
|
1402
|
+
warnings: [],
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
// Check for warnings (e.g., plain text API keys)
|
|
1406
|
+
const warnings = [];
|
|
1407
|
+
if (parsed.llm?.apiKey && !parsed.llm.apiKey.startsWith('$')) {
|
|
1408
|
+
warnings.push({
|
|
1409
|
+
path: 'llm.apiKey',
|
|
1410
|
+
message: 'Consider using environment variable instead of plain text',
|
|
1411
|
+
code: 'SECURITY_WARNING',
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
res.json({
|
|
1415
|
+
valid: true,
|
|
1416
|
+
errors: [],
|
|
1417
|
+
warnings,
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
return next(error);
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
// Save agent configuration
|
|
1425
|
+
const AgentConfigSaveSchema = z.object({
|
|
1426
|
+
yaml: z.string().min(1, 'YAML content is required'),
|
|
1427
|
+
});
|
|
1428
|
+
app.post('/api/agent/config', express.json(), async (req, res, next) => {
|
|
1429
|
+
try {
|
|
1430
|
+
ensureAgentAvailable();
|
|
1431
|
+
const { yaml } = parseBody(AgentConfigSaveSchema, req.body);
|
|
1432
|
+
// Validate YAML syntax first
|
|
1433
|
+
let parsed;
|
|
1434
|
+
try {
|
|
1435
|
+
parsed = yamlParse(yaml);
|
|
1436
|
+
}
|
|
1437
|
+
catch (parseError) {
|
|
1438
|
+
throw new DextoValidationError([
|
|
1439
|
+
{
|
|
1440
|
+
code: AgentErrorCode.INVALID_CONFIG,
|
|
1441
|
+
message: `Invalid YAML syntax: ${parseError.message}`,
|
|
1442
|
+
scope: ErrorScope.AGENT,
|
|
1443
|
+
type: ErrorType.USER,
|
|
1444
|
+
severity: 'error',
|
|
1445
|
+
},
|
|
1446
|
+
]);
|
|
1447
|
+
}
|
|
1448
|
+
// Validate schema
|
|
1449
|
+
const validationResult = AgentConfigSchema.safeParse(parsed);
|
|
1450
|
+
if (!validationResult.success) {
|
|
1451
|
+
throw new DextoValidationError(validationResult.error.errors.map((err) => ({
|
|
1452
|
+
code: AgentErrorCode.INVALID_CONFIG,
|
|
1453
|
+
message: `${err.path.join('.')}: ${err.message}`,
|
|
1454
|
+
scope: ErrorScope.AGENT,
|
|
1455
|
+
type: ErrorType.USER,
|
|
1456
|
+
severity: 'error',
|
|
1457
|
+
})));
|
|
1458
|
+
}
|
|
1459
|
+
// Get target file path
|
|
1460
|
+
const agentPath = activeAgent.getAgentFilePath();
|
|
1461
|
+
// Create backup
|
|
1462
|
+
const backupPath = `${agentPath}.backup`;
|
|
1463
|
+
await fs.copyFile(agentPath, backupPath);
|
|
1464
|
+
try {
|
|
1465
|
+
// Write new config
|
|
1466
|
+
await fs.writeFile(agentPath, yaml, 'utf-8');
|
|
1467
|
+
// Reload configuration to detect what changed
|
|
1468
|
+
const reloadResult = await activeAgent.reloadConfig();
|
|
1469
|
+
// If any changes require restart, automatically restart the agent
|
|
1470
|
+
if (reloadResult.restartRequired.length > 0) {
|
|
1471
|
+
logger.info(`Auto-restarting agent to apply changes: ${reloadResult.restartRequired.join(', ')}`);
|
|
1472
|
+
await activeAgent.restart();
|
|
1473
|
+
logger.info('Agent restarted successfully with all event subscribers reconnected');
|
|
1474
|
+
}
|
|
1475
|
+
// Clean up backup file after successful save
|
|
1476
|
+
await fs.unlink(backupPath).catch(() => {
|
|
1477
|
+
// Ignore errors if backup file doesn't exist
|
|
1478
|
+
});
|
|
1479
|
+
logger.info(`Agent configuration saved and applied: ${agentPath}`);
|
|
1480
|
+
res.json({
|
|
1481
|
+
ok: true,
|
|
1482
|
+
path: agentPath,
|
|
1483
|
+
reloaded: true,
|
|
1484
|
+
restarted: reloadResult.restartRequired.length > 0,
|
|
1485
|
+
changesApplied: reloadResult.restartRequired,
|
|
1486
|
+
message: reloadResult.restartRequired.length > 0
|
|
1487
|
+
? 'Configuration saved and applied successfully (agent restarted)'
|
|
1488
|
+
: 'Configuration saved successfully (no changes detected)',
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
catch (writeError) {
|
|
1492
|
+
// Restore backup on error
|
|
1493
|
+
await fs.copyFile(backupPath, agentPath);
|
|
1494
|
+
throw writeError;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
catch (error) {
|
|
1498
|
+
return next(error);
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
// Export effective agent configuration (with masked secrets)
|
|
1502
|
+
const ExportConfigQuerySchema = z.object({
|
|
1503
|
+
sessionId: z.string().optional(),
|
|
1504
|
+
});
|
|
1505
|
+
app.get('/api/agent/config/export', async (req, res, next) => {
|
|
700
1506
|
try {
|
|
701
|
-
|
|
1507
|
+
ensureAgentAvailable();
|
|
1508
|
+
const { sessionId } = parseQuery(ExportConfigQuerySchema, req.query);
|
|
702
1509
|
const config = activeAgent.getEffectiveConfig(sessionId);
|
|
703
1510
|
// Export config as YAML, masking sensitive data
|
|
704
1511
|
const maskedConfig = {
|
|
@@ -717,21 +1524,14 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
717
1524
|
return next(error);
|
|
718
1525
|
}
|
|
719
1526
|
});
|
|
720
|
-
//
|
|
721
|
-
app.get('/api/greeting', async (req, res, next) => {
|
|
722
|
-
try {
|
|
723
|
-
const sessionId = req.query.sessionId;
|
|
724
|
-
const config = activeAgent.getEffectiveConfig(sessionId);
|
|
725
|
-
res.json({ greeting: config.greeting });
|
|
726
|
-
}
|
|
727
|
-
catch (error) {
|
|
728
|
-
return next(error);
|
|
729
|
-
}
|
|
730
|
-
});
|
|
1527
|
+
// ============= LLM MANAGEMENT =============
|
|
731
1528
|
// Get current LLM configuration
|
|
1529
|
+
const GetCurrentLLMQuerySchema = z.object({
|
|
1530
|
+
sessionId: z.string().optional(),
|
|
1531
|
+
});
|
|
732
1532
|
app.get('/api/llm/current', async (req, res, next) => {
|
|
733
1533
|
try {
|
|
734
|
-
const { sessionId } = req.query;
|
|
1534
|
+
const { sessionId } = parseQuery(GetCurrentLLMQuerySchema, req.query);
|
|
735
1535
|
// Use session-specific config if sessionId is provided, otherwise use default
|
|
736
1536
|
const currentConfig = sessionId
|
|
737
1537
|
? activeAgent.getEffectiveConfig(sessionId).llm
|
|
@@ -751,37 +1551,35 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
751
1551
|
return next(error);
|
|
752
1552
|
}
|
|
753
1553
|
});
|
|
754
|
-
// (Deprecated) /api/llm/providers has been replaced by /api/llm/catalog
|
|
755
1554
|
// LLM Catalog: providers, models, and API key presence (with filters)
|
|
1555
|
+
const LLMCatalogQuerySchema = z.object({
|
|
1556
|
+
provider: z
|
|
1557
|
+
.union([z.string(), z.array(z.string())])
|
|
1558
|
+
.optional()
|
|
1559
|
+
.transform((value) => Array.isArray(value) ? value : value ? value.split(',') : undefined),
|
|
1560
|
+
hasKey: z
|
|
1561
|
+
.union([z.literal('true'), z.literal('false'), z.literal('1'), z.literal('0')])
|
|
1562
|
+
.optional()
|
|
1563
|
+
.transform((raw) => raw === 'true' || raw === '1'
|
|
1564
|
+
? true
|
|
1565
|
+
: raw === 'false' || raw === '0'
|
|
1566
|
+
? false
|
|
1567
|
+
: undefined),
|
|
1568
|
+
router: z.enum(LLM_ROUTERS).optional(),
|
|
1569
|
+
fileType: z.enum(SUPPORTED_FILE_TYPES).optional(),
|
|
1570
|
+
defaultOnly: z
|
|
1571
|
+
.union([z.literal('true'), z.literal('false'), z.literal('1'), z.literal('0')])
|
|
1572
|
+
.optional()
|
|
1573
|
+
.transform((raw) => raw === 'true' || raw === '1'
|
|
1574
|
+
? true
|
|
1575
|
+
: raw === 'false' || raw === '0'
|
|
1576
|
+
? false
|
|
1577
|
+
: undefined),
|
|
1578
|
+
mode: z.enum(['grouped', 'flat']).default('grouped'),
|
|
1579
|
+
});
|
|
756
1580
|
app.get('/api/llm/catalog', async (req, res, next) => {
|
|
757
1581
|
try {
|
|
758
|
-
|
|
759
|
-
const QuerySchema = z.object({
|
|
760
|
-
provider: z
|
|
761
|
-
.union([z.string(), z.array(z.string())])
|
|
762
|
-
.optional()
|
|
763
|
-
.transform((value) => Array.isArray(value) ? value : value ? value.split(',') : undefined),
|
|
764
|
-
hasKey: z
|
|
765
|
-
.union([z.literal('true'), z.literal('false'), z.literal('1'), z.literal('0')])
|
|
766
|
-
.optional()
|
|
767
|
-
.transform((raw) => raw === 'true' || raw === '1'
|
|
768
|
-
? true
|
|
769
|
-
: raw === 'false' || raw === '0'
|
|
770
|
-
? false
|
|
771
|
-
: undefined),
|
|
772
|
-
router: z.enum(LLM_ROUTERS).optional(),
|
|
773
|
-
fileType: z.enum(SUPPORTED_FILE_TYPES).optional(),
|
|
774
|
-
defaultOnly: z
|
|
775
|
-
.union([z.literal('true'), z.literal('false'), z.literal('1'), z.literal('0')])
|
|
776
|
-
.optional()
|
|
777
|
-
.transform((raw) => raw === 'true' || raw === '1'
|
|
778
|
-
? true
|
|
779
|
-
: raw === 'false' || raw === '0'
|
|
780
|
-
? false
|
|
781
|
-
: undefined),
|
|
782
|
-
mode: z.enum(['grouped', 'flat']).optional().default('grouped'),
|
|
783
|
-
});
|
|
784
|
-
const queryParams = QuerySchema.parse(req.query);
|
|
1582
|
+
const queryParams = LLMCatalogQuerySchema.parse(req.query);
|
|
785
1583
|
const providers = {};
|
|
786
1584
|
for (const provider of LLM_PROVIDERS) {
|
|
787
1585
|
const info = LLM_REGISTRY[provider];
|
|
@@ -876,13 +1674,13 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
876
1674
|
}
|
|
877
1675
|
});
|
|
878
1676
|
// Save provider API key (never echoes the key back)
|
|
1677
|
+
const SaveProviderApiKeyBodySchema = z.object({
|
|
1678
|
+
provider: z.enum(LLM_PROVIDERS),
|
|
1679
|
+
apiKey: z.string().min(1, 'API key is required'),
|
|
1680
|
+
});
|
|
879
1681
|
app.post('/api/llm/key', express.json({ limit: '4kb' }), async (req, res, next) => {
|
|
880
1682
|
try {
|
|
881
|
-
const
|
|
882
|
-
provider: z.enum(LLM_PROVIDERS),
|
|
883
|
-
apiKey: z.string().min(1, 'API key is required'),
|
|
884
|
-
});
|
|
885
|
-
const body = schema.parse(req.body);
|
|
1683
|
+
const body = parseBody(SaveProviderApiKeyBodySchema, req.body);
|
|
886
1684
|
const meta = await saveProviderApiKey(body.provider, body.apiKey, process.cwd());
|
|
887
1685
|
return sendJsonResponse(res, { ok: true, provider: body.provider, envVar: meta.envVar }, 200);
|
|
888
1686
|
}
|
|
@@ -891,11 +1689,15 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
891
1689
|
}
|
|
892
1690
|
});
|
|
893
1691
|
// Switch LLM configuration
|
|
1692
|
+
const SwitchLLMBodySchema = z
|
|
1693
|
+
.object({
|
|
1694
|
+
sessionId: z.string().optional(),
|
|
1695
|
+
})
|
|
1696
|
+
.passthrough(); // Allow additional LLM config fields
|
|
894
1697
|
app.post('/api/llm/switch', express.json(), async (req, res, next) => {
|
|
895
1698
|
try {
|
|
896
|
-
const
|
|
897
|
-
const
|
|
898
|
-
const { sessionId: _omit, ...llmCandidate } = body;
|
|
1699
|
+
const parsed = parseBody(SwitchLLMBodySchema, req.body);
|
|
1700
|
+
const { sessionId, ...llmCandidate } = parsed;
|
|
899
1701
|
const llmConfig = LLMUpdatesSchema.parse(llmCandidate);
|
|
900
1702
|
const config = await activeAgent.switchLLM(llmConfig, sessionId);
|
|
901
1703
|
return res.status(200).json({ config, sessionId });
|
|
@@ -906,7 +1708,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
906
1708
|
});
|
|
907
1709
|
// Session Management APIs
|
|
908
1710
|
// List all active sessions
|
|
909
|
-
app.get('/api/sessions', async (
|
|
1711
|
+
app.get('/api/sessions', async (_req, res, next) => {
|
|
910
1712
|
try {
|
|
911
1713
|
const sessionIds = await activeAgent.listSessions();
|
|
912
1714
|
const sessions = await Promise.all(sessionIds.map(async (id) => {
|
|
@@ -917,6 +1719,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
917
1719
|
createdAt: metadata?.createdAt || null,
|
|
918
1720
|
lastActivity: metadata?.lastActivity || null,
|
|
919
1721
|
messageCount: metadata?.messageCount || 0,
|
|
1722
|
+
title: metadata?.title || null,
|
|
920
1723
|
};
|
|
921
1724
|
}
|
|
922
1725
|
catch (_error) {
|
|
@@ -926,6 +1729,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
926
1729
|
createdAt: null,
|
|
927
1730
|
lastActivity: null,
|
|
928
1731
|
messageCount: 0,
|
|
1732
|
+
title: null,
|
|
929
1733
|
};
|
|
930
1734
|
}
|
|
931
1735
|
}));
|
|
@@ -936,9 +1740,12 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
936
1740
|
}
|
|
937
1741
|
});
|
|
938
1742
|
// Create a new session
|
|
1743
|
+
const CreateSessionBodySchema = z.object({
|
|
1744
|
+
sessionId: z.string().optional(),
|
|
1745
|
+
});
|
|
939
1746
|
app.post('/api/sessions', express.json(), async (req, res, next) => {
|
|
940
1747
|
try {
|
|
941
|
-
const { sessionId } = req.body;
|
|
1748
|
+
const { sessionId } = parseBody(CreateSessionBodySchema, req.body);
|
|
942
1749
|
const session = await activeAgent.createSession(sessionId);
|
|
943
1750
|
const metadata = await activeAgent.getSessionMetadata(session.id);
|
|
944
1751
|
return res.status(201).json({
|
|
@@ -947,6 +1754,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
947
1754
|
createdAt: metadata?.createdAt || Date.now(),
|
|
948
1755
|
lastActivity: metadata?.lastActivity || Date.now(),
|
|
949
1756
|
messageCount: metadata?.messageCount || 0,
|
|
1757
|
+
title: metadata?.title || null,
|
|
950
1758
|
},
|
|
951
1759
|
});
|
|
952
1760
|
}
|
|
@@ -955,7 +1763,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
955
1763
|
}
|
|
956
1764
|
});
|
|
957
1765
|
// Get current working session (must come before parameterized route)
|
|
958
|
-
app.get('/api/sessions/current', async (
|
|
1766
|
+
app.get('/api/sessions/current', async (_req, res, next) => {
|
|
959
1767
|
try {
|
|
960
1768
|
const currentSessionId = activeAgent.getCurrentSessionId();
|
|
961
1769
|
return res.json({ currentSessionId });
|
|
@@ -965,9 +1773,12 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
965
1773
|
}
|
|
966
1774
|
});
|
|
967
1775
|
// Get session details
|
|
1776
|
+
const GetSessionDetailsParamsSchema = z.object({
|
|
1777
|
+
sessionId: z.string().min(1, 'Session ID is required'),
|
|
1778
|
+
});
|
|
968
1779
|
app.get('/api/sessions/:sessionId', async (req, res, next) => {
|
|
969
1780
|
try {
|
|
970
|
-
const { sessionId } = req.params;
|
|
1781
|
+
const { sessionId } = parseQuery(GetSessionDetailsParamsSchema, req.params);
|
|
971
1782
|
const metadata = await activeAgent.getSessionMetadata(sessionId);
|
|
972
1783
|
const history = await activeAgent.getSessionHistory(sessionId);
|
|
973
1784
|
return res.json({
|
|
@@ -976,6 +1787,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
976
1787
|
createdAt: metadata?.createdAt || null,
|
|
977
1788
|
lastActivity: metadata?.lastActivity || null,
|
|
978
1789
|
messageCount: metadata?.messageCount || 0,
|
|
1790
|
+
title: metadata?.title || null,
|
|
979
1791
|
history: history.length,
|
|
980
1792
|
},
|
|
981
1793
|
});
|
|
@@ -985,9 +1797,12 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
985
1797
|
}
|
|
986
1798
|
});
|
|
987
1799
|
// Get session conversation history
|
|
1800
|
+
const GetSessionHistoryParamsSchema = z.object({
|
|
1801
|
+
sessionId: z.string().min(1, 'Session ID is required'),
|
|
1802
|
+
});
|
|
988
1803
|
app.get('/api/sessions/:sessionId/history', async (req, res, next) => {
|
|
989
1804
|
try {
|
|
990
|
-
const { sessionId } = req.params;
|
|
1805
|
+
const { sessionId } = parseQuery(GetSessionHistoryParamsSchema, req.params);
|
|
991
1806
|
// getSessionHistory already checks existence via getSession
|
|
992
1807
|
const history = await activeAgent.getSessionHistory(sessionId);
|
|
993
1808
|
return res.json({ history });
|
|
@@ -997,6 +1812,13 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
997
1812
|
}
|
|
998
1813
|
});
|
|
999
1814
|
// Search messages across all sessions or within a specific session
|
|
1815
|
+
const SearchQuerySchema = z.object({
|
|
1816
|
+
q: z.string().min(1, 'Search query is required'),
|
|
1817
|
+
limit: z.coerce.number().min(1).max(100).optional(),
|
|
1818
|
+
offset: z.coerce.number().min(0).optional(),
|
|
1819
|
+
sessionId: z.string().optional(),
|
|
1820
|
+
role: z.enum(['user', 'assistant', 'system', 'tool']).optional(),
|
|
1821
|
+
});
|
|
1000
1822
|
app.get('/api/search/messages', async (req, res, next) => {
|
|
1001
1823
|
try {
|
|
1002
1824
|
const { q: query, limit, offset, sessionId, role, } = parseQuery(SearchQuerySchema, req.query);
|
|
@@ -1014,9 +1836,12 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
1014
1836
|
}
|
|
1015
1837
|
});
|
|
1016
1838
|
// Search sessions that contain the query
|
|
1839
|
+
const SearchSessionsQuerySchema = z.object({
|
|
1840
|
+
q: z.string().min(1, 'Search query is required'),
|
|
1841
|
+
});
|
|
1017
1842
|
app.get('/api/search/sessions', async (req, res, next) => {
|
|
1018
1843
|
try {
|
|
1019
|
-
const { q: query } = parseQuery(
|
|
1844
|
+
const { q: query } = parseQuery(SearchSessionsQuerySchema, req.query);
|
|
1020
1845
|
const searchResults = await activeAgent.searchSessions(query);
|
|
1021
1846
|
return sendJsonResponse(res, searchResults);
|
|
1022
1847
|
}
|
|
@@ -1025,9 +1850,12 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
1025
1850
|
}
|
|
1026
1851
|
});
|
|
1027
1852
|
// Delete a session
|
|
1853
|
+
const DeleteSessionParamsSchema = z.object({
|
|
1854
|
+
sessionId: z.string().min(1, 'Session ID is required'),
|
|
1855
|
+
});
|
|
1028
1856
|
app.delete('/api/sessions/:sessionId', async (req, res, next) => {
|
|
1029
1857
|
try {
|
|
1030
|
-
const { sessionId } = req.params;
|
|
1858
|
+
const { sessionId } = parseQuery(DeleteSessionParamsSchema, req.params);
|
|
1031
1859
|
// deleteSession already checks existence internally
|
|
1032
1860
|
await activeAgent.deleteSession(sessionId);
|
|
1033
1861
|
return res.json({ status: 'deleted', sessionId });
|
|
@@ -1036,10 +1864,40 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
1036
1864
|
return next(error);
|
|
1037
1865
|
}
|
|
1038
1866
|
});
|
|
1867
|
+
// Rename session title
|
|
1868
|
+
const PatchSessionBodySchema = z.object({
|
|
1869
|
+
title: z.string().min(1, 'Title is required').max(120, 'Title too long'),
|
|
1870
|
+
});
|
|
1871
|
+
const PatchSessionParamsSchema = z.object({
|
|
1872
|
+
sessionId: z.string().min(1, 'Session ID is required'),
|
|
1873
|
+
});
|
|
1874
|
+
app.patch('/api/sessions/:sessionId', express.json(), async (req, res, next) => {
|
|
1875
|
+
try {
|
|
1876
|
+
const { sessionId } = parseQuery(PatchSessionParamsSchema, req.params);
|
|
1877
|
+
const { title } = parseBody(PatchSessionBodySchema, req.body);
|
|
1878
|
+
await activeAgent.setSessionTitle(sessionId, title);
|
|
1879
|
+
const metadata = await activeAgent.getSessionMetadata(sessionId);
|
|
1880
|
+
return res.json({
|
|
1881
|
+
session: {
|
|
1882
|
+
id: sessionId,
|
|
1883
|
+
createdAt: metadata?.createdAt || null,
|
|
1884
|
+
lastActivity: metadata?.lastActivity || null,
|
|
1885
|
+
messageCount: metadata?.messageCount || 0,
|
|
1886
|
+
title: metadata?.title || title,
|
|
1887
|
+
},
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
catch (error) {
|
|
1891
|
+
return next(error);
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1039
1894
|
// Load session as current working session and set as default
|
|
1895
|
+
const LoadSessionParamsSchema = z.object({
|
|
1896
|
+
sessionId: z.string().min(1, 'Session ID is required'),
|
|
1897
|
+
});
|
|
1040
1898
|
app.post('/api/sessions/:sessionId/load', async (req, res, next) => {
|
|
1041
1899
|
try {
|
|
1042
|
-
const { sessionId } = req.params;
|
|
1900
|
+
const { sessionId } = parseQuery(LoadSessionParamsSchema, req.params);
|
|
1043
1901
|
// Handle null/reset case
|
|
1044
1902
|
if (sessionId === 'null' || sessionId === 'undefined') {
|
|
1045
1903
|
await activeAgent.loadSessionAsDefault(null);
|
|
@@ -1064,6 +1922,11 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
1064
1922
|
});
|
|
1065
1923
|
// Webhook Management APIs
|
|
1066
1924
|
// Register a new webhook endpoint
|
|
1925
|
+
const WebhookRequestSchema = z.object({
|
|
1926
|
+
url: z.string().url('Invalid URL format'),
|
|
1927
|
+
secret: z.string().optional(),
|
|
1928
|
+
description: z.string().optional(),
|
|
1929
|
+
});
|
|
1067
1930
|
app.post('/api/webhooks', express.json(), async (req, res, next) => {
|
|
1068
1931
|
try {
|
|
1069
1932
|
const { url, secret, description } = parseBody(WebhookRequestSchema, req.body);
|
|
@@ -1092,7 +1955,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
1092
1955
|
}
|
|
1093
1956
|
});
|
|
1094
1957
|
// List all registered webhooks
|
|
1095
|
-
app.get('/api/webhooks', async (
|
|
1958
|
+
app.get('/api/webhooks', async (_req, res, next) => {
|
|
1096
1959
|
try {
|
|
1097
1960
|
const webhooks = webhookSubscriber.getWebhooks().map((webhook) => ({
|
|
1098
1961
|
id: webhook.id,
|
|
@@ -1107,9 +1970,12 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
1107
1970
|
}
|
|
1108
1971
|
});
|
|
1109
1972
|
// Get a specific webhook
|
|
1973
|
+
const GetWebhookParamsSchema = z.object({
|
|
1974
|
+
webhookId: z.string().min(1, 'Webhook ID is required'),
|
|
1975
|
+
});
|
|
1110
1976
|
app.get('/api/webhooks/:webhookId', async (req, res, next) => {
|
|
1111
1977
|
try {
|
|
1112
|
-
const { webhookId } = req.params;
|
|
1978
|
+
const { webhookId } = parseQuery(GetWebhookParamsSchema, req.params);
|
|
1113
1979
|
const webhook = webhookSubscriber.getWebhook(webhookId);
|
|
1114
1980
|
if (!webhook) {
|
|
1115
1981
|
return res.status(404).json({ error: 'Webhook not found' });
|
|
@@ -1128,9 +1994,12 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
1128
1994
|
}
|
|
1129
1995
|
});
|
|
1130
1996
|
// Remove a webhook endpoint
|
|
1997
|
+
const DeleteWebhookParamsSchema = z.object({
|
|
1998
|
+
webhookId: z.string().min(1, 'Webhook ID is required'),
|
|
1999
|
+
});
|
|
1131
2000
|
app.delete('/api/webhooks/:webhookId', async (req, res, next) => {
|
|
1132
2001
|
try {
|
|
1133
|
-
const { webhookId } = req.params;
|
|
2002
|
+
const { webhookId } = parseQuery(DeleteWebhookParamsSchema, req.params);
|
|
1134
2003
|
const removed = webhookSubscriber.removeWebhook(webhookId);
|
|
1135
2004
|
if (!removed) {
|
|
1136
2005
|
return res.status(404).json({ error: 'Webhook not found' });
|
|
@@ -1143,9 +2012,12 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
1143
2012
|
}
|
|
1144
2013
|
});
|
|
1145
2014
|
// Test a webhook endpoint
|
|
2015
|
+
const TestWebhookParamsSchema = z.object({
|
|
2016
|
+
webhookId: z.string().min(1, 'Webhook ID is required'),
|
|
2017
|
+
});
|
|
1146
2018
|
app.post('/api/webhooks/:webhookId/test', async (req, res, next) => {
|
|
1147
2019
|
try {
|
|
1148
|
-
const { webhookId } = req.params;
|
|
2020
|
+
const { webhookId } = parseQuery(TestWebhookParamsSchema, req.params);
|
|
1149
2021
|
const webhook = webhookSubscriber.getWebhook(webhookId);
|
|
1150
2022
|
if (!webhook) {
|
|
1151
2023
|
return res.status(404).json({ error: 'Webhook not found' });
|
|
@@ -1170,8 +2042,8 @@ export async function initializeApi(agent, agentCardOverride, listenPort, agentN
|
|
|
1170
2042
|
app.use(errorHandler);
|
|
1171
2043
|
return { app, server, wss, webSubscriber, webhookSubscriber };
|
|
1172
2044
|
}
|
|
1173
|
-
export async function startApiServer(agent, port = 3000, agentCardOverride,
|
|
1174
|
-
const { server, wss, webSubscriber, webhookSubscriber } = await initializeApi(agent, agentCardOverride, port,
|
|
2045
|
+
export async function startApiServer(agent, port = 3000, agentCardOverride, agentId) {
|
|
2046
|
+
const { server, wss, webSubscriber, webhookSubscriber } = await initializeApi(agent, agentCardOverride, port, agentId);
|
|
1175
2047
|
// API server for REST endpoints and WebSocket connections
|
|
1176
2048
|
server.listen(port, '0.0.0.0', () => {
|
|
1177
2049
|
const networkInterfaces = os.networkInterfaces();
|