feat: Add intelligent auto-router and enhanced integrations
- Add intelligent-router.sh hook for automatic agent routing - Add AUTO-TRIGGER-SUMMARY.md documentation - Add FINAL-INTEGRATION-SUMMARY.md documentation - Complete Prometheus integration (6 commands + 4 tools) - Complete Dexto integration (12 commands + 5 tools) - Enhanced Ralph with access to all agents - Fix /clawd command (removed disable-model-invocation) - Update hooks.json to v5 with intelligent routing - 291 total skills now available - All 21 commands with automatic routing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
305
dexto/packages/server/src/hono/index.ts
Normal file
305
dexto/packages/server/src/hono/index.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import { OpenAPIHono } from '@hono/zod-openapi';
|
||||
import type { Context } from 'hono';
|
||||
import type { DextoAgent, AgentCard } from '@dexto/core';
|
||||
import { logger } from '@dexto/core';
|
||||
import { createHealthRouter } from './routes/health.js';
|
||||
import { createGreetingRouter } from './routes/greeting.js';
|
||||
import { createMessagesRouter } from './routes/messages.js';
|
||||
import { createLlmRouter } from './routes/llm.js';
|
||||
import { createSessionsRouter } from './routes/sessions.js';
|
||||
import { createSearchRouter } from './routes/search.js';
|
||||
import { createMcpRouter } from './routes/mcp.js';
|
||||
import { createA2aRouter } from './routes/a2a.js';
|
||||
import { createA2AJsonRpcRouter } from './routes/a2a-jsonrpc.js';
|
||||
import { createA2ATasksRouter } from './routes/a2a-tasks.js';
|
||||
import { createWebhooksRouter } from './routes/webhooks.js';
|
||||
import { createPromptsRouter } from './routes/prompts.js';
|
||||
import { createResourcesRouter } from './routes/resources.js';
|
||||
import { createMemoryRouter } from './routes/memory.js';
|
||||
import { createAgentsRouter, type AgentsRouterContext } from './routes/agents.js';
|
||||
import { createApprovalsRouter } from './routes/approvals.js';
|
||||
import { createQueueRouter } from './routes/queue.js';
|
||||
import { createOpenRouterRouter } from './routes/openrouter.js';
|
||||
import { createKeyRouter } from './routes/key.js';
|
||||
import { createToolsRouter } from './routes/tools.js';
|
||||
import { createDiscoveryRouter } from './routes/discovery.js';
|
||||
import { createModelsRouter } from './routes/models.js';
|
||||
import { createDextoAuthRouter } from './routes/dexto-auth.js';
|
||||
import {
|
||||
createStaticRouter,
|
||||
createSpaFallbackHandler,
|
||||
type WebUIRuntimeConfig,
|
||||
} from './routes/static.js';
|
||||
import { WebhookEventSubscriber } from '../events/webhook-subscriber.js';
|
||||
import { A2ASseEventSubscriber } from '../events/a2a-sse-subscriber.js';
|
||||
import { handleHonoError } from './middleware/error.js';
|
||||
import { prettyJsonMiddleware, redactionMiddleware } from './middleware/redaction.js';
|
||||
import { createCorsMiddleware } from './middleware/cors.js';
|
||||
import { createAuthMiddleware } from './middleware/auth.js';
|
||||
import { ApprovalCoordinator } from '../approval/approval-coordinator.js';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const packageJson = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8')) as {
|
||||
version: string;
|
||||
};
|
||||
|
||||
// Dummy context for type inference and runtime fallback
|
||||
// Used when running in single-agent mode (CLI, Docker, etc.) where multi-agent
|
||||
// features aren't available. Agents router is always mounted for consistent API
|
||||
// structure, but will return clear errors if multi-agent endpoints are called.
|
||||
// This ensures type safety across different deployment modes.
|
||||
const dummyAgentsContext: AgentsRouterContext = {
|
||||
switchAgentById: async () => {
|
||||
throw new Error('Multi-agent features not available in single-agent mode');
|
||||
},
|
||||
switchAgentByPath: async () => {
|
||||
throw new Error('Multi-agent features not available in single-agent mode');
|
||||
},
|
||||
resolveAgentInfo: async () => {
|
||||
throw new Error('Multi-agent features not available in single-agent mode');
|
||||
},
|
||||
ensureAgentAvailable: () => {},
|
||||
getActiveAgentId: () => undefined,
|
||||
};
|
||||
|
||||
// Type for async getAgent with context support
|
||||
export type GetAgentFn = (ctx: Context) => DextoAgent | Promise<DextoAgent>;
|
||||
|
||||
export type CreateDextoAppOptions = {
|
||||
/**
|
||||
* Prefix for API routes. Defaults to '/api'.
|
||||
*/
|
||||
apiPrefix?: string;
|
||||
getAgent: GetAgentFn;
|
||||
getAgentCard: () => AgentCard;
|
||||
approvalCoordinator: ApprovalCoordinator;
|
||||
webhookSubscriber: WebhookEventSubscriber;
|
||||
sseSubscriber: A2ASseEventSubscriber;
|
||||
agentsContext?: AgentsRouterContext;
|
||||
/** Absolute path to WebUI build output. If provided, static files will be served. */
|
||||
webRoot?: string;
|
||||
/** Runtime configuration to inject into WebUI (analytics, etc.) */
|
||||
webUIConfig?: WebUIRuntimeConfig;
|
||||
/** Disable built-in auth middleware. Use when you have your own auth layer. */
|
||||
disableAuth?: boolean;
|
||||
};
|
||||
|
||||
// Default API prefix as a const literal for type inference
|
||||
const DEFAULT_API_PREFIX = '/api' as const;
|
||||
|
||||
export function createDextoApp(options: CreateDextoAppOptions) {
|
||||
const {
|
||||
apiPrefix,
|
||||
getAgent,
|
||||
getAgentCard,
|
||||
approvalCoordinator,
|
||||
webhookSubscriber,
|
||||
sseSubscriber,
|
||||
agentsContext,
|
||||
webRoot,
|
||||
webUIConfig,
|
||||
disableAuth = false,
|
||||
} = options;
|
||||
|
||||
// Security check: Warn when auth is disabled
|
||||
if (disableAuth) {
|
||||
logger.warn(
|
||||
`⚠️ Authentication disabled (disableAuth=true). createAuthMiddleware() skipped. Ensure external auth is in place.`
|
||||
);
|
||||
}
|
||||
|
||||
const app = new OpenAPIHono({ strict: false });
|
||||
|
||||
// Global CORS middleware for cross-origin requests (must be first)
|
||||
app.use('*', createCorsMiddleware());
|
||||
|
||||
// Global authentication middleware (after CORS, before routes)
|
||||
// Can be disabled when using an external auth layer
|
||||
if (!disableAuth) {
|
||||
app.use('*', createAuthMiddleware());
|
||||
}
|
||||
|
||||
// Global error handling for all routes
|
||||
app.onError((err, ctx) => handleHonoError(ctx, err));
|
||||
|
||||
// Normalize prefix: strip trailing slashes, treat '' as '/'
|
||||
const rawPrefix = apiPrefix ?? DEFAULT_API_PREFIX;
|
||||
const normalizedPrefix = rawPrefix === '' ? '/' : rawPrefix.replace(/\/+$/, '') || '/';
|
||||
const middlewarePattern = normalizedPrefix === '/' ? '/*' : `${normalizedPrefix}/*`;
|
||||
|
||||
app.use(middlewarePattern, prettyJsonMiddleware);
|
||||
app.use(middlewarePattern, redactionMiddleware);
|
||||
|
||||
// Cast to literal type for RPC client type inference (webui uses default '/api')
|
||||
const routePrefix = normalizedPrefix as typeof DEFAULT_API_PREFIX;
|
||||
|
||||
// Mount all API routers at the configured prefix for proper type inference
|
||||
// Each router is mounted individually so Hono can properly track route types
|
||||
const fullApp = app
|
||||
// Public health endpoint
|
||||
.route('/health', createHealthRouter(getAgent))
|
||||
// Follows A2A discovery protocol
|
||||
.route('/', createA2aRouter(getAgentCard))
|
||||
.route('/', createA2AJsonRpcRouter(getAgent, sseSubscriber))
|
||||
.route('/', createA2ATasksRouter(getAgent, sseSubscriber))
|
||||
// Add agent-specific routes
|
||||
.route(routePrefix, createGreetingRouter(getAgent))
|
||||
.route(routePrefix, createMessagesRouter(getAgent, approvalCoordinator))
|
||||
.route(routePrefix, createLlmRouter(getAgent))
|
||||
.route(routePrefix, createSessionsRouter(getAgent))
|
||||
.route(routePrefix, createSearchRouter(getAgent))
|
||||
.route(routePrefix, createMcpRouter(getAgent))
|
||||
.route(routePrefix, createWebhooksRouter(getAgent, webhookSubscriber))
|
||||
.route(routePrefix, createPromptsRouter(getAgent))
|
||||
.route(routePrefix, createResourcesRouter(getAgent))
|
||||
.route(routePrefix, createMemoryRouter(getAgent))
|
||||
.route(routePrefix, createApprovalsRouter(getAgent, approvalCoordinator))
|
||||
.route(routePrefix, createAgentsRouter(getAgent, agentsContext || dummyAgentsContext))
|
||||
.route(routePrefix, createQueueRouter(getAgent))
|
||||
.route(routePrefix, createOpenRouterRouter())
|
||||
.route(routePrefix, createKeyRouter())
|
||||
.route(routePrefix, createToolsRouter(getAgent))
|
||||
.route(routePrefix, createDiscoveryRouter())
|
||||
.route(routePrefix, createModelsRouter())
|
||||
.route(routePrefix, createDextoAuthRouter(getAgent));
|
||||
|
||||
// Expose OpenAPI document
|
||||
// Current approach uses @hono/zod-openapi's .doc() method for OpenAPI spec generation
|
||||
// Alternative: Use openAPIRouteHandler from hono-openapi (third-party) for auto-generation
|
||||
// Keeping current approach since:
|
||||
// 1. @hono/zod-openapi is official Hono package with first-class support
|
||||
// 2. We already generate spec via scripts/generate-openapi-spec.ts to docs/
|
||||
// 3. Switching would require adding hono-openapi dependency and migration effort
|
||||
// See: https://honohub.dev/docs/openapi/zod#generating-the-openapi-spec
|
||||
fullApp.doc('/openapi.json', {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'Dexto API',
|
||||
version: packageJson.version,
|
||||
description: 'OpenAPI spec for the Dexto REST API server',
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: 'http://localhost:3001',
|
||||
description: 'Local development server (default port)',
|
||||
},
|
||||
{
|
||||
url: 'http://localhost:{port}',
|
||||
description: 'Local development server (custom port)',
|
||||
variables: {
|
||||
port: {
|
||||
default: '3001',
|
||||
description: 'API server port',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
tags: [
|
||||
{
|
||||
name: 'system',
|
||||
description: 'System health and status endpoints',
|
||||
},
|
||||
{
|
||||
name: 'config',
|
||||
description: 'Agent configuration and greeting management',
|
||||
},
|
||||
{
|
||||
name: 'messages',
|
||||
description: 'Send messages to the agent and manage conversations',
|
||||
},
|
||||
{
|
||||
name: 'sessions',
|
||||
description: 'Create and manage conversation sessions',
|
||||
},
|
||||
{
|
||||
name: 'llm',
|
||||
description: 'Configure and switch between LLM providers and models',
|
||||
},
|
||||
{
|
||||
name: 'mcp',
|
||||
description: 'Manage Model Context Protocol (MCP) servers and tools',
|
||||
},
|
||||
{
|
||||
name: 'webhooks',
|
||||
description: 'Register and manage webhook endpoints for agent events',
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
description: 'Search through messages and sessions',
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
description: 'Store and retrieve agent memories for context',
|
||||
},
|
||||
{
|
||||
name: 'prompts',
|
||||
description: 'Manage custom prompts and templates',
|
||||
},
|
||||
{
|
||||
name: 'resources',
|
||||
description: 'Access and manage resources from MCP servers and internal providers',
|
||||
},
|
||||
{
|
||||
name: 'agent',
|
||||
description: 'Current agent configuration and file operations',
|
||||
},
|
||||
{
|
||||
name: 'agents',
|
||||
description: 'Install, switch, and manage agent configurations',
|
||||
},
|
||||
{
|
||||
name: 'queue',
|
||||
description: 'Manage message queue for busy sessions',
|
||||
},
|
||||
{
|
||||
name: 'openrouter',
|
||||
description: 'OpenRouter model validation and cache management',
|
||||
},
|
||||
{
|
||||
name: 'discovery',
|
||||
description: 'Discover available providers and capabilities',
|
||||
},
|
||||
{
|
||||
name: 'tools',
|
||||
description:
|
||||
'List and inspect available tools from internal, custom, and MCP sources',
|
||||
},
|
||||
{
|
||||
name: 'models',
|
||||
description: 'List and manage local GGUF models and Ollama models',
|
||||
},
|
||||
{
|
||||
name: 'auth',
|
||||
description: 'Dexto authentication status and management',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Mount static file router for WebUI if webRoot is provided
|
||||
if (webRoot) {
|
||||
fullApp.route('/', createStaticRouter(webRoot));
|
||||
// SPA fallback: serve index.html for unmatched routes without file extensions
|
||||
// Must be registered as notFound handler so it runs AFTER all routes (including /openapi.json)
|
||||
// webUIConfig is injected into index.html for runtime configuration (analytics, etc.)
|
||||
fullApp.notFound(createSpaFallbackHandler(webRoot, webUIConfig));
|
||||
}
|
||||
|
||||
// NOTE: Subscribers and approval handler are wired in CLI layer before agent.start()
|
||||
// This ensures proper initialization order and validation
|
||||
// We attach webhookSubscriber as a property but don't include it in the return type
|
||||
// to preserve Hono's route type inference
|
||||
Object.assign(fullApp, { webhookSubscriber });
|
||||
|
||||
return fullApp;
|
||||
}
|
||||
|
||||
// Export inferred AppType
|
||||
// Routes are now properly typed since they're all mounted directly
|
||||
export type AppType = ReturnType<typeof createDextoApp>;
|
||||
|
||||
// Re-export types needed by CLI
|
||||
export type { WebUIRuntimeConfig } from './routes/static.js';
|
||||
Reference in New Issue
Block a user