feat: add comprehensive tests for LLM sessions and skills services

- Introduced tests for session synchronization, file delegation, session updates, and artifact deletion in sessions.test.ts.
- Added tests for skill discovery and invocation across various scopes in skills.test.ts.
- Created new types for MCP and provider skills to enhance type safety and clarity.
- Refactored routes to use the updated llmSessionsService from the i-runtime module.
- Removed deprecated session indexers and consolidated related functionality.
This commit is contained in:
Haileyesus
2026-04-07 13:53:59 +03:00
parent 779bc63556
commit 664713776a
49 changed files with 202 additions and 194 deletions

View File

@@ -34,7 +34,7 @@
"preview": "vite preview",
"typecheck:client": "tsc --noEmit -p tsconfig.json",
"typecheck:server": "tsc --noEmit -p server/tsconfig.json",
"test:server": "tsx --tsconfig server/tsconfig.json --test server/src/modules/llm/tests/*.test.ts",
"test:server": "tsx --tsconfig server/tsconfig.json --test server/src/modules/ai-runtime/tests/*.test.ts",
"verify:server": "npm run typecheck:server && npm run test:server && npm run server:build",
"typecheck": "npm run typecheck:client && npm run typecheck:server",
"lint": "eslint src/",

View File

@@ -1,8 +1,8 @@
import type { IProvider } from '@/modules/llm/providers/provider.interface.js';
import { ClaudeProvider } from '@/modules/llm/providers/claude.provider.js';
import { CodexProvider } from '@/modules/llm/providers/codex.provider.js';
import { CursorProvider } from '@/modules/llm/providers/cursor.provider.js';
import { GeminiProvider } from '@/modules/llm/providers/gemini.provider.js';
import type { IProvider } from '@/modules/ai-runtime/types/index.js';
import { ClaudeProvider } from '@/modules/ai-runtime/providers/claude/claude.provider.js';
import { CodexProvider } from '@/modules/ai-runtime/providers/codex/codex.provider.js';
import { CursorProvider } from '@/modules/ai-runtime/providers/cursor/cursor.provider.js';
import { GeminiProvider } from '@/modules/ai-runtime/providers/gemini/gemini.provider.js';
import type { LLMProvider } from '@/shared/types/app.js';
import { AppError } from '@/shared/utils/app-error.js';

View File

@@ -3,10 +3,10 @@ import express, { type NextFunction, type Request, type Response } from 'express
import { asyncHandler } from '@/shared/http/async-handler.js';
import { AppError } from '@/shared/utils/app-error.js';
import { createApiErrorResponse, createApiSuccessResponse } from '@/shared/http/api-response.js';
import { llmService } from '@/modules/llm/services/llm.service.js';
import { llmSessionsService } from '@/modules/llm/services/sessions.service.js';
import type { McpScope, McpTransport, UpsertProviderMcpServerInput } from '@/modules/llm/providers/provider.interface.js';
import { llmMessagesUnifier } from '@/modules/llm/services/messages-unifier.service.js';
import { llmService } from '@/modules/ai-runtime/services/ai-runtime.service.js';
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.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';
import { logger } from '@/shared/utils/logger.js';
@@ -530,7 +530,7 @@ router.use((error: unknown, _req: Request, res: Response, _next: NextFunction) =
const message = error instanceof Error ? error.message : 'Unexpected LLM route failure.';
logger.error(message, {
module: 'llm.routes',
module: 'ai-runtime.routes',
});
res.status(500).json(createApiErrorResponse('INTERNAL_ERROR', message));

View File

@@ -9,7 +9,7 @@ import type {
ProviderSessionEvent,
ProviderSessionSnapshot,
StartSessionInput,
} from '@/modules/llm/providers/provider.interface.js';
} from '@/modules/ai-runtime/types/index.js';
import type { LLMProvider } from '@/shared/types/app.js';
const MAX_EVENT_BUFFER_SIZE = 2_000;

View File

@@ -4,14 +4,14 @@ import type { ChildProcessWithoutNullStreams } from 'node:child_process';
import spawn from 'cross-spawn';
import { AbstractProvider } from '@/modules/llm/providers/abstract.provider.js';
import { AbstractProvider } from '@/modules/ai-runtime/providers/base/abstract.provider.js';
import type {
MutableProviderSession,
ProviderCapabilities,
ProviderSessionEvent,
ProviderSessionSnapshot,
StartSessionInput,
} from '@/modules/llm/providers/provider.interface.js';
} from '@/modules/ai-runtime/types/index.js';
import { createStreamLineAccumulator } from '@/shared/platform/stream.js';
import type { LLMProvider } from '@/shared/types/app.js';

View File

@@ -1,13 +1,13 @@
import { randomUUID } from 'node:crypto';
import { AbstractProvider } from '@/modules/llm/providers/abstract.provider.js';
import { AbstractProvider } from '@/modules/ai-runtime/providers/base/abstract.provider.js';
import type {
MutableProviderSession,
ProviderCapabilities,
ProviderSessionEvent,
ProviderSessionSnapshot,
StartSessionInput,
} from '@/modules/llm/providers/provider.interface.js';
} from '@/modules/ai-runtime/types/index.js';
import type { LLMProvider } from '@/shared/types/app.js';
type CreateSdkExecutionInput = StartSessionInput & {

View File

@@ -6,8 +6,8 @@ import type {
McpScope,
ProviderMcpServer,
UpsertProviderMcpServerInput,
} from '@/modules/llm/providers/provider.interface.js';
import { BaseProviderMcpRuntime } from '@/modules/llm/providers/runtimes/base-provider-mcp.runtime.js';
} from '@/modules/ai-runtime/types/index.js';
import { BaseProviderMcpRuntime } from '@/modules/ai-runtime/providers/shared/mcp/base-provider-mcp.runtime.js';
import {
readJsonConfig,
readObjectRecord,
@@ -15,7 +15,7 @@ import {
readStringArray,
readStringRecord,
writeJsonConfig,
} from '@/modules/llm/providers/runtimes/mcp-runtime.utils.js';
} from '@/modules/ai-runtime/providers/shared/mcp/mcp-runtime.utils.js';
/**
* Claude MCP runtime backed by `~/.claude.json` and project `.mcp.json`.

View File

@@ -2,8 +2,8 @@ import { readFile } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import type { IProviderSkillsRuntime, ProviderSkill } from '@/modules/llm/providers/provider.interface.js';
import { deduplicateSkills, listSkillsFromDirectory } from '@/modules/llm/providers/runtimes/skills-runtime.utils.js';
import type { IProviderSkillsRuntime, ProviderSkill } from '@/modules/ai-runtime/types/index.js';
import { deduplicateSkills, listSkillsFromDirectory } from '@/modules/ai-runtime/providers/shared/skills/skills-runtime.utils.js';
/**
* Claude skills runtime backed by user/project/plugin skill directories.

View File

@@ -7,7 +7,7 @@ import {
import path from 'node:path';
import { readFile } from 'node:fs/promises';
import { BaseSdkProvider } from '@/modules/llm/providers/base-sdk.provider.js';
import { BaseSdkProvider } from '@/modules/ai-runtime/providers/base/base-sdk.provider.js';
import type {
IProviderMcpRuntime,
IProviderSkillsRuntime,
@@ -15,9 +15,9 @@ import type {
ProviderSessionEvent,
RuntimePermissionMode,
StartSessionInput,
} from '@/modules/llm/providers/provider.interface.js';
import { ClaudeMcpRuntime } from '@/modules/llm/providers/runtimes/claude-mcp.runtime.js';
import { ClaudeSkillsRuntime } from '@/modules/llm/providers/runtimes/claude-skills.runtime.js';
} from '@/modules/ai-runtime/types/index.js';
import { ClaudeMcpRuntime } from '@/modules/ai-runtime/providers/claude/claude-mcp.runtime.js';
import { ClaudeSkillsRuntime } from '@/modules/ai-runtime/providers/claude/claude-skills.runtime.js';
type ClaudeExecutionInput = StartSessionInput & {
sessionId: string;

View File

@@ -6,8 +6,8 @@ import type {
McpScope,
ProviderMcpServer,
UpsertProviderMcpServerInput,
} from '@/modules/llm/providers/provider.interface.js';
import { BaseProviderMcpRuntime } from '@/modules/llm/providers/runtimes/base-provider-mcp.runtime.js';
} from '@/modules/ai-runtime/types/index.js';
import { BaseProviderMcpRuntime } from '@/modules/ai-runtime/providers/shared/mcp/base-provider-mcp.runtime.js';
import {
readObjectRecord,
readOptionalString,
@@ -15,7 +15,7 @@ import {
readStringRecord,
readTomlConfig,
writeTomlConfig,
} from '@/modules/llm/providers/runtimes/mcp-runtime.utils.js';
} from '@/modules/ai-runtime/providers/shared/mcp/mcp-runtime.utils.js';
/**
* Codex MCP runtime backed by user/project `.codex/config.toml`.

View File

@@ -1,13 +1,13 @@
import os from 'node:os';
import path from 'node:path';
import type { IProviderSkillsRuntime, ProviderSkill, ProviderSkillScope } from '@/modules/llm/providers/provider.interface.js';
import type { IProviderSkillsRuntime, ProviderSkill, ProviderSkillScope } from '@/modules/ai-runtime/types/index.js';
import {
deduplicateDirectories,
deduplicateSkills,
findGitRepoRoot,
listSkillsFromDirectory,
} from '@/modules/llm/providers/runtimes/skills-runtime.utils.js';
} from '@/modules/ai-runtime/providers/shared/skills/skills-runtime.utils.js';
/**
* Codex skills runtime backed by repo/user/admin/system skill directories.

View File

@@ -2,16 +2,16 @@ import os from 'node:os';
import path from 'node:path';
import { readFile } from 'node:fs/promises';
import { BaseSdkProvider } from '@/modules/llm/providers/base-sdk.provider.js';
import { BaseSdkProvider } from '@/modules/ai-runtime/providers/base/base-sdk.provider.js';
import type {
IProviderMcpRuntime,
IProviderSkillsRuntime,
ProviderModel,
ProviderSessionEvent,
StartSessionInput,
} from '@/modules/llm/providers/provider.interface.js';
import { CodexMcpRuntime } from '@/modules/llm/providers/runtimes/codex-mcp.runtime.js';
import { CodexSkillsRuntime } from '@/modules/llm/providers/runtimes/codex-skills.runtime.js';
} from '@/modules/ai-runtime/types/index.js';
import { CodexMcpRuntime } from '@/modules/ai-runtime/providers/codex/codex-mcp.runtime.js';
import { CodexSkillsRuntime } from '@/modules/ai-runtime/providers/codex/codex-skills.runtime.js';
import { AppError } from '@/shared/utils/app-error.js';
type CodexExecutionInput = StartSessionInput & {

View File

@@ -6,8 +6,8 @@ import type {
McpScope,
ProviderMcpServer,
UpsertProviderMcpServerInput,
} from '@/modules/llm/providers/provider.interface.js';
import { BaseProviderMcpRuntime } from '@/modules/llm/providers/runtimes/base-provider-mcp.runtime.js';
} from '@/modules/ai-runtime/types/index.js';
import { BaseProviderMcpRuntime } from '@/modules/ai-runtime/providers/shared/mcp/base-provider-mcp.runtime.js';
import {
readJsonConfig,
readObjectRecord,
@@ -15,7 +15,7 @@ import {
readStringArray,
readStringRecord,
writeJsonConfig,
} from '@/modules/llm/providers/runtimes/mcp-runtime.utils.js';
} from '@/modules/ai-runtime/providers/shared/mcp/mcp-runtime.utils.js';
/**
* Cursor MCP runtime backed by user/project `.cursor/mcp.json`.

View File

@@ -1,12 +1,12 @@
import os from 'node:os';
import path from 'node:path';
import type { IProviderSkillsRuntime, ProviderSkill, ProviderSkillScope } from '@/modules/llm/providers/provider.interface.js';
import type { IProviderSkillsRuntime, ProviderSkill, ProviderSkillScope } from '@/modules/ai-runtime/types/index.js';
import {
deduplicateDirectories,
deduplicateSkills,
listSkillsFromDirectory,
} from '@/modules/llm/providers/runtimes/skills-runtime.utils.js';
} from '@/modules/ai-runtime/providers/shared/skills/skills-runtime.utils.js';
/**
* Cursor skills runtime backed by user/project skill directories.

View File

@@ -1,12 +1,12 @@
import { BaseCliProvider } from '@/modules/llm/providers/base-cli.provider.js';
import { BaseCliProvider } from '@/modules/ai-runtime/providers/base/base-cli.provider.js';
import type {
IProviderMcpRuntime,
IProviderSkillsRuntime,
ProviderModel,
StartSessionInput,
} from '@/modules/llm/providers/provider.interface.js';
import { CursorMcpRuntime } from '@/modules/llm/providers/runtimes/cursor-mcp.runtime.js';
import { CursorSkillsRuntime } from '@/modules/llm/providers/runtimes/cursor-skills.runtime.js';
} from '@/modules/ai-runtime/types/index.js';
import { CursorMcpRuntime } from '@/modules/ai-runtime/providers/cursor/cursor-mcp.runtime.js';
import { CursorSkillsRuntime } from '@/modules/ai-runtime/providers/cursor/cursor-skills.runtime.js';
type CursorExecutionInput = StartSessionInput & {
sessionId: string;

View File

@@ -6,8 +6,8 @@ import type {
McpScope,
ProviderMcpServer,
UpsertProviderMcpServerInput,
} from '@/modules/llm/providers/provider.interface.js';
import { BaseProviderMcpRuntime } from '@/modules/llm/providers/runtimes/base-provider-mcp.runtime.js';
} from '@/modules/ai-runtime/types/index.js';
import { BaseProviderMcpRuntime } from '@/modules/ai-runtime/providers/shared/mcp/base-provider-mcp.runtime.js';
import {
readJsonConfig,
readObjectRecord,
@@ -15,7 +15,7 @@ import {
readStringArray,
readStringRecord,
writeJsonConfig,
} from '@/modules/llm/providers/runtimes/mcp-runtime.utils.js';
} from '@/modules/ai-runtime/providers/shared/mcp/mcp-runtime.utils.js';
/**
* Gemini MCP runtime backed by user/project `.gemini/settings.json`.

View File

@@ -1,12 +1,12 @@
import os from 'node:os';
import path from 'node:path';
import type { IProviderSkillsRuntime, ProviderSkill, ProviderSkillScope } from '@/modules/llm/providers/provider.interface.js';
import type { IProviderSkillsRuntime, ProviderSkill, ProviderSkillScope } from '@/modules/ai-runtime/types/index.js';
import {
deduplicateDirectories,
deduplicateSkills,
listSkillsFromDirectory,
} from '@/modules/llm/providers/runtimes/skills-runtime.utils.js';
} from '@/modules/ai-runtime/providers/shared/skills/skills-runtime.utils.js';
/**
* Gemini skills runtime backed by user/project skill directories.

View File

@@ -1,12 +1,12 @@
import { BaseCliProvider } from '@/modules/llm/providers/base-cli.provider.js';
import { BaseCliProvider } from '@/modules/ai-runtime/providers/base/base-cli.provider.js';
import type {
IProviderMcpRuntime,
IProviderSkillsRuntime,
ProviderModel,
StartSessionInput,
} from '@/modules/llm/providers/provider.interface.js';
import { GeminiMcpRuntime } from '@/modules/llm/providers/runtimes/gemini-mcp.runtime.js';
import { GeminiSkillsRuntime } from '@/modules/llm/providers/runtimes/gemini-skills.runtime.js';
} from '@/modules/ai-runtime/types/index.js';
import { GeminiMcpRuntime } from '@/modules/ai-runtime/providers/gemini/gemini-mcp.runtime.js';
import { GeminiSkillsRuntime } from '@/modules/ai-runtime/providers/gemini/gemini-skills.runtime.js';
type GeminiExecutionInput = StartSessionInput & {
sessionId: string;

View File

@@ -0,0 +1 @@
export * from '@/modules/ai-runtime/types/index.js';

View File

@@ -6,13 +6,13 @@ import type {
McpTransport,
ProviderMcpServer,
UpsertProviderMcpServerInput,
} from '@/modules/llm/providers/provider.interface.js';
} from '@/modules/ai-runtime/types/index.js';
import {
normalizeServerName,
resolveWorkspacePath,
runHttpServerProbe,
runStdioServerProbe,
} from '@/modules/llm/providers/runtimes/mcp-runtime.utils.js';
} from '@/modules/ai-runtime/providers/shared/mcp/mcp-runtime.utils.js';
/**
* Shared MCP runtime for provider-specific config readers/writers.

View File

@@ -5,7 +5,7 @@ import { once } from 'node:events';
import spawn from 'cross-spawn';
import TOML from '@iarna/toml';
import type { ProviderMcpServer } from '@/modules/llm/providers/provider.interface.js';
import type { ProviderMcpServer } from '@/modules/ai-runtime/types/index.js';
import { AppError } from '@/shared/utils/app-error.js';
/**

View File

@@ -2,7 +2,7 @@ import { access, readFile, readdir } from 'node:fs/promises';
import path from 'node:path';
import type { LLMProvider } from '@/shared/types/app.js';
import type { ProviderSkill, ProviderSkillScope } from '@/modules/llm/providers/provider.interface.js';
import type { ProviderSkill, ProviderSkillScope } from '@/modules/ai-runtime/types/index.js';
/**
* Tests whether a path exists.

View File

@@ -1,6 +1,6 @@
import type { LLMProvider } from '@/shared/types/app.js';
import { AppError } from '@/shared/utils/app-error.js';
import { llmProviderRegistry } from '@/modules/llm/llm.registry.js';
import { llmProviderRegistry } from '@/modules/ai-runtime/ai-runtime.registry.js';
import type {
McpScope,
ProviderMcpServer,
@@ -10,7 +10,7 @@ import type {
RuntimePermissionMode,
StartSessionInput,
UpsertProviderMcpServerInput,
} from '@/modules/llm/providers/provider.interface.js';
} from '@/modules/ai-runtime/types/index.js';
/**
* Converts unknown request values into optional trimmed strings.

View File

@@ -1,4 +1,4 @@
import type { ProviderSessionEvent } from '@/modules/llm/providers/provider.interface.js';
import type { ProviderSessionEvent } from '@/modules/ai-runtime/types/index.js';
import type { LLMProvider } from '@/shared/types/app.js';
export type UnifiedMessageType =

View File

@@ -3,7 +3,7 @@ import os from 'node:os';
import path from 'node:path';
import { promises as fsPromises } from 'node:fs';
import { llmSessionsService } from '@/modules/llm/services/sessions.service.js';
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.service.js';
import type { LLMProvider } from '@/shared/types/app.js';
import { logger } from '@/shared/utils/logger.js';

View File

@@ -5,8 +5,8 @@ import { scanStateDb } from '@/shared/database/repositories/scan-state.db.js';
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
import type { LLMProvider } from '@/shared/types/app.js';
import { AppError } from '@/shared/utils/app-error.js';
import { sessionIndexers } from '@/modules/llm/session-indexers/index.js';
import { llmMessagesUnifier, type UnifiedChatMessage } from '@/modules/llm/services/messages-unifier.service.js';
import { sessionIndexers } from '@/modules/ai-runtime/session-indexers/index.js';
import { llmMessagesUnifier, type UnifiedChatMessage } from '@/modules/ai-runtime/services/messages-unifier.service.js';
type SyncResult = {
processedByProvider: Record<LLMProvider, number>;

View File

@@ -8,8 +8,8 @@ import {
findFilesRecursivelyCreatedAfter,
normalizeSessionName,
readFileTimestamps,
} from '@/modules/llm/session-indexers/session-indexer.utils.js';
import type { ISessionIndexer } from '@/modules/llm/session-indexers/session-indexer.interface.js';
} from '@/modules/ai-runtime/session-indexers/session-indexer.utils.js';
import type { ISessionIndexer } from '@/modules/ai-runtime/session-indexers/session-indexer.interface.js';
type ParsedSession = {
sessionId: string;

View File

@@ -8,8 +8,8 @@ import {
findFilesRecursivelyCreatedAfter,
normalizeSessionName,
readFileTimestamps,
} from '@/modules/llm/session-indexers/session-indexer.utils.js';
import type { ISessionIndexer } from '@/modules/llm/session-indexers/session-indexer.interface.js';
} from '@/modules/ai-runtime/session-indexers/session-indexer.utils.js';
import type { ISessionIndexer } from '@/modules/ai-runtime/session-indexers/session-indexer.interface.js';
type ParsedSession = {
sessionId: string;

View File

@@ -11,8 +11,8 @@ import {
listDirectoryEntriesSafe,
normalizeSessionName,
readFileTimestamps,
} from '@/modules/llm/session-indexers/session-indexer.utils.js';
import type { ISessionIndexer } from '@/modules/llm/session-indexers/session-indexer.interface.js';
} from '@/modules/ai-runtime/session-indexers/session-indexer.utils.js';
import type { ISessionIndexer } from '@/modules/ai-runtime/session-indexers/session-indexer.interface.js';
type ParsedSession = {
sessionId: string;

View File

@@ -7,8 +7,8 @@ import {
findFilesRecursivelyCreatedAfter,
normalizeSessionName,
readFileTimestamps,
} from '@/modules/llm/session-indexers/session-indexer.utils.js';
import type { ISessionIndexer } from '@/modules/llm/session-indexers/session-indexer.interface.js';
} from '@/modules/ai-runtime/session-indexers/session-indexer.utils.js';
import type { ISessionIndexer } from '@/modules/ai-runtime/session-indexers/session-indexer.interface.js';
type ParsedSession = {
sessionId: string;

View File

@@ -0,0 +1,15 @@
import type { ISessionIndexer } from '@/modules/ai-runtime/session-indexers/session-indexer.interface.js';
import { ClaudeSessionIndexer } from '@/modules/ai-runtime/session-indexers/claude.session-indexer.js';
import { CodexSessionIndexer } from '@/modules/ai-runtime/session-indexers/codex.session-indexer.js';
import { CursorSessionIndexer } from '@/modules/ai-runtime/session-indexers/cursor.session-indexer.js';
import { GeminiSessionIndexer } from '@/modules/ai-runtime/session-indexers/gemini.session-indexer.js';
/**
* Provider-specific session indexers used by the sync orchestrator.
*/
export const sessionIndexers: ISessionIndexer[] = [
new ClaudeSessionIndexer(),
new CodexSessionIndexer(),
new CursorSessionIndexer(),
new GeminiSessionIndexer(),
];

View File

@@ -6,11 +6,11 @@ import test from 'node:test';
import { AppError } from '@/shared/utils/app-error.js';
import { llmAssetsService } from '@/modules/assets/assets.service.js';
import { ClaudeProvider } from '@/modules/llm/providers/claude.provider.js';
import { CodexProvider } from '@/modules/llm/providers/codex.provider.js';
import { CursorProvider } from '@/modules/llm/providers/cursor.provider.js';
import { GeminiProvider } from '@/modules/llm/providers/gemini.provider.js';
import { llmService } from '@/modules/llm/services/llm.service.js';
import { ClaudeProvider } from '@/modules/ai-runtime/providers/claude/claude.provider.js';
import { CodexProvider } from '@/modules/ai-runtime/providers/codex/codex.provider.js';
import { CursorProvider } from '@/modules/ai-runtime/providers/cursor/cursor.provider.js';
import { GeminiProvider } from '@/modules/ai-runtime/providers/gemini/gemini.provider.js';
import { llmService } from '@/modules/ai-runtime/services/ai-runtime.service.js';
const asyncEvents = async function* (events: unknown[]) {
for (const event of events) {

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/llm/services/llm.service.js';
import { llmService } from '@/modules/ai-runtime/services/ai-runtime.service.js';
const patchHomeDir = (nextHomeDir: string) => {
const original = os.homedir;

View File

@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { llmMessagesUnifier } from '@/modules/llm/services/messages-unifier.service.js';
import { llmMessagesUnifier } from '@/modules/ai-runtime/services/messages-unifier.service.js';
/**
* This test covers helper-3 Claude normalization: user/assistant/thinking/tool-use/tool-result/error.

View File

@@ -5,11 +5,11 @@ import path from 'node:path';
import test from 'node:test';
import { AppError } from '@/shared/utils/app-error.js';
import { llmService } from '@/modules/llm/services/llm.service.js';
import { CursorProvider } from '@/modules/llm/providers/cursor.provider.js';
import { GeminiProvider } from '@/modules/llm/providers/gemini.provider.js';
import { CodexProvider } from '@/modules/llm/providers/codex.provider.js';
import { ClaudeProvider } from '@/modules/llm/providers/claude.provider.js';
import { llmService } from '@/modules/ai-runtime/services/ai-runtime.service.js';
import { CursorProvider } from '@/modules/ai-runtime/providers/cursor/cursor.provider.js';
import { GeminiProvider } from '@/modules/ai-runtime/providers/gemini/gemini.provider.js';
import { CodexProvider } from '@/modules/ai-runtime/providers/codex/codex.provider.js';
import { ClaudeProvider } from '@/modules/ai-runtime/providers/claude/claude.provider.js';
const asyncEvents = async function* (events: unknown[]) {
for (const event of events) {

View File

@@ -7,10 +7,10 @@ import test from 'node:test';
import { AppError } from '@/shared/utils/app-error.js';
import { scanStateDb } from '@/shared/database/repositories/scan-state.db.js';
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
import { llmSessionsService } from '@/modules/llm/services/sessions.service.js';
import { sessionIndexers } from '@/modules/llm/session-indexers/index.js';
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.service.js';
import { sessionIndexers } from '@/modules/ai-runtime/session-indexers/index.js';
import { conversationSearchService } from '@/modules/conversations/conversation-search.service.js';
import type { ISessionIndexer } from '@/modules/llm/session-indexers/session-indexer.interface.js';
import type { ISessionIndexer } from '@/modules/ai-runtime/session-indexers/session-indexer.interface.js';
const patchMethod = <T extends object, K extends keyof T>(target: T, key: K, replacement: T[K]) => {
const original = target[key];

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/llm/services/llm.service.js';
import { llmService } from '@/modules/ai-runtime/services/ai-runtime.service.js';
const patchHomeDir = (nextHomeDir: string) => {
const original = os.homedir;

View File

@@ -0,0 +1,3 @@
export * from '@/modules/ai-runtime/types/provider.types.js';
export * from '@/modules/ai-runtime/types/mcp.types.js';
export * from '@/modules/ai-runtime/types/skills.types.js';

View File

@@ -0,0 +1,66 @@
import type { LLMProvider } from '@/shared/types/app.js';
export type McpScope = 'user' | 'local' | 'project';
export type McpTransport = 'stdio' | 'http' | 'sse';
/**
* Provider MCP server descriptor normalized for frontend consumption.
*/
export type ProviderMcpServer = {
provider: LLMProvider;
name: string;
scope: McpScope;
transport: McpTransport;
command?: string;
args?: string[];
env?: Record<string, string>;
cwd?: string;
url?: string;
headers?: Record<string, string>;
envVars?: string[];
bearerTokenEnvVar?: string;
envHttpHeaders?: Record<string, string>;
};
/**
* Shared payload shape for MCP server create/update operations.
*/
export type UpsertProviderMcpServerInput = {
name: string;
scope?: McpScope;
transport: McpTransport;
workspacePath?: string;
command?: string;
args?: string[];
env?: Record<string, string>;
cwd?: string;
url?: string;
headers?: Record<string, string>;
envVars?: string[];
bearerTokenEnvVar?: string;
envHttpHeaders?: Record<string, string>;
};
/**
* MCP runtime contract for one provider.
*/
export interface IProviderMcpRuntime {
listServers(options?: { workspacePath?: string }): Promise<Record<McpScope, ProviderMcpServer[]>>;
listServersForScope(scope: McpScope, options?: { workspacePath?: string }): Promise<ProviderMcpServer[]>;
upsertServer(input: UpsertProviderMcpServerInput): Promise<ProviderMcpServer>;
removeServer(
input: { name: string; scope?: McpScope; workspacePath?: string },
): Promise<{ removed: boolean; provider: LLMProvider; name: string; scope: McpScope }>;
runServer(
input: { name: string; scope?: McpScope; workspacePath?: string },
): Promise<{
provider: LLMProvider;
name: string;
scope: McpScope;
transport: McpTransport;
reachable: boolean;
statusCode?: number;
error?: string;
}>;
}

View File

@@ -1,4 +1,6 @@
import type { LLMProvider } from '@/shared/types/app.js';
import type { IProviderMcpRuntime } from '@/modules/ai-runtime/types/mcp.types.js';
import type { IProviderSkillsRuntime } from '@/modules/ai-runtime/types/skills.types.js';
export type ProviderExecutionFamily = 'sdk' | 'cli';
@@ -6,12 +8,6 @@ export type ProviderSessionStatus = 'running' | 'completed' | 'failed' | 'stoppe
export type RuntimePermissionMode = 'ask' | 'allow' | 'deny';
export type McpScope = 'user' | 'local' | 'project';
export type McpTransport = 'stdio' | 'http' | 'sse';
export type ProviderSkillScope = 'user' | 'project' | 'plugin' | 'repo' | 'admin' | 'system';
/**
* Advertises optional provider behaviors so route/service code can gate features.
*/
@@ -20,57 +16,6 @@ export type ProviderCapabilities = {
supportsThinkingModeControl: boolean;
};
/**
* Provider MCP server descriptor normalized for frontend consumption.
*/
export type ProviderMcpServer = {
provider: LLMProvider;
name: string;
scope: McpScope;
transport: McpTransport;
command?: string;
args?: string[];
env?: Record<string, string>;
cwd?: string;
url?: string;
headers?: Record<string, string>;
envVars?: string[];
bearerTokenEnvVar?: string;
envHttpHeaders?: Record<string, string>;
};
/**
* Shared payload shape for MCP server create/update operations.
*/
export type UpsertProviderMcpServerInput = {
name: string;
scope?: McpScope;
transport: McpTransport;
workspacePath?: string;
command?: string;
args?: string[];
env?: Record<string, string>;
cwd?: string;
url?: string;
headers?: Record<string, string>;
envVars?: string[];
bearerTokenEnvVar?: string;
envHttpHeaders?: Record<string, string>;
};
/**
* Unified skill descriptor returned by provider skill runtimes.
*/
export type ProviderSkill = {
provider: LLMProvider;
scope: ProviderSkillScope;
name: string;
description?: string;
invocation: string;
filePath: string;
pluginName?: string;
};
/**
* Provider model descriptor normalized for frontend consumption.
*/
@@ -145,36 +90,6 @@ export interface IProvider {
listSessions(): ProviderSessionSnapshot[];
}
/**
* MCP runtime contract for one provider.
*/
export interface IProviderMcpRuntime {
listServers(options?: { workspacePath?: string }): Promise<Record<McpScope, ProviderMcpServer[]>>;
listServersForScope(scope: McpScope, options?: { workspacePath?: string }): Promise<ProviderMcpServer[]>;
upsertServer(input: UpsertProviderMcpServerInput): Promise<ProviderMcpServer>;
removeServer(
input: { name: string; scope?: McpScope; workspacePath?: string },
): Promise<{ removed: boolean; provider: LLMProvider; name: string; scope: McpScope }>;
runServer(
input: { name: string; scope?: McpScope; workspacePath?: string },
): Promise<{
provider: LLMProvider;
name: string;
scope: McpScope;
transport: McpTransport;
reachable: boolean;
statusCode?: number;
error?: string;
}>;
}
/**
* Skills runtime contract for one provider.
*/
export interface IProviderSkillsRuntime {
listSkills(options?: { workspacePath?: string }): Promise<ProviderSkill[]>;
}
/**
* Internal mutable session state used by provider base classes.
*/

View File

@@ -0,0 +1,23 @@
import type { LLMProvider } from '@/shared/types/app.js';
export type ProviderSkillScope = 'user' | 'project' | 'plugin' | 'repo' | 'admin' | 'system';
/**
* Unified skill descriptor returned by provider skill runtimes.
*/
export type ProviderSkill = {
provider: LLMProvider;
scope: ProviderSkillScope;
name: string;
description?: string;
invocation: string;
filePath: string;
pluginName?: string;
};
/**
* Skills runtime contract for one provider.
*/
export interface IProviderSkillsRuntime {
listSkills(options?: { workspacePath?: string }): Promise<ProviderSkill[]>;
}

View File

@@ -6,7 +6,7 @@ import os from 'os';
import TOML from '@iarna/toml';
import { getCodexSessions } from '../../../projects.js';
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
import { llmSessionsService } from '@/modules/llm/services/sessions.service.js';
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.service.js';
const router = express.Router();

View File

@@ -1,6 +1,6 @@
import express from 'express';
import sessionManager from '../../../sessionManager.js';
import { llmSessionsService } from '@/modules/llm/services/sessions.service.js';
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.service.js';
const router = express.Router();

View File

@@ -1,15 +0,0 @@
import type { ISessionIndexer } from '@/modules/llm/session-indexers/session-indexer.interface.js';
import { ClaudeSessionIndexer } from '@/modules/llm/session-indexers/claude.session-indexer.js';
import { CodexSessionIndexer } from '@/modules/llm/session-indexers/codex.session-indexer.js';
import { CursorSessionIndexer } from '@/modules/llm/session-indexers/cursor.session-indexer.js';
import { GeminiSessionIndexer } from '@/modules/llm/session-indexers/gemini.session-indexer.js';
/**
* Provider-specific session indexers used by the sync orchestrator.
*/
export const sessionIndexers: ISessionIndexer[] = [
new ClaudeSessionIndexer(),
new CodexSessionIndexer(),
new CursorSessionIndexer(),
new GeminiSessionIndexer(),
];

View File

@@ -11,7 +11,7 @@ import {
} from '../../../projects.js';
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
import { llmSessionsService } from '@/modules/llm/services/sessions.service.js';
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.service.js';
import { authenticateToken } from '../auth/auth.middleware.js';
import { getWorkspaceNameFromPath, WORKSPACES_ROOT, validateWorkspacePath } from './projects.utils.js';

View File

@@ -1,6 +1,6 @@
import path from 'node:path';
import { llmSessionsService } from '@/modules/llm/services/sessions.service.js';
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.service.js';
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
import type { SessionsRow } from '@/shared/database/types.js';

View File

@@ -8,7 +8,7 @@ import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { initializeDatabase } from '@/shared/database/init-db.js';
import { initializeWatcher } from '@/modules/llm/services/sessions-watcher.service.js';
import { initializeWatcher } from '@/modules/ai-runtime/services/sessions-watcher.service.js';
import { configureWebPush } from '@/modules/push-sub/push-sub.services.js';
import { getConnectableHost } from '@/shared/utils/networkHosts.js';
import { logger } from '@/shared/utils/logger.js';
@@ -92,7 +92,7 @@ const [
importRoute('./modules/workspaces/workspaces.routes.js'),
importRoute('./modules/projects/projects.inline.routes.js'),
importRoute('./modules/files/files.routes.js'),
importRoute('./modules/llm/llm.routes.js'),
importRoute('./modules/ai-runtime/ai-runtime.routes.js'),
importRoute('./modules/assets/assets.routes.js'),
]);