mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-16 01:12:46 +00:00
refactor: move mcp and skills logic to dedicated services
This commit is contained in:
@@ -5,6 +5,8 @@ import { AppError } from '@/shared/utils/app-error.js';
|
||||
import { createApiErrorResponse, createApiSuccessResponse } from '@/shared/http/api-response.js';
|
||||
import { llmService } from '@/modules/ai-runtime/services/ai-runtime.service.js';
|
||||
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.service.js';
|
||||
import { llmMcpService } from '@/modules/ai-runtime/services/mcp.service.js';
|
||||
import { llmSkillsService } from '@/modules/ai-runtime/services/skills.service.js';
|
||||
import type { McpScope, McpTransport, UpsertProviderMcpServerInput } from '@/modules/ai-runtime/types/index.js';
|
||||
import { llmMessagesUnifier } from '@/modules/ai-runtime/services/messages-unifier.service.js';
|
||||
import type { LLMProvider } from '@/shared/types/app.js';
|
||||
@@ -304,12 +306,12 @@ router.get(
|
||||
const scope = parseMcpScope(req.query.scope);
|
||||
|
||||
if (scope) {
|
||||
const servers = await llmService.listProviderMcpServersForScope(provider, scope, { workspacePath });
|
||||
const servers = await llmMcpService.listProviderMcpServersForScope(provider, scope, { workspacePath });
|
||||
res.json(createApiSuccessResponse({ provider, scope, servers }));
|
||||
return;
|
||||
}
|
||||
|
||||
const groupedServers = await llmService.listProviderMcpServers(provider, { workspacePath });
|
||||
const groupedServers = await llmMcpService.listProviderMcpServers(provider, { workspacePath });
|
||||
res.json(createApiSuccessResponse({ provider, scopes: groupedServers }));
|
||||
}),
|
||||
);
|
||||
@@ -322,7 +324,7 @@ router.post(
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const provider = parseProvider(req.params.provider);
|
||||
const payload = parseMcpUpsertPayload(req.body);
|
||||
const server = await llmService.upsertProviderMcpServer(provider, payload);
|
||||
const server = await llmMcpService.upsertProviderMcpServer(provider, payload);
|
||||
res.status(201).json(createApiSuccessResponse({ server }));
|
||||
}),
|
||||
);
|
||||
@@ -338,7 +340,7 @@ router.put(
|
||||
...((req.body && typeof req.body === 'object') ? req.body as Record<string, unknown> : {}),
|
||||
name: readPathParam(req.params.name, 'name'),
|
||||
});
|
||||
const server = await llmService.upsertProviderMcpServer(provider, payload);
|
||||
const server = await llmMcpService.upsertProviderMcpServer(provider, payload);
|
||||
res.json(createApiSuccessResponse({ server }));
|
||||
}),
|
||||
);
|
||||
@@ -352,7 +354,7 @@ router.delete(
|
||||
const provider = parseProvider(req.params.provider);
|
||||
const scope = parseMcpScope(req.query.scope);
|
||||
const workspacePath = readOptionalQueryString(req.query.workspacePath);
|
||||
const result = await llmService.removeProviderMcpServer(provider, {
|
||||
const result = await llmMcpService.removeProviderMcpServer(provider, {
|
||||
name: readPathParam(req.params.name, 'name'),
|
||||
scope,
|
||||
workspacePath,
|
||||
@@ -371,7 +373,7 @@ router.post(
|
||||
const body = (req.body as Record<string, unknown> | undefined) ?? {};
|
||||
const scope = parseMcpScope(body.scope ?? req.query.scope);
|
||||
const workspacePath = readOptionalQueryString(body.workspacePath ?? req.query.workspacePath);
|
||||
const result = await llmService.runProviderMcpServer(provider, {
|
||||
const result = await llmMcpService.runProviderMcpServer(provider, {
|
||||
name: readPathParam(req.params.name, 'name'),
|
||||
scope,
|
||||
workspacePath,
|
||||
@@ -393,7 +395,7 @@ router.post(
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
const results = await llmService.addMcpServerToAllProviders({
|
||||
const results = await llmMcpService.addMcpServerToAllProviders({
|
||||
...payload,
|
||||
scope: payload.scope === 'user' ? 'user' : 'project',
|
||||
});
|
||||
@@ -409,7 +411,7 @@ router.get(
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const provider = parseProvider(req.params.provider);
|
||||
const workspacePath = readOptionalQueryString(req.query.workspacePath);
|
||||
const skills = await llmService.listProviderSkills(provider, { workspacePath });
|
||||
const skills = await llmSkillsService.listProviderSkills(provider, { workspacePath });
|
||||
res.json(createApiSuccessResponse({ provider, skills }));
|
||||
}),
|
||||
);
|
||||
@@ -424,7 +426,7 @@ router.get(
|
||||
const workspacePath = readOptionalQueryString(req.query.workspacePath);
|
||||
if (providerQuery) {
|
||||
const provider = parseProvider(providerQuery);
|
||||
const skills = await llmService.listProviderSkills(provider, { workspacePath });
|
||||
const skills = await llmSkillsService.listProviderSkills(provider, { workspacePath });
|
||||
res.json(createApiSuccessResponse({ provider, skills }));
|
||||
return;
|
||||
}
|
||||
@@ -434,7 +436,7 @@ router.get(
|
||||
await Promise.all(
|
||||
providers.map(async (provider) => ([
|
||||
provider,
|
||||
await llmService.listProviderSkills(provider, { workspacePath }),
|
||||
await llmSkillsService.listProviderSkills(provider, { workspacePath }),
|
||||
])),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,14 +2,10 @@ import type { LLMProvider } from '@/shared/types/app.js';
|
||||
import { AppError } from '@/shared/utils/app-error.js';
|
||||
import { llmProviderRegistry } from '@/modules/ai-runtime/ai-runtime.registry.js';
|
||||
import type {
|
||||
McpScope,
|
||||
ProviderMcpServer,
|
||||
ProviderModel,
|
||||
ProviderSkill,
|
||||
ProviderSessionSnapshot,
|
||||
RuntimePermissionMode,
|
||||
StartSessionInput,
|
||||
UpsertProviderMcpServerInput,
|
||||
} from '@/modules/ai-runtime/types/index.js';
|
||||
|
||||
/**
|
||||
@@ -131,113 +127,6 @@ export const llmService = {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.stopSession(sessionId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Lists MCP servers for one provider grouped by supported scopes.
|
||||
*/
|
||||
async listProviderMcpServers(
|
||||
providerName: string,
|
||||
options?: { workspacePath?: string },
|
||||
): Promise<Record<McpScope, ProviderMcpServer[]>> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.listServers(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Lists MCP servers for one provider scope.
|
||||
*/
|
||||
async listProviderMcpServersForScope(
|
||||
providerName: string,
|
||||
scope: McpScope,
|
||||
options?: { workspacePath?: string },
|
||||
): Promise<ProviderMcpServer[]> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.listServersForScope(scope, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds or updates one provider MCP server.
|
||||
*/
|
||||
async upsertProviderMcpServer(
|
||||
providerName: string,
|
||||
input: UpsertProviderMcpServerInput,
|
||||
): Promise<ProviderMcpServer> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.upsertServer(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes one provider MCP server.
|
||||
*/
|
||||
async removeProviderMcpServer(
|
||||
providerName: string,
|
||||
input: { name: string; scope?: McpScope; workspacePath?: string },
|
||||
): Promise<{ removed: boolean; provider: LLMProvider; name: string; scope: McpScope }> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.removeServer(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Runs one provider MCP server probe.
|
||||
*/
|
||||
async runProviderMcpServer(
|
||||
providerName: string,
|
||||
input: { name: string; scope?: McpScope; workspacePath?: string },
|
||||
): Promise<{
|
||||
provider: LLMProvider;
|
||||
name: string;
|
||||
scope: McpScope;
|
||||
transport: 'stdio' | 'http' | 'sse';
|
||||
reachable: boolean;
|
||||
statusCode?: number;
|
||||
error?: string;
|
||||
}> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.runServer(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds one HTTP/stdio MCP server to every provider.
|
||||
*/
|
||||
async addMcpServerToAllProviders(
|
||||
input: Omit<UpsertProviderMcpServerInput, 'scope'> & { scope?: Exclude<McpScope, 'local'> },
|
||||
): Promise<Array<{ provider: LLMProvider; created: boolean; error?: string }>> {
|
||||
if (input.transport !== 'stdio' && input.transport !== 'http') {
|
||||
throw new AppError('Global MCP add supports only "stdio" and "http".', {
|
||||
code: 'INVALID_GLOBAL_MCP_TRANSPORT',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const scope = input.scope ?? 'project';
|
||||
const results: Array<{ provider: LLMProvider; created: boolean; error?: string }> = [];
|
||||
const providers = llmProviderRegistry.listProviders();
|
||||
for (const provider of providers) {
|
||||
try {
|
||||
await provider.mcp.upsertServer({ ...input, scope });
|
||||
results.push({ provider: provider.id, created: true });
|
||||
} catch (error) {
|
||||
results.push({
|
||||
provider: provider.id,
|
||||
created: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Lists skills for one provider.
|
||||
*/
|
||||
async listProviderSkills(
|
||||
providerName: string,
|
||||
options?: { workspacePath?: string },
|
||||
): Promise<ProviderSkill[]> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.skills.listSkills(options);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
106
server/src/modules/ai-runtime/services/mcp.service.ts
Normal file
106
server/src/modules/ai-runtime/services/mcp.service.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { LLMProvider } from '@/shared/types/app.js';
|
||||
import { AppError } from '@/shared/utils/app-error.js';
|
||||
import { llmProviderRegistry } from '@/modules/ai-runtime/ai-runtime.registry.js';
|
||||
import type {
|
||||
McpScope,
|
||||
ProviderMcpServer,
|
||||
UpsertProviderMcpServerInput,
|
||||
} from '@/modules/ai-runtime/types/index.js';
|
||||
|
||||
export const llmMcpService = {
|
||||
/**
|
||||
* Lists MCP servers for one provider grouped by supported scopes.
|
||||
*/
|
||||
async listProviderMcpServers(
|
||||
providerName: string,
|
||||
options?: { workspacePath?: string },
|
||||
): Promise<Record<McpScope, ProviderMcpServer[]>> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.listServers(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Lists MCP servers for one provider scope.
|
||||
*/
|
||||
async listProviderMcpServersForScope(
|
||||
providerName: string,
|
||||
scope: McpScope,
|
||||
options?: { workspacePath?: string },
|
||||
): Promise<ProviderMcpServer[]> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.listServersForScope(scope, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds or updates one provider MCP server.
|
||||
*/
|
||||
async upsertProviderMcpServer(
|
||||
providerName: string,
|
||||
input: UpsertProviderMcpServerInput,
|
||||
): Promise<ProviderMcpServer> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.upsertServer(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes one provider MCP server.
|
||||
*/
|
||||
async removeProviderMcpServer(
|
||||
providerName: string,
|
||||
input: { name: string; scope?: McpScope; workspacePath?: string },
|
||||
): Promise<{ removed: boolean; provider: LLMProvider; name: string; scope: McpScope }> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.removeServer(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Runs one provider MCP server probe.
|
||||
*/
|
||||
async runProviderMcpServer(
|
||||
providerName: string,
|
||||
input: { name: string; scope?: McpScope; workspacePath?: string },
|
||||
): Promise<{
|
||||
provider: LLMProvider;
|
||||
name: string;
|
||||
scope: McpScope;
|
||||
transport: 'stdio' | 'http' | 'sse';
|
||||
reachable: boolean;
|
||||
statusCode?: number;
|
||||
error?: string;
|
||||
}> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.runServer(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds one HTTP/stdio MCP server to every provider.
|
||||
*/
|
||||
async addMcpServerToAllProviders(
|
||||
input: Omit<UpsertProviderMcpServerInput, 'scope'> & { scope?: Exclude<McpScope, 'local'> },
|
||||
): Promise<Array<{ provider: LLMProvider; created: boolean; error?: string }>> {
|
||||
if (input.transport !== 'stdio' && input.transport !== 'http') {
|
||||
throw new AppError('Global MCP add supports only "stdio" and "http".', {
|
||||
code: 'INVALID_GLOBAL_MCP_TRANSPORT',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const scope = input.scope ?? 'project';
|
||||
const results: Array<{ provider: LLMProvider; created: boolean; error?: string }> = [];
|
||||
const providers = llmProviderRegistry.listProviders();
|
||||
for (const provider of providers) {
|
||||
try {
|
||||
await provider.mcp.upsertServer({ ...input, scope });
|
||||
results.push({ provider: provider.id, created: true });
|
||||
} catch (error) {
|
||||
results.push({
|
||||
provider: provider.id,
|
||||
created: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
};
|
||||
15
server/src/modules/ai-runtime/services/skills.service.ts
Normal file
15
server/src/modules/ai-runtime/services/skills.service.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { llmProviderRegistry } from '@/modules/ai-runtime/ai-runtime.registry.js';
|
||||
import type { ProviderSkill } from '@/modules/ai-runtime/types/index.js';
|
||||
|
||||
export const llmSkillsService = {
|
||||
/**
|
||||
* Lists skills for one provider.
|
||||
*/
|
||||
async listProviderSkills(
|
||||
providerName: string,
|
||||
options?: { workspacePath?: string },
|
||||
): Promise<ProviderSkill[]> {
|
||||
const provider = llmProviderRegistry.resolveProvider(providerName);
|
||||
return provider.skills.listSkills(options);
|
||||
},
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import test from 'node:test';
|
||||
import TOML from '@iarna/toml';
|
||||
|
||||
import { AppError } from '@/shared/utils/app-error.js';
|
||||
import { llmService } from '@/modules/ai-runtime/services/ai-runtime.service.js';
|
||||
import { llmMcpService } from '@/modules/ai-runtime/services/mcp.service.js';
|
||||
|
||||
const patchHomeDir = (nextHomeDir: string) => {
|
||||
const original = os.homedir;
|
||||
@@ -27,14 +27,14 @@ const readJson = async (filePath: string): Promise<Record<string, unknown>> => {
|
||||
* This test covers Claude MCP support for all scopes (user/local/project) and all transports (stdio/http/sse),
|
||||
* including add, update/list, and remove operations.
|
||||
*/
|
||||
test('llmService handles claude MCP scopes/transports with file-backed persistence', { concurrency: false }, async () => {
|
||||
test('llmMcpService handles claude MCP scopes/transports with file-backed persistence', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-claude-'));
|
||||
const workspacePath = path.join(tempRoot, 'workspace');
|
||||
await fs.mkdir(workspacePath, { recursive: true });
|
||||
|
||||
const restoreHomeDir = patchHomeDir(tempRoot);
|
||||
try {
|
||||
await llmService.upsertProviderMcpServer('claude', {
|
||||
await llmMcpService.upsertProviderMcpServer('claude', {
|
||||
name: 'claude-user-stdio',
|
||||
scope: 'user',
|
||||
transport: 'stdio',
|
||||
@@ -43,7 +43,7 @@ test('llmService handles claude MCP scopes/transports with file-backed persisten
|
||||
env: { API_KEY: 'secret' },
|
||||
});
|
||||
|
||||
await llmService.upsertProviderMcpServer('claude', {
|
||||
await llmMcpService.upsertProviderMcpServer('claude', {
|
||||
name: 'claude-local-http',
|
||||
scope: 'local',
|
||||
transport: 'http',
|
||||
@@ -52,7 +52,7 @@ test('llmService handles claude MCP scopes/transports with file-backed persisten
|
||||
workspacePath,
|
||||
});
|
||||
|
||||
await llmService.upsertProviderMcpServer('claude', {
|
||||
await llmMcpService.upsertProviderMcpServer('claude', {
|
||||
name: 'claude-project-sse',
|
||||
scope: 'project',
|
||||
transport: 'sse',
|
||||
@@ -61,13 +61,13 @@ test('llmService handles claude MCP scopes/transports with file-backed persisten
|
||||
workspacePath,
|
||||
});
|
||||
|
||||
const grouped = await llmService.listProviderMcpServers('claude', { workspacePath });
|
||||
const grouped = await llmMcpService.listProviderMcpServers('claude', { workspacePath });
|
||||
assert.ok(grouped.user.some((server) => server.name === 'claude-user-stdio' && server.transport === 'stdio'));
|
||||
assert.ok(grouped.local.some((server) => server.name === 'claude-local-http' && server.transport === 'http'));
|
||||
assert.ok(grouped.project.some((server) => server.name === 'claude-project-sse' && server.transport === 'sse'));
|
||||
|
||||
// update behavior is the same upsert route with same name
|
||||
await llmService.upsertProviderMcpServer('claude', {
|
||||
await llmMcpService.upsertProviderMcpServer('claude', {
|
||||
name: 'claude-project-sse',
|
||||
scope: 'project',
|
||||
transport: 'sse',
|
||||
@@ -81,7 +81,7 @@ test('llmService handles claude MCP scopes/transports with file-backed persisten
|
||||
const projectServer = projectServers['claude-project-sse'] as Record<string, unknown>;
|
||||
assert.equal(projectServer.url, 'https://example.com/sse-updated');
|
||||
|
||||
const removeResult = await llmService.removeProviderMcpServer('claude', {
|
||||
const removeResult = await llmMcpService.removeProviderMcpServer('claude', {
|
||||
name: 'claude-local-http',
|
||||
scope: 'local',
|
||||
workspacePath,
|
||||
@@ -97,14 +97,14 @@ test('llmService handles claude MCP scopes/transports with file-backed persisten
|
||||
* This test covers Codex MCP support for user/project scopes, stdio/http formats,
|
||||
* and validation for unsupported scope/transport combinations.
|
||||
*/
|
||||
test('llmService handles codex MCP TOML config and capability validation', { concurrency: false }, async () => {
|
||||
test('llmMcpService handles codex MCP TOML config and capability validation', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-codex-'));
|
||||
const workspacePath = path.join(tempRoot, 'workspace');
|
||||
await fs.mkdir(workspacePath, { recursive: true });
|
||||
|
||||
const restoreHomeDir = patchHomeDir(tempRoot);
|
||||
try {
|
||||
await llmService.upsertProviderMcpServer('codex', {
|
||||
await llmMcpService.upsertProviderMcpServer('codex', {
|
||||
name: 'codex-user-stdio',
|
||||
scope: 'user',
|
||||
transport: 'stdio',
|
||||
@@ -115,7 +115,7 @@ test('llmService handles codex MCP TOML config and capability validation', { con
|
||||
cwd: '/tmp',
|
||||
});
|
||||
|
||||
await llmService.upsertProviderMcpServer('codex', {
|
||||
await llmMcpService.upsertProviderMcpServer('codex', {
|
||||
name: 'codex-project-http',
|
||||
scope: 'project',
|
||||
transport: 'http',
|
||||
@@ -139,7 +139,7 @@ test('llmService handles codex MCP TOML config and capability validation', { con
|
||||
assert.equal(projectHttp.url, 'https://codex.example.com/mcp');
|
||||
|
||||
await assert.rejects(
|
||||
llmService.upsertProviderMcpServer('codex', {
|
||||
llmMcpService.upsertProviderMcpServer('codex', {
|
||||
name: 'codex-local',
|
||||
scope: 'local',
|
||||
transport: 'stdio',
|
||||
@@ -152,7 +152,7 @@ test('llmService handles codex MCP TOML config and capability validation', { con
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
llmService.upsertProviderMcpServer('codex', {
|
||||
llmMcpService.upsertProviderMcpServer('codex', {
|
||||
name: 'codex-sse',
|
||||
scope: 'project',
|
||||
transport: 'sse',
|
||||
@@ -173,14 +173,14 @@ test('llmService handles codex MCP TOML config and capability validation', { con
|
||||
/**
|
||||
* This test covers Gemini/Cursor MCP JSON formats and user/project scope persistence.
|
||||
*/
|
||||
test('llmService handles gemini and cursor MCP JSON config formats', { concurrency: false }, async () => {
|
||||
test('llmMcpService handles gemini and cursor MCP JSON config formats', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-gc-'));
|
||||
const workspacePath = path.join(tempRoot, 'workspace');
|
||||
await fs.mkdir(workspacePath, { recursive: true });
|
||||
|
||||
const restoreHomeDir = patchHomeDir(tempRoot);
|
||||
try {
|
||||
await llmService.upsertProviderMcpServer('gemini', {
|
||||
await llmMcpService.upsertProviderMcpServer('gemini', {
|
||||
name: 'gemini-stdio',
|
||||
scope: 'user',
|
||||
transport: 'stdio',
|
||||
@@ -190,7 +190,7 @@ test('llmService handles gemini and cursor MCP JSON config formats', { concurren
|
||||
cwd: './server',
|
||||
});
|
||||
|
||||
await llmService.upsertProviderMcpServer('gemini', {
|
||||
await llmMcpService.upsertProviderMcpServer('gemini', {
|
||||
name: 'gemini-http',
|
||||
scope: 'project',
|
||||
transport: 'http',
|
||||
@@ -199,7 +199,7 @@ test('llmService handles gemini and cursor MCP JSON config formats', { concurren
|
||||
workspacePath,
|
||||
});
|
||||
|
||||
await llmService.upsertProviderMcpServer('cursor', {
|
||||
await llmMcpService.upsertProviderMcpServer('cursor', {
|
||||
name: 'cursor-stdio',
|
||||
scope: 'project',
|
||||
transport: 'stdio',
|
||||
@@ -209,7 +209,7 @@ test('llmService handles gemini and cursor MCP JSON config formats', { concurren
|
||||
workspacePath,
|
||||
});
|
||||
|
||||
await llmService.upsertProviderMcpServer('cursor', {
|
||||
await llmMcpService.upsertProviderMcpServer('cursor', {
|
||||
name: 'cursor-http',
|
||||
scope: 'user',
|
||||
transport: 'http',
|
||||
@@ -240,14 +240,14 @@ test('llmService handles gemini and cursor MCP JSON config formats', { concurren
|
||||
* This test covers the global MCP adder requirement: only http/stdio are allowed and
|
||||
* one payload is written to all providers.
|
||||
*/
|
||||
test('llmService global adder writes to all providers and rejects unsupported transports', { concurrency: false }, async () => {
|
||||
test('llmMcpService global adder writes to all providers and rejects unsupported transports', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-global-'));
|
||||
const workspacePath = path.join(tempRoot, 'workspace');
|
||||
await fs.mkdir(workspacePath, { recursive: true });
|
||||
|
||||
const restoreHomeDir = patchHomeDir(tempRoot);
|
||||
try {
|
||||
const globalResult = await llmService.addMcpServerToAllProviders({
|
||||
const globalResult = await llmMcpService.addMcpServerToAllProviders({
|
||||
name: 'global-http',
|
||||
scope: 'project',
|
||||
transport: 'http',
|
||||
@@ -271,7 +271,7 @@ test('llmService global adder writes to all providers and rejects unsupported tr
|
||||
assert.ok((cursorProject.mcpServers as Record<string, unknown>)['global-http']);
|
||||
|
||||
await assert.rejects(
|
||||
llmService.addMcpServerToAllProviders({
|
||||
llmMcpService.addMcpServerToAllProviders({
|
||||
name: 'global-sse',
|
||||
scope: 'project',
|
||||
transport: 'sse',
|
||||
@@ -292,7 +292,7 @@ test('llmService global adder writes to all providers and rejects unsupported tr
|
||||
/**
|
||||
* This test covers "run" behavior for both stdio and http MCP servers.
|
||||
*/
|
||||
test('llmService runProviderServer probes stdio and http MCP servers', { concurrency: false }, async () => {
|
||||
test('llmMcpService runProviderServer probes stdio and http MCP servers', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-run-'));
|
||||
const workspacePath = path.join(tempRoot, 'workspace');
|
||||
await fs.mkdir(workspacePath, { recursive: true });
|
||||
@@ -309,7 +309,7 @@ test('llmService runProviderServer probes stdio and http MCP servers', { concurr
|
||||
assert.ok(address && typeof address === 'object');
|
||||
const url = `http://127.0.0.1:${address.port}/mcp`;
|
||||
|
||||
await llmService.upsertProviderMcpServer('gemini', {
|
||||
await llmMcpService.upsertProviderMcpServer('gemini', {
|
||||
name: 'probe-http',
|
||||
scope: 'project',
|
||||
transport: 'http',
|
||||
@@ -317,7 +317,7 @@ test('llmService runProviderServer probes stdio and http MCP servers', { concurr
|
||||
workspacePath,
|
||||
});
|
||||
|
||||
await llmService.upsertProviderMcpServer('cursor', {
|
||||
await llmMcpService.upsertProviderMcpServer('cursor', {
|
||||
name: 'probe-stdio',
|
||||
scope: 'project',
|
||||
transport: 'stdio',
|
||||
@@ -326,7 +326,7 @@ test('llmService runProviderServer probes stdio and http MCP servers', { concurr
|
||||
workspacePath,
|
||||
});
|
||||
|
||||
const httpProbe = await llmService.runProviderMcpServer('gemini', {
|
||||
const httpProbe = await llmMcpService.runProviderMcpServer('gemini', {
|
||||
name: 'probe-http',
|
||||
scope: 'project',
|
||||
workspacePath,
|
||||
@@ -334,7 +334,7 @@ test('llmService runProviderServer probes stdio and http MCP servers', { concurr
|
||||
assert.equal(httpProbe.reachable, true);
|
||||
assert.equal(httpProbe.transport, 'http');
|
||||
|
||||
const stdioProbe = await llmService.runProviderMcpServer('cursor', {
|
||||
const stdioProbe = await llmMcpService.runProviderMcpServer('cursor', {
|
||||
name: 'probe-stdio',
|
||||
scope: 'project',
|
||||
workspacePath,
|
||||
@@ -347,3 +347,4 @@ test('llmService runProviderServer probes stdio and http MCP servers', { concurr
|
||||
await fs.rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import { llmService } from '@/modules/ai-runtime/services/ai-runtime.service.js';
|
||||
import { llmSkillsService } from '@/modules/ai-runtime/services/skills.service.js';
|
||||
|
||||
const patchHomeDir = (nextHomeDir: string) => {
|
||||
const original = os.homedir;
|
||||
@@ -34,7 +34,7 @@ const createSkill = async (
|
||||
/**
|
||||
* This test covers Claude skills fetching from user/project/plugin locations and plugin namespace invocation.
|
||||
*/
|
||||
test('llmService lists claude user/project/plugin skills with proper invocation names', { concurrency: false }, async () => {
|
||||
test('llmSkillsService lists claude user/project/plugin skills with proper invocation names', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-claude-'));
|
||||
const workspacePath = path.join(tempRoot, 'workspace');
|
||||
const pluginInstallPath = path.join(tempRoot, 'plugin-install');
|
||||
@@ -80,7 +80,7 @@ test('llmService lists claude user/project/plugin skills with proper invocation
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const skills = await llmService.listProviderSkills('claude', { workspacePath });
|
||||
const skills = await llmSkillsService.listProviderSkills('claude', { workspacePath });
|
||||
assert.ok(skills.some((skill) => skill.scope === 'user' && skill.invocation === '/user-helper'));
|
||||
assert.ok(skills.some((skill) => skill.scope === 'project' && skill.invocation === '/project-helper'));
|
||||
assert.ok(skills.some((skill) => skill.scope === 'plugin' && skill.invocation === '/example-skills:plugin-helper'));
|
||||
@@ -93,7 +93,7 @@ test('llmService lists claude user/project/plugin skills with proper invocation
|
||||
/**
|
||||
* This test covers Codex skills discovery across repo/user/system locations and `$` invocation prefix.
|
||||
*/
|
||||
test('llmService lists codex skills from repo/user/system locations with dollar invocation', { concurrency: false }, async () => {
|
||||
test('llmSkillsService lists codex skills from repo/user/system locations with dollar invocation', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-codex-'));
|
||||
const repoRoot = path.join(tempRoot, 'repo');
|
||||
const workspacePath = path.join(repoRoot, 'packages', 'app');
|
||||
@@ -123,7 +123,7 @@ test('llmService lists codex skills from repo/user/system locations with dollar
|
||||
description: 'system skill',
|
||||
});
|
||||
|
||||
const skills = await llmService.listProviderSkills('codex', { workspacePath });
|
||||
const skills = await llmSkillsService.listProviderSkills('codex', { workspacePath });
|
||||
assert.ok(skills.some((skill) => skill.name === 'cwd-skill' && skill.invocation === '$cwd-skill'));
|
||||
assert.ok(skills.some((skill) => skill.name === 'parent-skill' && skill.invocation === '$parent-skill'));
|
||||
assert.ok(skills.some((skill) => skill.name === 'repo-root-skill' && skill.invocation === '$repo-root-skill'));
|
||||
@@ -138,7 +138,7 @@ test('llmService lists codex skills from repo/user/system locations with dollar
|
||||
/**
|
||||
* This test covers Gemini skill fetch locations and slash-based invocation format.
|
||||
*/
|
||||
test('llmService lists gemini skills from documented directories', { concurrency: false }, async () => {
|
||||
test('llmSkillsService lists gemini skills from documented directories', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-gemini-'));
|
||||
const workspacePath = path.join(tempRoot, 'workspace');
|
||||
await fs.mkdir(workspacePath, { recursive: true });
|
||||
@@ -162,7 +162,7 @@ test('llmService lists gemini skills from documented directories', { concurrency
|
||||
description: 'project agents skill',
|
||||
});
|
||||
|
||||
const skills = await llmService.listProviderSkills('gemini', { workspacePath });
|
||||
const skills = await llmSkillsService.listProviderSkills('gemini', { workspacePath });
|
||||
assert.ok(skills.some((skill) => skill.invocation === '/home-gemini'));
|
||||
assert.ok(skills.some((skill) => skill.invocation === '/home-agents'));
|
||||
assert.ok(skills.some((skill) => skill.invocation === '/project-gemini'));
|
||||
@@ -176,7 +176,7 @@ test('llmService lists gemini skills from documented directories', { concurrency
|
||||
/**
|
||||
* This test covers Cursor skill fetch locations and slash-based invocation format.
|
||||
*/
|
||||
test('llmService lists cursor skills from documented directories', { concurrency: false }, async () => {
|
||||
test('llmSkillsService lists cursor skills from documented directories', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-cursor-'));
|
||||
const workspacePath = path.join(tempRoot, 'workspace');
|
||||
await fs.mkdir(workspacePath, { recursive: true });
|
||||
@@ -196,7 +196,7 @@ test('llmService lists cursor skills from documented directories', { concurrency
|
||||
description: 'user cursor skill',
|
||||
});
|
||||
|
||||
const skills = await llmService.listProviderSkills('cursor', { workspacePath });
|
||||
const skills = await llmSkillsService.listProviderSkills('cursor', { workspacePath });
|
||||
assert.ok(skills.some((skill) => skill.invocation === '/project-agents'));
|
||||
assert.ok(skills.some((skill) => skill.invocation === '/project-cursor'));
|
||||
assert.ok(skills.some((skill) => skill.invocation === '/user-cursor'));
|
||||
@@ -205,3 +205,4 @@ test('llmService lists cursor skills from documented directories', { concurrency
|
||||
await fs.rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user