@open-mercato/ai-assistant 0.4.2-canary-c02407ff85
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/AGENTS.md +1090 -0
- package/README.md +607 -0
- package/build.mjs +92 -0
- package/dist/di.js +8 -0
- package/dist/di.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandFooter.js +80 -0
- package/dist/frontend/components/CommandPalette/CommandFooter.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandHeader.js +53 -0
- package/dist/frontend/components/CommandPalette/CommandHeader.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandInput.js +29 -0
- package/dist/frontend/components/CommandPalette/CommandInput.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandItem.js +92 -0
- package/dist/frontend/components/CommandPalette/CommandItem.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPalette.js +244 -0
- package/dist/frontend/components/CommandPalette/CommandPalette.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js +42 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js +18 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js.map +7 -0
- package/dist/frontend/components/CommandPalette/DebugPanel.js +215 -0
- package/dist/frontend/components/CommandPalette/DebugPanel.js.map +7 -0
- package/dist/frontend/components/CommandPalette/MessageBubble.js +64 -0
- package/dist/frontend/components/CommandPalette/MessageBubble.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js +91 -0
- package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolCallDisplay.js +47 -0
- package/dist/frontend/components/CommandPalette/ToolCallDisplay.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolChatPage.js +74 -0
- package/dist/frontend/components/CommandPalette/ToolChatPage.js.map +7 -0
- package/dist/frontend/components/CommandPalette/index.js +28 -0
- package/dist/frontend/components/CommandPalette/index.js.map +7 -0
- package/dist/frontend/constants.js +41 -0
- package/dist/frontend/constants.js.map +7 -0
- package/dist/frontend/hooks/index.js +13 -0
- package/dist/frontend/hooks/index.js.map +7 -0
- package/dist/frontend/hooks/useCommandPalette.js +1094 -0
- package/dist/frontend/hooks/useCommandPalette.js.map +7 -0
- package/dist/frontend/hooks/useMcpTools.js +66 -0
- package/dist/frontend/hooks/useMcpTools.js.map +7 -0
- package/dist/frontend/hooks/usePageContext.js +48 -0
- package/dist/frontend/hooks/usePageContext.js.map +7 -0
- package/dist/frontend/hooks/useRecentActions.js +56 -0
- package/dist/frontend/hooks/useRecentActions.js.map +7 -0
- package/dist/frontend/hooks/useRecentTools.js +55 -0
- package/dist/frontend/hooks/useRecentTools.js.map +7 -0
- package/dist/frontend/index.js +35 -0
- package/dist/frontend/index.js.map +7 -0
- package/dist/frontend/types.js +1 -0
- package/dist/frontend/types.js.map +7 -0
- package/dist/frontend/utils/index.js +7 -0
- package/dist/frontend/utils/index.js.map +7 -0
- package/dist/frontend/utils/toolMatcher.js +95 -0
- package/dist/frontend/utils/toolMatcher.js.map +7 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/ai_assistant/acl.js +14 -0
- package/dist/modules/ai_assistant/acl.js.map +7 -0
- package/dist/modules/ai_assistant/api/chat/route.js +152 -0
- package/dist/modules/ai_assistant/api/chat/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/health/route.js +27 -0
- package/dist/modules/ai_assistant/api/health/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/route/route.js +123 -0
- package/dist/modules/ai_assistant/api/route/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/settings/route.js +60 -0
- package/dist/modules/ai_assistant/api/settings/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/tools/execute/route.js +58 -0
- package/dist/modules/ai_assistant/api/tools/execute/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/tools/route.js +48 -0
- package/dist/modules/ai_assistant/api/tools/route.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +28 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/cli.js +192 -0
- package/dist/modules/ai_assistant/cli.js.map +7 -0
- package/dist/modules/ai_assistant/di.js +11 -0
- package/dist/modules/ai_assistant/di.js.map +7 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +257 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/index.js +13 -0
- package/dist/modules/ai_assistant/index.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-sdk.js +13 -0
- package/dist/modules/ai_assistant/lib/ai-sdk.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js +249 -0
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +177 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js +210 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +7 -0
- package/dist/modules/ai_assistant/lib/auth.js +87 -0
- package/dist/modules/ai_assistant/lib/auth.js.map +7 -0
- package/dist/modules/ai_assistant/lib/chat-config.js +117 -0
- package/dist/modules/ai_assistant/lib/chat-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/client-factory.js +60 -0
- package/dist/modules/ai_assistant/lib/client-factory.js.map +7 -0
- package/dist/modules/ai_assistant/lib/http-server.js +367 -0
- package/dist/modules/ai_assistant/lib/http-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/in-process-client.js +126 -0
- package/dist/modules/ai_assistant/lib/in-process-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-client.js +146 -0
- package/dist/modules/ai_assistant/lib/mcp-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +283 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-server-config.js +160 -0
- package/dist/modules/ai_assistant/lib/mcp-server-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-server.js +156 -0
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +44 -0
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +7 -0
- package/dist/modules/ai_assistant/lib/opencode-client.js +247 -0
- package/dist/modules/ai_assistant/lib/opencode-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/opencode-handlers.js +398 -0
- package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +7 -0
- package/dist/modules/ai_assistant/lib/schema-utils.js +94 -0
- package/dist/modules/ai_assistant/lib/schema-utils.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-executor.js +55 -0
- package/dist/modules/ai_assistant/lib/tool-executor.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-index-config.js +125 -0
- package/dist/modules/ai_assistant/lib/tool-index-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-loader.js +88 -0
- package/dist/modules/ai_assistant/lib/tool-loader.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-registry.js +65 -0
- package/dist/modules/ai_assistant/lib/tool-registry.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-search.js +192 -0
- package/dist/modules/ai_assistant/lib/tool-search.js.map +7 -0
- package/dist/modules/ai_assistant/lib/types.js +1 -0
- package/dist/modules/ai_assistant/lib/types.js.map +7 -0
- package/package.json +108 -0
- package/src/di.ts +11 -0
- package/src/frontend/components/CommandPalette/CommandFooter.tsx +113 -0
- package/src/frontend/components/CommandPalette/CommandHeader.tsx +76 -0
- package/src/frontend/components/CommandPalette/CommandInput.tsx +50 -0
- package/src/frontend/components/CommandPalette/CommandItem.tsx +111 -0
- package/src/frontend/components/CommandPalette/CommandPalette.tsx +276 -0
- package/src/frontend/components/CommandPalette/CommandPaletteProvider.tsx +60 -0
- package/src/frontend/components/CommandPalette/CommandPaletteWrapper.tsx +21 -0
- package/src/frontend/components/CommandPalette/DebugPanel.tsx +257 -0
- package/src/frontend/components/CommandPalette/MessageBubble.tsx +73 -0
- package/src/frontend/components/CommandPalette/ToolCallConfirmation.tsx +130 -0
- package/src/frontend/components/CommandPalette/ToolCallDisplay.tsx +57 -0
- package/src/frontend/components/CommandPalette/ToolChatPage.tsx +125 -0
- package/src/frontend/components/CommandPalette/index.ts +14 -0
- package/src/frontend/constants.ts +35 -0
- package/src/frontend/hooks/index.ts +5 -0
- package/src/frontend/hooks/useCommandPalette.ts +1389 -0
- package/src/frontend/hooks/useMcpTools.ts +73 -0
- package/src/frontend/hooks/usePageContext.ts +61 -0
- package/src/frontend/hooks/useRecentActions.ts +64 -0
- package/src/frontend/hooks/useRecentTools.ts +69 -0
- package/src/frontend/index.ts +39 -0
- package/src/frontend/types.ts +260 -0
- package/src/frontend/utils/index.ts +1 -0
- package/src/frontend/utils/toolMatcher.ts +127 -0
- package/src/index.ts +92 -0
- package/src/modules/ai_assistant/acl.ts +10 -0
- package/src/modules/ai_assistant/api/chat/route.ts +213 -0
- package/src/modules/ai_assistant/api/health/route.ts +30 -0
- package/src/modules/ai_assistant/api/route/route.ts +149 -0
- package/src/modules/ai_assistant/api/settings/route.ts +73 -0
- package/src/modules/ai_assistant/api/tools/execute/route.ts +71 -0
- package/src/modules/ai_assistant/api/tools/route.ts +57 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +26 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +12 -0
- package/src/modules/ai_assistant/cli.ts +233 -0
- package/src/modules/ai_assistant/di.ts +9 -0
- package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +418 -0
- package/src/modules/ai_assistant/index.ts +11 -0
- package/src/modules/ai_assistant/lib/ai-sdk.ts +5 -0
- package/src/modules/ai_assistant/lib/api-discovery-tools.ts +334 -0
- package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +243 -0
- package/src/modules/ai_assistant/lib/api-endpoint-index.ts +381 -0
- package/src/modules/ai_assistant/lib/auth.ts +185 -0
- package/src/modules/ai_assistant/lib/chat-config.ts +152 -0
- package/src/modules/ai_assistant/lib/client-factory.ts +130 -0
- package/src/modules/ai_assistant/lib/http-server.ts +498 -0
- package/src/modules/ai_assistant/lib/in-process-client.ts +205 -0
- package/src/modules/ai_assistant/lib/mcp-client.ts +221 -0
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +373 -0
- package/src/modules/ai_assistant/lib/mcp-server-config.ts +287 -0
- package/src/modules/ai_assistant/lib/mcp-server.ts +214 -0
- package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +76 -0
- package/src/modules/ai_assistant/lib/opencode-client.ts +426 -0
- package/src/modules/ai_assistant/lib/opencode-handlers.ts +676 -0
- package/src/modules/ai_assistant/lib/schema-utils.ts +142 -0
- package/src/modules/ai_assistant/lib/tool-executor.ts +71 -0
- package/src/modules/ai_assistant/lib/tool-index-config.ts +178 -0
- package/src/modules/ai_assistant/lib/tool-loader.ts +149 -0
- package/src/modules/ai_assistant/lib/tool-registry.ts +114 -0
- package/src/modules/ai_assistant/lib/tool-search.ts +308 -0
- package/src/modules/ai_assistant/lib/types.ts +147 -0
- package/test-schema.ts +37 -0
- package/tsconfig.json +10 -0
- package/watch.mjs +6 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/lib/mcp-server-config.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\n\n// Types\n\n/**\n * MCP server connection type.\n */\nexport type McpServerType = 'http' | 'stdio'\n\n/**\n * Configuration for an external MCP server.\n */\nexport interface McpServerConfig {\n /** Unique identifier */\n id: string\n /** User-defined name */\n name: string\n /** Connection type */\n type: McpServerType\n /** Server URL (for HTTP type) */\n url?: string\n /** Command to run (for stdio type) */\n command?: string\n /** Command arguments (for stdio type) */\n args?: string[]\n /** API key for authentication (stored as reference, not the actual secret) */\n apiKeyId?: string\n /** Whether the server is enabled */\n enabled: boolean\n /** When the config was created */\n createdAt: string\n /** When the config was last updated */\n updatedAt: string\n}\n\n/**\n * Input for creating a new MCP server config.\n */\nexport type McpServerConfigInput = Omit<McpServerConfig, 'id' | 'createdAt' | 'updatedAt'>\n\n/**\n * Input for updating an MCP server config.\n */\nexport type McpServerConfigUpdate = Partial<Omit<McpServerConfig, 'id' | 'createdAt' | 'updatedAt'>>\n\n// Constants\n\nexport const MCP_SERVERS_CONFIG_KEY = 'mcp_servers'\n\n// Resolver type\n\ntype Resolver = {\n resolve: <T = unknown>(name: string) => T\n}\n\n// Config functions\n\n/**\n * Get all MCP server configurations.\n */\nexport async function getMcpServerConfigs(\n resolver: Resolver\n): Promise<McpServerConfig[]> {\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n return []\n }\n\n try {\n const value = await service.getValue<McpServerConfig[]>(\n 'ai_assistant',\n MCP_SERVERS_CONFIG_KEY,\n { defaultValue: [] }\n )\n return value ?? []\n } catch {\n return []\n }\n}\n\n/**\n * Get a single MCP server configuration by ID.\n */\nexport async function getMcpServerConfig(\n resolver: Resolver,\n serverId: string\n): Promise<McpServerConfig | null> {\n const configs = await getMcpServerConfigs(resolver)\n return configs.find((c) => c.id === serverId) ?? null\n}\n\n/**\n * Get only enabled MCP server configurations.\n */\nexport async function getEnabledMcpServerConfigs(\n resolver: Resolver\n): Promise<McpServerConfig[]> {\n const configs = await getMcpServerConfigs(resolver)\n return configs.filter((c) => c.enabled)\n}\n\n/**\n * Save an MCP server configuration (create or update).\n */\nexport async function saveMcpServerConfig(\n resolver: Resolver,\n config: McpServerConfigInput & { id?: string }\n): Promise<McpServerConfig> {\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n throw new Error('Configuration service unavailable')\n }\n\n const configs = await getMcpServerConfigs(resolver)\n const now = new Date().toISOString()\n\n let updatedConfigs: McpServerConfig[]\n let savedConfig: McpServerConfig\n\n if (config.id) {\n // Update existing\n const existingIndex = configs.findIndex((c) => c.id === config.id)\n if (existingIndex === -1) {\n throw new Error(`MCP server config not found: ${config.id}`)\n }\n\n savedConfig = {\n ...configs[existingIndex],\n ...config,\n id: config.id,\n updatedAt: now,\n }\n\n updatedConfigs = [\n ...configs.slice(0, existingIndex),\n savedConfig,\n ...configs.slice(existingIndex + 1),\n ]\n } else {\n // Create new\n savedConfig = {\n ...config,\n id: generateId(),\n createdAt: now,\n updatedAt: now,\n }\n\n updatedConfigs = [...configs, savedConfig]\n }\n\n await service.setValue('ai_assistant', MCP_SERVERS_CONFIG_KEY, updatedConfigs)\n return savedConfig\n}\n\n/**\n * Update an existing MCP server configuration.\n */\nexport async function updateMcpServerConfig(\n resolver: Resolver,\n serverId: string,\n updates: McpServerConfigUpdate\n): Promise<McpServerConfig> {\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n throw new Error('Configuration service unavailable')\n }\n\n const configs = await getMcpServerConfigs(resolver)\n const existingIndex = configs.findIndex((c) => c.id === serverId)\n\n if (existingIndex === -1) {\n throw new Error(`MCP server config not found: ${serverId}`)\n }\n\n const updatedConfig: McpServerConfig = {\n ...configs[existingIndex],\n ...updates,\n id: serverId, // Ensure ID is preserved\n updatedAt: new Date().toISOString(),\n }\n\n const updatedConfigs = [\n ...configs.slice(0, existingIndex),\n updatedConfig,\n ...configs.slice(existingIndex + 1),\n ]\n\n await service.setValue('ai_assistant', MCP_SERVERS_CONFIG_KEY, updatedConfigs)\n return updatedConfig\n}\n\n/**\n * Delete an MCP server configuration.\n */\nexport async function deleteMcpServerConfig(\n resolver: Resolver,\n serverId: string\n): Promise<boolean> {\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n throw new Error('Configuration service unavailable')\n }\n\n const configs = await getMcpServerConfigs(resolver)\n const existingIndex = configs.findIndex((c) => c.id === serverId)\n\n if (existingIndex === -1) {\n return false\n }\n\n const updatedConfigs = [\n ...configs.slice(0, existingIndex),\n ...configs.slice(existingIndex + 1),\n ]\n\n await service.setValue('ai_assistant', MCP_SERVERS_CONFIG_KEY, updatedConfigs)\n return true\n}\n\n/**\n * Toggle the enabled state of an MCP server configuration.\n */\nexport async function toggleMcpServerEnabled(\n resolver: Resolver,\n serverId: string\n): Promise<McpServerConfig> {\n const config = await getMcpServerConfig(resolver, serverId)\n if (!config) {\n throw new Error(`MCP server config not found: ${serverId}`)\n }\n\n return updateMcpServerConfig(resolver, serverId, {\n enabled: !config.enabled,\n })\n}\n\n// Helpers\n\n/**\n * Generate a unique ID for a new MCP server config.\n */\nfunction generateId(): string {\n return `mcp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`\n}\n\n/**\n * Validate an MCP server configuration.\n */\nexport function validateMcpServerConfig(\n config: McpServerConfigInput\n): { valid: boolean; error?: string } {\n if (!config.name?.trim()) {\n return { valid: false, error: 'Name is required' }\n }\n\n if (!['http', 'stdio'].includes(config.type)) {\n return { valid: false, error: 'Type must be \"http\" or \"stdio\"' }\n }\n\n if (config.type === 'http') {\n if (!config.url?.trim()) {\n return { valid: false, error: 'URL is required for HTTP servers' }\n }\n\n try {\n new URL(config.url)\n } catch {\n return { valid: false, error: 'Invalid URL format' }\n }\n }\n\n if (config.type === 'stdio') {\n if (!config.command?.trim()) {\n return { valid: false, error: 'Command is required for stdio servers' }\n }\n }\n\n return { valid: true }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import {
|
|
4
|
+
ListToolsRequestSchema,
|
|
5
|
+
CallToolRequestSchema
|
|
6
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
8
|
+
import { getToolRegistry } from "./tool-registry.js";
|
|
9
|
+
import { executeTool } from "./tool-executor.js";
|
|
10
|
+
import { loadAllModuleTools, indexToolsForSearch } from "./tool-loader.js";
|
|
11
|
+
import { authenticateMcpRequest, hasRequiredFeatures } from "./auth.js";
|
|
12
|
+
async function createMcpServer(options) {
|
|
13
|
+
const { config, container, context, apiKeySecret } = options;
|
|
14
|
+
let tenantId = null;
|
|
15
|
+
let organizationId = null;
|
|
16
|
+
let userId = null;
|
|
17
|
+
let userFeatures = [];
|
|
18
|
+
let isSuperAdmin = false;
|
|
19
|
+
if (apiKeySecret) {
|
|
20
|
+
const authResult = await authenticateMcpRequest(apiKeySecret, container);
|
|
21
|
+
if (!authResult.success) {
|
|
22
|
+
throw new Error(`API key authentication failed: ${authResult.error}`);
|
|
23
|
+
}
|
|
24
|
+
tenantId = authResult.tenantId;
|
|
25
|
+
organizationId = authResult.organizationId;
|
|
26
|
+
userId = authResult.userId;
|
|
27
|
+
userFeatures = authResult.features;
|
|
28
|
+
isSuperAdmin = authResult.isSuperAdmin;
|
|
29
|
+
console.error(`[MCP Server] Authenticated via API key: ${authResult.keyName}`);
|
|
30
|
+
} else if (context) {
|
|
31
|
+
tenantId = context.tenantId;
|
|
32
|
+
organizationId = context.organizationId;
|
|
33
|
+
userId = context.userId;
|
|
34
|
+
if (userId) {
|
|
35
|
+
try {
|
|
36
|
+
const rbacService = container.resolve("rbacService");
|
|
37
|
+
const acl = await rbacService.loadAcl(userId, {
|
|
38
|
+
tenantId,
|
|
39
|
+
organizationId
|
|
40
|
+
});
|
|
41
|
+
userFeatures = acl.features;
|
|
42
|
+
isSuperAdmin = acl.isSuperAdmin;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("[MCP Server] Failed to load user ACL:", error);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
isSuperAdmin = true;
|
|
48
|
+
console.error("[MCP Server] No user specified, running with superadmin access");
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
isSuperAdmin = true;
|
|
52
|
+
console.error("[MCP Server] No auth context, running with superadmin access");
|
|
53
|
+
}
|
|
54
|
+
const toolContext = {
|
|
55
|
+
tenantId,
|
|
56
|
+
organizationId,
|
|
57
|
+
userId,
|
|
58
|
+
container,
|
|
59
|
+
userFeatures,
|
|
60
|
+
isSuperAdmin,
|
|
61
|
+
apiKeySecret
|
|
62
|
+
};
|
|
63
|
+
const server = new Server(
|
|
64
|
+
{ name: config.name, version: config.version },
|
|
65
|
+
{ capabilities: { tools: {} } }
|
|
66
|
+
);
|
|
67
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
68
|
+
const registry = getToolRegistry();
|
|
69
|
+
const tools = Array.from(registry.getTools().values());
|
|
70
|
+
const accessibleTools = tools.filter(
|
|
71
|
+
(tool) => hasRequiredFeatures(tool.requiredFeatures, userFeatures, isSuperAdmin)
|
|
72
|
+
);
|
|
73
|
+
if (config.debug) {
|
|
74
|
+
console.error(
|
|
75
|
+
`[MCP Server] Listing ${accessibleTools.length}/${tools.length} tools (filtered by ACL)`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
tools: accessibleTools.map((tool) => ({
|
|
80
|
+
name: tool.name,
|
|
81
|
+
description: tool.description,
|
|
82
|
+
// Cast to any for Zod v4 compatibility with zod-to-json-schema
|
|
83
|
+
inputSchema: zodToJsonSchema(tool.inputSchema)
|
|
84
|
+
}))
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
88
|
+
const { name, arguments: args } = request.params;
|
|
89
|
+
if (config.debug) {
|
|
90
|
+
console.error(`[MCP Server] Calling tool: ${name}`, JSON.stringify(args));
|
|
91
|
+
}
|
|
92
|
+
const result = await executeTool(name, args ?? {}, toolContext);
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: JSON.stringify({ error: result.error, code: result.errorCode })
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
isError: true
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: JSON.stringify(result.result, null, 2)
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
return server;
|
|
114
|
+
}
|
|
115
|
+
async function runMcpServer(options) {
|
|
116
|
+
await loadAllModuleTools();
|
|
117
|
+
try {
|
|
118
|
+
const searchService = options.container.resolve("searchService");
|
|
119
|
+
await indexToolsForSearch(searchService);
|
|
120
|
+
const { indexApiEndpoints } = await import("./api-endpoint-index.js");
|
|
121
|
+
const endpointCount = await indexApiEndpoints(searchService);
|
|
122
|
+
if (endpointCount > 0) {
|
|
123
|
+
console.error(`[MCP Server] Indexed ${endpointCount} API endpoints for hybrid search`);
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error("[MCP Server] Search indexing skipped (search service not available)");
|
|
127
|
+
}
|
|
128
|
+
const server = await createMcpServer(options);
|
|
129
|
+
const transport = new StdioServerTransport();
|
|
130
|
+
const toolCount = getToolRegistry().listToolNames().length;
|
|
131
|
+
console.error(`[MCP Server] Starting ${options.config.name} v${options.config.version}`);
|
|
132
|
+
if (options.apiKeySecret) {
|
|
133
|
+
console.error(`[MCP Server] Authentication: API key`);
|
|
134
|
+
} else if (options.context) {
|
|
135
|
+
console.error(`[MCP Server] Tenant: ${options.context.tenantId ?? "(none)"}`);
|
|
136
|
+
console.error(`[MCP Server] Organization: ${options.context.organizationId ?? "(none)"}`);
|
|
137
|
+
console.error(`[MCP Server] User: ${options.context.userId ?? "(superadmin)"}`);
|
|
138
|
+
} else {
|
|
139
|
+
console.error(`[MCP Server] Authentication: none (superadmin mode)`);
|
|
140
|
+
}
|
|
141
|
+
console.error(`[MCP Server] Tools registered: ${toolCount}`);
|
|
142
|
+
await server.connect(transport);
|
|
143
|
+
console.error("[MCP Server] Connected and ready for requests");
|
|
144
|
+
const shutdown = async () => {
|
|
145
|
+
console.error("[MCP Server] Shutting down...");
|
|
146
|
+
await server.close();
|
|
147
|
+
process.exit(0);
|
|
148
|
+
};
|
|
149
|
+
process.on("SIGINT", shutdown);
|
|
150
|
+
process.on("SIGTERM", shutdown);
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
createMcpServer,
|
|
154
|
+
runMcpServer
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=mcp-server.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/lib/mcp-server.ts"],
|
|
4
|
+
"sourcesContent": ["import { Server } from '@modelcontextprotocol/sdk/server/index.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n ListToolsRequestSchema,\n CallToolRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools, indexToolsForSearch } from './tool-loader'\nimport { authenticateMcpRequest, hasRequiredFeatures } from './auth'\nimport type { McpServerOptions, McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\n\n/**\n * Create and configure an MCP server instance.\n */\nexport async function createMcpServer(options: McpServerOptions): Promise<Server> {\n const { config, container, context, apiKeySecret } = options\n\n let tenantId: string | null = null\n let organizationId: string | null = null\n let userId: string | null = null\n let userFeatures: string[] = []\n let isSuperAdmin = false\n\n // API key authentication takes precedence\n if (apiKeySecret) {\n const authResult = await authenticateMcpRequest(apiKeySecret, container)\n if (!authResult.success) {\n throw new Error(`API key authentication failed: ${authResult.error}`)\n }\n tenantId = authResult.tenantId\n organizationId = authResult.organizationId\n userId = authResult.userId\n userFeatures = authResult.features\n isSuperAdmin = authResult.isSuperAdmin\n console.error(`[MCP Server] Authenticated via API key: ${authResult.keyName}`)\n } else if (context) {\n // Manual context provided\n tenantId = context.tenantId\n organizationId = context.organizationId\n userId = context.userId\n\n if (userId) {\n try {\n const rbacService = container.resolve('rbacService') as {\n loadAcl: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null }\n ) => Promise<{\n isSuperAdmin: boolean\n features: string[]\n }>\n }\n const acl = await rbacService.loadAcl(userId, {\n tenantId,\n organizationId,\n })\n userFeatures = acl.features\n isSuperAdmin = acl.isSuperAdmin\n } catch (error) {\n console.error('[MCP Server] Failed to load user ACL:', error)\n }\n } else {\n // No user specified - grant superadmin access for development/testing\n isSuperAdmin = true\n console.error('[MCP Server] No user specified, running with superadmin access')\n }\n } else {\n // No context and no API key - superadmin for dev/testing\n isSuperAdmin = true\n console.error('[MCP Server] No auth context, running with superadmin access')\n }\n\n const toolContext: McpToolContext = {\n tenantId,\n organizationId,\n userId,\n container,\n userFeatures,\n isSuperAdmin,\n apiKeySecret,\n }\n\n const server = new Server(\n { name: config.name, version: config.version },\n { capabilities: { tools: {} } }\n )\n\n // List tools handler\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n // Filter tools based on user permissions\n const accessibleTools = tools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, userFeatures, isSuperAdmin)\n )\n\n if (config.debug) {\n console.error(\n `[MCP Server] Listing ${accessibleTools.length}/${tools.length} tools (filtered by ACL)`\n )\n }\n\n return {\n tools: accessibleTools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n // Cast to any for Zod v4 compatibility with zod-to-json-schema\n inputSchema: zodToJsonSchema(tool.inputSchema as any) as Record<string, unknown>,\n })),\n }\n })\n\n // Call tool handler\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params\n\n if (config.debug) {\n console.error(`[MCP Server] Calling tool: ${name}`, JSON.stringify(args))\n }\n\n const result = await executeTool(name, args ?? {}, toolContext)\n\n if (!result.success) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ error: result.error, code: result.errorCode }),\n },\n ],\n isError: true,\n }\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n }\n })\n\n return server\n}\n\n/**\n * Run MCP server with stdio transport.\n * This keeps the process running until terminated.\n *\n * Supports two authentication modes:\n * 1. API key: Provide `apiKeySecret` option\n * 2. Manual context: Provide `context` with tenant/org/user\n */\nexport async function runMcpServer(options: McpServerOptions): Promise<void> {\n // Load tools from all modules before starting\n await loadAllModuleTools()\n\n // Index tools and API endpoints for hybrid search discovery (if search service available)\n try {\n const searchService = options.container.resolve('searchService') as SearchService\n\n // Index MCP tools\n await indexToolsForSearch(searchService)\n\n // Index API endpoints for api_discover\n const { indexApiEndpoints } = await import('./api-endpoint-index')\n const endpointCount = await indexApiEndpoints(searchService)\n if (endpointCount > 0) {\n console.error(`[MCP Server] Indexed ${endpointCount} API endpoints for hybrid search`)\n }\n } catch (error) {\n // Search service might not be configured - discovery will use fallback\n console.error('[MCP Server] Search indexing skipped (search service not available)')\n }\n\n const server = await createMcpServer(options)\n const transport = new StdioServerTransport()\n\n const toolCount = getToolRegistry().listToolNames().length\n\n console.error(`[MCP Server] Starting ${options.config.name} v${options.config.version}`)\n\n if (options.apiKeySecret) {\n console.error(`[MCP Server] Authentication: API key`)\n } else if (options.context) {\n console.error(`[MCP Server] Tenant: ${options.context.tenantId ?? '(none)'}`)\n console.error(`[MCP Server] Organization: ${options.context.organizationId ?? '(none)'}`)\n console.error(`[MCP Server] User: ${options.context.userId ?? '(superadmin)'}`)\n } else {\n console.error(`[MCP Server] Authentication: none (superadmin mode)`)\n }\n\n console.error(`[MCP Server] Tools registered: ${toolCount}`)\n\n await server.connect(transport)\n\n console.error('[MCP Server] Connected and ready for requests')\n\n // Handle shutdown gracefully\n const shutdown = async () => {\n console.error('[MCP Server] Shutting down...')\n await server.close()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { dynamicTool } from "ai";
|
|
2
|
+
import { toSafeZodSchema } from "./schema-utils.js";
|
|
3
|
+
function convertMcpToolsToAiSdk(mcpClient, mcpTools) {
|
|
4
|
+
const aiTools = {};
|
|
5
|
+
for (const mcpTool of mcpTools) {
|
|
6
|
+
try {
|
|
7
|
+
const safeSchema = toSafeZodSchema(mcpTool.inputSchema);
|
|
8
|
+
aiTools[mcpTool.name] = dynamicTool({
|
|
9
|
+
description: mcpTool.description,
|
|
10
|
+
inputSchema: safeSchema,
|
|
11
|
+
execute: async (args) => {
|
|
12
|
+
const result = await mcpClient.callTool(mcpTool.name, args);
|
|
13
|
+
if (!result.success) {
|
|
14
|
+
throw new Error(result.error || "Tool execution failed");
|
|
15
|
+
}
|
|
16
|
+
return formatToolResult(result.result);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error(`[MCP Adapter] Error converting tool "${mcpTool.name}":`, error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return aiTools;
|
|
24
|
+
}
|
|
25
|
+
function formatToolResult(result) {
|
|
26
|
+
if (result === null || result === void 0) {
|
|
27
|
+
return "No result returned";
|
|
28
|
+
}
|
|
29
|
+
if (typeof result === "string") {
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
if (typeof result === "number" || typeof result === "boolean") {
|
|
33
|
+
return String(result);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return JSON.stringify(result, null, 2);
|
|
37
|
+
} catch {
|
|
38
|
+
return String(result);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
convertMcpToolsToAiSdk
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=mcp-tool-adapter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/lib/mcp-tool-adapter.ts"],
|
|
4
|
+
"sourcesContent": ["import { dynamicTool, type Tool } from 'ai'\nimport type { InProcessMcpClient, ToolInfoWithSchema } from './in-process-client'\nimport { toSafeZodSchema } from './schema-utils'\n\n/**\n * Convert MCP tools to Vercel AI SDK format.\n *\n * This adapter takes tools from an MCP client and converts them\n * to the format expected by the AI SDK's streamText function.\n *\n * Uses dynamicTool for dynamic schema support, which allows\n * tools with runtime-determined schemas (from MCP servers).\n *\n * @param mcpClient - MCP client to execute tools\n * @param mcpTools - List of tools with Zod schemas\n * @returns Record of AI SDK tools\n */\nexport function convertMcpToolsToAiSdk(\n mcpClient: InProcessMcpClient,\n mcpTools: ToolInfoWithSchema[]\n): Record<string, Tool<unknown, unknown>> {\n const aiTools: Record<string, Tool<unknown, unknown>> = {}\n\n for (const mcpTool of mcpTools) {\n try {\n // Convert schema using Zod4's toJSONSchema with unrepresentable: 'any'\n // This handles Date types by converting them to 'any' in JSON Schema,\n // then we convert back to a clean Zod schema\n const safeSchema = toSafeZodSchema(mcpTool.inputSchema)\n\n aiTools[mcpTool.name] = dynamicTool({\n description: mcpTool.description,\n inputSchema: safeSchema,\n execute: async (args: unknown) => {\n const result = await mcpClient.callTool(mcpTool.name, args)\n\n if (!result.success) {\n throw new Error(result.error || 'Tool execution failed')\n }\n\n // Return the result in a format suitable for LLM consumption\n return formatToolResult(result.result)\n },\n })\n } catch (error) {\n console.error(`[MCP Adapter] Error converting tool \"${mcpTool.name}\":`, error)\n }\n }\n\n return aiTools\n}\n\n/**\n * Format tool result for LLM consumption.\n * Converts various result types to a string representation.\n */\nfunction formatToolResult(result: unknown): string {\n if (result === null || result === undefined) {\n return 'No result returned'\n }\n\n if (typeof result === 'string') {\n return result\n }\n\n if (typeof result === 'number' || typeof result === 'boolean') {\n return String(result)\n }\n\n // For objects and arrays, return JSON representation\n try {\n return JSON.stringify(result, null, 2)\n } catch {\n return String(result)\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,mBAA8B;AAEvC,SAAS,uBAAuB;AAezB,SAAS,uBACd,WACA,UACwC;AACxC,QAAM,UAAkD,CAAC;AAEzD,aAAW,WAAW,UAAU;AAC9B,QAAI;AAIF,YAAM,aAAa,gBAAgB,QAAQ,WAAW;AAEtD,cAAQ,QAAQ,IAAI,IAAI,YAAY;AAAA,QAClC,aAAa,QAAQ;AAAA,QACrB,aAAa;AAAA,QACb,SAAS,OAAO,SAAkB;AAChC,gBAAM,SAAS,MAAM,UAAU,SAAS,QAAQ,MAAM,IAAI;AAE1D,cAAI,CAAC,OAAO,SAAS;AACnB,kBAAM,IAAI,MAAM,OAAO,SAAS,uBAAuB;AAAA,UACzD;AAGA,iBAAO,iBAAiB,OAAO,MAAM;AAAA,QACvC;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,MAAM,wCAAwC,QAAQ,IAAI,MAAM,KAAK;AAAA,IAC/E;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,iBAAiB,QAAyB;AACjD,MAAI,WAAW,QAAQ,WAAW,QAAW;AAC3C,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,WAAW,UAAU;AAC9B,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,WAAW,YAAY,OAAO,WAAW,WAAW;AAC7D,WAAO,OAAO,MAAM;AAAA,EACtB;AAGA,MAAI;AACF,WAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AAAA,EACvC,QAAQ;AACN,WAAO,OAAO,MAAM;AAAA,EACtB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
class OpenCodeClient {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
4
|
+
this.headers = {
|
|
5
|
+
"Content-Type": "application/json"
|
|
6
|
+
};
|
|
7
|
+
if (config.password) {
|
|
8
|
+
const credentials = Buffer.from(`opencode:${config.password}`).toString("base64");
|
|
9
|
+
this.headers["Authorization"] = `Basic ${credentials}`;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Subscribe to SSE event stream for real-time updates.
|
|
14
|
+
* Returns an abort function to stop the stream.
|
|
15
|
+
*/
|
|
16
|
+
subscribeToEvents(onEvent, onError) {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const connect = async () => {
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(`${this.baseUrl}/event`, {
|
|
21
|
+
headers: {
|
|
22
|
+
...this.headers,
|
|
23
|
+
Accept: "text/event-stream"
|
|
24
|
+
},
|
|
25
|
+
signal: controller.signal
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok || !res.body) {
|
|
28
|
+
throw new Error(`SSE connection failed: ${res.status}`);
|
|
29
|
+
}
|
|
30
|
+
const reader = res.body.getReader();
|
|
31
|
+
const decoder = new TextDecoder();
|
|
32
|
+
let buffer = "";
|
|
33
|
+
while (true) {
|
|
34
|
+
const { done, value } = await reader.read();
|
|
35
|
+
if (done) break;
|
|
36
|
+
buffer += decoder.decode(value, { stream: true });
|
|
37
|
+
const lines = buffer.split("\n");
|
|
38
|
+
buffer = lines.pop() || "";
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (line.startsWith("data: ")) {
|
|
41
|
+
try {
|
|
42
|
+
const data = JSON.parse(line.slice(6));
|
|
43
|
+
onEvent(data);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error.name !== "AbortError") {
|
|
51
|
+
onError?.(error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
connect();
|
|
56
|
+
return () => controller.abort();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check OpenCode server health.
|
|
60
|
+
*/
|
|
61
|
+
async health() {
|
|
62
|
+
const res = await fetch(`${this.baseUrl}/global/health`, {
|
|
63
|
+
headers: this.headers
|
|
64
|
+
});
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
throw new Error(`Health check failed: ${res.status}`);
|
|
67
|
+
}
|
|
68
|
+
return res.json();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get MCP server connection status.
|
|
72
|
+
*/
|
|
73
|
+
async mcpStatus() {
|
|
74
|
+
const res = await fetch(`${this.baseUrl}/mcp`, {
|
|
75
|
+
headers: this.headers
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
throw new Error(`MCP status check failed: ${res.status}`);
|
|
79
|
+
}
|
|
80
|
+
return res.json();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create a new conversation session.
|
|
84
|
+
*/
|
|
85
|
+
async createSession() {
|
|
86
|
+
const res = await fetch(`${this.baseUrl}/session`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: this.headers,
|
|
89
|
+
body: JSON.stringify({})
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
const error = await res.text();
|
|
93
|
+
throw new Error(`Failed to create session: ${error}`);
|
|
94
|
+
}
|
|
95
|
+
return res.json();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get an existing session by ID.
|
|
99
|
+
*/
|
|
100
|
+
async getSession(sessionId) {
|
|
101
|
+
const res = await fetch(`${this.baseUrl}/session/${sessionId}`, {
|
|
102
|
+
headers: this.headers
|
|
103
|
+
});
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
throw new Error(`Failed to get session: ${res.status}`);
|
|
106
|
+
}
|
|
107
|
+
return res.json();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Send a message to a session and wait for response.
|
|
111
|
+
*/
|
|
112
|
+
async sendMessage(sessionId, message, options) {
|
|
113
|
+
const body = {
|
|
114
|
+
parts: [{ type: "text", text: message }]
|
|
115
|
+
};
|
|
116
|
+
if (options?.model) {
|
|
117
|
+
body.model = options.model;
|
|
118
|
+
}
|
|
119
|
+
const res = await fetch(`${this.baseUrl}/session/${sessionId}/message`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: this.headers,
|
|
122
|
+
body: JSON.stringify(body)
|
|
123
|
+
});
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
const error = await res.text();
|
|
126
|
+
throw new Error(`Failed to send message: ${error}`);
|
|
127
|
+
}
|
|
128
|
+
return res.json();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Set authentication credentials for a provider.
|
|
132
|
+
*/
|
|
133
|
+
async setAuth(providerId, apiKey) {
|
|
134
|
+
const res = await fetch(`${this.baseUrl}/auth/${providerId}`, {
|
|
135
|
+
method: "PUT",
|
|
136
|
+
headers: this.headers,
|
|
137
|
+
body: JSON.stringify({ type: "api", key: apiKey })
|
|
138
|
+
});
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
throw new Error(`Failed to set auth: ${res.status}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get current configuration.
|
|
145
|
+
*/
|
|
146
|
+
async getConfig() {
|
|
147
|
+
const res = await fetch(`${this.baseUrl}/config`, {
|
|
148
|
+
headers: this.headers
|
|
149
|
+
});
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
throw new Error(`Failed to get config: ${res.status}`);
|
|
152
|
+
}
|
|
153
|
+
return res.json();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get pending questions that need user response.
|
|
157
|
+
*/
|
|
158
|
+
async getPendingQuestions() {
|
|
159
|
+
const res = await fetch(`${this.baseUrl}/question`, {
|
|
160
|
+
headers: this.headers
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok) {
|
|
163
|
+
throw new Error(`Failed to get questions: ${res.status}`);
|
|
164
|
+
}
|
|
165
|
+
return res.json();
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Answer a pending question.
|
|
169
|
+
* OpenCode expects: POST /question/{requestID}/reply with { answers: [["label"]] }
|
|
170
|
+
* Each answer is an array of selected option labels (for multi-select support).
|
|
171
|
+
*/
|
|
172
|
+
async answerQuestion(questionId, answerIndex) {
|
|
173
|
+
const questions = await this.getPendingQuestions();
|
|
174
|
+
const question = questions.find((q) => q.id === questionId);
|
|
175
|
+
if (!question) {
|
|
176
|
+
throw new Error(`Question ${questionId} not found`);
|
|
177
|
+
}
|
|
178
|
+
const answers = [];
|
|
179
|
+
for (const q of question.questions) {
|
|
180
|
+
const selectedOption = q.options[answerIndex];
|
|
181
|
+
if (selectedOption) {
|
|
182
|
+
answers.push([selectedOption.label]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const body = { answers };
|
|
186
|
+
console.log("[OpenCode Client] Answering question", questionId, "with body:", JSON.stringify(body));
|
|
187
|
+
const res = await fetch(`${this.baseUrl}/question/${questionId}/reply`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: this.headers,
|
|
190
|
+
body: JSON.stringify(body)
|
|
191
|
+
});
|
|
192
|
+
const responseText = await res.text();
|
|
193
|
+
console.log("[OpenCode Client] Answer response:", res.status, responseText.substring(0, 200));
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
throw new Error(`Failed to answer question: ${res.status} - ${responseText}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Reject a pending question.
|
|
200
|
+
*/
|
|
201
|
+
async rejectQuestion(questionId) {
|
|
202
|
+
console.log("[OpenCode Client] Rejecting question", questionId);
|
|
203
|
+
const res = await fetch(`${this.baseUrl}/question/${questionId}/reject`, {
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: this.headers
|
|
206
|
+
});
|
|
207
|
+
if (!res.ok) {
|
|
208
|
+
const responseText = await res.text();
|
|
209
|
+
throw new Error(`Failed to reject question: ${res.status} - ${responseText}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get session status (idle, busy, waiting for question).
|
|
214
|
+
* Falls back to inferring status from pending questions if endpoint doesn't exist.
|
|
215
|
+
*/
|
|
216
|
+
async getSessionStatus(sessionId) {
|
|
217
|
+
try {
|
|
218
|
+
const res = await fetch(`${this.baseUrl}/session/${sessionId}/status`, {
|
|
219
|
+
headers: this.headers
|
|
220
|
+
});
|
|
221
|
+
if (res.ok) {
|
|
222
|
+
const contentType = res.headers.get("content-type");
|
|
223
|
+
if (contentType && contentType.includes("application/json")) {
|
|
224
|
+
return res.json();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
const questions = await this.getPendingQuestions();
|
|
230
|
+
const sessionQuestion = questions.find((q) => q.sessionID === sessionId);
|
|
231
|
+
if (sessionQuestion) {
|
|
232
|
+
return { status: "waiting", questionId: sessionQuestion.id };
|
|
233
|
+
}
|
|
234
|
+
return { status: "unknown" };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function createOpenCodeClient(config) {
|
|
238
|
+
return new OpenCodeClient({
|
|
239
|
+
baseUrl: config?.baseUrl ?? process.env.OPENCODE_URL ?? "http://localhost:4096",
|
|
240
|
+
password: config?.password ?? process.env.OPENCODE_PASSWORD
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
export {
|
|
244
|
+
OpenCodeClient,
|
|
245
|
+
createOpenCodeClient
|
|
246
|
+
};
|
|
247
|
+
//# sourceMappingURL=opencode-client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/lib/opencode-client.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * OpenCode Agent Client\n *\n * Client for communicating with OpenCode server running in headless mode.\n * OpenCode is used as an AI agent that can execute MCP tools.\n */\n\nexport type OpenCodeClientConfig = {\n baseUrl: string\n password?: string\n}\n\nexport type OpenCodeSession = {\n id: string\n slug: string\n version: string\n projectID: string\n directory: string\n title: string\n time: {\n created: number\n updated: number\n }\n}\n\nexport type OpenCodeMessagePart = {\n type: 'text'\n text: string\n}\n\nexport type OpenCodeMessageInfo = {\n id: string\n sessionID: string\n role: 'user' | 'assistant'\n time: {\n created: number\n completed?: number\n }\n modelID?: string\n providerID?: string\n tokens?: {\n input: number\n output: number\n }\n error?: {\n name: string\n data: Record<string, unknown>\n }\n}\n\nexport type OpenCodeMessage = {\n info: OpenCodeMessageInfo\n parts: Array<{\n id: string\n type: string\n text?: string\n [key: string]: unknown\n }>\n}\n\nexport type OpenCodeHealth = {\n healthy: boolean\n version: string\n}\n\nexport type OpenCodeMcpStatus = Record<\n string,\n {\n status: 'connected' | 'failed' | 'connecting'\n error?: string\n }\n>\n\nexport type OpenCodeQuestionOption = {\n label: string\n description: string\n}\n\nexport type OpenCodeQuestion = {\n id: string\n sessionID: string\n questions: Array<{\n question: string\n header: string\n options: OpenCodeQuestionOption[]\n }>\n tool: {\n messageID: string\n callID: string\n }\n}\n\n/**\n * SSE Event from OpenCode event stream.\n */\nexport type OpenCodeSSEEvent = {\n type: string\n properties: Record<string, unknown>\n}\n\n/**\n * Callback for SSE events.\n */\nexport type OpenCodeSSECallback = (event: OpenCodeSSEEvent) => void\n\n/**\n * Client for OpenCode server API.\n */\nexport class OpenCodeClient {\n private baseUrl: string\n private headers: Record<string, string>\n\n constructor(config: OpenCodeClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '')\n this.headers = {\n 'Content-Type': 'application/json',\n }\n\n if (config.password) {\n const credentials = Buffer.from(`opencode:${config.password}`).toString('base64')\n this.headers['Authorization'] = `Basic ${credentials}`\n }\n }\n\n /**\n * Subscribe to SSE event stream for real-time updates.\n * Returns an abort function to stop the stream.\n */\n subscribeToEvents(\n onEvent: OpenCodeSSECallback,\n onError?: (error: Error) => void\n ): () => void {\n const controller = new AbortController()\n\n const connect = async () => {\n try {\n const res = await fetch(`${this.baseUrl}/event`, {\n headers: {\n ...this.headers,\n Accept: 'text/event-stream',\n },\n signal: controller.signal,\n })\n\n if (!res.ok || !res.body) {\n throw new Error(`SSE connection failed: ${res.status}`)\n }\n\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n\n // Process complete SSE messages\n const lines = buffer.split('\\n')\n buffer = lines.pop() || '' // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6))\n onEvent(data)\n } catch {\n // Ignore parse errors\n }\n }\n }\n }\n } catch (error) {\n if ((error as Error).name !== 'AbortError') {\n onError?.(error as Error)\n }\n }\n }\n\n connect()\n\n return () => controller.abort()\n }\n\n /**\n * Check OpenCode server health.\n */\n async health(): Promise<OpenCodeHealth> {\n const res = await fetch(`${this.baseUrl}/global/health`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`Health check failed: ${res.status}`)\n }\n\n return res.json()\n }\n\n /**\n * Get MCP server connection status.\n */\n async mcpStatus(): Promise<OpenCodeMcpStatus> {\n const res = await fetch(`${this.baseUrl}/mcp`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`MCP status check failed: ${res.status}`)\n }\n\n return res.json()\n }\n\n /**\n * Create a new conversation session.\n */\n async createSession(): Promise<OpenCodeSession> {\n const res = await fetch(`${this.baseUrl}/session`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({}),\n })\n\n if (!res.ok) {\n const error = await res.text()\n throw new Error(`Failed to create session: ${error}`)\n }\n\n return res.json()\n }\n\n /**\n * Get an existing session by ID.\n */\n async getSession(sessionId: string): Promise<OpenCodeSession> {\n const res = await fetch(`${this.baseUrl}/session/${sessionId}`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`Failed to get session: ${res.status}`)\n }\n\n return res.json()\n }\n\n /**\n * Send a message to a session and wait for response.\n */\n async sendMessage(\n sessionId: string,\n message: string,\n options?: {\n model?: { providerID: string; modelID: string }\n }\n ): Promise<OpenCodeMessage> {\n const body: Record<string, unknown> = {\n parts: [{ type: 'text', text: message }],\n }\n\n if (options?.model) {\n body.model = options.model\n }\n\n const res = await fetch(`${this.baseUrl}/session/${sessionId}/message`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(body),\n })\n\n if (!res.ok) {\n const error = await res.text()\n throw new Error(`Failed to send message: ${error}`)\n }\n\n return res.json()\n }\n\n /**\n * Set authentication credentials for a provider.\n */\n async setAuth(providerId: string, apiKey: string): Promise<void> {\n const res = await fetch(`${this.baseUrl}/auth/${providerId}`, {\n method: 'PUT',\n headers: this.headers,\n body: JSON.stringify({ type: 'api', key: apiKey }),\n })\n\n if (!res.ok) {\n throw new Error(`Failed to set auth: ${res.status}`)\n }\n }\n\n /**\n * Get current configuration.\n */\n async getConfig(): Promise<Record<string, unknown>> {\n const res = await fetch(`${this.baseUrl}/config`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`Failed to get config: ${res.status}`)\n }\n\n return res.json()\n }\n\n /**\n * Get pending questions that need user response.\n */\n async getPendingQuestions(): Promise<OpenCodeQuestion[]> {\n const res = await fetch(`${this.baseUrl}/question`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`Failed to get questions: ${res.status}`)\n }\n\n return res.json()\n }\n\n /**\n * Answer a pending question.\n * OpenCode expects: POST /question/{requestID}/reply with { answers: [[\"label\"]] }\n * Each answer is an array of selected option labels (for multi-select support).\n */\n async answerQuestion(questionId: string, answerIndex: number): Promise<void> {\n // First get the question to find the selected option label\n const questions = await this.getPendingQuestions()\n const question = questions.find((q) => q.id === questionId)\n\n if (!question) {\n throw new Error(`Question ${questionId} not found`)\n }\n\n // Build answers array - each question's answer is an array of selected labels\n const answers: string[][] = []\n for (const q of question.questions) {\n const selectedOption = q.options[answerIndex]\n if (selectedOption) {\n // Each answer is an array of selected labels (supports multi-select)\n answers.push([selectedOption.label])\n }\n }\n\n const body = { answers }\n\n console.log('[OpenCode Client] Answering question', questionId, 'with body:', JSON.stringify(body))\n\n const res = await fetch(`${this.baseUrl}/question/${questionId}/reply`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(body),\n })\n\n const responseText = await res.text()\n console.log('[OpenCode Client] Answer response:', res.status, responseText.substring(0, 200))\n\n if (!res.ok) {\n throw new Error(`Failed to answer question: ${res.status} - ${responseText}`)\n }\n }\n\n /**\n * Reject a pending question.\n */\n async rejectQuestion(questionId: string): Promise<void> {\n console.log('[OpenCode Client] Rejecting question', questionId)\n\n const res = await fetch(`${this.baseUrl}/question/${questionId}/reject`, {\n method: 'POST',\n headers: this.headers,\n })\n\n if (!res.ok) {\n const responseText = await res.text()\n throw new Error(`Failed to reject question: ${res.status} - ${responseText}`)\n }\n }\n\n /**\n * Get session status (idle, busy, waiting for question).\n * Falls back to inferring status from pending questions if endpoint doesn't exist.\n */\n async getSessionStatus(sessionId: string): Promise<{ status: string; questionId?: string }> {\n try {\n const res = await fetch(`${this.baseUrl}/session/${sessionId}/status`, {\n headers: this.headers,\n })\n\n if (res.ok) {\n const contentType = res.headers.get('content-type')\n if (contentType && contentType.includes('application/json')) {\n return res.json()\n }\n }\n } catch {\n // Endpoint doesn't exist or network error - fall through to inference\n }\n\n // Fall back to inferring status from pending questions\n // Note: We can't tell if OpenCode is busy without the status endpoint\n // Return 'unknown' to let SSE events determine actual state\n const questions = await this.getPendingQuestions()\n const sessionQuestion = questions.find((q) => q.sessionID === sessionId)\n if (sessionQuestion) {\n return { status: 'waiting', questionId: sessionQuestion.id }\n }\n // Don't assume idle - we can't know without SSE events\n return { status: 'unknown' }\n }\n}\n\n/**\n * Create an OpenCode client with default configuration from environment.\n */\nexport function createOpenCodeClient(config?: Partial<OpenCodeClientConfig>): OpenCodeClient {\n return new OpenCodeClient({\n baseUrl: config?.baseUrl ?? process.env.OPENCODE_URL ?? 'http://localhost:4096',\n password: config?.password ?? process.env.OPENCODE_PASSWORD,\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|