refactor: move mcp and skills logic to dedicated services

This commit is contained in:
Haileyesus
2026-04-07 14:17:36 +03:00
parent b54a2839e3
commit 6589867d78
6 changed files with 170 additions and 156 deletions

View File

@@ -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 }),
])),
),
);

View File

@@ -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);
},
};
/**

View 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;
},
};

View 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);
},
};

View File

@@ -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 });
}
});

View File

@@ -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 });
}
});